ベンダーが許可していない場合は、SELECTを取得して作成することはできません...しかし、

TL; DR: GitHub:// PastorGL / AQLSelectEx







Aerospike AQL SELECT







かつては寒い季節ではなく、すでに冬の季節で、具体的には数か月前に、私が取り組んでいたプロジェクト(ビッグデータに基づく地理空間)のために、高速なNoSQL / Key-Valueストレージが必要でした。







Apache Sparkを使用してテラバイトのソースコードを噛み砕きますが、計算の最終結果はとんでもない量(わずか数百万のレコード)に崩壊し、どこかに保存する必要があります。 また、結果の各行に関連付けられたメタデータ(これは1桁)を使用してすばやく見つけて送信できるように保存することが非常に望ましいです(ただし、非常に多くあります)。







この意味でのKhadupovスタックのフォーマットはほとんど役に立たず、数百万のレコード上のリレーショナルデータベースの速度が低下し、メタデータのセットは通常のRDBMSの厳格なスキーム(この場合はPostgreSQL)に収まるほど固定されていません。 いいえ、通常はJSONをサポートしていますが、それでも数百万のレコードのインデックスに問題があります。 インデックスが大きくなり、テーブルをパーティション化する必要が生じ、そのような管理の手間がnafig-nafigから始まります。







歴史的に、MongoDBはプロジェクトでNoSQLとして使用されていましたが、時間が経つにつれて、mongaはますます悪化し(特に安定性の観点から)、徐々に廃止されました。 より近代的で、高速で、バグが少なく、一般的に優れた代替品をすばやく検索すると、 Aerospikeにつながりました。 多くの大げさな人がそれを支持しており、私はチェックすることにしました。







テストでは、実際には、データはホイッスルを使用してSparkジョブから直接ストーリーに保存され、何百万ものレコードの検索がmongの場合よりもはるかに高速であることが示されています。 そして彼女はより少ない記憶を食べる。 しかし、1つの「しかし」と判明しました。 AeroはんだのクライアントAPIは純粋に機能的であり、宣言的ではありません。







ストーリーに記録する場合、これは重要ではありません。各結果レコードのすべてのタイプのフィールドは、ジョブ自体でローカルに決定する必要があり、コンテキストは失われないためです。 機能的なスタイルがここにあります。特に、異なる方法でコードを書くと機能しないためです。 しかし、結果を外部にアップロードするWeb銃口では、通常のSpring Webアプリケーションであるため、ユーザーフォームから標準のSQL SELECTを作成する方がはるかに論理的です。 、-WHERE句内。







そのような合成例との違いを説明します。







SELECT foo, bar, baz, qux, quux FROM namespace.set WITH (baz!='a') WHERE (foo>2 AND (bar<=3 OR foo>5) AND quux LIKE '%force%') OR NOT (qux WITHIN CAST('{\"type\": \"Polygon\", \"coordinates\": [0.0, 0.0],[1.0, 0.0],[1.0, 1.0],[0.0, 1.0],[0.0, 0.0]}' AS GEOJSON)
      
      





-読みやすく、顧客がどのレコードを受け取りたかったかは比較的明確です。 そのような要求がそのままログに直接スローされる場合は、手動でデバッグするために後でプルできます。 これは、あらゆる種類の奇妙な状況を解析するときに非常に便利です。







次に、関数型の述語APIの呼び出しを見てみましょう。







 Statement reference = new Statement(); reference.setSetName("set"); reference.setNamespace("namespace"); reference.setBinNames("foo", "bar", "baz", "qux", "quux"); reference.setFillter(Filter.stringNotEqual("baz", "a")); reference.setPredExp(// 20 expressions in RPN PredExp.integerBin("foo") , PredExp.integerValue(2) , PredExp.integerGreater() , PredExp.integerBin("bar") , PredExp.integerValue(3) , PredExp.integerLessEq() , PredExp.integerBin("foo") , PredExp.integerValue(5) , PredExp.integerGreater() , PredExp.or(2) , PredExp.and(2) , PredExp.stringBin("quux") , PredExp.stringValue(".*force.*") , PredExp.stringRegex(RegexFlag.ICASE) , PredExp.and(2) , PredExp.geoJSONBin("qux") , PredExp.geoJSONValue("{\"type\": \"Polygon\", \"coordinates\": [0.0, 0.0],[1.0, 0.0],[1.0, 1.0],[0.0, 1.0],[0.0, 0.0]}") , PredExp.geoJSONWithin() , PredExp.not() , PredExp.or(2) );
      
      





ここにコードの壁があり、 逆ポーランド記法でもです。 いいえ、スタックマシンは、エンジン自体のプログラマーの観点から実装するのが簡単で便利であることを理解していますが、クライアントアプリケーションからRPNで述語をパズルおよび記述するには...私は個人的にベンダーについて考えたくない、私はこのAPIの消費者として欲しい便利でした。 また、ベンダーのクライアント拡張(概念的にはJava Persistence Criteria APIに似ています)を使用した述語でも、書くのは不便です。 また、クエリログには読み取り可能なSELECTがありません。







一般的に、SQLは、自然に近い鳥の言語で基準ベースのクエリを記述するために発明されました。 それで、一体何なんだろう?







待ってください、何かが正しくありません... KDPVには、SELECTが完全に記述されているエアロゾル処理の公式文書のスクリーンショットがありますか?







はい、説明されています。 これは単なるAQLです。これは、暗い夜に左後ろの足で書かれたサードパーティのユーティリティで、3年前にエアロスパイクの前のバージョンでベンダーによって放棄されました。 ヒキガエルに書かれていますが、クライアントライブラリとは何の関係もありません。







3年前のバージョンには述語APIがなかったため、AQLでは述語はサポートされておらず、WHEREが実際にインデックス(セカンダリまたはプライマリ)にアクセスした後はすべてサポートされません。 まあ、つまり、USEやWITHなどのSQL拡張機能に近い。 つまり、AQLソースを取得してスペアパーツに分解し、アプリケーションで述語呼び出しに使用することはできません。







さらに、すでに述べたように、それは暗い夜に左後足で書かれており、AQLが涙なしでクエリを解析するANTLR4 文法を見ることは不可能です。 まあ、私の好みのために。 何らかの理由で、文法の宣言的な定義がヒキガエルのコードの断片と混ざっておらず、非常にクールなヌードルがそこに醸造されているとき、私はそれが大好きです。







幸いなことに、ANTLRの実行方法も知っているようです。 確かに、私は長い間チェッカーを手に入れず、最後に3番目のバージョンで書いた。 4番目-すべてが私たちの前に書かれていて、通常の訪問者がいるなら、誰が手動ASTツアーを書きたいので、それははるかに良いです。それでは始めましょう。







SQLite構文をベースとして、不要なものはすべて捨てようとします。 SELECTだけが必要で、それ以上は必要ありません。







 grammar SQLite; simple_select_stmt : ( K_WITH K_RECURSIVE? common_table_expression ( ',' common_table_expression )* )? select_core ( K_ORDER K_BY ordering_term ( ',' ordering_term )* )? ( K_LIMIT expr ( ( K_OFFSET | ',' ) expr )? )? ; select_core : K_SELECT ( K_DISTINCT | K_ALL )? result_column ( ',' result_column )* ( K_FROM ( table_or_subquery ( ',' table_or_subquery )* | join_clause ) )? ( K_WHERE expr )? ( K_GROUP K_BY expr ( ',' expr )* ( K_HAVING expr )? )? | K_VALUES '(' expr ( ',' expr )* ')' ( ',' '(' expr ( ',' expr )* ')' )* ; expr : literal_value | BIND_PARAMETER | ( ( database_name '.' )? table_name '.' )? column_name | unary_operator expr | expr '||' expr | expr ( '*' | '/' | '%' ) expr | expr ( '+' | '-' ) expr | expr ( '<<' | '>>' | '&' | '|' ) expr | expr ( '<' | '<=' | '>' | '>=' ) expr | expr ( '=' | '==' | '!=' | '<>' | K_IS | K_IS K_NOT | K_IN | K_LIKE | K_GLOB | K_MATCH | K_REGEXP ) expr | expr K_AND expr | expr K_OR expr | function_name '(' ( K_DISTINCT? expr ( ',' expr )* | '*' )? ')' | '(' expr ')' | K_CAST '(' expr K_AS type_name ')' | expr K_COLLATE collation_name | expr K_NOT? ( K_LIKE | K_GLOB | K_REGEXP | K_MATCH ) expr ( K_ESCAPE expr )? | expr ( K_ISNULL | K_NOTNULL | K_NOT K_NULL ) | expr K_IS K_NOT? expr | expr K_NOT? K_BETWEEN expr K_AND expr | expr K_NOT? K_IN ( '(' ( select_stmt | expr ( ',' expr )* )? ')' | ( database_name '.' )? table_name ) | ( ( K_NOT )? K_EXISTS )? '(' select_stmt ')' | K_CASE expr? ( K_WHEN expr K_THEN expr )+ ( K_ELSE expr )? K_END | raise_function ;
      
      





うーん... SELECTが多すぎます。 そして、過剰を取り除くのが非常に簡単な場合、結果として生じる回避策の構造そのものに関してもう1つの悪いことがあります。







最終的な目標は、RPNと暗黙のスタックマシンを使用して述語APIに変換することです。 そして、左から右への通常の分析を意味するので、ここでアトミックexprはそのような変換には一切寄与しません。 はい、再帰的に定義されています。







つまり、合成例を取得できますが、左から右に書かれているとおりに正確に読み取られます。







 (foo>2  (bar<=3  foo>5)  quux _ '%force%')  (qux _('{\"type\": \"Polygon\", \"coordinates\": [0.0, 0.0],[1.0, 0.0],[1.0, 1.0],[0.0, 1.0],[0.0, 0.0]}')
      
      





解析の優先順位を決定する括弧があります(つまり、スタックを前後にぶら下げる必要があることを意味します)。また、一部の演算子は関数呼び出しのように動作します。







そして、このシーケンスが必要です。







 foo 2 > bar 3 <= foo 5 >   quux ".*force.*" _  qux "{\"type\": \"Polygon\", \"coordinates\": [0.0, 0.0],[1.0, 0.0],[1.0, 1.0],[0.0, 1.0],[0.0, 0.0]}" _  
      
      





Brr、スズ、読むのが苦手。 しかし、括弧なしでは、コールの順序にロールバックや誤解はありません。 そして、私たちはどのように別のものに翻訳しますか?







そして、貧しい脳では、チョコが起こります! -こんにちは、これは多くの古典的なシャンティングヤードです。 教授 ダイクストラ! 通常、私のようなokolobigdatovskimiシャーマンはアルゴリズムを必要としません。なぜなら、データ悪魔主義者によって既に書かれたプロトタイプをPythonからヒキガエルに単純に転送し、科学的ではなく純粋にエンジニアリング(==シャーマニスティック)方法によって得られたソリューションの長く退屈なパフォーマンスのために。







しかし、その後、突然アルゴリズムを知ることが必要になりました。 または少なくともそのアイデア。 幸いなことに、過去数年間、大学のコース全体が忘れられているわけではありません。また、スタックマシンについて覚えているので、関連するアルゴリズムについて何か他のものを発掘することもできます。







わかった シャンティングヤードによって研ぎ澄まされた文法では、トップレベルのSELECTは次のようになります。







 select_stmt : K_SELECT ( STAR | column_name ( COMMA column_name )* ) ( K_FROM from_set )? ( (K_USE | K_WITH) index_expr )? ( K_WHERE where_expr )? ; where_expr : ( atomic_expr | OPEN_PAR | CLOSE_PAR | logic_op )+ ; logic_op : K_NOT | K_AND | K_OR ; atomic_expr : column_name ( equality_op | regex_op ) STRING_LITERAL | ( column_name | meta_name ) ( equality_op | comparison_op ) NUMERIC_LITERAL | column_name map_op iter_expr | column_name list_op iter_expr | column_name geo_op cast_expr ;
      
      





つまり、角かっこに対応するトークンは重要であり、再帰exprを使用しないでください。 代わりに、private_exprの束があり、すべてが有限です。







訪問者がこのツリーに実装するヒキガエルのコードでは、物事はもう少し中毒性があります-アルゴリズムに厳密に従って、それ自体がぶら下がっているlogic_opを処理し、括弧のバランスをとります。 抜粋は行いません( GCを自分で見てください)が、次の点を考慮します。







aerospikeの作者がAQLで述語サポートを気にしなかった理由が明らかになり、3年前にそれを放棄しました。 厳密に入力されており、エアロスパイク自体はスキーマレスストーリーとして表示されるためです。 そのため、事前に決められたスキームなしでは、むき出しのSQLからクエリを取得およびガットすることは単に不可能です。 おっと







しかし、私たちは焦がされており、最も重要なのはrog慢です。 フィールドタイプのスキームが必要なので、フィールドタイプのスキームがあります。 さらに、クライアントライブラリにはすでに必要なすべての定義があり、それらを選択するだけです。 タイプごとに多くのコードを書く必要がありました(56行目から同じリンクを参照)。







今初期化...







 final HashMap FOO_BAR_BAZ = new HashMap() {{ put("namespace.set0", new HashMap() {{ put("foo", ParticleType.INTEGER); put("bar", ParticleType.DOUBLE); put("baz", ParticleType.STRING); put("qux", ParticleType.GEOJSON); put("quux", ParticleType.STRING); put("quuux", ParticleType.LIST); put("corge", ParticleType.MAP); put("corge.uier", ParticleType.INTEGER); }}); put("namespace.set1", new HashMap() {{ put("grault", ParticleType.INTEGER); put("garply", ParticleType.STRING); }}); }}; AQLSelectEx selectEx = AQLSelectEx.forSchema(FOO_BAR_BAZ);
      
      





...そして出来事、今私たちの合成クエリはエアロゾルデリングから簡単かつ明確にジャークします:







 Statement statement = selectEx.fromString("SELECT foo,bar,baz,qux,quux FROM namespace.set WITH (baz='a') WHERE (foo>2 AND (bar <=3 OR foo>5) AND quux LIKE '%force%') OR NOT (qux WITHIN CAST('{\"type\": \"Polygon\", \"coordinates\": [0.0, 0.0],[1.0, 0.0],[1.0, 1.0],[0.0, 1.0],[0.0, 0.0]}' AS GEOJSON)");
      
      





そして、フォームをWeb銃口からリクエスト自体に変換するために、Web銃口でずっと前に書かれた大量のコードを取得します...最終的にプロジェクトに到達したとき、そうでなければ顧客はそれを棚に置きました。 それは残念だ、気にしない、私はほぼ一週間を過ごした。







AQLSelectExライブラリが誰かにとって有用であり、アプローチ自体がANTLRに特化したHabrの他の記事よりも少し現実的なチュートリアルになることを願っています。








All Articles