2 mai 2016

Exadata la DBA Lounge

Cu sprijinul celor de la SCC am reușit să organizăm o nouă ediție a "DBA Lounge"-ului.

De data asta a prezentat subsemnatul și a fost despre Oracle Exadata. N-a fost ceva foarte tehnic, ci mai degrabă o scurtă introducere în această tehnologie. Era cât pe ce să zic "această nouă tehnologie", dar nu mai e cazul...

Am profitat și de faptul că avem un Exadata la SCC și am putut sa arătăm niște demo-uri live.


Exadata from talek

Ca de obicei, cârcotașii din zona SQL Server, au fost cei mai "agresivi". Povestindu-le despre tavița KVM unde îți poți așeza, dacă vrei, o bere, simpaticii au sugerat că ar merge pus mai degrabă un laptop pe care să ruleze un SQL Server 2016 și așa să se vadă, clar, că e mai rapid decât Exadata. Ar fi un experiment interesant de făcut la următorul DBA Lounge.

Le mulțumesc tuturor celor care au participat, SCC-ului și Cătălinei pentru organizarea evenimentului.

Mai jos, câteva poze (înainte de bere... cele de la băuta de după au ieșit cam "mișcate", din motive lesne de înțeles, prin urmare nu le-am mai pus):

7 ianuarie 2016

Blogu' în engleză

N-am mai blog-uit demult pe oracle-cookies, blogu' pe care mai scriam articole în engleză. Bine, nici aici nu prea a fost activitate în ultima vreme. În fine, idee e că nu prea mă mai simt atașat de oracle-cookies, dar a fost o experiența interesantă, dat fiind că a fost prima tenativă de blog-uială.

Ce m-a făcut să renunț, printre altele, a fost și GUI-ul puțin cretinel din Blogger. Nu mă împac nicicum cu editarea într-un textarea în browser. Scriam în Vim, dup-aia copy&paste în browser. Aiurea! Prin urmare am început să-mi fac un fel de wiki personal, dar folosind Vim-ul meu pe steroizi, din care salvam în Dropbox și, mai departe, se sincroniza automat cu platforma de blogging Scriptogr.am. Era mai mult un blog privat, deși, dacă știai link-ul, nu era nici o problema în a-l accesa. Ca idee, mi-a plăcut. În plus, faptul că scriam în markdown era un avantaj pentru că nu mai trebuia să-mi bat capu cu tot felul de tag-uri.

Oricum, Nelly Furtado are dreptate în cântecelu' ei: "All good things come to an end"... Băieții de la Scriptogr.am închid mustăria!



Asta e! Prin urmare a trebuit să migrez spre altceva. Pentru ca vroiam să păstrez același stil în care notițele le scriu în Vim, am ales varianta Github. Deja am niște proiecțele p-acolo, de ce nu și blog-ul? Prin urmare, pentru cine-i interesat, adresa noului blog în engleza e: http://talek.github.io/blog/.

Na, că i-am făcut și reclamă!

12 decembrie 2013

Hâc, Hâc. Io cu cine votez?

Încerc să-mi revin după ghioaga primită drept în moalele capului, de la prea-onorabilii noștri aleși. Cine nu știe despre ce-i vorba, îi invit să ia aminte și să citească cu mare atenție articolul lui Dan Tapalagă.

După cum se poate observa citind pe site-ul Camerei Deputaților, a fost un vot masiv în favoarea acestei mârșăvii fără margini.

Am avut o curiozitate personală. Cine sunt deputații aleși în circumscripțiile din Iași, care nu au avut nici cea mai mică problemă să voteze (cu ambele mâini dacă se poate) această cretinătate de lege. Iată răspunsul:
set serveroutput on
set define off
declare
  MAIN_ADDR constant varchar2(100) := 'http://www.cdep.ro';
  l_xml clob;
  l_dep_iasi dbms_sql.varchar2_table;

  function deputati_iasi return dbms_sql.varchar2_table as
    req   utl_http.req;
    resp  utl_http.resp;
    value varchar2(1024);
    idx   pls_integer := 1;
    c     dbms_sql.varchar2_table;
  begin
    req := utl_http.begin_request(MAIN_ADDR || '/pls/parlam/structura.ce?cir=24&leg=2012&prn=1');
    resp := utl_http.get_response(req);
    loop
      utl_http.read_line(resp, value, true);
      if regexp_like(value, '/pls/parlam/structura.mp\?idm=') then
        c(idx) := regexp_replace(value, '.*>([^>]+)<.*', '\1');
        idx := idx + 1;
      end if;
    end loop;
    utl_http.end_response(resp);
    return c;
  exception
    when utl_http.end_of_body then
      utl_http.end_response(resp);
      return c;
  end;

  function get_vot_rusinos return clob as
    req   utl_http.req;
    resp  utl_http.resp;
    value varchar2(1024);
    c     clob;
  begin
    req := utl_http.begin_request(MAIN_ADDR || '/pls/steno/evot.xml?par1=2&par2=11386');
    resp := utl_http.get_response(req);
    loop
      utl_http.read_line(resp, value, true);
      c := c || value || chr(10);
    end loop;
    utl_http.end_response(resp);
    return c;
  exception
    when utl_http.end_of_body then
      utl_http.end_response(resp);
      return c;
  end;

begin
  l_xml := get_vot_rusinos;
  l_dep_iasi := deputati_iasi;
  for l_rec in (SELECT votid, nume, prenume, grup, vot
                  FROM XMLTABLE('/ROWSET/ROW'
                      PASSING xmltype(l_xml)
                  COLUMNS 
                          votid INTEGER PATH 'VOTID',
                          nume VARCHAR2(100) PATH 'NUME',
                          prenume VARCHAR2(500) PATH 'PRENUME',
                          grup VARCHAR2(100) PATH 'GRUP',
                          vot varchar2(30) path 'VOT') 
                        src) loop
    for i in l_dep_iasi.first .. l_dep_iasi.last loop
      if l_dep_iasi(i) = (l_rec.nume || ' ' || l_rec.prenume) and l_rec.vot = 'DA' then
        dbms_output.put_line(l_dep_iasi(i) || ' (' || l_rec.grup || ')' || ' => ' || 'RUSINEEE!!!');
      end if;
    end loop;
  end loop;
end;
/
Rulând acest cod, obținem rezultatul de mai jos:
Stanciu Anghel (PSD) => RUSINEEE!!!
Craciunescu Grigore (PNL) => RUSINEEE!!!
Mocanu Vasile (PSD) => RUSINEEE!!!
Oajdea Daniel Vasile (PP-DD) => RUSINEEE!!!
Ratoi Neculai (PSD) => RUSINEEE!!!
Manolescu Oana (Mino.) => RUSINEEE!!!
Fenechiu Relu (PNL) => RUSINEEE!!!
Emacu Gheorghe (PSD) => RUSINEEE!!!
Stancu Ionel (Mino.) => RUSINEEE!!!
Adascalitei Constantin (PSD) => RUSINEEE!!!
Iacoban Sorin-Avram (PSD) => RUSINEEE!!!
Alexe Costel (PNL) => RUSINEEE!!!
Nichita Cristina (PSD) => RUSINEEE!!!
Felicitări și la mai mare!

PS 1:
  • Site-ul Camerei Deputaților arată jalnic. Zici că e făcut de un student/elev care abia învață HTML. Varză! Multe coloane după care nu poți sorta, un design îngrozitor.
  • S-ar parea că băieții au în spate o bază de date Oracle, mai ales dacă ne uităm la formatul XML de aici.
  • Simpaticii folosesc și ceva de Oracle Application/HTTP Server. Dacă ne uităm în header-ul HTTP de răspuns găsim: "Oracle-Application-Server-10g/10.1.3.5.0 Oracle-HTTP-Server".

PS 2:
Doresc să o felicit din suflet și pe doamna Manolescu Oana, de la Asociația Liga Albanezilor din România. Taică-meu primește lună de lună "spam" de la ei pentru că bunicu' a fost albanez și cei de la ALAR, cumva, încearcă să păstreze legătura cu cei care mai au de-a face, încă, cu această comunitate. Doamnă, poate în următorul număr al revistei SPAM ne veți explica mai detaliat rațiunea votului dumneavoastră. Dacă se poate...

10 decembrie 2013

Păzitorul tranzacțiilor

Mă uitam zilele trecute la ce au mai băgat nou simplaticii de la Oracle în versiunea 12c a serverului de baze de date. Am dat de capitolul Using Transaction Guard. "Hmm, ce nume pompos, îmi spun... Probabil că e o altă denumire cu dichis pentru o funcționalitate exotică pe care nu o va folosi nimeni".

Ei bine, m-am înșelat... e cu adevărat ceva interesant pentru că rezolvă o problemă "veche de când lumea", generatoare de bug-uri voodoo și de mari dureri de cap pentru programatori și testeri.

Haideți mai întâi să vă aburesc cu felul în care este implementată de obicei interacțiunea cu severul Oracle pe partea de client a unei aplicații. Vom merge pe un exemplu Java. C/C++ sau .NET nu știu suficient de bine. Să zicem că avem o aplicație bancară, iar la sfârșitul fiecărei luni trebuie să impozităm dobânda aferentă fiecărui depozit. Dom'le, nici mie nu-mi place, da' dacă așa vrea "cel mai cinstit guvern"... Asta e, ne conformăm!

În baza de date avem o tabelă care arată cam așa:
create table bank_deposit (
  id integer primary key,
  balance number(12, 2)
);

insert into bank_deposit (id, balance) values (1, 10000);
commit;
Precum se poate observa, discutăm de o bancă falimentară cu un singur client. Nu vrem să complicăm prea mult lucurile, așa că vom presupune că impozitul este de 1 RON, indiferent de valoarea depozitului. E o prostie, dar nu ne batem capul cu detalii.

Codul java arată cam așa:
public class TaxBank {

 static final String USER = "BANK";
 static final String PASSWORD = "secret";
 static final String DB_URL = "jdbc:oracle:thin:@db";

 public static void main(String[] args) throws SQLException, IOException {
  OracleDataSource ods = null;
  OracleConnection conn = null;
  boolean taxApplied = false;
  ods = new OracleDataSource();
  ods.setUser(USER);
  ods.setPassword(PASSWORD);
  ods.setURL(DB_URL);
  conn = (OracleConnection) ods.getConnection();
  while (!taxApplied) {
   try {
    applyTax(conn);
    taxApplied = true;
   } catch (SQLRecoverableException e) {
    try {
     conn.close();
    } catch (Exception ex) {
     // I don't care
    }
    // Reconnect. We have HA baby!
    conn = (OracleConnection) ods.getConnection();
   }
  }
 }

 private static void applyTax(OracleConnection conn) throws SQLException {
  conn.setAutoCommit(false);
  Statement stmt = null;
  try {
   stmt = conn.createStatement();
   stmt.executeUpdate("UPDATE bank_deposit SET balance=balance-1");
  } catch (SQLException e) {
   throw e;
  } finally {
   if (stmt != null)
    stmt.close();
  }
  conn.commit();
 }
 
}
Ne uităm la codul de mai sus și suntem plăcut surprinși. Băieții programatori chiar au pus suflet și au venit cu o soluție elegantă, mai ales că au luat în considerare că baza de date s-ar putea să fie destul de inteligentă pentru a asigura ceea ce numim noi (și alții) HA, adică "high availability". Spre exemplu, poate că e un RAC, și una din instanțe a murit, iar serverul a facut automat fail-over pe cealaltă instanță. Sau poate, a fost un mic "hâc" pe rețea: ok, primim eroare, dar codul nostru elegant se prinde și face un lucru foarte draguț: retrimite tranzacția spre server, astfel că nea Clientu' nici nu își va da seama că tranzacția sa inițială a avut probleme. Asta nu-i puțin lucru. Clientu' nu vrea să vadă erori, e sensibil și trebuie protejat.

Buuun, unde-i problema? E clar că ceva e "putred" p-acolo, altfel articolul ăsta nu și-ar avea rostul... Cum era cu "o imagine care valorează cât o mie de cuvinte"? Ia să ne uităm puțin la următoarea figură.

În figura de mai sus am încercat să surprind una din cele mai comune probleme: clientul pornește pe server o tranzacție pe care o și comite cu succes. Server-ul, după ce își face treabușoara, trimite spre client confirmarea că tranzacția sa a fost comisă cu success. Fără această confirmare, clientul nu are nici cea mai vagă idee ce s-a întâmplat cu tranzacția sa. Este vorba de pasul 2 din figura de mai sus. Problema e că acestă confirmare s-ar putea să nu mai ajungă la client din cauza a N motive, cea mai comună probabil fiind o eroare pe rețea. Spre exemplu, Dorel s-a împiedicat de cablul de rețea și l-a scos din switch. În acest caz, clientul va primi o eroare.

Ce face codul de mai sus? Zice: păi asta e o eroare de tip "Recoverable", prin urmare e suficient să retrimitem tranzacția inițială spre server. Oare? Păi tranzacția a fost deja comisă pe server, e persistentă, gata! Problema a fost că mesajul de confirmare că tranzacția a fost comisă nu a ajuns la client. Știu, pare destul de improbabil să se întâmple așa ceva, dar se întâmplă! Și atunci ajungem la acele bug-uri voodoo de care vă vorbeam, foarte greu de reprodus. În exemplul nostru, se întâmplă un fapt deloc plăcut, practic se impozitează depozitul (dobânda) clientului de două ori. Prin urmare, în loc de 1 LEU se vor lua din cont 2 LEI.

Mulți vor spune: "în Oracle, commit-ul e foarte rapid. Adică vrei să spui că în fracțiunea aia de secundă se întâmplă ceva care face ca mesajul de confirmare să nu mai ajungă la client?" Și zic: "Da, se poate întâmpla!" Mai mult, putem și testa acest lucru forțând puțin lucrurile. În baza de date facem următoarele modificări:
create table bank_deposit_mv as select * from bank_deposit;

create materialized view bank_deposit_mv
on prebuilt table
refresh on commit
as
select * from bank_deposit;

create or replace trigger trg_bank_deposit_mv before 
insert or update or delete on bank_deposit_mv for each row
begin
  dbms_lock.sleep(10);
end;
/
Ce giumbușlucuri face codul de mai sus? Un banal view materializat, bazat pe tabela cu depozitele băncii. View-ului ăsta materializat i-am zis să își facă "refresh" la momentul COMMIT-ului. Asta înseamnă că mesajul care să confirme că tranzacția a fost comisă nu va fi trimis decât după ce view-ul materializat se va fi refresh-uit. Pentru că scopul meu e să obțin un COMMIT care să dureze foarte mult, am făcut un trigger pe view-ul materializat (în mod normal, acest lucru nu este suportat), trigger care nu face nimic altceva decât stă și asteaptă 10 secunde. Asta înseamnă că, virtual, comiterea unei tranzacții cu un singur DML va dura aproximativ 10 secunde (atenție COMMIT-ul, nu DML-ul). E perfect! Asta înseamnă că avem la dispoziție 10 secunde să simulăm o problemă pe rețea.

Modificăm codul java astfel. Înainte de "conn.commit()" din metoda applyTax() punem un mesaj informativ:
System.out.println("Slow commit... You may disable network if you want."); // add this
conn.commit();
Apoi, în metoda "main", înainte de a trage o conexiune nouă din "pool", adăugăm următoarele linii de cod:
// Reconnect. We have HA baby!
System.out.println("Enable network again and press ENTER."); // add this
System.in.read();                                            // and this
conn = (OracleConnection) ods.getConnection();
Haideți să mai verificăm o dată ce avem în tabela "BANK_DEPOSIT":
11:53:56 SQL> select * from bank_deposit;

        ID    BALANCE
---------- ----------
         1      10000
Bun, rulăm codul java modificat. Când apare mesajul: "Slow commit... You may disable network if you want." înseamnă că serverul Oracle stă în commit. Ne ducem frumușel în "Control Panel" și dăm "disable" la placa de rețea. Codul java va afișa "Enable network again and press ENTER.". Deci, facem fix ce ne spunem: dăm enable la placa de rețea și apăsăm ENTER. Dup-aia așteptăm să se termine rularea progrămelului. Perfect! Haideți să vedem ce a ieșit:
12:06:12 SQL> select * from bank_deposit;

        ID    BALANCE
---------- ----------
         1       9998
Hopaaa, lipsesc doi leuți. Fix ce vorbeam. Am reușit să reproducem bug-ul voodoo.

Eh, acu' intră în acțiune "păzitorul tranzacțiilor", adică acest minunat "transaction guard", disponibil în Oracle 12c. Mecanismul are la baza un așa-numit "Logical Transaction ID". Adică, pentru o tranzacție pe care o pornim pe server, Oracle îi va asocia un identificator logic de tranzacție, existând posibilitatea ca, ulterior, să aflăm ce s-a întâmplat cu tranzacția respectivă, în special dacă a fost comisă sau nu. Asta e versiunea scurtă! Pentru cei interesați de mai multe detalii, le recomand să arunce un ochi vigilent pe documentația Oracle.

Haideți să modificăm codul java și să-l facem mai deștept! În primul rând, vom mai adăuga o metodă nouă care să ne spună dacă o tranzacție a fost comisă sau nu, pe baza identificatorului său logic.
 private static boolean getTransactionOutcome(Connection conn, LogicalTransactionId ltxid)
   throws SQLException {
  boolean committed = false;
  CallableStatement cstmt = null;
  try {
   cstmt = conn.prepareCall("begin get_ltxid_outcome_wrapper(:1, :2, :3); end;");
   cstmt.setBytes(1, ltxid.getBytes());
   cstmt.registerOutParameter(2, OracleTypes.BIT);
   cstmt.registerOutParameter(3, OracleTypes.BIT);
   cstmt.execute();
   committed = cstmt.getBoolean(2);
  } catch (SQLException sqlexc) {
   throw sqlexc;
  } finally {
   if (cstmt != null)
    cstmt.close();
  }
  return committed;
 } 
Practic, invocăm o procedură din baza de date care arată cam așa:
CREATE OR REPLACE PROCEDURE GET_LTXID_OUTCOME_WRAPPER(ltxid in raw, 
                                                      is_committed out number, 
                                                      is_call_completed out number) is
 committed boolean;
 call_completed boolean;
begin
  dbms_app_cont.get_ltxid_outcome(ltxid, committed, call_completed);
  if committed then
   is_committed := 1;
  else
   is_committed := 0;
  end if;
  if call_completed then
   is_call_completed := 1;
  else
   is_call_completed := 0;
  end if;
end;
/
E o procedură de tip "wrapper", care apelează procedura GET_LTXID_OUTCOME din pachetul DBMS_APP_CONT și nu face nimic altceva decât o conversie din INTEGER în BOOLEAN și invers. Asta pentru că JDBC-ul nu știe de tipul PL/SQL BOOLEAN.

Mai trebuie să adăugăm această logică și în metoda "main":
   } catch (SQLRecoverableException e) {
    LogicalTransactionId lxid = conn.getLogicalTransactionId(); // add this line
    try {
     conn.close();
    } catch (Exception ex) {
     // I don't care
    }
    // Reconnect. We have HA baby!
    System.out.println("Enable network again and press ENTER.");
    System.in.read();
    conn = (OracleConnection) ods.getConnection();
    taxApplied = getTransactionOutcome(conn, lxid); // add this line
   }
Cu aceste modificări, haideți să rulăm din nou codul îmbunătățit. Ne amintim că, la acest moment, în cont-ul bancar avem 9998 LEI/RON. Urmăm fix aceeași pași: pornim progrămelul, dăm "disable" la placa de rețea, apoi "enable" și asteptăm să se termine de rulat progrămelul. Rezultatul?
12:28:37 SQL> select * from bank_deposit;

        ID    BALANCE
---------- ----------
         1       9997
Aaașaa da! Deși am avut probleme cu rețeaua, bănuții nu au fost luați de două ori din cont.

Câteva observații de final:
  • baza de date trebuie să fie neaparat 12c
  • clientul oracle trebuie să fie tot 12c
  • doar varianta "thin" de JDBC funcționează. Dacă încercăm cu frățiorul "oci", primim un mesaj de eroare cum că "unimplemented feature".

5 decembrie 2013

Jucărioară în PL/SQL: Quine

Astăzi, plictiseală mare... Așa că m-am gândit să mă joc puțin în PL/SQL. Datele problemei sunt simple:

"Să se dezvolte un progrămel în PL/SQL care se afișează pe el însuși".

Este ceea ce în literatura de specialitate se cheamă quine.

Mai jos, soluția la care am ajuns:
declare c char(112):='declare c char(112):=;q char(1):=chr(39);begin dbms_output.put_line(substr(c,1,21)||q||c||q||substr(c,-91));end;';q char(1):=chr(39);begin dbms_output.put_line(substr(c,1,21)||q||c||q||substr(c,-91));end;

Quine-ul meu în PL/SQL are o lungime de 226 de caractere. Nu prea sunt mulțumit de rezultat. Un "quine" din-ăla "WOOOW" ar trebui să aibă o lungime cât mai mică. Normal, aici avem și limitările care țin de limbajul de programare, iar PL/SQL-ul, din păcate, este un limbaj destul de logoreic.

Câteva observații:
  • am folosit CHAR în loc de VARCHAR[2] pentru simplul fapt că VARCHAR are trei caractere în plus (ca sintaxă zic)
  • am renunțat la toate formatările. Spațiile lungesc inutil "quine"-ul.
  • evident, dacă rulăm în Sqlplus, ar trebui să avem SET SERVEROUTPUT ON.
  • m-am limitat la a folosi doar pachete Oracle standard. Aș fi putut folosi niște proceduri "wrapper" (spre exemplu, procedura "P" care invocă dbms_output.put_line), dar nu-i voie cu din-astea.
  • nu am mai pus "/"-ul de final. El are sens totuși dacă se rulează codul de mai sus în Sqlplus.

Acestea fiind zise, are cineva vreo idee de îmbunătățire?

28 noiembrie 2013

Proiectarea Interfețelor Grafice

Deja parcă văd o sprânceană ridicată... Probabil că cititorii fideli ai acestui blog, nu prea mulți conform statisticilor pe care îmi mai arunc un ochi din când în când, vor fi puțin bulversați de subiect: "Ce l-o fi apucat dom'le? Proiectarea interfețelor grafice? Sună ca un curs din-ala plictisitoooor, de-ți vine să-ți pui capul pe masă și să tragi un pui de somn!"

Ei bine, am găsit un articol foarte interesant, fix pe acest subiect. Vi-l recomand!
Se cheamă: What Screens Want

Citindu-l am avut momentele acela de: aha, așa-i, mhmmm... wow! Și n-am putut sa nu ma gandesc la cât de diferit mi-au fost prezentate lucrurile în facultate.

Vă mai amintiți? Dragi studenți, principiile proiectării interfețelor grafice sunt:

  • consistență, 
  • claritate, metafore
  • bla bla bla. 

În parte, articolul se ocupă și de principii de proiectare (vezi metaforele), dar se simte entuziasmul autorului, se fac trimiteri la alte domenii conexe, sunt presărate întrebări rusești.

Paranteză. İmi amintesc că avem un curs care se chema "Monedă și Credit". Se vorbea acolo despre un document de trasă bancară. Nu auzisem niciodată de așa ceva, nu știam ce înseamnă, era ceva complet nou pentru mine. Așa că mi-am luat inima în dinți și am întrebat:

- Ce este mai exact un document de trasa bancară? Ne puteți arăta un exemplu de astfel de document? Cum se completează?

Răspunsul a venit prompt:

- Domnule student! Aici noi predăm un curs academic. Astfel de detalii le veți afla dacă o sa faceți practică sau vă veți angaja la o bancă.

Hmmm... right! Urmarea firească a fost că, domeniul ăsta legat de finanțe și bănci nu s-a prins de mine nicicum. Asta e, poate că generațiile viitoare deja formate de domnul Mugur Isărescu nu vor avea nevoie de lămuriri suplimentare legate de trasa bancară.

Să ne întelegem: nu generalizez! Am avut parte și de profesori foarte buni în facultate. De fapt, eu am terminat studiile universitare acum mulți ani și aceste lucruri s-ar putea să se mai fi schimbat... Oare?

Revenind la chestiune, "What Screens Want" e un articol de citit și salvat în bookmarks. Obligaatoriuuu!

22 octombrie 2013

CONNECTu' buclucaș

Din ciclul "ha? wtf!", am pațit-o iarăși. Cică mă sună nea clientul să verific dacă schema cutărică are tot ce-i trebuie pentru instalarea unei nu știu ce aplicații obscure. S-ar părea că cei care s-au ocupat de infrastructura lor, înainte de a prelua noi mustăria, pregătiseră puțin terenul, adică facuseră aceste minunate scheme, dar nu era nimeni sigur că ele erau configurate corect.

Întreb: Ce trebuie să fac?
Clientu': Păi verifică dacă "user"-ul se poate conecta și poate crea obiecte de DB! Adică, să aiba rolurile CONNECT și RESOURCE, plus drepturi de CREATE VIEW.
Eu: Atât?
Clientu': Atât!

Buooon, mare vrăjeală îmi zic! Ăsta da "task"! Mă apuc frumușel de verificat.
SQL> select grantee, granted_role
    from dba_role_privs
   where grantee in ('MUCI')
   union
  select grantee, privilege
    from dba_sys_privs
   where grantee in ('MUCI')
   order by 1; 

GRANTEE                        GRANTED_ROLE
------------------------------ ----------------------------------------
MUCI                           CONNECT
MUCI                           CREATE VIEW
MUCI                           RESOURCE
MUCI                           UNLIMITED TABLESPACE
Contactez clientul și îi transmit cu o mică zeflemea în glas: "Am verificat! E în regulă!". După care, din lipsă de altceva interesant, mai bag niște linii de cod în Vorax.

A doua zi primesc mail de la client:

Urgent!!! Am început procesul de instalare, dar primim următoarea eroare:
ORA-01045: user MUCI lacks CREATE SESSION privilege; logon denied
Haaa? WTF! Iau din nou la verificat. Rulez iar SELECT-ul de mai sus, același rezultat. Zic: să vezi că e o mică cretinătate la mijloc și aștia au umblat la rolul CONNECT și i-au scos dreptul de CREATE SESSION. Verificăm:
SQL> select * from dba_sys_privs where grantee='CONNECT';

GRANTEE                        PRIVILEGE                                ADM
------------------------------ ---------------------------------------- ---
CONNECT                        CREATE SESSION                           NO
Hmmm, nu-i asta! Rolul CONNECT arată bine! Ce-ar mai putea fi? Încerc să nu iau încă în considerare un posibil super-mega-bug. Noroc că îmi amintesc că era o fază cu rolurile. Le puteai activa sau dezactiva din aplicație. Am și folosit treaba asta pentru implementarea sistemului de autorizare folosit de o parte din aplicațiile dezvoltate de FITS, dar era vorba de roluri de aplicație și niciodată nu mi-a trecut prin cap să mă joc cu roluri sistem în felul ăsta.

În fine, s-ar părea că instinctul meu de DBA "bătrân" s-a confirmat:
SQL> SELECT * FROM DBA_ROLE_PRIVS WHERE GRANTEE='MUCI';

GRANTEE GRANTED_ROLE ADMIN_OPT DEFAULT_R
------- ------------ --------- ---------
MUCI    CONNECT      NO        NO
MUCI    RESOURCE     NO        YES
N-am nici cea mai vagă idee de ce simpaticii dinaintea noastră au configurat în felul ăsta, cert e că, de îndată ce m-am prins despre ce era vorba, soluția a fost simplă ca bună ziua:
SQL> alter user muci default role all;

User altered.
Note: Evident, la client, schema cu pricina nu se chema "MUCI". Orice asemănare cu "personaje" reale e pur întâmplătoare!