lafadot

L'utente non ha condiviso informazioni biografiche

Homepage: http://aivpills.wordpress.com

Gestione degli errori

La gestione degli errori è un elemento molto importante per la buona riuscita di un software di qualità. A nessuno piace ricevere messaggi di errore incomprensibili, con conseguenti comportamenti inattesi e magari anche la possibilità di perdere del lavoro già fatto.

La presenza di bug logici, che non vengono segnalati in fase di compilazione, è quasi inevitabile, o comunque molto difficile da evitare, ed è quindi molto importante prepararsi a gestire eventuali errori logici.

Quando il nostro programma esegue delle istruzioni che generano dei problemi, o non sono consentite o supportate, o comunque vanno in errore, viene generata una “eccezione”. Una eccezione è appunto un “evento” che notifica un problema accaduto in una qualsiasi funzione, il quale viene propagato a ritroso alla funzione che ha chiamato quella funzione, e cosi via fino alla funzione in testa a tutte (ad esempio il main).

In c++, come in molti altri linguaggi, esiste uno strumento per intercettare queste eccezioni, che è il costrutto try-catch.

Se abbiamo un blocco di codice di cui vogliamo intercettare eventuali eccezioni, possiamo quindi inserirlo in un try-catch in questo modo:

try

{

// istruzioni

}

catch(exception ex)

{

//codice in risposta dell’eccezione

}

In questo modo, se nel blocco di codice racchiuso nel try viene generata un’eccezione, l’esecuzione salta direttamente al blocco di codice contenuto nel “catch”, mettendo a disposizione un oggetto che contiene informazioni riguardo all’errore, come ad esempio il messaggio, un eventuale id, informazioni sullo stato ecc.

Potremmo ad esempio inserire del codice che mostri un messaggio user friendly tipo ” si è verificato un errore, contattare l’assistenza per ottenere un supporto”, piuttosto che un errore incomprensibile per l’utente tipo “Fatal error 0x03432 in system32.dll”

I tipi di eccezione sono tanti, tutti derivati dalla classe “exception”, ed ognuno è specifico al tipo di errore verificatosi. Esistono ad esempio eccezioni per deferenziazioni a puntatori nulli (il famoso null reference pointer”), o argomenti nulli passati a una funzione, o a errori di accesso in lettura o scrittura agli stream, e cosi via.

Può verificarsi anche il caso in cui uno stesso blocco di codice possa generare più tipi di eccezioni, e che noi vogliamo definire dei comportamenti diversi a seconda dell’eccezione generata.  Per farlo possiamo inserire più blocchi catch, specificando per ciascuno il tipo di eccezione che vogliamo intercettare.

E’ importante fare attenzione all’ordine in cui si inseriscono i blocchi catch, perchè essendo l’esecuzione del codice sequenziale, alcuni blocchi catch potrebbero essere esclusi. Vediamo un esempio:

try

{
//istruzioni
}

catch(exception ex)

{ // istruzioni di gestione dell’errore generico }

catch(overflow_error ex)

{//codice eseguito in seguito ad errore di tipo overflow_error }

 

In questo blocco try-catch abbiamo due blocchi catch, uno per gestire un’eccezione generica (exception) e uno per gestire un’eccezione di tipo overflow_error.

Se viene generata un’eccezione generica, viene eseguito il codice corrispondente al blocco catch(exception ex), e tutto va come previsto. Se invece si solleva un’eccezione di tipo overflow_error, siccome il blocco catch che intercetta le eccezioni generiche si trova più in alto, e siccome overflow_error è in effetti una classe ereditata da exception, ed è quindi anch’essa assimilabile al tipo exception, il codice eseguito è di nuovo quello del primo catch.

Invertendo l’ordine dei catch, ovvero mettendo l’eccezione più specifica in alto e quella più generica in basso, ciascun catch intercetterà soltanto le eccezioni del tipo specifico indicato, o di tipi ereditati da esso.

try

{
//istruzioni
}

catch(overflow_error ex)

{//codice eseguito in seguito ad errore di tipo overflow_error }

catch(exception ex)

{ // istruzioni di gestione dell’errore generico }

 

Possiamo sollevare noi stessi eccezioni in particolari situazioni, in seguito a dei controlli tali per cui riteniamo che l’esecuzione non possa continuare. Se ad esempio stiamo scrivendo una funzione che divide due numeri, dovremmo controllare che il divisore non sia zero. In tal caso, possiamo sollevare un’eccezione, del tipo che riteniamo più opportuno:

float Dividi(float a, float b)

{

if(b==0)

{ throw exception(“divisione per zero non consentita”); }

return a/b;

}

con throw tipoeccezione(parametri) generiamo un’eccezione del tipo specificato, usando uno dei costruttori messi a disposizione dal tipo specifico di eccezione. Essa verrà propagata a ritroso nella pila di chiamate a funzione fino alla prima, a meno che in una delle funzioni chiamate a cascata ci sia un blocco try-catch del tipo uguale all’eccezione da noi sollevata, o di un tipo da cui essa eredita.

Se invece ci accorgessimo che non esiste un tipo di eccezione che coincide esattamente con le nostre esigenze, possiamo crearne una, ereditando dal tipo più generico exception o da un’eccezione simile a quella che ci serve, estendendola a nostra discrezione.

E’ nostra cura specificare i tipi di eccezione sollevati dai metodi che scriviamo. Per il caso precedente, nell’header della nostra classe scriveremo ad esempio:

float Dividi(float a, float b) throw(exception, miaccezione)

in questo caso chi utilizzerà la nostra funzione, saprà che il metodo può generare due tipi di eccezioni, ovvero excepion e miaeccezione, e adeguerà il suo codice con eventuali blocchi try-catch, se lo riterrà necessario.

 

Lascia un commento

Overloading di operatori

L’overloading di un operatore consiste nella ridefinizione del funzionamento di un operatore esistente. L’overload di un operatore è possibile a patto che i parametri dell’operatore siano diversi dalla definizione standard.

Non si possono ridefinire operatori di tipi primitivi, come gli int, char, float, ecc.

Questa caratteristica ci consente di definire degli operatori per le classi scritte da noi nel nostro progetto, in modo da scrivere codice compatto e pulito.

Se ad esempio abbiamo una classe Warrior, che rappresenta un guerriero di un gioco, il quale ha un membro di tipo int che contiene un ID univoco, potremmo voler avere un operatore che ci consenta di sapere se due variabili di tipo Warrior siano effettivamente lo stesso warrior, ovvero entrambe abbiano lo stesso ID.

Se facessimo qualcosa di questo tipo :

Warrior w1;

w1.ID = 10;

Warrior w2;

w2.ID = 10;

return (w1 == w2) ;

otterremmo un false come ritorno, perchè l’implementazione di default dell’operatore == non prevede il confronto che ci aspettiamo e che ci serve. Chiaramente potremmo fare direttamente:

return(w1.ID == w2.ID)

ma ci sono decine di motivazioni per spiegare i vantaggi di scrivere questo controllo in un posto solo, tra cui i vantaggi dell’incapsulamento, della riusabilità, della manutenibilità, il debugging, e tanti altri.

Detto questo, vediamo come ridefinire l’operatore ==:

bool Warrior::operator==(const Warrior &other) const {

… return this->ID == other->ID;

}

L’operatore di uguaglianza da come output un valore booleano vero o falso, per cui come valore di ritorno della funzione di overload useremo un bool. Warrior:: sta ad indicare che l’operatore è sovraccaricato nella classe che ci interessa (e lo scriveremo quindi nel file .cpp della nostra classe Warrior).  La keywork “operator” indica che stiamo appunto effettuando l’overload di un operatore, e il simbolo == indica quale operatore stiamo ridefinendo.

Come parametro di ingresso, l’operatore == vuole un riferimento costante a un tipo, e visto che dobbiamo comparare due Warrior, indicheremo come parametro un “const Warrior &other”, ovvero un riferimento a un oggetto di tipo Warrior, ma costante. Viene passato un riferimento costante per due motivi: 1) il riferimento implicitamente si traduce in un passaggio di un puntatore, che è molto più efficiente rispetto a un passaggio per valore 2) il fatto che sia costante è garanzia del fatto che all’interno dell’operatore l’oggetto da comparare non venga modificato.

A ulteriore indicazione che il metodo non modificherà nessun dato, è aggiunta la keyword const anche al corpo del metodo, che impedisce appunto la modifica di dati al suo interno.

La logica dell’operatore è banale, basta ritornare infatti un semplice confronto tra i membri ID dei nostri Warrior, dove this è un puntatore all’oggetto su cui è chiamato l’operatore, mentre other è l’oggetto di confronto. In pratica, se facciamo

a==b

stiamo chiamando l ‘operatore == sull’oggetto a, il che significa che sarà chiamata l’implementazione dell’operatore == definita per quel tipo, dove this è a, e il parametro passato alla funzione è il riferimento a b

quindi, avendo ridefinito l’operatore == per il nostro Warrior, rieseguendo il codice:

return w1 == w2; 

dove w1 e w2 sono due oggetti di tipo Warrior, verrà richiamata la funzione definita da noi nella nostra classe Warrior, che eseguirà il confronto tra gli ID.

Allo stesso modo possiamo effettuare l’overload di un operatore a livello globale, ovvero non dentro una classe ma in una funzione globale, in questo modo:

si può ridefinire un operatore a livello globale:
bool operator ==(const Warrior& w1, const Warrior& w2)
{
return w1->ID == w2->ID;
}

la differenza rispetto all’overload nella nostra classe è che l’operatore, essendo globale, non ha nessun puntatore implicito a se stesso, ovvero nessun “this”. Ha bisogno per cui che gli passiamo sia il primo che il secondo oggetto da confrontare.

Lascia un commento

STL e iteratori

Gli iteratori sono degli oggetti che ci permettono di accedere agli elementi di un container di prima classe (quindi non quelli che al loro interno contengono a loro volta un container, come lo stack o la coda), in modo causale (tramite indice) o in avanti o indietro. La caratteristica del container è che le modalità con cui li utilizziamo su container diversi sono sempre le stesse, anche se poi per ciascun container esiste un iterator specifico i cui metodi implementano logiche specifiche e ottimizzate per ciascun container.

Un iterator in sostanza si comporta come un puntatore, mantenendo un riferimento a un certo elemento del container, e possiamo quindi accedere al valore a cui fa riferimento con l’operatore *, o avanzare all’elemento successivo con l’operatore ++, o a quello precedente con l’operatore –, e cosi via.

le diverse implementazioni degli iteratori sono in realtà delle classi innestate nelle classi del rispettivo contenitore; questo significa che nel container “vector” c’è una classe vector::iterator che definisce metodi e membri del relativo iterator e il comportamento che deve avere.

I container hanno due metodi fondamentali, begin() e end(), che restituiscono rispettivamente un iteratore che punta al primo elemento del container, e un iteratore che punta all’indirizzo di memoria successivo all’ultimo elemento, quindi a una POSIZIONE VUOTA.

Vediamo come utilizzare un iterator:

vector v; // dichiaro un container di tipo vector di int
v.push_back(1);// inserisco valori
v.push_back(2);// inserisco valori
v.push_back(3);// inserisco valori
v.push_back(4);// inserisco valori

vector::iterator it = v.begin(); // dichiaro una variabile di tipo iteratore di vector, e la inizializzo chiamando il metodo begin() del vettore, che restituisce un iteratore alla prima posizione

count << *it; // stampo il valore referenziato dall’iteratore, che è al primo posto del container

it++; // incremento l’iteratore di una posizione, a prescindere da come è fatto il container. Al suo interno l’iteratore ha un suo overload dell’operatore ++ ottimizzato per la struttura dati del vector. Se fosse stata una list, avrei sempre utilizzato it++, ma al suo interno l’iteratore avrebbe avuto un overload diverso dell’operatore ++, basato sulla struttura dati della lista.

Oppure possiamo fare un ciclo dal primo all’ultimo elemento in questo modo:

for(vector::iterator it = v.begin(); it < v.end(); ++it)
{
count << *it;
}

la prima parte del for dichiara una variabile di tipo iteratore di vector e la inizializza con l’iteratore alla prima posizione del mio vector.
La seconda parte controlla che l’iteratore sia sempre ad una posizione precedente a quella restituita da end() (ovvero la posizione dopo l’ultimo elemento).
La terza parte incrementa l’iteratore per spostarlo alla posizione successiva.

Esistono inoltre diversi tipi di iteratori, come ad esempio il reverse_iterator, che ridefiniscono l’effetto di alcuni operatori per consentire di utilizzare la stessa logica avendo comportamenti diversi.
Ad esempio il reverse_iterator si comporta in modo opposto all’iterator, infatti usando l’operatore ++, otteniamo uno spostamento indietro dell’operatore, mentre usando il — otteniamo uno spostamento in avanti.
La sintassi del ciclo cambierà solo per quanto riguarda il tipo di iteratore istanziato, mentre il resto rimane invariato.
Per ciclare un vector dalla fine all’inizio, possiamo ad esempio fare cosi:

vector::reverse_iterator revIt = v.end();
while (revIt != v.begin()) {
std::cout << ‘ ‘ << *revIt;
++revIt;
}

in questo caso l’istruzione ++revIt decrementa l’iteratore, invece di incrementarlo, proprio perchè il reverse_iterator ha un’implementazione diversa dall’operatore ++ rispetto all’iterator normale.

Come visto per i costruttori dei container, si può istanziare un container specificando due iteratori come indirizzi di inizio e fine memoria da cui copiare, e lo si può fare passando degli iteratori, in questo modo:

vector<int> v2(v.begin(), v.end());

in questo modo indico a v2 di copiarsi e aggiungere al suo interno degli elementi presi dall’iteratore restituito da v.begin(), e iterato fino a v.end(). Ovviamente v.end() non contiene elementi, per cui è escluso dai limiti della copia.

 

Lascia un commento

STL e Container

STL sta per Standard Template Library, ovvero una libreria di classi basate su template. Particolarmente utili sono i containers che STL mette a disposizione, ovvero dei contenitori di oggetti, ognuno con caratteristiche diverse, dei pro e dei contro.

E’ molto importante sapere quale container utilizzare in ogni situazione, perchè sbagliando il container ne potremmo perdere molto in prestazioni, magari vanificando gli sforzi fatti per ottenere perfomance in altri punti del nostro programma.

I container non sono altro che contenitori dinamici di oggetti, ovvero si ridimensionano a nostro piacimento quando aggiungiamo o togliamo elementi, togliendoci l’onere di gestione che avevamo con gli array statici, a cui dobbiamo dare una dimensione fissa al momento della dichiarazione. la parola “template” indica che quando si crea un container bisogna specificare il tipo di elementi che dovrà contenere, in modo che il container sappia come trattarli. Questo li rende molto flessibili perchè con un ristretto numero di tipi di container abbiamo un numero grandissimo di possibilità.

I container sono di due tipi: associativi o sequenziali. Quelli sequenziali si basano sul concetto che ciascun elemento abbia una posizione specifica rispetto agli altri (è il primo, è l’ultimo, è il quinto), mentre quelli associativi si preoccupano di associare un elemento a una chiave, prescindendo dall’ordine effettivo in cui gli elementi si trovino nel container.

Queste due particolarità sono già il primo criterio di scelta di un container: sceglieremo un container sequenziale quando ad esempio cicleremo molto spesso il container (esempio: ogni tot secondi devo rigenerare dei punti vita a un’armata di soldati), useremo un container associativo quando dobbiamo accedere frequentemente a degli elementi specifici del container, in quanto i container associativi sono organizzati secondo un albero binario, per cui la ricerca di un elemento al loro interno ha una complessità O(log n) che è minore di una ricerca sequenzale, che ha complessità O(n). Per approfondire gli alberi binari, Albero binario da Wikipedia

Altri aspetti importanti da tenere presente:

1. aggiungendo un elemento ad un container, viene fatta una copia dell’oggetto e salvata nel container.

2. il container non si occupa di eliminare gli oggetti, per cui prima di eliminare il container, vanno distrutti a mano gli oggetti al suo interno.

Vediamo ora i container più comuni:

1. Vector

Il vector è un container sequenziale, ed ha lo stesso comportamento di un normale array, salvo che non dobbiamo decidere a priori la sua lunghezza. Sarà infatti il container ad auto adattarsi in base al numero di elementi che inseriamo al suo interno.

Dietro le quinte, il vector utilizza in effetti un array statico. Quando inseriamo un elemento in più di quelli che l’array può contenere, il vector si occupa di creare un array più grande, di una dimensione proporzionale al numero di elementi contenuti, e copiare tutti gli elementi dal vecchio al nuovo array.

Basandosi su un singolo array, gli elementi allocati in memoria sono contigui, e questo rende molto veloce l’iterazione degli elementi e l’accesso tramite indice agli elementi (accesso casuale), e consente inoltre di utilizzare l’aritmetica dei puntatori (dato un puntatore p, posso fare p++, p–, ecc)

Quando rimuoviamo elementi, la memoria già allocata rimane tale, come vale per un normale array, per cui in termini di risorse utilizzate, più aggiungiamo elementi e più la memoria occupata aumenta, senza mai diminuire fino alla distruzione del vector stesso.

Quando inseriamo un elemento in testa o in mezzo a due altri elementi, il vector è meno performante di altri container perchè gli elementi successivi a quello inserito devono essere tutti spostati di un posto verso la coda, e questo richiede tempo. La stessa cosa vale per la cancellazione di elementi in testa o in mezzo, che comporta lo spostamento di un posto verso la testa di ciascun elemento a partire da quello eliminato.

vediamo i costruttori e i metodi del vector, ma più in generale della maggior parte dei container, a parte casi specifici di alcuni container.

il vector ha una size , ovvero il numero di elementi realmente contenuti al suo interno, e una capacity, ovvero il numero massimo di elementi che si possono inserire prima che il vector si ridimensioni  (di norma del doppio dell’attuale capacità).

costruttori di un vector:

vector<int> v; // costruttore senza parametri, istanzia un vector di interi (<int>) di size = 0 e capacity = 0

vector<int> v[5];  // istanzia un vector in cui vengono inseriti 5 elementi inizializzati chiamando il loro costruttore di default  in base al tipo specificato

vector<int> v[5,2]; // istanzia un vettore di 5 elementi interi di valore 2

vector<int> v[begin,end]; // istanzia un vettore copiando elementi compresi tra due puntatori o due posizioni di iteratore (equivalenti di fatto a puntatori) che fanno da indirizzi di inizio e fine da cui copiare gli elementi. Da notare che l’indice indicato come inizio è compreso nella copia degli elementi, mentre l’indice di fine è escluso

metodi di un vector:

.size() // numero di elementi contenuti

.capacity // numero di elementi che può contenere senza doversi ridimensionare

.push_back() // aggiunge un elemento in coda

.back() // restituisce l’ultimo elemento

.front() //restituisce il primo elemento

.reserve(int) // imposta la capacity, utile per controllare manualmente la memoria allocata dal vector

.pop_back() // toglie un elemento in coda

.remove() // rimuove un elemento, ma la memoria rimane allocata. se l’elemento è in mezzo, vengono spostati gli elementi dalla coda alla testa per riempire i vuoti, e gli slot in coda rimangono allocati

.erase(index) // cancella effettivamente l’elemento, liberando anche la memoria.

.clear() elimina tutti gli elementi del vettore, la capacity rimane ovviamente uguale

.at[index] restituisce l’elemento presente alla posizione indicata con index, lancia un’eccezione se l’indice non è compreso tra i limiti del vector, ovvero tra 0 e .size()-1

2.Deque

Deque sta per double-ended queue, ovvero coda a due estremi. Anch’esso sequenziale, è ottimizzato per inserire e rimuovere elementi sia in testa che in coda. Quando un deque è pieno, viene allocata ulteriore memoria sia in testa che in coda, di cui viene tenuta traccia con un array di puntatori, per passare da un blocco all’altro di memoria nelle operazioni di lettura e scrittura.
Un vantaggio del deque rispetto al vector è negli inserimenti in testa, perchè il deque non deve spostare tutti gli elementi verso la coda perchè li salva in un nuovo blocco di memoria, salvandosi anche il percorso che deve fare per saltare da un blocco all’altro in modo ordinato.
Un altro vantaggio rispetto al vector è che rimuovendo elementi dal deque, quando un intero blocco di memoria si libera, viene deallocato e quindi rilasciato dalla memoria, a differenza del vector che non si restringe mai una volta espanso.

Il deque aggiunge principalmente due metodi a quelli già visti nel vector:

.push_front() // inserisce un elemento in testa

.pop_front() // rimuove un elemento in testa

3.List

La list è un insieme sequenziale di elementi, ciascuno dei quali ha un puntatore al precedente e al successivo. Questo comporta la possibilità di poter salvare gli elementi non necessariamente in posizioni di memoria contigue, perchè in qualsiasi posto si trovino, ogni elemento sa dove si trova il suo prev e il suo next.
Il vantaggio di questa gestione della memoria è che l’inserimento in mezzo a due elementi è il più efficiente tra i container sequenziali, in quanto non serve nessuno spostamento degli elementi successivi, né in inserimento né in cancellazione, ma si vanno a modificare solo i puntatori a prev e next degli elementi coinvolti.
Lo svantaggio è che ogni elemento occupa, a parte la memoria necessaria per se stesso, anche lo spazio per i puntatori al next e al prev.
Non è inoltre possibile accedere tramite indice a un elemento.

Per quanto riguarda i metodi, i fondamentali della list sono quelli già visti per il vector e il deque.

4.Map

La map è un container associativo, ed è paragonabile ad un dizionario di coppie chiave/valore. In pratica ogni elemento è formato da un oggetto pair, che ha come membri “first” che contiene la chiave di ricerca e “second” che contiene l’elemento vero e proprio da conservare. Lo scopo di una map è quello di rendere molto efficiente l’accesso a un elemento a partire dalla sua chiave. Gli elementi di una map vengono ordinati in base ad un albero binario, che ad ogni inserimento o rimozione viene bilanciato per minimizzarne la complessità durante le ricerche. In una map non è possibile aggiungere elementi con la stessa chiave. E’ possibile invece farlo con la multimap, che è identica alla map ma accetta chiavi duplicate.

Questo vuol dire che mentre una map è molto efficiente per accedere frequentemente ad un elemento, è molto meno efficiente l’inserimento e la rimozione. Ne consegue che se si prevede di inserire e cancellare elementi di frequente, probabilmente la map non è la scelta giusta.

E’ stimato che la map sia più efficiente di un vector soltanto superati i 10 elementi, per cui anche questo è da tener presente scegliendo il tipo di container da usare.

A differenza degli altri container, per istanziare una map bisogna indicare due tipi invece che uno, che saranno il tipo della chiave e il tipo dell’elemento. Ad esempio per fare una map che come chiave abbia un numero e come elemento una stringa, possiamo scrivere map<int, string>.

Per costruire l’albero binario, del valore che verrà passato come chiave verrà calcolato l’hash, la cui efficienza è tanto maggiore quanto più semplice è la chiave. Specificando ad esempio come chiave un intero, avremo che l’hash di un intero è l’intero stesso, ed è quindi molto efficiente. Se specifichiamo invece un tipo complesso, il calcolo dell’hash sarà più lento. Abbiamo detto comunque che la map ha senso se ho un set di elementi che cambiano pochissimo nel tempo o non cambiano affatto, per cui l’eventuale hash iniziale per il calcolo dell’albero binario potrebbero non incidere cosi tanto sulle performance finali.

Il modo più esplicito per inserire valori in una map è quello di fare un pair, assegnare chiave e valore ai membri first e second del pair, e aggiungerlo quindi alla map, come segue:

map<int, string> m;

pair<first k, second v> v;

v.first = key

v.second = value;

m.insert(v);

// oppure

pair<int, int> p = make_pair(3,5);

m.insert(p);

5. Set

Il set è simile alla map, con la differenza che la chiave e il valore coincidono. ne consegue che, essendo anch’esso ordinato in modo binario, gli elementi sono automaticamente ordinati ad ogni inserimento e cancellazione in seguito al bilanciamento dell’albero binario.

dichiarando un set, oltre a specificare il tipo come tutti gli altri container, possiamo indicare un ordinamento diverso da quello di default, che è crescente.

Altra particolarità del set è che non accetta valori duplicati, in quanto essi stessi fanno da chiave di ricerca. E’ possibile inserire valori duplicati utilizzando il multiset al posto del set, che ha le stesse caratteristiche del set, salvo accettare anche valori duplicati.

6. Stack

Lo stack non è considerato propriamente un container puro, ma viene detto adattatore, perchè in effetti nella pratica non implementa un modo diverso di salvare dati, ma sfrutto un container esistente per ridefinire delle logiche specifiche di lettura e scrittura dati.

Uno stack è una PILA, ovvero un modo di immagazzinare dati in modalità LIFO (Last in, First Out). In una pila, come in una coda, gli elementi possono essere inseriti solo da un lato, e rimossi solo dallo stesso lato da cui vengono inseriti, da questo deriva che il primo elemento che entra nella pila sia per forza di cose l’ultimo ad uscirne, perchè dovendo uscire dallo stesso lato da cui è entrato, non può uscire finchè tutti quelli inseriti dopo di lui siano usciti.

Per default, lo stack utilizza un deque come base per il salvataggio dei dati, ma possiamo specificare noi che container usare in fase di dichiarazione, ad esempio con stack<int, vector<int>> dichiariamo uno stack di interi, indicando che al posto di deque vogliamo utilizzare un vector.

Gli unici metodi implementati per lo stack sono:

push() // inserisce un elemento in testa

pop() // rimuove un elemento dalla testa, senza restituire nessun esito o valore rimosso

top() // legge l’ultimo valore inserito, ovvero quello in testa in quel dato momento.

7.Queue

La queue implementa il concetto di coda, o di fila, per cui il primo elemento che entra nella coda è anche il primo ad uscirne, ovvero l’opposto dello stack. Anche la queue è un adattatore, e sfrutta di default un deque, ma possiamo indicare in fase di dichiarazione quale container usare, come per lo stack. 

I metodi implementati per la queue sono:

front() // restituisce il primo elemento entrato (quello in testa)

back()  // restituisce l’ultimo elemento entrato (quello in coda)

push_back() // inserisce un elemento in coda

pop_front() // rimuove in testa

 

Lascia un commento

Best Practices c++

Un po di best practices e consigli da seguire per del codice ben fatto:

1. se non c’è nel vostro progetto, create un file header chiamato “stdafx.h”, in cui vanno messe per convenzione tutte le inclusioni di header di sistema o di librerie di terze parti, ovvero gli #include <…>, e tutte le direttive “using namespace …”

NON vanno inclusi assolutamente gli header scritti da voi.

2. Quando creiamo un header, è molto importante aggiungere subito la direttiva #pragma once, che impedisce che l’header venga incluso più di una volta.

3. Se all’interno della nostra classe facciamo riferimento ad altre classi, possiamo evitare di fare l’include del rispettivo header, utilizzando la forward declaration, ovvero “class NomeClasse;”. Il compilatore accetta questa dichiarazione come un tipo e ci consente di compilare.

Gli unici #include che andrebbero fatti sono quelli nel file .cpp relativo all’omonimo file .h e quelli relativi alle classi che stiamo eventualmente estendendo/ereditando.

4. Usare una convenzione standard per i nomi di membri, variabili e metodi è la base per lavorare in gruppo senza perdere tempo a interpretare geroglifici. Quella suggerita prevede che le variabili membro siano chiamate m_+iniziale tipo+nome esplicativo con notazione camel case. Ad esempio:

m_vWarrior = variabile membro di una classe, di un tipo generico v), che rappresenta molto probabilmente un’istanza di una classe Warrior

m_pWarrior = variabile membro di una classe, di tipo puntatore (p), che rappresenta molto probabilmente un’istanza di una classe Warrior

m_iWarEnergy = variabile membro di una classe, di tipo intero, che conterrà l’energia di un Warrior

5. Potreste avere degli errori riguardo a delle intestazioni precompilate non trovate in fase di compilazione. Per evitarlo, andate nelle proprietà del progetto, alla voce Proprietà di configurazione -> C/C++ -> Intestazioni Precompilate  e impostate la voce “Intestazione Precompilata” a “Senza Intestazioni Precompilate”

6. Nella definizione di metodi virtual, quindi ridefinibili in una classe derivata, è bene tener presente che un metodo virtual in fase di esecuzione va ad occupare un indirizzo di memoria (4 byte) nella “virtual table”, che si occupa di capire quale metodo deve richiamare quando ad esempio viene richiamato un metodo presente sia nella classe base che nella classe ereditata. Per cui se non è necessario e sapete che non ci sarà mai l’esigenza di sovrascrivere, non fate metodi virtual.

 

 

 

Lascia un commento

Caricare file csv come tabella Oracle

Se avete già fatto questa operazione con Access, MySql, SqlServer o che ne so io, dimenticate di trovare una guida del tipo “fate tasto destro e…” perchè in Oracle tasto destro non l’hanno nemmeno implementato, non so se esiste qualche interfaccia Oracle sensibile al tasto destro ma penso di no.

Al dilà di quello che leggerete sotto su come caricare un csv in Oracle, sappiate che non c’è scritta tutta una serie di cose, tipo che se nel csv avete testi che vanno a capo non funziona, che lettere accentate o caratteri strani vi causeranno problemi anche su installazioni di oracle in italiano, che probabilmente passerete del tempo a sistemare i maledetti dati che vi sono arrivati a seguito di un commerciale che si sarà venduto qualcosa al grido di “vabbè ma tanto poi i dati li carichiamo da excel”.
L’unico vero consiglio che vi posso dare è il seguente: non andate in giro a dire che caricate fogli excel in Oracle, perchè non è cosi.

Ad ogni modo vediamo come caricare il maledetto csv o excel che ci ha mandato il cliente, dopo averlo con cura riempito di dati incoerenti, mal formattati, colorati e incasellati stile battaglia navale.

Per caricare un file csv, o un excel salvato in csv, è innanzitutto necessario copiare il file in una directory Oracle registrata.
Per conoscere quali directory sono registrate in Oracle, basta eseguire questa query:
select * from all_directories
con questa query possiamo sapere il nome della directory e il relativo percorso su filesystem.
Scegliere una delle directory, ad esempio DATA_PUMP_DIR e copiare il file csv nel relativo percorso.
A questo punto bisogna creare una tabella collegata a dati esterni, con un numero e tipo di campi coerenti con il file csv di partenza, come nell’esempio seguente.
Supponendo che il file csv abbia 3 campi, Nome, Cognome, Indirizzo, lo script di creazione sarà
CREATE TABLE DATIESTERNI_XLS (
NOME VARCHAR2(50),
COGNOME VARCHAR2(50),
INDIRIZZO VARCHAR2(100)
)

Organization external
(type oracle_loader
default directory DATA_PUMP_DIR
access parameters (records delimited by newline
fields terminated by ‘;’
MISSING FIELD VALUES ARE NULL )
location (‘fileesterno.csv’))
reject limit 15000 ;
Come si nota, il nome della tabella è indifferente, mentre il tipo e la dimensione dei campi deve essere uguale o superiore(la dimensione) ai dati contenuti nel csv.
“records delimited by newline” indica che la terminazione del record è identificata dal carattere di fine riga/nuova linea
mentre “fields terminated by ‘;'” indica quale separatore cercare per identificare i valori di ogni singolo campo.
“location (‘fileesterno.csv’)” è invece il nome del file csv da importare, senza l’intero percorso.
Una volta lanciato lo script, verrà creata la tabella in ogni caso, e verranno creati due file di log, nella cartella in cui è contenuto il file csv.
Il primo è un log della procedura, che riporta eventuali errori di importazione per ogni singola riga, e si chiamerà, in questo esempio, DATIESTERNI_XLS_xxxx_xxxx.log.
Il secondo file riporta le righe del csv che non sono state importate in seguito ad errori di caricamento, e si chiamerà, in questo esempio, DATIESTERNI_XLS_xxxx_xxxx.bad.
ATTENZIONE: finchè non facciamo nessuna query di selezione, Oracle non cercherà di importare i dati e quindi, non vedendo log di errori, potreste pensare che sia andato tutto ok, ma non è cosi. Dovete fare almeno una SELECT perchè effettivamente siano caricati i dati in tabella.

Se la procedura è andata completamente a buon fine, eseguendo la query
select count(*) from DATIESTERNI_XLS;
otterremo lo stesso numero di righe del file csv.
N.B: se la prima riga del csv contiene le intestazioni di colonna, esse verranno importate come una normale riga dati, per cui è bene toglierla dal file csv editandolo con il notepad.
Fatto questo, la tabella DATIESTERNI_XLS è utilizzabile come una normalissima tabella Oracle IN SOLA LETTURA.
E’ anche vero che se apriamo il file csv e modifichiamo una riga, automaticamente essa verrà riportata nella tabella collegata.
Se si desidera utilizzare la tabella anche in scrittura, basta creare una tabella identica a quella collegata, con il comando
CREATE TABLE DATIESTERNI AS SELECT * FROM DATIESTERNI_XLS
In questo modo avremo una tabella Oracle disponibile al 100%, anche in scrittura, non più collegata al file excel e gestita con i normali meccanismi di storage di Oracle.

Lascia un commento

Recyclebin di Oracle

A partire dalla versione 10g, Oracle ha implementato il recyclebin, che non è altro che il cestino.
Chi non ha mai sbrasato una tabella fondamentale del suo schema, piena zeppa di dati più che fondamentali, perdendo diversi anni di vita nello sgomento, pensando a come insabbiare il tutto o scaricare immediatamente la responsabilità su qualche meccanismo di backup/ripristino andato a male? evidentemente alla Oracle qualcuno ha fatto presente questo scenario, e quindi si sono inventati il recyclebin, e quindi tanto di cappello, considerando che parliamo di database.
Ad ogni modo, chissà come, chissà quando, chissà quale algoritmo lunare avranno inventato, ma guarda che strano cancello roba e non mi si libera lo storage, ma che problema ci sarà, chi sia stato non si sa…. ed ecco svelato ilcano. Il recyclebin rinomina gli oggetti cancellati, loggandosi ovviamente la cancellazione e togliendoli dalle consuete viste di sistema che listano oggetti.

Piccola digressione: non ho ancora capito se una tabella in oracle si può rinominare o no. diversi tool di gestione database consentono la rinomina, ma di fatto dietro le quinte creano una nuova tabella, ci travasano i dati e cancellano la vecchia, il che potrebbe anche fare incazzare più di qualcuno, tra indici, statistiche, ottimizzazioni e cavoli a merenda.

Ad ogni modo, strumento utilissimo che vi salva appunto le terga nel momento in cui cancellate per sbaglio una oggetto. Attenzione, parliamo di oggetti, non di dati, per cui tutto questo discorso vale solo se cancellate ad esempio un intera tabella, non certo se fate un bel DELETE FROM, da quello non vi salva certo il recyclebin ma dovete affidarvi ai meccanismi di rollback, flashback, flash gordon e capitan america che ritenete più opportuni, ammesso che li abbiate mai attivati sul vostro db, visto che su Oracle qualsiasi virgola muovi serve un team di architetti di sistema con almeno 3 anni di servizio sulla USS Enterprise.

Per cui, caro amico, se hai fatto DELETE o TRUNCATE sui dati e speravi di aver trovato ciò che ti avrebbe salvato, una pacca sulla spalla non te la leva nessuno, puoi riprendere a googlare e a sudare freddo.

Vediamo Come funziona

Ci sono due viste di RB(chiameremo RB il recyclebin): USER_RECYCLEBIN e DBA_RECYCLEBIN.
Di defaut, se leggete RECYCLEBIN, state leggendo USER_RECYCLEBIN. Sempre di default, il RB
è abilitato di default dalla 10g, ma si può disabilitare dal parametro RECYCLEBIN a livello di sistema o di sessione.
Come dicevamo, quando droppiamo un oggetto, questo viene rinominato, e ovviamente tutti gli altri oggetti eliminati a cascata(indici, chiavi, trigger, ecc), con un nome che comincia con BIN$.

Vediamo un esempio, creiamo una tabella con un record di prova
SQL> create table tst (col varchar2(10), row_chng_dt date);

Table created.

SQL> insert into tst values (‘Version1′, sysdate);

1 row created.

SQL> select * from tst ;

COL ROW_CHNG
———- ——–
Version1 16:10:03
Se il RB è attivo, droppando la tabella, la ritroveremo nella vista RECYCLEBIN:
SQL> drop table tst;

Table dropped.

SQL> select object_name, original_name, type, can_undrop as “UND”, can_purge as “PUR”, droptime
2 from recyclebin
SQL> /

OBJECT_NAME ORIGINAL_NAME TYPE UND PUR DROPTIME
—————————— ————- —– — ——————-
BIN$HGnc55/7rRPgQPeM/qQoRw==$0 TST TABLE YES YES 2006-09-01:16:10:12

Da notare che i dati sono ancora in tabella e possono essere recuperati con una normale query, ovviamente usando il nuovo nome assegnato alla tabella:
SQL> alter session set nls_date_format=’HH24:MI:SS’ ;

Session altered.

SQL> select * from “BIN$HGnc55/7rRPgQPeM/qQoRw==$0” ;

COL ROW_CHNG
———- ——–
Version1 16:10:03

Proprio per questo motivo è possibile tornare indietro e annullare il drop, facendo un “flashback drop”. Il comando è FLASHBACK TABLE… TO BEFORE DROP, e come scaltramente starete pensando, non fa altro che riportare la tabella al nome originale :
SQL> flashback table tst to before drop;

Flashback complete.

SQL> select * from tst ;

COL ROW_CHNG
———- ——–
Version1 16:10:03

SQL> select * from recyclebin ;

no rows selected
Proprio per questo se droppiamo una tabella anche di dimensioni sensibili non vedremo liberarsi spazio nel tablespace. Di fatto i dati non si sono mossi da dov’erano, e non lo faranno finchè non decideremo di eliminare definitivamente la tabella dal nostro RB. Per farlo basta il comando PURGE. Vedete nell’esempio come dopo il purge l’oggetto non compaia piu nel RB, il che vuol dire che è stato definitivamente eliminato, e quindi non più ripristinabile.
SQL> select object_name, original_name, type, can_undrop as “UND”, can_purge as “PUR”, droptime
2 from recyclebin
SQL> /

OBJECT_NAME ORIGINAL_NAME TYPE UND PUR DROPTIME
—————————— ————- ————————- — — ——————-
BIN$HGnc55/7rRPgQPeM/qQoRw==$0 TST TABLE YES YES 2006-09-01:16:10:12

SQL> purge table “BIN$HGnc55/7rRPgQPeM/qQoRw==$0” ;

Table purged.

SQL> select * from recyclebin ;

no rows selected

Se vi siete sbagliati pure qua, lasciate stare, o si è fatto veramente troppo tardi, o dovevate andare a letto prima, oppure avete il cervello bruciato da qualche acido importante.
Ma proseguiamo…

Ci sono vari modi di fare il purge. facendo PURGE RECYCLEBIN si svuota il recyclebin dell’utente (ricordate il sinonimo RECYCLEBIN corrisponde a USER_RECYCLEBIN), mentre se siamo dba possiamo fare PURGE DBA_RECYCLEBIN e cancellare tutto il cancellabile, in un impeto di megalomania.

Se non sappiamo o non possiamo disattivare il recyclebin, ma abbiamo comunque bisogno di sfruttare al massimo il nostro tablespace o la nostra quota utente, il RB ci viene in aiuto, per quello che può. In sostanza, quando abbiamo finito lo spazio, il RB comincia a cancellare gli oggetti a partire dai meno recenti, quel tanto che basta per far spazio ai nuovi oggetti e dati che stiamo scrivendo. Se il nostro tablespace è AUTOEXTEND ON, ovvero si autoestende, prima di estendere un datafile il RB cancellerà degli oggetti. Il che vuol dire che l’ingombro del cestino non influirà sulla larghezza massima del tablespace, anche se è autoestendibile.

…e se cancello più volte una tabella con lo stesso nome?

A parte il fatto che probabilmente se lo stai facendo sei un po confuso, proviamo a farlo:
SQL> create table tst (col varchar2(10), row_chng_dt date);

Table created.

SQL> insert into tst values (‘Version1’, sysdate);

1 row created.

SQL> drop table tst;

Table dropped.

SQL> create table tst (col varchar2(10), row_chng_dt date);

Table created.

SQL> insert into tst values (‘Version2’, sysdate);

1 row created.

SQL> drop table tst;

Table dropped.

SQL> select object_name, original_name, type, can_undrop as “UND”, can_purge as “PUR”, droptime
2 from recyclebin;

OBJECT_NAME ORIGINAL_NAME TYPE UND PUR DROPTIME
—————————— ————- —– — — ——————-
BIN$HGnc55/7rRPgQPeM/qQoRw==$0 TST TABLE YES YES 2006-09-01:16:10:12
BIN$HGnc55/8rRPgQPeM/qQoRw==$0 TST TABLE YES YES 2006-09-01:16:19:53
Query the two dropped tables to verify that they are different:

SQL> select * from “BIN$HGnc55/7rRPgQPeM/qQoRw==$0”;

COL ROW_CHNG
———- ——–
Version1 16:10:03

SQL> select * from “BIN$HGnc55/8rRPgQPeM/qQoRw==$0” ;

COL ROW_CHNG
———- ——–
Version2 16:19:45

Come vedete dai risultati interrogando il RB otteniamo due tabelle droppate, a orari differenti ovviamente. Se facciamo un FLASHBACK DROP per la tabella TST, quale versione ripristinerà Oracle?
SQL> flashback table tst to before drop;

Flashback complete.

SQL> select * from tst;

COL ROW_CHNG
———- ——–
Version2 16:19:45

Oracle ripristinerà sempre la versione più recente dell’oggetto droppato.
Per ripristinare una versione meno recente dell’ultima droppata, possiamo rilanciare il comando di flashback drop finchè non otteniamo la versione che ci interessa, oppure possiamo lanciare il comando sulla versione esatta da ripristinare. Droppiamo di nuovo due volte la tabella e ne ripristiniamo dal RB la versione meno recente, chiamandola per nome (occhio cambia solo /7 e /9 tra i nomi delle due versioni):
SQL> drop table tst;

Table dropped.

SQL> select object_name, original_name, type, can_undrop as “UND”, can_purge as “PUR”, droptime
2 from recyclebin;

OBJECT_NAME ORIGINAL_NAME TYPE UND PUR DROPTIME
—————————— ————- —— — — ——————-
BIN$HGnc55/7rRPgQPeM/qQoRw==$0 TST TABLE YES YES 2006-09-01:16:10:12
BIN$HGnc55/9rRPgQPeM/qQoRw==$0 TST TABLE YES YES 2006-09-01:16:21:00
To flashback to the first version, refer to the BIN$… name of the first version of TST:

SQL> flashback table “BIN$HGnc55/7rRPgQPeM/qQoRw==$0” to before drop;

Flashback complete.

SQL> select * from tst;

COL ROW_CHNG
———- ——–
Version1 16:10:03
Cosi facendo la versione meno recente è stata ripristinata in TST e non è più presente nel RB, mentre è invece presente la seconda tabella droppata, che non abbiamo flashbackato.
SQL> select object_name, original_name, operation, can_undrop as “UND”, can_purge as “PUR”, droptime
2 from recyclebin;

OBJECT_NAME ORIGINAL_NAME OPERATION UND PUR DROPTIME
—————————— ————– ——— — — ——————-
BIN$HGnc55/9rRPgQPeM/qQoRw==$0 TST DROP YES YES 2006-09-01:16:21:00
Oggetti Dipendenti

Ovviamente droppando una tabella, droppiamo anche tutti gli oggetti dipendenti da essa(indici, constraints, triggers, ecc). Con il RB abilitato, droppando la tabella, Oracle la rinomina insieme a tutti gli oggetti correlati, mantenendo intatte le relazioni tra essi. I trigger e gli indici ad esempio vengono modificati per puntare al nuovo nome della tabella droppata (quello che comincia con BIN$), mentre le stored procedure verranno invalidate
Ad esempio, aggiungiamo un indice, droppiamo la tabella e vediamo cosa troviamo nel cestino:
SQL> truncate table tst;

Table truncated.

SQL> insert into tst values (‘Version3′, sysdate);

1 row created.

SQL> create index ind_tst_col on tst(col);

Index created.

SQL> select * from tst;

COL ROW_CHNG
———- ——–
Version3 16:26:10

SQL> drop table tst ;

Table dropped.

SQL> select object_name, original_name, type, can_undrop as “UND”, can_purge as “PUR”, droptime
2 from recyclebin
3 order by droptime;

OBJECT_NAME ORIGINAL_NAME TYPE UND PUR DROPTIME
—————————— ————– —— — — ——————-
BIN$HGnc55/9rRPgQPeM/qQoRw==$0 TST TABLE YES YES 2006-09-01:16:21:00
BIN$HGnc55//rRPgQPeM/qQoRw==$0 TST TABLE YES YES 2006-09-01:16:27:36
BIN$HGnc55/+rRPgQPeM/qQoRw==$0 IND_TST_COL INDEX NO YES 2006-09-01:16:27:36
The RECYCLEBIN views have a few other columns that make the relationship between TST and IND_TST_COL clear:

SQL> select object_name, original_name, type, can_undrop as “UND”,
2 can_purge as “PUR”, droptime, base_object, purge_object
3 from recyclebin
4 order by droptime;

OBJECT_NAME ORIGINAL_NAME TYPE UND PUR DROPTIME BASE_OBJECT PURGE_OBJECT
—————————— ————— —– — — ——————- ———– ————
BIN$HGnc55/9rRPgQPeM/qQoRw==$0 TST TABLE YES YES 2006-09-01:16:21:00 233032 233032
BIN$HGnc55//rRPgQPeM/qQoRw==$0 TST TABLE YES YES 2006-09-01:16:27:36 233031 233031
BIN$HGnc55/+rRPgQPeM/qQoRw==$0 IND_TST_COL INDEX NO YES 2006-09-01:16:27:36 233031 233434
Troviamo quindi nel RB anche l’indice questa volta, rinominato. Se flashbackiamo la nostra tabella TST, noteremo che anche l’indice viene ripristinato, ma Oracle non lo rinomina col nome originale, e lo lascia col nome che aveva nel RB, ovvero BIN$xxx, come possiamo vedere:
SQL> flashback table tst to before drop;

Flashback complete.

SQL> select * from tst ;

COL ROW_CHNG
———- ——–
Version3 16:26:10

SQL> select index_name from user_indexes where table_name=’TST’ ;

INDEX_NAME
——————————
BIN$HGnc55/+rRPgQPeM/qQoRw==$0
Se droppiamo di nuovo la tabella TST, l’indice verrà droppato con il nome originale cambiato, ovvero quello con cui era nominato nel RB quando era stato già droppato in precedenza, e già qui abbiamo fatto un bel casotto, ma ripeto siamo sempre nella zona dormitopoco/ètardi/acidi, per cui è giusto anche cosi:
SQL> drop table tst;

Table dropped.

SQL> select object_name, original_name, type, can_undrop as “UND”, can_purge as “PUR”,
2 droptime, base_object, purge_object
3 from recyclebin
4 order by droptime;

OBJECT_NAME ORIGINAL_NAME TYPE UND PUR DROPTIME BASE_OBJECT PURGE_OBJECT
—————————— ————— —– — — ——————- ———– ————
BIN$HGnc55/9rRPgQPeM/qQoRw==$0 TST TABLE YES YES 2006-09-01:16:21:00 233032 233032
BIN$HGnc56ABrRPgQPeM/qQoRw==$0 TST TABLE YES YES 2006-09-01:16:31:43 233031 233031
BIN$HGnc56AArRPgQPeM/qQoRw==$1 BIN$HGnc55/+rRP INDEX NO YES 2006-09-01:16:31:43 233031 233434
gQPeM/qQoRw==$0

Notate il campo CAN_UNDROP e CAN_PURGE, riportarti per praticità come UND E PUR. Un indice non può essere flashbackato senza la sua tabella, per cui non lo si può flashbackare se non flashbackando la sua tabella. Se ne può invece fare il purge separatamente dalla tabella.
Vediamo come facendo il purge del solo indice e ripristinando la tabella, non troviamo più l’indice:
SQL> purge index “BIN$HGnc56AArRPgQPeM/qQoRw==$1” ;

Index purged.

SQL> select object_name, original_name, type, can_undrop as “UND”, can_purge as “PUR”,
2 droptime, base_object, purge_object
3 from recyclebin
4 order by droptime;

OBJECT_NAME ORIGINAL_NAME TYPE UND PUR DROPTIME BASE_OBJECT PURGE_OBJECT
—————————— ————– —– — — ——————- ———– ————
BIN$HGnc55/9rRPgQPeM/qQoRw==$0 TST TABLE YES YES 2006-09-01:16:21:00 233032 233032
BIN$HGnc56ABrRPgQPeM/qQoRw==$0 TST TABLE YES YES 2006-09-01:16:31:43 233031 233031
Now, if we restore the table, it will be restored without the index:

SQL> flashback table tst to before drop;

Flashback complete.

SQL> select * from tst ;

COL ROW_CHNG
———- ——–
Version3 16:26:10

SQL> select index_name from user_indexes where table_name=’TST’ ;

no rows selected
Per quanto riguarda i segmenti LOB, quelli creati ad esempio con i campi BLOB E CLOB, il procedimento del RB è sempre lo stesso, salvo che non se ne può fare il purge singolarmente. Sono ripristinati e cancellati sempre e solo in seguito al flashback o al purge della tabella di appartenenza.

Limitazioni

Ci sono delle limitazioni da tener presente, smanettando con l’RB.
Gli Indici Bitmap non finiscono nel RB, per cui se li avete droppati sono cancellati, e se ripristinate la tabella di origine non ve li ritroverete.
Stessa cosa per i logs delle viste materializzate, e per le foreign keys verso altre tabelle, quando droppate la tabella ve le perdete.

Se per problemi di spazio il RB comincia a fare il purge degli oggetti più vecchi, a parità di posizione, droppa prima gli indici. Ovviamente se ripristinate una tabella i cui indici sono stati droppati, verrà ripristinata senza indici…

Disabilitare il Recyblebin

Per non usare il RB in una singola drop, basta specificare l’opzione PURGE alla fine del drop, e l’oggetto verrà definitivamente cancellato senza passare dal RB:
SQL> purge recyclebin;

Recyclebin purged.

SQL> select * from recyclebin;

no rows selected

SQL> create table my_new_table (dummy varchar2(1));

Table created.

SQL> drop table my_new_table purge;

Table dropped.

SQL> select * from recyclebin;

no rows selected

Se disabilitiamo il RB a livello di sessione, con ALTER SESSION SET RECYCLEBIN=OFF, è la stessa cosa di fare ciascun drop mettendo il PURGE alla fine, per l’intera sessione o finchè non lo riattiviamo. Nulla ci impedisce comunque di ripristinare oggetti messi nel RB prima che lo disabilitassimo.

In conclusione…

Che dire, sicuramente il recyclebin è uno strumento utile che ci mette una rete di sicurezza in più ai casini che possiamo fare nella fretta o nella disattenzione del momento. E’ anche vero che ripristinando tabelle con indici e trigger dovremo ripassare a rinominare, ma almeno i dati sono salvi, ed è quello l’importante.
Certo è che, come i classici avvisi di conferma di cancellazione, se clicchiamo più volte senza leggere non c’è sistema che ci possa salvare se non li backup, quando c’è e quando è possibile ripristinarlo.
Mi è capitato di droppare l’intero schema SYSTEM di un cliente una volta, semplicemente per aver inavvertitamente scrollato con la rotella del mouse sul selettore delle connessioni del tool che utilizzavo per gestire il db, e quando me ne sono accorto ci mancava giusto mezzo schema di roba, per cui… parlo con cognizione di causa 😉

Lascia un commento