SQLAlchemyでは優先順位のグループ化はどのように実装されていますか?

4
matthewgdv 2019-07-15 12:51.

私はSQLAlchemyAPIを調べてきましたが、それは非常に複雑なので、誰かがこれをある程度消化しやすい形式で説明できるかどうかをここで確認するように依頼したいと思いました。

SQLAlchemyと同様の構文でOffice365RESTAPIクエリを作成するためのO365PythonAPIのラッパーを作成しています。

O365は、次のような流暢なクエリクラスを提供します。

Message.new_query().on_attribute("subject").contains("Hello Friend!").chain("and").on_attribute("from").equals("[email protected]")

そして、私は現在、次のように機能するものを持っています:

Message.where(Subject.contains("Hello Friend!") & (From == "[email protected]")).execute()

正確なコードは実際には関係ありませんが、簡単に言うと、演算子のマジックメソッドを実装し、.contains()などのメソッドを追加することでBooleanExpressionオブジェクトを構築します。例えば:

From == "[email protected]"

BooleanExpressionを返します。

次に、BooleanExpressionオブジェクトが「&」または「|」と結合されます。BooleanExpressionClauseオブジェクトを返す演算子。これは基本的に、2つの式ごとにどの演算子が結合されているかを追跡するBooleanExpressionオブジェクトのリストです。

最後に、.where()メソッドは単一のBooleanExpressionClauseを消費し、内部でそれに対する流暢なクエリを構築します。

ここまでは順調ですね。

したがって、私が直面した障害には、優先順位のグループ化が含まれます。

「こんにちは!」のメッセージがすべて必要だったとしましょう。アドレスに「john」を含む送信者、またはアドレスに「doe」を含む送信者のいずれかによる件名。このようなクエリがあった場合:

From.contains("john") | From.contains("doe") & Subject.contains("Hi!")

MicrosoftのAPIは実際に結果のRESTリクエストを次のように読み取るため、アドレスに「john」が含まれている人からすべてのメッセージを受け取ります。

From.contains("john") | (From.contains("doe") & Subject.contains("Hi!"))

私が欲しいのは:

(From.contains("john") | From.contains("doe")) & Subject.contains("Hi!")

ただし、現在のAPIを使用して単純に記述した場合、括弧なしで記述した場合と何ら変わりはありません。これは、私が知る限り、Pythonに対して、最初の例(優先順位グループなし)と3番目の例(優先グループが必要)は、インタプリタがこのような句を左から右に読み取るだけなので、まったく同じように見えます。

それはついに私の質問に私をもたらします。SQLAlchemyはどういうわけか優先グループを理解することができますが、それがどのように行われるかを私は一生理解できません。

例えば:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm.session import sessionmaker
from sqlalchemy import engine, Column
from sqlalchemy.types import Integer, String

engine = engine("some_engine_url")
Base = declarative_base()
s = sessionmaker(bind=engine)()

class Person(Base):
    __tablename__ = "person"
    id            = Column(Integer, primary_key=True)
    name          = Column(String)
    sex           = Column(String(1))

print(s.query(Person).filter( (Person.name == "john") | (Person.name == "doe") & (Person.sex == "M") ))
print(s.query(Person).filter( ((Person.name == "john") | (Person.name == "doe")) & (Person.sex == "M") ))

これらのprintステートメントは、それぞれ、

SELECT person.id AS person_id, person.name AS person_name, person.sex AS person_sex 
FROM person 
WHERE person.name = ? OR person.name = ? AND person.sex = ?

そして

SELECT person.id AS person_id, person.name AS person_name, person.sex AS person_sex 
FROM person 
WHERE (person.name = ? OR person.name = ?) AND person.sex = ?

SQLAlchemyの内部は、これら2つのフィルター句の違いをどのように見分けることができますか?私の知る限り、Pythonはそれらを同じように処理する必要がありますが、明らかに、私が気付いていない場所でいくつかの魔法が起こっています。

この動作をどのように再現できますか?

本当にありがとう!

1 answers

7
Martijn Pieters 2019-07-15 20:36.

それはついに私の質問に私をもたらします。SQLAlchemyはどういうわけか優先グループを理解することができますが、それがどのように行われるかを私は一生理解できません。

SQLAlchemyはここで多くの作業を行う必要はありません。ほとんどの作業は、特定の順序でオブジェクトを解析するPythonによって行われます。Pythonは、演算子の優先順位の規則に従って式を解析するため、優先順位に基づいて特定の順序で結合された式を実行します。その優先順位がアプリケーションにとって正しく、ネストされた式を常にグループ化することを気にしない場合は、設定されています。これはSQLでは常に当てはまるわけではなく、SQLAlchemyは最小限の余分な括弧の使用で有効なSQL式を出力したいので、SQLAlchemyは独自の優先順位テーブルを参照します。このように(...)して、出力でグループ化が必要になる時期を決定できます。

SQLAlchemyは*Clause*、オペランドの演算を表す専用の式オブジェクト(それぞれをさらに式にすることができます)を返し、それらの演算オブジェクトが演算でも使用される場合は、それらをさらに結合します。最終的には、オブジェクトのツリーが作成され、SQLへのコンパイル中にそのツリーをトラバースすると、必要に応じて、表示されるグループ化された出力が生成されます。優先順位が必要な場合、SQLAlchemyはsqlalchemy.sql.elements.Grouping()オブジェクトを挿入し、グループ化のための正しい構文を生成するのはSQLダイアレクト次第です。

SQLAlchemyのソースコードを見ている場合は、sqlalchemy.sql.operators.ColumnOperatorsクラスとその親クラスであるsqlalchemy.sql.operators.Operators、の呼び出しとして実装さ__or__れているself.operate(or_, other)operator.or_()関数を渡す)ことを確認する必要があります。SQLAlchemyでは、これは複雑に見えます。これは、さまざまなタイプのオブジェクトとSQLダイアレクトのさまざまなタイプの比較に委任する必要があるためです。

しかし、ベースにはsqlalchemy.sql.default_comparatorモジュールがあり、ここでor_and_は(間接的に)のclassmethodsにマップされsqlalchemy.sql.elements.BooleanClauseList、そのクラスのインスタンスを生成します。

このBooleanClauseList._construct()メソッドは.self_group()、次の2つの句のメソッドに委任することにより、そこでのグループ化を処理する責任があります。

convert_clauses = [
    c.self_group(against=operator) for c in convert_clauses
]

これはoperator.or_またはを渡すoperator.and_ため、各オペランドGrouping()は優先順位に基づいてインスタンスを使用する必要があるかどうかを判断できます。以下のためのBooleanClauseListオブジェクト(の結果に... | ...又は... & ...その後組み合わさ互いに|又は&オペレータ)、ClauseList.self_group()この方法は、生成されるGrouping()場合self.operatorと比較して、より低いかまたは等しい優先順位を有しますagainst

def self_group(self, against=None):
    # type: (Optional[Any]) -> ClauseElement
    if self.group and operators.is_precedent(self.operator, against):
        return Grouping(self)
    else:
        return self

ここでsqlalchemy.sql.operators.is_precedent()、式の優先順位テーブルを参照します。

_PRECEDENCE = {
    # ... many lines elided

    and_: 3,
    or_: 2,

    # ... more lines elided
}

def is_precedent(operator, against):
    if operator is against and is_natural_self_precedent(operator):
        return False
    else:
        return _PRECEDENCE.get(
            operator, getattr(operator, "precedence", _smallest)
        ) <= _PRECEDENCE.get(against, getattr(against, "precedence", _largest))

では、2つの表現はどうなりますか?Python()括弧のグループ化を採用しました。まず、式を基本コンポーネントに簡略化しましょう。基本的には次のようになります。

A | B & C
(A | B) & C

Pythonは、独自の優先ルールに従ってこれら2つの式を解析し、独自の抽象構文ツリーを生成します

>>> import ast
>>> ast.dump(ast.parse('A | B & C', mode='eval').body)
"BinOp(left=Name(id='A', ctx=Load()), op=BitOr(), right=BinOp(left=Name(id='B', ctx=Load()), op=BitAnd(), right=Name(id='C', ctx=Load())))"
>>> ast.dump(ast.parse('(A | B) & C', mode='eval').body)
"BinOp(left=BinOp(left=Name(id='A', ctx=Load()), op=BitOr(), right=Name(id='B', ctx=Load())), op=BitAnd(), right=Name(id='C', ctx=Load()))"

これらは

BinOp(
    left=A,
    op=or_,
    right=BinOp(left=B, op=and_, right=C)
)

そして

BinOp(
    left=BinOp(left=A, op=or_, right=B),
    op=and_,
    right=C
)

これにより、オブジェクトが結合される順序が変わります。したがって、最初の結果は次のとおりです。

# process A, then B | C

leftop = A
rightop = BooleanClauseList(and_, (B, C))

# combine into A & (B | C)
final = BooleanClauseList(or_, (leftop, rightop))

# which is
BooleanClauseList(or_, (A, BooleanClauseList(and_, (B, C))))

ここでの2番目の句はBooleanClauseList(and_, ...)インスタンスである.self_group()ため、その句を呼び出してもGrouping();は返されません。そこself.operatorand_低い、高い3の優先順位を有している、いないかのも優先し、等しくor_親句の2 ==。

もう1つの式は、Pythonによって異なる順序で実行されます。

# process A | B, then C

leftop = BooleanClauseList(or_, (A, B))
rightop = C

# combine into (A | B) & C
final = BooleanClauseList(and_, (leftop, rightop))

# which is
BooleanClauseList(and_, (BooleanClauseList(or_, (A, B)), C))

これで、最初の句はBooleanClauseList(or_, ...)インスタンスになり、実際にはGroupingインスタンスが生成されます。これself.operatoror_、isでありand_、親句リストよりも優先度が低いため、オブジェクトツリーは次のようになります。

BooleanClauseList(and_, (Grouping(BooleanClauseList(or_, (A, B))), C))

さて、あなたがしたいのがあなたの表現が正しい順序でグループ化されていることを確認することだけであるなら、あなたは本当にあなた自身のGrouping()オブジェクトを注入する必要はありません。オブジェクトツリーを処理するか、トラバーサルで処理するand_(or_(A, B), C)かは実際には問題ではありませんが、and_((or_(A, B)), C)テキストを再度出力する必要がある場合(SQLAlchemyがデータベースに送信する必要がある場合など)、Grouping()オブジェクトは必要な場所を記録するのに非常に役立ちます(...)テキストを追加します。

SQLAlchemyでは、これはSQLコンパイラで発生します。SQLコンパイラは、ビジターパターンを使用してsqlalchemy.sql.compiler.SQLCompiler.visit_grouping()メソッドを呼び出します。

 def visit_grouping(self, grouping, asfrom=False, **kwargs):
     return "(" + grouping.element._compiler_dispatch(self, **kwargs) + ")"

その式は、単に、コンパイル出力が何であれ、(前後に配置することを意味します。各SQLダイアレクトは基本コンパイラのサブクラスを提供しますが、メソッドをオーバーライドするものはありません。)grouping.elementvisit_grouping()

Related questions

MORE COOL STUFF

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは、夫に会ったとき、典型的な交際のアドバイスに逆らいました。

マイケルシーンが非営利の俳優である理由

マイケルシーンが非営利の俳優である理由

マイケルシーンは非営利の俳優ですが、それは正確にはどういう意味ですか?

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

特徴的なスターのコリン・エッグレスフィールドは、RomaDrama Liveでのスリル満点のファンとの出会いについて料理しました!加えて、大会での彼のINSPIREプログラム。

「たどりつけば」をオンラインでストリーミングできない理由

「たどりつけば」をオンラインでストリーミングできない理由

ノーザンエクスポージャーが90年代の最も人気のある番組の1つになった理由を確認するには、Blu-rayまたはDVDプレーヤーをほこりで払う必要があります。

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖は、世界で2番目に大きいボイリング湖です。そこにたどり着くまでのトレッキングは大変で長いですが、努力する価値は十分にあります。

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

サロンからのヘアトリミングや個人的な寄付は、油流出を吸収して環境を保護するのに役立つマットとして再利用できます。

ホワイトハウスの最も記憶に残る結婚式を見てください

ホワイトハウスの最も記憶に残る結婚式を見てください

過去200年以上の間にホワイトハウスで結婚したのはほんの数人です。彼らは誰でしたか、そしてそこで結婚式を獲得するために何が必要ですか?

グッドジョブ、ESPN

グッドジョブ、ESPN

今日のホワイトハウスの記者会見で、報道官のサラ・ハッカビー・サンダースは、スポーツセンターのホストであるイェメル・ヒルのドナルド・トランプに関する最近のツイートについてコメントするよう求められ、大統領と彼の政策を白人至上主義者として説明した。ヒルは彼女のつぶやきのためにESPNによって公に叱責されました。

アマゾンからのこのレミントンツールセールで髪を整える、スタイルを整える、乾かす、または取り除く

アマゾンからのこのレミントンツールセールで髪を整える、スタイルを整える、乾かす、または取り除く

基本的に頭のあらゆる部分から髪の毛を整えたり、ブロードライしたり、まっすぐにしたり、脱毛したりする必要がある場合は、このレミントンゴールドボックスが最適です。今日だけ、Amazonは、すでに人気のあるShortcut Pro Self-HaircutKitやPearlPro Ceramic Flat Ironのように、グルーミングをはるかに簡単にするヘアツールをマークダウンしています。

カナダの元桂冠詩人が、史上最高の文化の盗用でトゥパックとマヤアンジェロウから盗んだ

カナダの元桂冠詩人が、史上最高の文化の盗用でトゥパックとマヤアンジェロウから盗んだ

トゥパックシャクール(ティムモーゼンフェルダー/ゲッティイメージズ); マヤアンジェロウ(マーティンゴッドウィン/ゲッティイメージズ)移動、テイラースウィフト。ケンドールとカイリーは、必要な数の座席を持っています。

テスラは、ハリケーンイルマによる避難を容易にするために、フロリダでの車両の範囲を拡大しています

テスラは、ハリケーンイルマによる避難を容易にするために、フロリダでの車両の範囲を拡大しています

写真:Tesla Motorsフロリダに住んでいて、Tesla Model S、Model X 60、またはModel 60Dを使用している場合、車の自律性は50 km(約30マイル)高くなります。これは失敗ではなく、ハリケーンイルマによる避難作業を容易にするために会社自身が命じた自治権の一時的な延長です。

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya shared a sweet photo in honor of boyfriend Tom Holland's 26th birthday Wednesday

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

シーレン「Ms.JuicyBaby」ピアソンは、先月脳卒中で入院した後、「もう一度たくさんのことをする方法を学ばなければならない」ため、言語療法を受けていることを明らかにしました。

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

オスカー受賞者の世紀半ばの家には、3つのベッドルーム、2つのバス、オーシャンフロントの景色があります。

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、生後4か月の娘、モナコに母乳育児をしていると語った。

投資ノート:Bioscout AU$300万シード

投資ノート:Bioscout AU$300万シード

Bioscoutは、農家を運転席に置くという使命を負っています。Artesian(GrainInnovate)やUniseedと並んで、最新のシードラウンドでチームを支援できることをうれしく思います。問題真菌症による重大な作物の損失は、農民にとって試練であることが証明されています。

リトルマーケットリサーチ1| 2022年のクイックグリンプス遠隔医療市場

リトルマーケットリサーチ1| 2022年のクイックグリンプス遠隔医療市場

遠隔医療は、パンデミック後の時代では新しいものではなく、時代遅れの分野でもありません。しかし、業界を詳しく見ると、需要と供給の強力な持続可能性と、米国で絶え間ない革命となる強力な潜在的成長曲線を示しています。

スタートアップ資金調達環境:タイのスタートアップエコシステムの次は何ですか?

スタートアップ資金調達環境:タイのスタートアップエコシステムの次は何ですか?

2021年は、世界的なベンチャーキャピタル(VC)の資金調達にとって記録的な年でした。DealStreetAsiaによると、東南アジアも例外ではなく、この地域では年間で記録的な25の新しいユニコーンが採掘されました。

ムーアの法則を超えて

ムーアの法則を超えて

計算に対する私たちの欲求とムーアの法則が提供できるものとの間には、指数関数的に増大するギャップがあります。私たちの文明は計算に基づいています—建築と想像力の現在の限界を超える技術を見つけなければなりません。

Language