L'autore di Pony ORM è qui.
Pony traduce il generatore Python in query SQL in tre passaggi:
- Decompilazione del bytecode del generatore e ricostruzione del generatore AST (albero di sintassi astratto)
- Traduzione di Python AST in "SQL astratto" - rappresentazione universale basata su elenchi di una query SQL
- Conversione della rappresentazione SQL astratta in un dialetto SQL specifico dipendente dal database
La parte più complessa è il secondo passaggio, in cui Pony deve comprendere il "significato" delle espressioni Python. Sembra che tu sia più interessato al primo passaggio, quindi lascia che ti spieghi come funziona la decompilazione.
Consideriamo questa query:
>>> from pony.orm.examples.estore import *
>>> select(c for c in Customer if c.country == 'USA').show()
Che verrà tradotto nel seguente SQL:
SELECT "c"."id", "c"."email", "c"."password", "c"."name", "c"."country", "c"."address"
FROM "Customer" "c"
WHERE "c"."country" = 'USA'
E di seguito è il risultato di questa query che verrà stampato:
id|email |password|name |country|address
--+-------------------+--------+--------------+-------+---------
1 |john@example.com |*** |John Smith |USA |address 1
2 |matthew@example.com|*** |Matthew Reed |USA |address 2
4 |rebecca@example.com|*** |Rebecca Lawson|USA |address 4
La select()
funzione accetta un generatore Python come argomento e quindi analizza il suo bytecode. Possiamo ottenere istruzioni bytecode di questo generatore usando il dis
modulo standard python :
>>> gen = (c for c in Customer if c.country == 'USA')
>>> import dis
>>> dis.dis(gen.gi_frame.f_code)
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 26 (to 32)
6 STORE_FAST 1 (c)
9 LOAD_FAST 1 (c)
12 LOAD_ATTR 0 (country)
15 LOAD_CONST 0 ('USA')
18 COMPARE_OP 2 (==)
21 POP_JUMP_IF_FALSE 3
24 LOAD_FAST 1 (c)
27 YIELD_VALUE
28 POP_TOP
29 JUMP_ABSOLUTE 3
>> 32 LOAD_CONST 1 (None)
35 RETURN_VALUE
Pony ORM ha la funzione decompile()
all'interno del modulo pony.orm.decompiling
che può ripristinare un AST dal bytecode:
>>> from pony.orm.decompiling import decompile
>>> ast, external_names = decompile(gen)
Qui possiamo vedere la rappresentazione testuale dei nodi AST:
>>> ast
GenExpr(GenExprInner(Name('c'), [GenExprFor(AssName('c', 'OP_ASSIGN'), Name('.0'),
[GenExprIf(Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]))])]))
Vediamo ora come decompile()
funziona la funzione.
La decompile()
funzione crea un Decompiler
oggetto, che implementa il pattern Visitor. L'istanza del decompilatore riceve le istruzioni bytecode una alla volta. Per ogni istruzione l'oggetto decompilatore chiama il proprio metodo. Il nome di questo metodo è uguale al nome dell'istruzione bytecode corrente.
Quando Python calcola un'espressione, utilizza lo stack, che memorizza un risultato intermedio del calcolo. Anche l'oggetto decompilatore ha il proprio stack, ma questo stack non memorizza il risultato del calcolo dell'espressione, ma il nodo AST per l'espressione.
Quando viene chiamato il metodo del decompilatore per la successiva istruzione bytecode, prende i nodi AST dallo stack, li combina in un nuovo nodo AST e quindi mette questo nodo in cima allo stack.
Ad esempio, vediamo come c.country == 'USA'
viene calcolata la sottoespressione . Il frammento di bytecode corrispondente è:
9 LOAD_FAST 1 (c)
12 LOAD_ATTR 0 (country)
15 LOAD_CONST 0 ('USA')
18 COMPARE_OP 2 (==)
Quindi, l'oggetto decompilatore fa quanto segue:
- Chiamate
decompiler.LOAD_FAST('c')
. Questo metodo mette il Name('c')
nodo in cima allo stack del decompilatore.
- Chiamate
decompiler.LOAD_ATTR('country')
. Questo metodo prende il Name('c')
nodo dallo stack, crea il Geattr(Name('c'), 'country')
nodo e lo mette in cima allo stack.
- Chiamate
decompiler.LOAD_CONST('USA')
. Questo metodo mette il Const('USA')
nodo in cima allo stack.
- Chiamate
decompiler.COMPARE_OP('==')
. Questo metodo prende due nodi (Getattr e Const) dallo stack e quindi li mette Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
in cima allo stack.
Dopo che tutte le istruzioni bytecode sono state elaborate, lo stack del decompilatore contiene un singolo nodo AST che corrisponde all'intera espressione del generatore.
Poiché Pony ORM ha bisogno di decompilare solo generatori e lambda, questo non è così complesso, perché il flusso di istruzioni per un generatore è relativamente semplice: è solo un mucchio di loop annidati.
Attualmente Pony ORM copre l'intero set di istruzioni del generatore tranne due cose:
- Inline if espressioni:
a if b else c
- Confronti composti:
a < b < c
Se Pony incontra tale espressione solleva l' NotImplementedError
eccezione. Ma anche in questo caso puoi farlo funzionare passando l'espressione del generatore come stringa. Quando si passa un generatore come stringa, Pony non usa il modulo di decompilazione. Invece ottiene l'AST utilizzando la compiler.parse
funzione Python standard .
Spero che questo risponda alla tua domanda.
p
oggetto è un oggetto di un tipo implementato da Pony che guarda a quali metodi / proprietà si accede su di esso (ad esempioname
,startswith
) e li converte in SQL.