MySQLdb: un'interfaccia Python per MySQL Andy Dustman $Id: MySQLdb.sgml,v 1.4 2001/05/25 20:40:24 adustman Exp $ Traduzione a cura di Riccardo Fabris MySQLdb e un'interfaccia per il diffuso database MySQL, implementa l'API Python per i Database v.2.0 ed è utilizzabile con i thread. 1. Introduzione Questo modulo dovrebbe essere nella maggioranza dei casi ``compatibile'' con un'interfaccia più datata, scritta da Joe Skinner e altri. In ogni caso, tale versione dell'interfaccia è: a) antipatica da usare con i thread (le operazioni di un thread sul database potrebbero bloccare tutti gli altri thread); b) scritta per MySQL 3.21 (e di suo non compila sulle versioni più recenti); c) non più attivamente mantenuta secondo ogni evidenza. MySQLdb è un modulo completamente nuovo, distribuito come software libero sotto licenza GPL. 1.1 Piattaforme 1.1.1 Linux/UNIX Questo modulo viene sviluppato su Linux RedHat (attualmente 7.1) per Intel. Lo si dovrebbe poter generare senza troppi problemi su gran parte delle piattaforme usando lo script setup.py . Si suppone funzioni anche per MacOS X. Si faccia attenzione: è necessario avere il package Distutils fornito con Python 2.0. In caso non lo si abbia (per dire, si usa Python 1.5.2), lo si può trovare presso www.python.org. 1.1.2 Windows (3.11, 95, 98, NT, 2000, CE, BSOD, XYZ, ecc.) La piattaforma Windows _non_ è supportata. Comunque pare che lo script setup.py funzioni anche in questo caso. Probabilmente sulla pagina web c'è un link per ottenere un installer Windows precompilato. Si faccia attenzione al fatto che si tratta di un contributo di terzi, l'autore del modulo non può essere d'aiuto nel compilare e far funzionare il modulo sotto Windows. 1.2 Python MySQLdb richiede Python 1.5.2 o successivo. Le versioni anteriori non funzioneranno, poiché MySQL richiede il supporto per i long long del C. In caso si usino versioni anteriori di Python, si faccia l'upgrade a 1.5.2 o successivo. Lo sviluppo viene attualmente portato avanti su Python 2.1, ma Python 1.5.2 verrà ancora supportato nel prossimo futuro. 1.3 MySQL 1.3.1 MySQL-3.22 Si garantisce il funzionamento solo a partire dalla versione 3.22.32 di MySQL. Potrebbe comunque funzionare anche con versioni più vecchie. Comunque se si usa una vecchia versione di MySQL si dovrebbe considerare seriamente di effettuare l'upgrade, sia per le correzioni ai bachi che per questioni di sicurezza. MySQL-3.22 sembra avere un problema quando si tenta di inserire valori TIME con i secondi come frazioni. Valori come 12:56:13.00 vengono restituiti come 344:13:00. A quanto pare il valore originale viene interpretato come 12 giorni, 56 ore, 13 minuti, 0 secondi. (12 giorni più 56 ore fanno 344 ore). Per evitare il problema si usi il tipo DateTimeDelta. 1.3.2 MySQL-3.23 MySQL-3.23 ora è stabile (3.23.37 al momento della stesura). MySQLdb supporta le transazioni se il _server_le_supporta_, ma anche in questo caso non c'è garanzia assoluta che funzionino. Per cui si devono per forza usare tabelle a prova di transazione (Transaction-Safe Table). Attualmente le TST supportate sono BDB e InnoDB. Le GEMINI sono in programma per MySQL-4.0. Si noti che generalmente MySQL funziona in modo AUTOCOMMIT per default e MySQLdb lo dà per scontato. Per cambiare modo, si usi l'istruzione SQL SET AUTOCOMMIT=0. 1.4 DateTime Se si ha installato il package mx.DateTime (raccomandato), MySQLdb userà gli oggetti da esso forniti per date e orari. Altrimenti verranno passati a Python come stringhe. E` anche possibile modificare il dizionario di conversione di tipo per farli restituire come altre classi di oggetti, se si preferisce così. 1.5 MySQLmodule MySQLmodule, la vecchia interfaccia MySQL di Joe Skinner e altri, è suddivisa in una porzione C e una Python. La porzione C, MySQL, internamente è simile all'interfaccia perl DBI. In aggiunta c'è una porzione Python, Mysqldb, che fornisce un'interfaccia secondo la specifica API DB versione 1.0, scritta da James Henstridge. MySQLdb-0.2.2 e seguenti includono CompatMysqldb, un adattamento di Mysqldb a _mysql. Dovrebbe essere considerato sperimentale. Al contrario, la porzione C di MySQLdb, ``_mysql'', è progettata per imitare l'API C di MySQL in stile orientato agli oggetti. Un eventuale passaggio da MySQL a _mysql comporterebbe una certa quantità di lavoro. ``MySQLdb'' fornisce un'interfaccia conforme alla API DB v.2.0, che ha apportato alcuni cambiamenti alla versione 1.0. Ecco alcune cose cui prestare particolare attenzione: - Cambiamenti da Mysqldb a MySQLdb - ------------------------------------------------------------------------ Operazione: Connettersi Mysqldb: db = Mysqldb.Mysqldb("db@host utente password") MySQLdb: db = MySQLdb.connect(db='db', host='host', passwd='password') ------------------------------------------------------------------------ Operazione: Cursore implicito Mysqldb: db.execute(SQL) MySQLdb: I cursori impliciti sono stati abbandonati da DB API v2.0; si usi sempre c = db.cursor() ------------------------------------------------------------------------ Operazione: Prelevare righe come dizionari Mysqldb: c.fetchDict(), le chiavi sono "tabella.colonna" MySQLdb: non standard; la classe per cursori alternativi DictCursor fornisce un'interfaccia dizionario, le chiavi sono "colonna" o "tabella.colonna" se ci sono due colonne con lo stesso nome; si usi l'istruzione SQL AS per rinominare i campi. ------------------------------------------------------------------------ Operazione: Transazioni Mysqldb: db.commit() e db.rollback() esistono entrambi e non fanno nulla, senza però avvisare di ciò (*pericolo!*) MySQLdb: db.commit() e db.rollback() funzionano se il server MySQL supporta le transazioni, altrimenti db.rollback() fallisce sempre ------------------------------------------------------------------------ 1.6 Zope e ZmySQLDA Ho scritto ZMySQLDA per l'utilizzo con MySQLdb. E` un adattamento di ZOracleDA di Digital Creations, i creatori di Zope. 1.7 Documentazione La documentazione sulla pagina web potrebbe essere leggermente più aggiornata rispetto alla release attuale e potrebbe riportare funzionalità presenti solo nella release futura. 1.8 FAQ Una FAQ è disponibile presso http://dustman.net/andy/python/MySQLdb/faq/MySQLdb-FAQ.html 2. Il modulo _mysql Se si vogliono scrivere applicazioni portabili su altri database, si eviti di usare direttamente questo modulo. _mysql fornisce un'interfaccia che perlopiù implementa l'API C di MySQL. Per maggiori informazioni si consulti la documentazione MySQL. La documentazione di questo modulo è scarna di proposito, poiché nella gran parte dei casi è molto meglio usare il modulo ``MySQLdb''. Se davvero si ha bisogno di usarlo, si faccia riferimento ai doc standard di MySQL e li si adatti secondo necessità. 2.1 Traduzione dell'API C di MySQL L'API C di MySQL è stata incapsulata in stile orientato agli oggetti. Le uniche strutture dati MySQL implementate sono i tipi MYSQL (per gestire le connessioni al database) e MYSQL_RES (per gestire i risultati). In generale, qualunque funzione che prende MYSQL *mysql come argomento diventa un metodo dell'oggetto connessione e qualunque funzione che prende MYSQL_RES *result come argomento e un metodo dell'oggetto risultato. Le funzioni che non richiedeno alcuna struttura dati di MySQL sono implementate come funzioni nel modulo. Le funzioni che richiedono strutture dati di MySQL diverse dalle due sopra in genere non sono implementate. Le funzioni deprecate non sono implementate. In tutti i casi il prefisso mysql_ è stato eliminato. La maggior parte dei metodi "conn" elencati sono anche disponibili come metodi dell'oggetto Connessione di MySQLdb. Il loro uso impedisce la portabilità. - Mappatura delle funzioni C dell'API MySQL - C API _mysql ------------------------------+------------------------------ mysql_affected_rows() | conn.affected_rows() mysql_close() | conn.close() mysql_connect() | _mysql.connect() mysql_data_seek() | result.data_seek() mysql_debug() | _mysql.debug() mysql_dump_debug_info | conn.dump_debug_info() mysql_escape_string() | _mysql.escape_string() mysql_fetch_row() | result.fetch_row() mysql_get_client_info() | _mysql.get_client_info() mysql_get_host_info() | conn.get_host_info() mysql_get_proto_info() | conn.get_proto_info() mysql_get_server_info() | conn.get_server_info() mysql_info() | conn.info() mysql_insert_id() | conn.insert_id() mysql_num_fields() | result.num_fields() mysql_num_rows() | result.num_rows() mysql_options() | _mysql.connect() mysql_ping() | conn.ping() mysql_query() | conn.query() mysql_real_connect() | _mysql.connect() mysql_real_query() | conn.query() mysql_real_escape_string() | conn.escape_string() mysql_row_seek() | result.row_seek() mysql_row_tell() | result.row_tell() mysql_select_db() | conn.select_db() mysql_stat() | conn.stat() mysql_store_result() | conn.store_result() mysql_thread_id() | conn.thread_id() mysql_use_result() | conn.use_result() CLIENT_* | MySQLdb.constants.CLIENT.* CR_* | MySQLdb.constants.CR.* ER_* | MySQLdb.constants.ER.* FIELD_TYPE_* | MySQLdb.constants.FIELD_TYPE.* FLAG_* | MySQLdb.constants.FLAG.* ------------------------------+------------------------------ 2.2 Alcuni esempi con _mysql Va bene, volete usare comunque _mysql. Ecco degli esempi. La connessione più semplice al database è: ______________________________________________________________________ import _mysql db=_mysql.connect() ______________________________________________________________________ così si crea una connessione al server MySQL in esecuzione sulla macchina locale usando i socket UNIX standard, il proprio nome di login (preso dalla variabile di ambiente USER), nessuna password. E non USA un database. Magari funzionerà soprattutto se si ha impostato un file di configurazione, per dire ~/.my.cnf [notare che si sta parlando dell'uso su *ix NdT], ma è probabile che sia necessario fornire maggiori informazioni. ______________________________________________________________________ db=_mysql.connect("localhost","joebob","moonpie","thangs") ______________________________________________________________________ Questo crea una connessione al server MySQL in esecuzione sulla macchina locale _usando_TCP_sulla_porta_standard_(3306)_, il nome utente è "joebob", la password "moonpie" e si lavora inizialmente sul database "thangs". Non abbiamo nemmeno iniziato a parlare di tutti i parametri che connect() può ricevere e si noterà che se si usano parametri posizionali si è costretti di fatto a usare TCP, perdendo parecchio in velocità rispetto ai socket UNIX. (Ovviamente si deve usare comunque TCP se si tratta di un host remoto). Per questa ragione preferisco usare i parametri a parola chiave: ______________________________________________________________________ db=_mysql.connect(host="localhost",user="joebob", passwd="moonpie",db="thangs") ______________________________________________________________________ Questo fa esattamente la stessa cosa dell'esempio precedente, ma è certo più facile da leggere. Ora se si volessero usare proprio i socket UNIX e il nome di login fosse ``joebob'', si potrebbe abbreviare il tutto con: ______________________________________________________________________ db=_mysql.connect(passwd="moonpie",db="thangs") ______________________________________________________________________ Ci sono alcuni altri parametri che si potrebbero usare. Per la maggior parte non sono necessari, tranne uno. Pel resto si legga la documentazione inclusa nel modulo. Il modulo pydoc di Python 2.1 è davvero utile a questo scopo. Dunque, ora abbiamo una connessione aperta db e vogliamo sottoporre una interrogazione. Bene, non ci sono cursori in MySQL, nemmeno la sostituzione di parametri, quindi tocca passare un'intera interrogazione come stringa a db.query(): ______________________________________________________________________ db.query("""SELECT spam, eggs, sausage FROM breakfast WHERE price < 5""") ______________________________________________________________________ Questo non restituisce alcun valore, ma possono venir sollevate eccezioni. Le eccezioni sono definite in un modulo separato, _mysql_exceptions, ma _mysql le esporta. Si leggano le Specifiche API DB v.2.0 per sapere quali siano, oppure si usi MySQLError, che cattura qualunque cosa. A questo punto l'interrogazione è stata eseguita e si vogliono ricevere i risultati. Ci sono due opzioni: ______________________________________________________________________ r=db.store_result() # ...oppure... r=db.use_result() ______________________________________________________________________ Entrambi i metodi restituiscono un oggetto Risultato. Qual'è la differenza? store_result() restituisce immediatamente l'intero risultato al client. In caso esso sia molto ampio, questo comportamento potrebbe causare dei problemi. Un modo per evitarlo è aggiungere una clausola LIMIT all'interrogazione, per limitare il numero di righe restituite. L'altro è usare use_result(), che conserva il risultato nel server e lo invia riga per riga quando lo si preleva. Tutto ciò comunque pesa parecchio sulle risorse del server e sulla connessione: non si possono fare altre interrogazioni fino a che non si sono recuperate _tutte_ le righe. In genere raccomando di usare store_result() a meno che il risultato dell'interrogazione non sia davvero di dimensioni enormi e che per qualche ragione non si possa usare LIMIT. Ora, per ottenere effettivamente i risultati reali: ______________________________________________________________________ >>> r.fetch_row() (('3','2','0'),) ______________________________________________________________________ Potrebbe sembrare un po' insolito. La prima cosa da sapere è che fetch_row() accetta alcuni parametri aggiuntivi. Il primo dice quante righe (maxrows) dovrebbero venir restituite. Per default viene restituita una riga. Potrebbero venir restituite meno righe di quanto richiesto, ma mai di più. Impostando maxrows=0 vengono restituite tutte le righe del risultato. In caso si riceva indietro una tupla vuota, significa che si sono esaurite le righe. Il secondo parametro (how) dice come dovrebbe venir rappresentata la riga. Per default è zero, il che significa che va restituita come tupla. how=1 significa che va restituita come dizionario, dove le chiavi sono i nomi delle colonne, o tabella.colonna se ci sono due colonne con lo stesso nome (per dire nel caso di un join). how=2 significa lo stesso di how=1 eccetto che le chiavi sono _sempre_ tabella.colonna; serve per compatibilità con il vecchio modulo Mysqldb. Bene, allora perché abbiamo ottenuto una tupla di un solo elemento con dentro una tupla? Perché abbiamo implicitamente richiesto una sola riga, dato che non abbiamo specificato maxrows. L'altra cosa singolare è: assumendo che si tratti di colonne numeriche, perché vengono restituite come stringhe? Perché MySQL restituisce tutti i dati come stringhe e si aspetta che la conversione venga fatta in proprio. Questa rischia di essere una vera fregatura, ma in effetti lo può fare _mysql. (E MySQLdb lo fa). Per ottenere la conversione automatica di tipi è necessario creare un dizionario apposito e passarlo a connect() come parametro a parola chiave conv. Le chiavi di conv dovrebbero essere i tipi delle colonne MySQL, che nell'API C sono FIELD_TYPE_*. Si possono ottenere tali valori con: ______________________________________________________________________ from MySQLdb.constants import FIELD_TYPE ______________________________________________________________________ Per default qualsiasi tipo di colonna che non viene trovato in conv è restituito come stringa, il che funziona bene per un sacco di roba. Per i nostri scopi vogliamo probabilmente questo: ______________________________________________________________________ my_conv = { FIELD_TYPE.LONG: int } ______________________________________________________________________ Ciò significa che se c'è un FIELD_TYPE_LONG verrà chiamata su di esso la funzione primitiva int(). Si noti che FIELD_TYPE_LONG è una colonna INTEGER, che corrisponde a un long C, che è anche il tipo usato per i normali interi Python. Ma attenzione: se si tratta di una colonna UNSIGNED INTEGER potrebbero capitare degli overflow. Per questa ragione MySQLdb usa in realtà dei long() per fare la conversione. Per ora ignoreremo questo potenziale problema. Quindi, se si usa db=_mysql.connect(conv=my_conv...) i risultati verranno restituiti come ((3, 2, 0),), che è quanto ci si aspetterebbe. 3. MySQLdb -- interfaccia API verso il DB MySQLdb avvolge ``_mysql'' rendendolo compatibile con l'interfaccia API Python per i DB (versione 2). In realtà una piccola parte del codice che implementa l'API si trova in _mysql per ragioni di efficienza. Le Specifiche dell'API DB 2.0 dovrebbero costituire la guida principale nell'uso di questo modulo. Verrà qui documentato solo quanto si scosta da tali specifiche e altre faccende strettamente dipendenti dal database. 3.1 Funzioni e attributi Solo poche funzioni e attributi al livello più alto sono definiti all'interno di MySQLdb. connect(parametri...) Il costruttore per creare una connessione al database. Restituisce un Oggetto Connessione. I parametri sono gli stessi dell'API C MySQL. In aggiunta, ci sono alcune parole chiave addizionali che corrispondono a quanto si passerebbe a mysql_options() prima di connettersi. Si noti che alcuni parametri devono essere specificati come argomenti a parole chiave! Il valore predefinito per ciascun parametro è NULL o zero, come appropriato. Si consulti la documentazione MySQL per maggiori dettagli. I parametri importanti sono: host nome dell'host a cui connettersi. Default: usa localhost user utente con il quale autenticarsi. Default: l'utente attuale. password password con la quale autenticarsi. Default: nessuna password. db database da usare. Default: nessun database. port porta TCP del server MySQL. Default: la sua porta standard (3306). unix_socket collocazione del socket UNIX. Default: usa TCP. conve dizionario di conversione dei tipi. Default: una copia di MySQLdb.converters.conversions compress abilita il protocollo di compressione. Default: nessuna compressione. connect_timeout fallisce se la connessione non è completata entro un dato numero di secondi. Default: nessun timeout (?) named_pipe usa una pipe con nome (Windows). Default: non usarla. init_command comando iniziale da inviare al server subito dopo la connessione. Default: nessuno. read_default_file file di configurazione MySQL da leggere; si veda la documentazione MySQL per mysql_options(). read_default_group gruppo di default con cui leggere il file di configurazione; si veda la documentazione MySQL per mysql_options(). cursorclass classe cursore usata da cursor(), a meno di sovrascritture. Default: MySQLdb.cursors.Cursor. _Questo_dev'essere_un_parametro_ _a_parola_chiave_. apilevel costante stringa che specifica il livello dell'API DB supportato. E` pari a '2.0'. threadsafety costante intera che specifica il livello di supporto ai thread dell'interfaccia. Per MySQLdb versione 0.9.0 è pari a 1, che significa: i thread possono condividere il modulo. Il protocollo MySQL non può gestire più thread usando una stessa connessione in modo immediato. Alcune versioni precedenti di MySQLdb utilizzavano i lock per raggiungere un livello di supporto ai thread pari a 2. Non è cosa difficile da realizzare con la classe Cursore standard (che usa mysql_store_result()), è invece complicato per SSCursor (che usa mysql_use_result()). Nel secondo caso ci si deve assicurare che tutte le righe siano state lette prima che possa essere eseguita un'altra interrogazione. Le cose si complicano ulteriormente con l'aggiunta delle transazioni, dato che queste iniziano quando un cursore esegue un'interrogazione ma finiscono quando l'oggetto Connessione esegue un COMMIT o un ROLLBACK. Due thread non possono condividere una connessione mentre una transazione è in corso, oltre a non essere in grado di condividerla durante l'esecuzione di un'interrogazione. Tutto questo comporta una complicazione eccessiva a livello del codice, al punto che non ne vale proprio la pena. La conclusione generale è: *meglio non condividere connessioni fra thread*. Dato lo sforzo necessario non vale la pena e alla fine si rischia anche di compromettere le prestazioni del programma, dato che il server MySQL lancia un thread separato per ciascuna connessione. Si possono certamente fare cose come tenere un pool delle connessioni e passarle a un thread in una volta sola. Se invece si permette a due thread di usare simultaneamente una stessa connessione, con tutta probabilità la libreria client MySQL farà una brutta fine. L'avviso l'ho dato. paramstyle costante stringa che specifica il tipo di formattazione del marcatore di parametro che l'interfaccia si aspetta. Impostato a 'format' usa i codici di formato della printf di ANSI C, p.e. '...WHERE name=%s'. Se a conn.execute() viene passato un dizionario, allora l'interfaccia usera in realta 'pyformat', cioè i codici di formato Python estesi, p.e. '...WHERE name=%(name)s'. In ogni caso l'API attualmente non permette di specificare più di uno stile. Nota di compatibilità: il vecchio MySQLmodule usa uno schema simile, ma richiede che le stringhe di formato che contengano stringhe, date e altri dati carattere simili siano circondate da virgolette. Questo non è necessario per MySQLdb. Si raccomanda di usare %s (e non '%s') per tutti i parametri, di qualunque tipo siano. L'interfaccia aggiungerà tutte le virgolette necessarie. conv un dizionario che mappa i tipi MySQL (da FIELD_TYPE.*) in oggetti invocabili Python (di solito funzioni), che si occupano della conversione da stringa al tipo desiderato, e i tipi Python in oggetti invocabili Python, che effettuano la conversione opposta in stringhe letterali SQL. Il dizionario viene inizializzato con contenuti predefiniti appropriati per la gran parte dei tipi. Quando si crea un oggetto Connessione, è possibile passare il proprio dizionario di conversione come parametro a parola chiave. Altrimenti verrà usata una copia di MySQLdb.converters.conversions. Il dizionario include alcune delle funzioni fabbrica del modulo DateTime, se esso risulta disponibile. Parecchi tipi non standard sono restituiti come stringhe, che è il modo in cui MySQL restituisce tutte le colonne. Per maggiori dettagli si veda la documentazione incorporata nel modulo. A partire da MySQL-3.23, MySQL supporta set differenti di caratteri nel server e una nuova funzione di protezione per le stringhe nelle interrogazioni SQL, mysql_real_escape_string(). Questo richiede che la funzione sia un metodo associato a un oggetto Connessione. MySQLdb gestisce tutto ciò automaticamente. Nondimeno se si pensa di aver bisogno di fare qualcosa di strambo con le proprie stringhe, si potrà modificare il dizionario una volta aperta la connessione. In pratica non ci si dovrà mai preoccupare di tutto questo. 3.2 Oggetti Connessione Gli oggetti Connessione sono il prodotto della funzione connect(). commit() se il database e le tabelle supportano le transazioni questo metodo effettua il commit della transazione corrente, altrimenti non fa nulla [senza creare problemi ma nemmeno avvisare NdT]. rollback() se il database e le tabelle supportano le transazioni questo metodo fa il ``roll back'', in parole povere annulla, la transazione corrente, altrimenti viene sollevata un'eccezione NotSupportedError. Nota di compatibilità: nel vecchio ``MySQLmodule'' questo metodo invece di sollevare un'eccezione in caso le transazioni non siano supportate non fa proprio nulla, portando a credere che il rollback abbia avuto successo. Questo e un comportamento pericoloso, dato che un rollback riuscito indica che la transazione corrente è stata ritirata, il che non è vero, e fallisce nel comunicare al programmatore che ora il database dev'essere ripulito in altri modi. cursor([cursorclass]) MySQL non supporta nativamente i cursori. A ogni modo essi possono venir facilmente emulati. Si può fornire una classe cursore alternativa come parametro opzionale. In caso non sia presente, viene preso come default il valore passato al momento della creazione dell'oggetto Connessione, o la classe standard Cursor. Si vedano anche le classi cursori aggiuntive nella sezione ``Usare ed estendere il modulo''. begin() inizia in modo esplicito una transazione. Normalmente non è necessario usarlo: l'esecuzione di un'interrogazione inizia in modo implicito una nuova transazione se non ce ne sono altre in corso. Se è attivato il modo AUTOCOMMIT, si può usare begin() per disattivarlo temporaneamente. AUTOCOMMIT si riattiverà alla prossimo commit() o rollback. 3.3 Oggetti Cursore callproc() non implementato. close() chiude il cursore. Da qui in poi se si tenteranno operazioni sul cursore verrà sollevata un'eccezione ProgrammingError. Se si stanno usando i ``cursori lato server'' [``Server-Side Cursors''], è molto importante chiudere il cursore quando si ha finito di usarlo e prima di crearne uno nuovo. insert_id() restituisce l'ultimo valore AUTO_INCREMENT inserito nel database. (Non-standard) info() restituisce alcune informazioni sull'ultima interrogazione. Di norma non serve. Con il cursore predefinito, qualsiasi avviso emesso da MySQL causerà un'eccezione Warning. Se invece si sta usando una classe cursore senza avvisi, allora si potrebbe volerlo usare. Si veda la documentazione MySQL per mysql_info(). (Non-standard) setinputsizes() non fa nulla, senza avvisare. setoutputsizes() non fa nulla, senza avvisare. 3.4 Alcuni esempi Il metodo connect() lavora all'incirca come con _mysql: ______________________________________________________________________ import MySQLdb db=MySQLdb.connect(passwd="moonpie",db="thangs") ______________________________________________________________________ Per effettuare un'interrogazione serve innanzitutto un cursore, sul quale poi si andranno a eseguire le interrogazioni. ______________________________________________________________________ c=db.cursor() max_price=5 c.execute("""SELECT spam, eggs, sausage FROM breakfast WHERE price < %s""", (max_price,)) ______________________________________________________________________ In questo esempio max_price=5. Quindi perché usare %s nella stringa? perché MySQLdb la convertirà in un valore letterale SQL, la stringa '5'. Una volta finito, l'interrogazione in realta reciterà: ``...WHERE price < 5''. Perché la tupla? Perché la API DB richiede di passare come sequenza qualsiasi parametro. Ora ecco i risultati: ______________________________________________________________________ >>> c.fetchone() (3L, 2L, 0L) ______________________________________________________________________ Diversamente da quanto accadeva con _mysql, il nostro esempio restituisce una singola tupla, che sarebbe la riga, e i valori sono adeguatamente convertiti per default... tranne... ma cosa sono le L? Come detto in precedenza, mentre una colonna di MySQL di tipo INTEGER si traduce perfettamente in un intero Python, un UNSIGNED INTEGER potrebbe causare overflow. Tali valori sono quindi convertiti invece in interi long Python. Prima di Python 1.6, gli interi long conservavano la L quando convertiti in stringhe con str(). Da 1.6 in poi str() non include più la L. Naturalmente la L viene sempre stampata quando si usa repr(), il che è quanto è successo nel nostro caso. Quando si ha finito con una transazione, si dovrebbe eseguire db.commit() o db.rollback(). Se il server e le tabelle non supportano le transazioni, commit() funzionerà ancora, mentre rollback() solleverà un'eccezione. Si noti che questi sono metodi di _connection_, non di _cursor_, anche se è stato gh c.execute(...) a iniziare la transazione. Se si vogliono più righe si possono usare c.fetchmany(n) o c.fetchall(). Questi due fanno esattamente quanto ci si aspetta. Per c.fetchmany(n), n è opzionale e il valore predefinito è c.arraysize, che di norma è 100. Entrambi i metodi restituiscono una sequenza di righe, o una sequenza vuota se le righe sono finite. Se si usa una classe cursore personalizzata in modo particolare, le righe stesse potrebbero non essere tuple. Si noti che al contrario di quanto sopra, c.fetchone() restituisce None quando non ci sono più righe da poter prelevare. L'unico altro metodo che si userà quasi sicuramente serve negli inserimenti multiriga: ______________________________________________________________________ c.execute("""INSERT INTO breakfast (name, spam, eggs, sausage, price) VALUES (%s, %s, %s, %s, %s)""", [ ("Spam and Sausage Lover's Plate", 5, 1, 8, 7.95 ), ("Not So Much Spam Plate", 3, 2, 0, 3.95 ), ("Don't Wany ANY SPAM! Plate", 0, 4, 3, 5.95 ) ] ) ______________________________________________________________________ In questo esempio stiamo inserendo tre righe di cinque valori. Si noti che c'è una mescolanza di tipi (stringhe, interi, numeri a virgola mobile) ma stiamo ancora usando il solo %s. Si noti anche che abbiamo incluso le stringhe di formato per una sola riga. MySQLdb le individua e le duplica per ciascuna riga. (I fagioli al forno sono finiti!) 4. Usare ed estendere il modulo In generale probabilmente è una scelta saggia non interagire direttamente con l'API DB, eccetto che per piccole applicazioni. I database, anche SQL, variano ampiamente per capacità e possono avere varie caratteristiche non-standard. L'API DB fa un buon lavoro nel fornire un'interfaccia ragionevolmente portabile, ma alcuni metodi non sono portabili. Nello specifico i parametri accettati da ``connect()'' sono assolutamente dipendenti dall'implementazione. Se si crede che l'applicazione debba girare su diversi database, raccomando l'approccio seguente, basato sulla mia esperienza personale: si scriva un'API semplificata per la propria applicazione, che implementi le particolari interrogazioni e operazioni che l'applicazione deve usare. Si implementi tale API come classe base, che dovrebbe avere poche dipendenze dal database, e quindi se ne derivi una sottoclasse che implementi le dipendenze necessarie. In questo modo, portare la propria applicazione su un nuovo database dovrebbe essere una questione semplice, basta creare una nuova sottoclasse, dando per scontato che il nuovo database sia ragionevolmente standard. Per un esempio di tutto ciò si veda il modulo SQLDict dell'autore, che permette la definizione e l'accesso a interrogazioni standard usando un oggetto che somiglia a un dizionario, e legge/scrive oggetti definiti dall'utente. Dato che gli oggetti Connessione e Cursore di MySQLdb sono scritti in Python, è semplice derivare le proprie sottoclassi. Ci sono diverse classi Cursor in MySQLdb.cursors: BaseCursor la classe base per gli oggetti Cursore. Non solleva eccezioni Warning. CursorWarningMixIn in caso di avvisi prodotti da interrogazioni solleva un'eccezione Warning. CursorStoreResultMixIn fa sì che il Cursore usi la funzione mysql_store_result() per ricevere il risultato della interrogazione. L'intero risultato viene memorizzato dal lato client della connessione. CursorUseResultMixIn fa sì che il Cursore usi la funzione mysql_use_result() per ricevere il risultato dell'interrogazione. Il risultato è memorizzato sul lato server e trasferito riga per riga con operazioni di prelievo. CursorTupleRowsMixIn fa sì che il Cursore restituisca le righe come una tupla dei valori della colonna. CursorDictRowsMixIn fa sì che il Cursore restituisca le righe come un dizionario, dove le chiavi sono i nomi delle colonne e i valori sono i valori della colonne. Si noti che se i nomi delle colonne non sono unici, per dire si sta effettuando una selezione da due tabelle che condividono i nomi delle colonne, alcuni nomi verranno riscritti come "tabella.colonna". Lo si può evitare usando la parola chiave SQL AS. (Questa è un'ulteriore ragione per non usare * nelle interrogazioni SQL, specialmente quando è implicato un JOIN). Cursor La classe cursore di default. Questa classe è composta da CursorWarningMixIn, CursorStoreResultMixIn, CursorTupleRowsMixIn e BaseCursor, perciò solleva Warning, usa mysql_store_result() e restituisce le righe come tuple. DictCursor simile a Cursor, eccetto che restituisce le righe come dizionari. SSCursor un cursore lato server. Simile a Cursor ma utilizza CursorUseResultMixIn. Lo si usi solo in caso si abbia a che fare con risultati potenzialmente di grandi dimensioni. SSDictCursor simile a SSCursor, eccetto che restituisce le righe come dizionari. XXXCursorNW i cursori col suffisso ``NW'' non sollevano avvisi. Per un esempio di come usare queste classi, si legga il codice. Se si ha bisogno di cose più esotiche, si dovra fare da sè