Автор Pony ORM тут.
Pony перекладає генератор Python у SQL-запит у три етапи:
- Декомпіляція байт-коду генератора та відновлення генератора AST (абстрактне синтаксичне дерево)
- Переклад Python AST на "абстрактний SQL" - універсальне представлення списку SQL-запиту на основі списку
- Перетворення абстрактного представлення SQL в конкретний діалект SQL, що залежить від бази даних
Найскладніша частина - другий крок, де Поні повинен зрозуміти "значення" виразів Python. Здається, вас найбільше цікавить перший крок, тому дозвольте мені пояснити, як працює декомпіляція.
Розглянемо цей запит:
>>> from pony.orm.examples.estore import *
>>> select(c for c in Customer if c.country == 'USA').show()
Що буде переведено у наступний SQL:
SELECT "c"."id", "c"."email", "c"."password", "c"."name", "c"."country", "c"."address"
FROM "Customer" "c"
WHERE "c"."country" = 'USA'
А нижче - результат цього запиту, який буде роздруковано:
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()
Функція приймає пітона генератор в якості аргументу, а потім аналізує його байт - код. Ми можемо отримати інструкції щодо байт-коду цього генератора, використовуючи стандартний dis
модуль 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 має функцію decompile()
в модулі, pony.orm.decompiling
який може відновити AST з байтового коду:
>>> from pony.orm.decompiling import decompile
>>> ast, external_names = decompile(gen)
Тут ми можемо побачити текстове представлення вузлів AST:
>>> ast
GenExpr(GenExprInner(Name('c'), [GenExprFor(AssName('c', 'OP_ASSIGN'), Name('.0'),
[GenExprIf(Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]))])]))
Давайте тепер подивимося, як decompile()
функціонує функція.
decompile()
Функція створює Decompiler
об'єкт, який реалізує шаблон Visitor. Екземпляр декомпілятора отримує інструкції байт-коду по одному. Для кожної інструкції об'єкт декомпілятора викликає свій власний метод. Назва цього методу дорівнює назві поточної інструкції байт-коду.
Коли Python обчислює вираз, він використовує стек, який зберігає проміжний результат обчислення. Об'єкт декомпілятора також має власний стек, але цей стек зберігає не результат обчислення вираження, а вузол AST для виразу.
Коли викликається метод декомпілятора для наступної інструкції байт-коду, він бере вузли AST зі стека, об'єднує їх у новий вузол AST, а потім розміщує цей вузол у верхній частині стека.
Наприклад, давайте подивимось, як c.country == 'USA'
обчислюється підвираз . Відповідним фрагментом байт-коду є:
9 LOAD_FAST 1 (c)
12 LOAD_ATTR 0 (country)
15 LOAD_CONST 0 ('USA')
18 COMPARE_OP 2 (==)
Отже, об’єкт декомпілятора робить наступне:
- Дзвінки
decompiler.LOAD_FAST('c')
. Цей метод ставить Name('c')
вузол у верхній частині стека декомпілятора.
- Дзвінки
decompiler.LOAD_ATTR('country')
. Цей метод бере Name('c')
вузол зі стека, створює Geattr(Name('c'), 'country')
вузол і ставить його у верхній частині стека.
- Дзвінки
decompiler.LOAD_CONST('USA')
. Цей метод ставить Const('USA')
вузол поверх стека.
- Дзвінки
decompiler.COMPARE_OP('==')
. Цей метод бере два вузли (Getattr і Const) зі стека, а потім ставить Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
у верхній частині стека.
Після обробки всіх інструкцій байт-коду стек декомпілятора містить єдиний вузол AST, який відповідає всьому виразу генератора.
Оскільки Pony ORM потрібно декомпілювати лише генератори та лямбда, це не так вже й складно, тому що потік інструкцій для генератора порівняно простий - це лише купа вкладених циклів.
В даний час Pony ORM охоплює весь набір генераторів, крім двох речей:
- Вбудовані, якщо вирази:
a if b else c
- Складні порівняння:
a < b < c
Якщо Поні стикається з таким виразом, це викликає NotImplementedError
виняток. Але навіть у цьому випадку ви можете змусити його працювати, передаючи вираз генератора як рядок. Коли ви передаєте генератор як рядок, Pony не використовує модуль декомпілятора. Натомість він отримує AST за допомогою стандартної compiler.parse
функції Python .
Сподіваюся, що це відповість на ваше запитання.
p
об'єкт є об'єктом типу реалізованого Поні , який дивиться на те , що методи / властивість в даний час доступні на ньому (наприклад,name
,startswith
) і перетворює їх в SQL.