Pony ORMの作者はこちらです。
Ponyは、3つのステップでPythonジェネレーターをSQLクエリに変換します。
- ジェネレーターバイトコードの逆コンパイルとジェネレーターASTの再構築(抽象構文ツリー)
- Python ASTの「抽象SQL」への変換-SQLクエリの汎用リストベースの表現
- 抽象SQL表現を特定のデータベース依存SQL方言に変換する
最も複雑な部分は2番目のステップで、Ponyは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()
関数は、Pythonジェネレーターを引数として受け入れ、そのバイトコードを分析します。標準のPython dis
モジュールを使用して、このジェネレーターのバイトコード命令を取得できます。
>>> 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には、バイトコードからASTを復元できるdecompile()
モジュール内の関数がありますpony.orm.decompiling
。
>>> 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パターンを実装するオブジェクトを、。デコンパイラーのインスタンスは、バイトコード命令を1つずつ取得します。命令ごとに、逆コンパイラオブジェクトは独自のメソッドを呼び出します。このメソッドの名前は、現在のバイトコード命令の名前と同じです。
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('==')
。このメソッドは、スタックから2つのノード(GetattrとConst)を取得し、スタックCompare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
の一番上に配置します。
すべてのバイトコード命令が処理された後、逆コンパイラスタックには、ジェネレータ式全体に対応する単一のASTノードが含まれます。
Pony ORMはジェネレーターとラムダのみを逆コンパイルする必要があるので、ジェネレーターの命令フローは比較的単純なので、これはそれほど複雑ではありません。ネストされたループの集まりにすぎません。
現在、Pony ORMは、2つのことを除いて、ジェネレーター命令セット全体をカバーしています。
- インラインif式:
a if b else c
- 複合比較:
a < b < c
Ponyがそのような式に遭遇すると、NotImplementedError
例外が発生します。ただし、この場合でも、ジェネレータ式を文字列として渡すことで機能させることができます。ジェネレータを文字列として渡すと、Ponyは逆コンパイラモジュールを使用しません。代わりに、標準のPythonを使用してASTを取得しますcompiler.parse
関数ます。
これがあなたの質問に答えることを願っています。
p
オブジェクトは、アクセスされているメソッド/プロパティ(例:name
、startswith
)を調べてSQLに変換するPonyによって実装されたタイプのオブジェクトです。