Penulis Pony ORM ada di sini.
Pony menerjemahkan generator Python ke dalam kueri SQL dalam tiga langkah:
- Mendekompilasi bytecode generator dan membangun kembali AST generator (pohon sintaks abstrak)
- Terjemahan Python AST ke dalam "SQL abstrak" - representasi berbasis daftar universal dari kueri SQL
- Mengubah representasi SQL abstrak menjadi dialek SQL yang bergantung pada database tertentu
Bagian paling kompleks adalah langkah kedua, di mana Pony harus memahami "arti" dari ekspresi Python. Sepertinya Anda paling tertarik pada langkah pertama, jadi izinkan saya menjelaskan cara kerja dekompilasi.
Mari pertimbangkan kueri ini:
>>> from pony.orm.examples.estore import *
>>> select(c for c in Customer if c.country == 'USA').show()
Yang akan diterjemahkan ke dalam SQL berikut:
SELECT "c"."id", "c"."email", "c"."password", "c"."name", "c"."country", "c"."address"
FROM "Customer" "c"
WHERE "c"."country" = 'USA'
Dan di bawah ini adalah hasil dari query ini yang akan dicetak:
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
The select()
Fungsi menerima generator python sebagai argumen, dan kemudian menganalisa bytecode nya. Kita bisa mendapatkan instruksi bytecode dari generator ini menggunakan dis
modul python standar :
>>> 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 memiliki fungsi di decompile()
dalam modul pony.orm.decompiling
yang dapat mengembalikan AST dari bytecode:
>>> from pony.orm.decompiling import decompile
>>> ast, external_names = decompile(gen)
Di sini, kita dapat melihat representasi tekstual dari node AST:
>>> ast
GenExpr(GenExprInner(Name('c'), [GenExprFor(AssName('c', 'OP_ASSIGN'), Name('.0'),
[GenExprIf(Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]))])]))
Sekarang mari kita lihat bagaimana decompile()
fungsinya bekerja.
The decompile()
fungsi menciptakan Decompiler
objek, yang menerapkan pola pengunjung. Instance decompiler mendapatkan instruksi bytecode satu per satu. Untuk setiap instruksi, objek decompiler memanggil metodenya sendiri. Nama metode ini sama dengan nama instruksi bytecode saat ini.
Saat menghitung ekspresi, Python menggunakan tumpukan, yang menyimpan hasil penghitungan antara. Objek decompiler juga memiliki tumpukannya sendiri, tetapi tumpukan ini tidak menyimpan hasil kalkulasi ekspresi, tetapi node AST untuk ekspresi tersebut.
Ketika metode decompiler untuk instruksi bytecode berikutnya dipanggil, ia mengambil node AST dari stack, menggabungkannya menjadi node AST baru, dan kemudian meletakkan node ini di atas stack.
Misalnya, mari kita lihat bagaimana subekspresi c.country == 'USA'
dihitung. Fragmen bytecode terkait adalah:
9 LOAD_FAST 1 (c)
12 LOAD_ATTR 0 (country)
15 LOAD_CONST 0 ('USA')
18 COMPARE_OP 2 (==)
Jadi, objek decompiler melakukan hal berikut:
- Panggilan
decompiler.LOAD_FAST('c')
. Metode ini menempatkan Name('c')
node di atas tumpukan dekompiler.
- Panggilan
decompiler.LOAD_ATTR('country')
. Metode ini mengambil Name('c')
node dari tumpukan, membuat Geattr(Name('c'), 'country')
node dan meletakkannya di atas tumpukan.
- Panggilan
decompiler.LOAD_CONST('USA')
. Metode ini menempatkan Const('USA')
node di atas tumpukan.
- Panggilan
decompiler.COMPARE_OP('==')
. Metode ini mengambil dua node (Getattr dan Const) dari tumpukan, dan kemudian meletakkannya Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
di atas tumpukan.
Setelah semua instruksi bytecode diproses, tumpukan dekompiler berisi satu node AST yang sesuai dengan ekspresi generator secara keseluruhan.
Karena Pony ORM perlu mendekompilasi generator dan lambda saja, ini tidak terlalu rumit, karena aliran instruksi untuk generator relatif mudah - ini hanya sekumpulan loop bersarang.
Saat ini Pony ORM mencakup seluruh set instruksi generator kecuali dua hal:
- Sejajar jika ekspresi:
a if b else c
- Perbandingan senyawa:
a < b < c
Jika Pony menemukan ekspresi seperti itu, itu akan menimbulkan NotImplementedError
pengecualian. Tetapi bahkan dalam kasus ini Anda dapat membuatnya bekerja dengan meneruskan ekspresi generator sebagai string. Ketika Anda melewatkan generator sebagai string, Pony tidak menggunakan modul decompiler. Sebagai gantinya, ia mendapatkan AST menggunakan compiler.parse
fungsi Python standar .
Semoga ini menjawab pertanyaan Anda.
p
objek tersebut adalah objek dari tipe yang diimplementasikan oleh Pony yang melihat metode / properti apa yang sedang diakses padanya (misalnyaname
,startswith
) dan mengubahnya menjadi SQL.