[Python] psycopg2 e bytea

Daniele Varrazzo piro a develer.com
Mer 16 Feb 2011 16:59:50 CET


On Wed, 16 Feb 2011 16:13:16 +0100, Stefano Dal Pra <s.dalpra a gmail.com>
wrote:
> Ciao Daniele, credo che un problema analogo valga anche per i boolean in
> postgres.
> 
> Sto migrando un db mysql in uno equivalente postgres.
> faccio sostanzialmente cose tipo:
> 
> mysqlcurs.execute("select * from Tab")
> T = mysqlcurs.fetchall() #ci stanno, ci stanno... :-)
> ...
> fms = ','.join("%s" for x in range(len(T[0])))
> for tup in T:
>     pg.curs.execute("insert into %s values (%s)"%(tablename,fms),tup)
> 
> 
> mysqldump mappa false/true in 0/1, per cui
> psycopg2.execute("insert into T (boolvalue) values (%s)",1)
> da errore (TypeError se ricordo bene?).

No, questo TypeError viene dal fatto che in questo esempio hai usato "1" e
non una sequenza per passare gli argomenti.

Strano che postgres non converta valori numerici 1/0 in booleani, ma
confermo che è così:

    In [10]: cur.execute("create table testbool (x bool);")
    
    In [11]: cur.execute("insert into  testbool values (%s), (%s), (%s);",
(1,0,None))
   
---------------------------------------------------------------------------
    ProgrammingError                          Traceback (most recent call
last)
    
    /home/piro/src/psycopg2/<ipython console> in <module>()
    
    ProgrammingError: column "x" is of type boolean but expression is of
type integer
    LINE 1: insert into  testbool values (1), (0), (NULL);
                                          ^
    HINT:  You will need to rewrite or cast the expression.

È strano perchè quando viene forzato a fare un cast esplicito non ha
problemi:

    In [12]: cur.execute("insert into  testbool values (%s::bool),
(%s::bool), (%s::bool);", (1,0,None))
    
    In [13]: cur.execute("select * from testbool;")
    
    In [14]: cur.fetchall()
    Out[14]: [(True,), (False,), (None,)]

Un cast in postgres c'è, ma non è "implicito". Non saprei perché.


> Al momento risolvo la cosa con un dizionario:
> dbool = {0:False,1:True,None:None}
> (nota che bool(None) --> False, che per i db non va bene)
> e rimappando i campi boolean prima di inserirli...

Mi sembra una buona soluzione. Quella analoga al caso dei bytea
consisterebbe nel wrappare i bool (e non tutti gli interi)... anche se
esistesse questo adapter (sarebbe facile da scrivere) non darebbe nessun
vantaggio rispetto al mapping.

Una soluzione alternativa sarebbe quella di identificare quali sono i
campi booleani e usare dei cast dopo i segnaposto come nella [12]. Puoi
addirittura chiedere automaticamente a postgres quali siano i campi bool
nella tua tabella:

    In [21]: cur.execute("create table stuff (foo int, bar bool, baz
text);")

    In [22]: cur.execute("select * from stuff limit 0")

    In [23]: cur.description
    Out[23]: 
    (('foo', 23, None, 4, None, None, None),
     ('bar', 16, None, 1, None, None, None),
     ('baz', 25, None, -1, None, None, None))

I campi di tipo 16 sono i bool: puoi generare placeholder col cast da
questa informazione:

    In [25]: ",".join(t[1] == 16 and "%s::bool" or "%s" for t in
cur.description)
    Out[25]: '%s,%s::bool,%s'


> Versioni + recenti di psycopg2 si comportano diversamente in questo
caso?

No: il cast in sql viene scelto in base al tipo: se mysql passa un integer
sia per interi che per bool, python non fa distinzione di tipo tra i due e
psycopg usa lo stesso mapping per entrambi.


-- 
Daniele Varrazzo - Develer S.r.l. 
http://www.develer.com


Maggiori informazioni sulla lista Python