TL ؛ DR: GitHub: // PastorGL / AQLSelectEx .

مرة واحدة ، ليس في موسم البرد ، ولكن بالفعل في فصل الشتاء ، وبالتحديد قبل شهرين ، لمشروع كنت أعمل عليه (شيء يعتمد على الجغرافيا المكانية استنادًا إلى البيانات الكبيرة) ، كنت بحاجة إلى تخزين سريع لل NoSQL / Key-Value.
نقوم بمضغ تيرابايت من أكواد المصدر بمساعدة Apache Spark ، لكن النتيجة النهائية للحسابات ، التي انهارت إلى حد مثير للسخرية (فقط ملايين السجلات) ، تحتاج إلى تخزينها في مكان ما. ومن المرغوب فيه للغاية أن يتم تخزينه بطريقة يمكن العثور عليها بسرعة وإرسالها باستخدام بيانات التعريف المرتبطة بكل سطر من النتيجة (هذا رقم واحد) (ولكن هناك الكثير منها).
إن تنسيقات مكدس Khadupov بهذا المعنى قليلة الاستخدام ، وتبطئ قواعد البيانات العلائقية على ملايين السجلات ، ومجموعة البيانات الوصفية غير ثابتة بحيث تتناسب بشكل جيد مع المخطط الصارم لنظام RDBMS - PostgreSQL منتظم في حالتنا. لا ، إنها تدعم JSON عادة ، لكنها لا تزال تواجه مشاكل مع الفهارس على ملايين السجلات. تتضخم الفهارس ، يصبح من الضروري تقسيم الجدول ، وتبدأ هذه المتاعب مع الإدارة في nafig-nafig.
تاريخياً ، تم استخدام MongoDB كـ NoSQL في المشروع ، ولكن مع مرور الوقت ، تُظهر المنجا نفسها أسوأ وأسوأ (خاصة من حيث الاستقرار) ، لذلك تم إيقاف تشغيلها تدريجياً. البحث السريع عن بديل أكثر حداثة وأسرع وأقل عربات التي تجرها الدواب ، وأفضل عموما أدى إلى Aerospike . العديد من اللاعبين الكبار يرغبون في ذلك ، وقررت التحقق.
أظهرت الاختبارات أنه في الواقع ، يتم تخزين البيانات في القصة مباشرة من وظيفة Spark باستخدام صفارة ، وأن البحث في ملايين السجلات أسرع بكثير من البحث في المونج. وهي تأكل ذاكرة أقل. ولكن اتضح واحد "ولكن". واجهة برمجة تطبيقات العميل لجهاز لحام الهواء تعمل بحتة وليست تصريحية.
للتسجيل في القصة ، هذا ليس مهمًا ، لأن جميع أنواع الحقول لكل سجل ناتج يجب تحديدها محليًا في الوظيفة نفسها - ولا يضيع السياق. النمط الوظيفي موجود هنا ، خاصة وأن كتابة الكود بطريقة مختلفة لن ينجح. ولكن في كمامة الويب ، التي ينبغي أن تحمل النتيجة إلى العالم الخارجي ، وهو تطبيق ربيع عادي على شبكة الإنترنت ، سيكون من المنطقي أكثر بكثير تشكيل SQL SELECT قياسي من نموذج المستخدم ، والذي سيكون مليئًا بـ AND و OR - أي ، يتوقع ، - في جملة 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(
هنا هو جدار الشفرة ، وحتى في تدوين البولندية العكسي . لا ، أنا أفهم أن جهاز المكدس بسيط ومريح للتنفيذ من وجهة نظر مبرمج المحرك نفسه ، ولكن لغزا محيرا وكتابة التنبؤات في RPN من تطبيق العميل ... أنا شخصيا لا أريد أن أفكر في البائع ، أريد مني كمستهلك لواجهة برمجة التطبيقات هذه كانت مريحة. كما أن التنبؤات حتى مع وجود ملحق عميل بائع (يشبه نظريًا API Java Persistence Criteria API) غير مريحة للكتابة. وما زال لا يوجد SELECT قابل للقراءة في سجل الاستعلام.
بشكل عام ، تم اختراع SQL من أجل كتابة الاستعلامات المستندة إلى المعايير فيه بلغة الطيور ، بالقرب من الطبيعية. لذا ، يتساءل المرء ، ماذا بحق الجحيم؟
انتظر ، هناك شيء غير صحيح ... على KDPV ، هل هناك لقطة شاشة من الوثائق الرسمية الخاصة بالهباء الجوي ، والتي تم تحديد SELECT عليها تمامًا؟
نعم ، وصفها. هذا مجرد AQL - هذه أداة مساعدة تابعة لجهة خارجية كتبها القدم الخلفية اليسرى في ليلة مظلمة ، والتي تخلى عنها البائع قبل ثلاث سنوات خلال الإصدار السابق من الارتفاع الهوائي. لا علاقة له بمكتبة العميل ، على الرغم من أنه مكتوب على الضفدع - بما في ذلك.
لم يكن الإصدار منذ ثلاث سنوات يحتوي على واجهة برمجة التطبيقات الأصلية ، وبالتالي في AQL لا يوجد أي دعم للتنبؤات ، وكل ذلك بعد WHERE هو في الواقع دعوة إلى الفهرس (الثانوي أو الأساسي). حسنًا ، هذا أقرب إلى امتداد SQL مثل USE أو WITH. وهذا يعني أنه لا يمكنك فقط أخذ مصادر AQL وتفكيكها إلى قطع غيار واستخدامها في التطبيق الخاص بك لإجراء مكالمات أصلية.
بالإضافة إلى ذلك ، كما قلت ، تمت كتابته في الليل المظلم مع القدم اليسرى الخلفية ، ومن المستحيل إلقاء نظرة على قواعد ANTLR4 ، التي تقوم AQL بتوزيع الاستعلام عليها بدون دموع. حسنا ، لذوقي. لسبب ما ، أحبها عندما لا يتم خلط التعريف التعريفي للقواعد مع أجزاء من رمز الضفدع ، ويتم تحضير المعكرونة الرائعة جدًا هناك.
حسنًا ، لحسن الحظ ، يبدو لي أيضًا أنني أعرف كيفية القيام بـ ANTLR. صحيح ، لفترة طويلة لم أحمل صابرًا ، وآخر مرة كتبت فيها نسخة أخرى. رابعًا - إنها أجمل كثيرًا ، لأن من يريد كتابة جولة 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 كثيرًا. وإذا كان من السهل جدًا التخلص من الفائض ، فهناك شيء سيء آخر يتعلق بالبنية ذاتها للمشكلة الناتجة.
الهدف النهائي هو الترجمة إلى واجهة برمجة التطبيقات الأصلية باستخدام RPN وآلة مكدس الضمنية. وهنا لا يساهم expr atomic في هذا التحول بأي شكل من الأشكال ، لأنه يتضمن تحليلًا طبيعيًا من اليسار إلى اليمين. نعم ، وتحديد متكرر.
بمعنى ، يمكننا الحصول على مثالنا الصناعي ، ولكن سيتم قراءته تمامًا كما هو مكتوب ، من اليسار إلى اليمين:
(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]}" _
بر ، والقصدير ، الدماغ الفقراء لقراءة. ولكن من دون أقواس ، لا توجد حالات رجوع إلى الوراء وسوء الفهم بترتيب المكالمة. وكيف نترجم واحدة إلى أخرى؟
ثم في دماغ فقير ، يحدث شوك! - مرحبا ، هذا هو كلاسيكي Shunting يارد من العديد. أ. ديكسترا! عادةً ، لا تحتاج okolobigdatovskih shamans مثلي ، إلى خوارزميات ، لأننا ببساطة ننقل النماذج الأولية المكتوبة بالفعل من قِبل شيطانيي البيانات من الثعبان إلى الضفدع ، ثم للحصول على أداء طويل وممل للحل الذي تم الحصول عليه بواسطة الأساليب الهندسية البحتة (== الشامانية) ، وليس علميًا .
ولكن فجأة أصبح من الضروري معرفة الخوارزمية. أو على الأقل فكرة عن ذلك. لحسن الحظ ، لم يتم نسيان جميع الدورات الجامعية خلال السنوات الماضية ، وبما أنني أتذكر الآلات المكدسة ، فيمكنني أيضًا اكتشاف شيء آخر حول الخوارزميات المرتبطة بها.
حسنا في قواعد نحوية شحذها بواسطة Shunting Yard ، سيبدو 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 ;
وهذا يعني أن الرموز المميزة المقابلة للأقواس كبيرة ولا يجب أن يكون هناك خطأ عودية. بدلاً من ذلك ، سيكون هناك حفنة من كل private_expr ، وكلها محدودة.
في الكود الموجود على الضفدع ، والذي ينفذ الزائر لهذه الشجرة ، تكون الأشياء أكثر إدمانًا - بما يتوافق تمامًا مع الخوارزمية ، التي تعالج المنطق المعلقة وتوازن بين الأقواس. لن أعطي مقتطفات ( انظر إلى GC بنفسك) ، لكنني سأولي الاعتبار التالي.
يصبح من الواضح لماذا لم يزعج مؤلفو الارتفاع الجوي بالدعم الأصلي في AQL ، وتخلوا عنه قبل ثلاث سنوات. لأنه مكتوب بشكل صارم ، ويتم تقديم الارتفاع الهوائي نفسه كقصة بلا مخطط. ولذا فمن المستحيل أن تأخذ وتحصل على استعلام من SQL العارية دون مخطط محدد سلفًا. عفوًا
لكننا نحن احترقنا ، والأهم من ذلك ، متكبر. نحتاج إلى مخطط مع أنواع الحقول ، لذلك سيكون هناك مخطط مع أنواع الحقول. علاوة على ذلك ، تحتوي مكتبة العميل بالفعل على جميع التعاريف اللازمة ، فهي تحتاج فقط إلى التقاطها. على الرغم من أنني اضطررت إلى كتابة الكثير من التعليمات البرمجية لكل نوع (انظر الرابط نفسه ، من السطر 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);
... وفويلا ، الآن لدينا استفسار الاصطناعية ببساطة الهزات بوضوح من aerosoldering:
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)");
ولتحويل النموذج من كمامة الويب إلى الطلب نفسه ، نأخذ الكثير من الكود المكتوب منذ فترة طويلة في كمامة الويب ... عندما يصل المشروع أخيرًا ، وإلا فقد وضعه العميل على الرف الآن. إنه لأمر مخز ، اللعنة ، قضيت ما يقرب من أسبوع مرة.
آمل أن أقضيها مع الاستفادة ، وستكون مكتبة AQLSelectEx مفيدة لشخص ما ، وسيكون المنهج نفسه تعليميًا أكثر واقعية بقليل من المقالات الأخرى من المحور الذي يتعامل مع ANTLR.