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

ダイアナ妃は、8歳でウィリアム王子を寄宿学校に送るという決定に「涙を流した」

ダイアナ妃は、8歳でウィリアム王子を寄宿学校に送るという決定に「涙を流した」

ウィリアム王子が 8 歳のときに寄宿学校に通わせたことについて、ダイアナ妃がどのように感じたかを学びましょう。

シャキール・オニールは、レイカーズのスターが彼のチキン帝国を北テキサスに拡大するにつれて、ダラスの外に永住権を購入しました

シャキール・オニールは、レイカーズのスターが彼のチキン帝国を北テキサスに拡大するにつれて、ダラスの外に永住権を購入しました

Shaquille O'Neal は最近、Big Chicken レストラン帝国を拡大するため、ダラス郊外に住居を購入しました。

「90 日間の婚約者」: イヴが逮捕され、浮気スキャンダルの後、モハメドに対する家庭内暴力の容疑に直面している — 何が起こったのか?

「90 日間の婚約者」: イヴが逮捕され、浮気スキャンダルの後、モハメドに対する家庭内暴力の容疑に直面している — 何が起こったのか?

「90日の婚約者」シーズン9のスター、イヴ・アレラーノが逮捕され、モハメド・アブデルハメドへの暴行容疑で家庭内暴力の罪に問われている.

ナターシャ・リオンは、ピーウィー・ハーマンは「ビジネスで最高のGIFを送る」と言います

ナターシャ・リオンは、ピーウィー・ハーマンは「ビジネスで最高のGIFを送る」と言います

ナターシャ・リオンは、ピーウィー・ハーマン自身、ポール・ルーベンスと親密です。彼らの友情について彼女が言ったことを発見してください。

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セント ヘレナ島のジェイコブズ ラダーは 699 段の真っ直ぐ上る階段で、頂上に到達すると証明書が発行されるほどの難易度です。

The Secrets of Airline Travel Quiz

The Secrets of Airline Travel Quiz

Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

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

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

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

ショーン・ハニティーは「南北戦争」の意味を知らないようです

ショーン・ハニティーは「南北戦争」の意味を知らないようです

写真:APショーン・ハニティーは、彼の悪いテレビ番組を使って、私たちの悪い大統領のために毎晩サクラをしますが、彼はまた、毎日午後3時間の耐え難いほど放送されるラジオ番組を持っています。今日、彼はそのラジオ番組を使って、来たるべき内戦への恐れについて話しました。

シャーロッツビルの攻撃の目撃者を中傷するための訴訟で平手打ちされたインフォウォーズ

シャーロッツビルの攻撃の目撃者を中傷するための訴訟で平手打ちされたインフォウォーズ

バージニア州シャーロッツビルで開催された白人至上主義者集会での反対運動者の恐ろしい殺人の目撃者は、InfoWarsや他のいくつかのメディアを名誉毀損で訴えています。国務省の外交官であるブレナン・ギルモアは、彼が「深い国家」の陰謀の一部であったと主張することによって、アウトレットが彼のキャリアを傷つけたと主張している。

フェラーリは、ディーラーが中古フェラーリの走行距離計を変更することを故意に許可していることを認めています

フェラーリは、ディーラーが中古フェラーリの走行距離計を変更することを故意に許可していることを認めています

昨年、フェラーリが使用済みのフェラーリの走行距離計をロールバックしてその価値を劇的に(そして違法に)増加させる可能性のあるデバイスの使用を許可したと主張した、フェラーリの長年の営業担当者からの訴訟について報告したことを思い出してください。さて、デイリーメールの報告によると、フェラーリが何が起こっているのかを知っていて、それを承認したという証拠があります。

カニエ・ウェストは、私が今あなたに嘘をついているので、私が決して見るつもりはないという有名人の家族の確執のエピソードをちょうど録音しました

カニエ・ウェストは、私が今あなたに嘘をついているので、私が決して見るつもりはないという有名人の家族の確執のエピソードをちょうど録音しました

キム・カーダシアンとカニエ・ウェストは、2016年5月2日にニューヨーク市で開催されたメトロポリタン美術館のManus x Machina:Fashion In a Age ofTechnologyコスチュームインスティテュートガラに出席します。カーダシアン家のお騒がせ、クロエ&ラマー、私はケイト、カイリー、ケンダル・ゴー・ブラックボーイ・フィッシングのエピソードや、カーダシアンのショーの他の反復を見たことがありません。

ケイト・ミドルトンとウィリアム王子は、彼らが子供たちと行っているスパイをテーマにした活動を共有しています

ケイト・ミドルトンとウィリアム王子は、彼らが子供たちと行っているスパイをテーマにした活動を共有しています

ケイト・ミドルトンとウィリアム王子は、子供向けのパズルの本の序文を書き、ジョージ王子、シャーロット王女、ルイ王子と一緒にテキストを読むと述べた.

事故で押しつぶされたスイカは、動物を喜ばせ水分補給するために野生生物保護団体に寄付されました

事故で押しつぶされたスイカは、動物を喜ばせ水分補給するために野生生物保護団体に寄付されました

Yak's Produce は、数十個のつぶれたメロンを野生動物のリハビリ専門家であるレスリー グリーンと彼女のルイジアナ州の救助施設で暮らす 42 匹の動物に寄付しました。

デミ・ロヴァートは、新しいミュージシャンのボーイフレンドと「幸せで健康的な関係」にあります: ソース

デミ・ロヴァートは、新しいミュージシャンのボーイフレンドと「幸せで健康的な関係」にあります: ソース

8 枚目のスタジオ アルバムのリリースに向けて準備を進めているデミ ロヴァートは、「スーパー グレート ガイ」と付き合っている、と情報筋は PEOPLE に確認しています。

Plathville の Kim と Olivia Plath が数年ぶりに言葉を交わすことへようこそ

Plathville の Kim と Olivia Plath が数年ぶりに言葉を交わすことへようこそ

イーサン プラスの誕生日のお祝いは、TLC のウェルカム トゥ プラスビルのシーズン 4 のフィナーレで、戦争中の母親のキム プラスと妻のオリビア プラスを結びつけました。

水門の修理

水門の修理

天王星と海王星の間の領域に向かって宇宙を 3/4 g の低温で航行しながら、私たちは数週間燃え続けていました。Dawson Trawler の科学者が Yggdrasil ポータルと呼んだもの。

美しいもの

美しいもの

女性として、私は通常、関係を築くことをためらっています。私はいつも彼らに負けないように苦労しました。私は誰かと共有したいという衝動と戦わなければなりません。

逃走中の女性からの発信

最も家が必要なときに家のように感じる場所はありません。

逃走中の女性からの発信

私は誰よりも移動しました。父が住んでいた土地には、父が 1 歳馬を折るミニチュアの競馬場がありました。

死にゆく男から学んだ最大の人生の教訓

彼は、私たちが持っているのはこの現在の瞬間だけであることを知るのが遅すぎました。

死にゆく男から学んだ最大の人生の教訓

ブラッドは、カーキ色のショート パンツとポロ シャツを着たまま、白いゴルフ グローブを両手で高く引っ張ったまま、ベッドルームに入ってきました。彼は満面の笑みを浮かべながら、「今年は私の人生で最高の年だったと思います!」と言いました。通常は保守的な消費者である私たちは、通常とは異なることをしました。

Language