Autor Pony ORM jest tutaj.
Pony tłumaczy generator Pythona na zapytanie SQL w trzech krokach:
- Dekompilacja kodu bajtowego generatora i przebudowa generatora AST (abstrakcyjne drzewo składniowe)
- Tłumaczenie języka Python AST na „abstrakcyjny SQL” - uniwersalna reprezentacja zapytania SQL oparta na liście
- Konwersja abstrakcyjnej reprezentacji SQL na określony dialekt SQL zależny od bazy danych
Najbardziej złożoną częścią jest drugi krok, w którym Pony musi zrozumieć „znaczenie” wyrażeń Pythona. Wygląda na to, że najbardziej interesuje Cię pierwszy krok, więc pozwól mi wyjaśnić, jak działa dekompilacja.
Rozważmy to zapytanie:
>>> from pony.orm.examples.estore import *
>>> select(c for c in Customer if c.country == 'USA').show()
Który zostanie przetłumaczony na następujący SQL:
SELECT "c"."id", "c"."email", "c"."password", "c"."name", "c"."country", "c"."address"
FROM "Customer" "c"
WHERE "c"."country" = 'USA'
A poniżej wynik tego zapytania, który zostanie wydrukowany:
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
select()
Funkcja przyjmuje generator pytona jako argumentu, i analizuje jego bajtowego. Możemy uzyskać instrukcje kodu bajtowego tego generatora za pomocą standardowego dis
modułu Pythona :
>>> 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 ma funkcję decompile()
w module, pony.orm.decompiling
która może przywrócić AST z kodu bajtowego:
>>> from pony.orm.decompiling import decompile
>>> ast, external_names = decompile(gen)
Tutaj możemy zobaczyć tekstową reprezentację węzłów AST:
>>> ast
GenExpr(GenExprInner(Name('c'), [GenExprFor(AssName('c', 'OP_ASSIGN'), Name('.0'),
[GenExprIf(Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]))])]))
Zobaczmy teraz, jak decompile()
działa ta funkcja.
decompile()
Funkcja tworzy Decompiler
obiekt, który implementuje odwiedzający. Instancja dekompilatora pobiera instrukcje kodu bajtowego jedna po drugiej. Dla każdej instrukcji obiekt dekompilatora wywołuje własną metodę. Nazwa tej metody jest taka sama, jak nazwa bieżącej instrukcji kodu bajtowego.
Kiedy Python oblicza wyrażenie, używa stosu, który przechowuje pośredni wynik obliczeń. Obiekt dekompilatora również ma swój własny stos, ale ten stos przechowuje nie wynik obliczenia wyrażenia, ale węzeł AST dla wyrażenia.
Kiedy wywoływana jest metoda dekompilacji dla następnej instrukcji kodu bajtowego, pobiera węzły AST ze stosu, łączy je w nowy węzeł AST, a następnie umieszcza ten węzeł na szczycie stosu.
Na przykład zobaczmy, jak c.country == 'USA'
obliczane jest podwyrażenie . Odpowiedni fragment kodu bajtowego to:
9 LOAD_FAST 1 (c)
12 LOAD_ATTR 0 (country)
15 LOAD_CONST 0 ('USA')
18 COMPARE_OP 2 (==)
Tak więc obiekt dekompilatora wykonuje następujące czynności:
- Połączenia
decompiler.LOAD_FAST('c')
. Ta metoda umieszcza Name('c')
węzeł na szczycie stosu dekompilatora.
- Połączenia
decompiler.LOAD_ATTR('country')
. Ta metoda pobiera Name('c')
węzeł ze stosu, tworzy Geattr(Name('c'), 'country')
węzeł i umieszcza go na szczycie stosu.
- Połączenia
decompiler.LOAD_CONST('USA')
. Ta metoda umieszczaConst('USA')
węzeł na szczycie stosu.
- Połączenia
decompiler.COMPARE_OP('==')
. Ta metoda pobiera dwa węzły (Getattr i Const) ze stosu, a następnie umieszcza je Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
na szczycie stosu.
Po przetworzeniu wszystkich instrukcji kodu bajtowego stos dekompilatora zawiera pojedynczy węzeł AST, który odpowiada całemu wyrażeniu generatora.
Ponieważ Pony ORM musi dekompilować tylko generatory i lambdy, nie jest to takie skomplikowane, ponieważ przepływ instrukcji dla generatora jest stosunkowo prosty - jest to po prostu kilka zagnieżdżonych pętli.
Obecnie Pony ORM obejmuje cały zestaw instrukcji generatora z wyjątkiem dwóch rzeczy:
- Wbudowane wyrażenia if:
a if b else c
- Porównania złożone:
a < b < c
Jeśli Pony napotka takie wyrażenie, zgłosi NotImplementedError
wyjątek. Ale nawet w tym przypadku możesz sprawić, by działało, przekazując wyrażenie generatora jako ciąg. Kiedy przekazujesz generator jako ciąg znaków, Pony nie używa modułu dekompilatora. Zamiast tego pobiera AST za pomocą standardowej compiler.parse
funkcji Pythona .
Mam nadzieję, że to odpowiada na twoje pytanie.
p
obiekt jest obiektem typu zaimplementowanego przez Pony, który sprawdza, jakie metody / właściwości są na nim dostępne (np.name
,startswith
) I konwertuje je na SQL.