El autor de Pony ORM está aquí.
Pony traduce el generador de Python a una consulta SQL en tres pasos:
- Descompilación del código de bytes del generador y reconstrucción del AST del generador (árbol de sintaxis abstracta)
- Traducción de Python AST en "SQL abstracto": representación universal basada en listas de una consulta SQL
- Conversión de la representación SQL abstracta en un dialecto SQL específico dependiente de la base de datos
La parte más compleja es el segundo paso, donde Pony debe comprender el "significado" de las expresiones de Python. Parece que estás más interesado en el primer paso, así que déjame explicarte cómo funciona la descompilación.
Consideremos esta consulta:
>>> from pony.orm.examples.estore import *
>>> select(c for c in Customer if c.country == 'USA').show()
Que se traducirá al siguiente SQL:
SELECT "c"."id", "c"."email", "c"."password", "c"."name", "c"."country", "c"."address"
FROM "Customer" "c"
WHERE "c"."country" = 'USA'
Y a continuación se muestra el resultado de esta consulta que se imprimirá:
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()
función acepta un generador de Python como argumento y luego analiza su código de bytes. Podemos obtener instrucciones de código de bytes de este generador usando el dis
módulo estándar de 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 tiene la función decompile()
dentro del módulo pony.orm.decompiling
que puede restaurar un AST desde el código de bytes:
>>> from pony.orm.decompiling import decompile
>>> ast, external_names = decompile(gen)
Aquí, podemos ver la representación textual de los nodos AST:
>>> ast
GenExpr(GenExprInner(Name('c'), [GenExprFor(AssName('c', 'OP_ASSIGN'), Name('.0'),
[GenExprIf(Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]))])]))
Veamos ahora cómo decompile()
funciona la función.
La decompile()
función crea un Decompiler
objeto, que implementa el patrón Visitor. La instancia del descompilador obtiene instrucciones de código de bytes una por una. Para cada instrucción, el objeto descompilador llama a su propio método. El nombre de este método es igual al nombre de la instrucción de código de bytes actual.
Cuando Python calcula una expresión, usa pila, que almacena un resultado intermedio del cálculo. El objeto descompilador también tiene su propia pila, pero esta pila no almacena el resultado del cálculo de la expresión, sino el nodo AST para la expresión.
Cuando se llama al método de descompilador para la siguiente instrucción de código de bytes, toma los nodos AST de la pila, los combina en un nuevo nodo AST y luego coloca este nodo en la parte superior de la pila.
Por ejemplo, veamos cómo c.country == 'USA'
se calcula la subexpresión . El fragmento de código de bytes correspondiente es:
9 LOAD_FAST 1 (c)
12 LOAD_ATTR 0 (country)
15 LOAD_CONST 0 ('USA')
18 COMPARE_OP 2 (==)
Entonces, el objeto descompilador hace lo siguiente:
- Llamadas
decompiler.LOAD_FAST('c')
. Este método coloca el Name('c')
nodo en la parte superior de la pila del descompilador.
- Llamadas
decompiler.LOAD_ATTR('country')
. Este método toma el Name('c')
nodo de la pila, crea el Geattr(Name('c'), 'country')
nodo y lo coloca en la parte superior de la pila.
- Llamadas
decompiler.LOAD_CONST('USA')
. Este método coloca el Const('USA')
nodo en la parte superior de la pila.
- Llamadas
decompiler.COMPARE_OP('==')
. Este método toma dos nodos (Getattr y Const) de la pila y luego los coloca Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
en la parte superior de la pila.
Después de que se procesan todas las instrucciones de código de bytes, la pila del descompilador contiene un solo nodo AST que corresponde a toda la expresión del generador.
Dado que Pony ORM solo necesita descompilar generadores y lambdas, esto no es tan complejo, porque el flujo de instrucciones para un generador es relativamente sencillo: es solo un montón de bucles anidados.
Actualmente, Pony ORM cubre todo el conjunto de instrucciones del generador, excepto dos cosas:
- Expresiones if en línea:
a if b else c
- Comparaciones compuestas:
a < b < c
Si Pony encuentra tal expresión, plantea la NotImplementedError
excepción. Pero incluso en este caso, puede hacer que funcione pasando la expresión del generador como una cadena. Cuando pasas un generador como una cadena, Pony no usa el módulo descompilador. En su lugar, obtiene el AST utilizando la compiler.parse
función estándar de Python .
Espero que esto responda a su pregunta.
p
objeto es un objeto de un tipo implementado por Pony que mira qué métodos / propiedades se están accediendo en él (por ejemploname
,startswith
) y los convierte a SQL.