كتابة لغة خاصة بالمجال لاختيار الصفوف من الجدول

StackOverflow https://stackoverflow.com/questions/140026

سؤال

أنا أكتب خادمًا أتوقع أن يتم تشغيله بواسطة العديد من الأشخاص المختلفين، وليس جميعهم الذين سأتواصل معهم بشكل مباشر.سوف تتواصل الخوادم مع بعضها البعض في مجموعة.يتضمن جزء من وظائف الخادم تحديد مجموعة فرعية صغيرة من الصفوف من جدول يحتمل أن يكون كبيرًا جدًا.سيحتاج الاختيار الدقيق للصفوف المحددة إلى بعض الضبط، ومن المهم أنه من الممكن للشخص الذي يقوم بتشغيل المجموعة (على سبيل المثال، أنا) تحديث معايير التحديد دون مطالبة كل مسؤول خادم بنشر إصدار جديد من الخادم .

إن مجرد كتابة الوظيفة في Python ليس خيارًا حقًا، حيث لن يرغب أحد في تثبيت خادم يقوم بتنزيل وتنفيذ تعليمات برمجية عشوائية من Python في وقت التشغيل.

ما أحتاج إليه هو اقتراحات حول أبسط طريقة لتنفيذ لغة خاصة بالمجال لتحقيق هذا الهدف.يجب أن تكون اللغة قادرة على تقييم التعبير البسيط، بالإضافة إلى الاستعلام عن فهارس الجدول والتكرار خلال الصفوف التي تم إرجاعها.سهولة الكتابة وقراءة اللغة ثانوية بالنسبة لسهولة تنفيذها.أفضل أيضًا عدم الاضطرار إلى كتابة مُحسِّن استعلام كامل، لذا فإن شيئًا يحدد بشكل صريح الفهارس المطلوب الاستعلام عنها سيكون مثاليًا.

ستكون الواجهة التي سيتعين تجميعها مقابلها مشابهة في الإمكانيات لما يصدره مخزن بيانات App Engine:يمكنك الاستعلام عن النطاقات المتسلسلة على أي فهرس في الجدول (على سبيل المثال، أقل من، أكبر من، استعلامات النطاق والمساواة)، ثم قم بتصفية الصف الذي تم إرجاعه بواسطة أي تعبير منطقي.يمكنك أيضًا ربط عدة مجموعات نتائج مستقلة معًا.

أدرك أن هذا السؤال يبدو كثيرًا وكأنني أطلب SQL.ومع ذلك، لا أريد أن أطلب أن يكون مخزن البيانات الذي يدعم هذه البيانات قاعدة بيانات علائقية، ولا أريد تحمل تكاليف محاولة إعادة تنفيذ SQL بنفسي.أنا أتعامل أيضًا مع جدول واحد فقط بمخطط معروف.وأخيرا، لن تكون هناك حاجة إلى الانضمام.شيء أبسط بكثير سيكون أفضل بكثير.

يحرر:وصف موسع لتوضيح بعض المفاهيم الخاطئة.

هل كانت مفيدة؟

المحلول

بناء DSL ليتم تفسيره بواسطة بايثون.

الخطوة 1.إنشاء فئات وكائنات وقت التشغيل.ستحتوي هذه الفئات على جميع حلقات المؤشر وعبارات SQL وكل تلك المعالجة الخوارزمية الموجودة في أساليبها.سوف تستفيد بشكل كبير من يأمر و إستراتيجية أنماط التصميم لبناء هذه الفئات.معظم الأشياء عبارة عن أمر، والخيارات والاختيارات عبارة عن إستراتيجيات إضافية.انظر إلى تصميم Apache Ant's مهمة API - إنه مثال جيد.

الخطوة 2.التحقق من أن نظام الكائنات هذا يعمل بالفعل.تأكد من أن التصميم بسيط وكامل.ستقوم اختباراتك بإنشاء كائنات الأوامر والاستراتيجية، ثم تنفيذ كائن الأوامر ذي المستوى الأعلى.سوف تقوم كائنات الأوامر بهذا العمل.

في هذه المرحلة تكون قد انتهيت إلى حد كبير.وقت التشغيل الخاص بك هو مجرد تكوين للكائنات التي تم إنشاؤها من المجال أعلاه.[هذا ليس سهلا كما يبدو.يتطلب الأمر بعض العناية لتحديد مجموعة من الفئات التي يمكن إنشاء مثيل لها ثم "التحدث فيما بينها" للقيام بعمل تطبيقك.]

لاحظ أن ما ستحصل عليه لن يتطلب أكثر من مجرد تصريحات.ما هو الخطأ في الإجرائية؟بمجرد أن تبدأ في كتابة DSL مع العناصر الإجرائية، تجد أنك بحاجة إلى المزيد والمزيد من الميزات حتى تكتب Python بصيغة مختلفة.ليس جيدا.

علاوة على ذلك، من الصعب كتابة مترجمي اللغة الإجرائية.من الصعب ببساطة إدارة حالة التنفيذ ونطاق المراجع.

يمكنك استخدام Python الأصلي - والتوقف عن القلق بشأن "الخروج من وضع الحماية".في الواقع، هذه هي الطريقة التي ستختبر بها الوحدة كل شيء، باستخدام نص بايثون قصير لإنشاء الكائنات الخاصة بك.ستكون بايثون هي DSL.

"لكن انتظر" ، كما تقول ، "إذا كنت ببساطة استخدم Python حيث يمكن للأشخاص DSL تنفيذ أشياء تعسفية." يعتمد على ما هو على Pythonpath ، و Sys.path.انظر الى موقع وحدة لطرق التحكم في ما هو متاح.]

يعتبر DSL التعريفي هو الأبسط.إنه تمرين في التمثيل تمامًا.تعتبر كتلة Python التي تقوم فقط بتعيين قيم بعض المتغيرات أمرًا رائعًا.هذا ما يستخدمه جانغو.

يمكنك استخدام ال ConfigParser كلغة لتمثيل تكوين وقت التشغيل الخاص بك للكائنات.

يمكنك استخدام JSON أو يامل كلغة لتمثيل تكوين وقت التشغيل الخاص بك للكائنات.المحللون الجاهزون متاحون بالكامل.

يمكنك استخدام XML أيضًا.من الصعب التصميم والتحليل، لكنه يعمل بشكل جيد.الناس يحبون ذلك.هذه هي الطريقة التي يستخدم بها Ant وMaven (والكثير من الأدوات الأخرى) بناء الجملة التعريفي لوصف الإجراءات.لا أنصح به لأنه يسبب آلاماً في الرقبة.أوصي ببساطة باستخدام بايثون.

أو يمكنك الخروج من النهاية العميقة وابتكار بناء الجملة الخاص بك وكتابة المحلل اللغوي الخاص بك.

نصائح أخرى

أعتقد أننا سنحتاج إلى مزيد من المعلومات هنا.اسمحوا لي أن أعرف إذا كان أي مما يلي يستند إلى افتراضات غير صحيحة.

أولاً، كما أشرت بنفسك، يوجد بالفعل DSL لاختيار الصفوف من الجداول العشوائية - ويسمى "SQL".نظرًا لأنك لا ترغب في إعادة اختراع SQL، أفترض أنك تحتاج فقط إلى الاستعلام من جدول واحد بتنسيق ثابت.

إذا كان الأمر كذلك، فربما لا تحتاج إلى تنفيذ خدمة DSL (على الرغم من أن هذه بالتأكيد إحدى الطرق التي يجب اتباعها)؛قد يكون من الأسهل، إذا كنت معتادًا على اتجاه الكائن، إنشاء كائن تصفية.

وبشكل أكثر تحديدًا، مجموعة "Filter" التي قد تحتوي على كائن SelectionCriterion واحد أو أكثر.يمكنك تنفيذ هذه للوراثة من فئة أساسية واحدة أو أكثر تمثل أنواع التحديدات (Range، LessThan، ExactMatch، Like، إلخ.) بمجرد وضع هذه الفئات الأساسية في مكانها الصحيح، يمكنك إنشاء إصدارات موروثة خاصة بالعمود تكون مناسبة لذلك العمود .أخيرًا، اعتمادًا على مدى تعقيد الاستعلامات التي تريد دعمها، ستحتاج إلى تنفيذ نوع من الغراء الرابط للتعامل مع روابط AND وOR وNOT بين المعايير المختلفة.

إذا كنت ترغب في ذلك، يمكنك إنشاء واجهة مستخدم رسومية بسيطة لتحميل المجموعة؛سأنظر إلى التصفية في Excel كنموذج، إذا لم يكن لديك أي شيء آخر في الاعتبار.

أخيرًا، يجب أن يكون من السهل تحويل محتويات هذه المجموعة إلى SQL المقابلة، وتمرير ذلك إلى قاعدة البيانات.

لكن:إذا كان ما تسعى إليه هو البساطة، وكان المستخدمون يفهمون لغة SQL، فيمكنك ببساطة أن تطلب منهم كتابة محتويات عبارة WHERE، وإنشاء بقية الاستعلام برمجيًا.من منظور أمني، إذا كان الكود الخاص بك يتحكم في الأعمدة المحددة وعبارة FROM، وتم تعيين أذونات قاعدة البيانات الخاصة بك بشكل صحيح، وقمت ببعض التحقق من سلامة السلسلة الواردة من المستخدمين، فسيكون هذا خيارًا آمنًا نسبيًا.

"تنفيذ لغة خاصة بالمجال"

"لن يرغب أحد في تثبيت خادم يقوم بتنزيل وتنفيذ تعليمات برمجية عشوائية لـ Python في وقت التشغيل"

أريد DSL ولكني لا أريد أن تكون Python هي DSL.تمام.كيف سيتم تنفيذ هذا DSL؟ما وقت التشغيل يكون مقبول إن لم يكن بايثون؟

ماذا لو كان لدي برنامج C والذي قام بتضمين مترجم Python؟هل هذا مقبول؟

و-- إذا لم يكن وقت تشغيل Python مقبولاً-- فلماذا يحتوي هذا على علامة Python؟

لماذا لا تقوم بإنشاء لغة تقوم عند "تجميعها" بإنشاء لغة SQL أو أي لغة استعلام يتطلبها مخزن البيانات الخاص بك؟

ستقوم بشكل أساسي بإنشاء تجريد على طبقة الثبات الخاصة بك.

لقد ذكرت بايثون.لماذا لا تستخدم بايثون؟إذا كان بإمكان شخص ما "كتابة" تعبير في DSL الخاص بك، فيمكنه الكتابة بلغة Python.

ستحتاج إلى بعض القواعد المتعلقة ببنية التعبير، ولكن هذا أسهل كثيرًا من تنفيذ شيء جديد.

لقد قلت أنه لن يرغب أحد في تثبيت خادم يقوم بتنزيل وتنفيذ تعليمات برمجية عشوائية في وقت التشغيل.ومع ذلك، هذا هو بالضبط ما سيفعله خط المشترك الرقمي (DSL) الخاص بك (في النهاية)، لذلك ربما لا يكون هناك فرق كبير.ما لم تكن تفعل شيئًا محددًا للغاية فيما يتعلق بالبيانات، فلا أعتقد أن DSL ستشتري لك هذا القدر من المال وسوف يحبط المستخدمين الذين هم على دراية بالفعل بـ SQL.لا تقلل من حجم المهمة التي ستتولىها.

ومع ذلك، للإجابة على سؤالك، ستحتاج إلى التوصل إلى قواعد نحوية للغتك، أو شيء ما لتحليل النص والسير على الشجرة، أو إرسال تعليمات برمجية أو الاتصال بواجهة برمجة التطبيقات (API) التي كتبتها (وهذا هو سبب تعليقي بأنك لا يزال يتعين عليك شحن بعض التعليمات البرمجية).

هناك الكثير من النصوص التعليمية حول قواعد التعبيرات الرياضية التي يمكنك الرجوع إليها على الشبكة، وهذا أمر واضح ومباشر إلى حد ما.قد يكون لديك أداة مولد محلل مثل ANTLR أو Yacc يمكنك استخدامها لمساعدتك في إنشاء المحلل اللغوي (أو استخدم لغة مثل Lisp/Scheme والجمع بين الاثنين).لن يكون التوصل إلى قواعد نحوية معقولة لـ SQL أمرًا سهلاً.لكن ابحث في Google عن "BNF SQL" وشاهد ما توصلت إليه.

حظا سعيدا.

يبدو الأمر حقًا مثل SQL، ولكن ربما يكون من المفيد تجربة استخدام SQLite إذا كنت تريد إبقاء الأمر بسيطًا؟

يبدو أنك تريد إنشاء قواعد نحوية وليس DSL.سأنظر في أنتلر والذي سيسمح لك بإنشاء محلل محدد يقوم بتفسير النص وترجمته إلى أوامر محددة.يوفر ANTLR مكتبات لـ Python وSQL وJava وC++ وC وC# وما إلى ذلك.

وهذا أيضًا مثال جيد لـ ANTLR محرك الحساب تم إنشاؤها في C #

عادةً ما تحتوي القواعد النحوية الخالية من السياق على بنية تشبه الشجرة، كما تحتوي البرامج الوظيفية على بنية تشبه الشجرة أيضًا.لا أزعم أن ما يلي سيحل جميع مشاكلك، ولكنه خطوة جيدة في الاتجاه إذا كنت متأكدًا من أنك لا تريد استخدام شيء مثل SQLite3.

from functools import partial
def select_keys(keys, from_):
    return ({k : fun(v, row) for k, (v, fun) in keys.items()}
            for row in from_)

def select_where(from_, where):
    return (row for row in from_
            if where(row))

def default_keys_transform(keys, transform=lambda v, row: row[v]):
    return {k : (k, transform) for k in keys}

def select(keys=None, from_=None, where=None):
    """
    SELECT v1 AS k1, 2*v2 AS k2 FROM table WHERE v1 = a AND v2 >= b OR v3 = c

    translates to 

    select(dict(k1=(v1, lambda v1, r: r[v1]), k2=(v2, lambda v2, r: 2*r[v2])
        , from_=table
        , where= lambda r : r[v1] = a and r[v2] >= b or r[v3] = c)
    """
    assert from_ is not None
    idfunc = lambda k, t : t
    select_k = idfunc if keys is None  else select_keys
    if isinstance(keys, list):
        keys = default_keys_transform(keys)
    idfunc = lambda t, w : t
    select_w = idfunc if where is None else select_where
    return select_k(keys, select_w(from_, where))

كيف تتأكد من أنك لا تمنح المستخدمين القدرة على تنفيذ تعليمات برمجية عشوائية.يقبل هذا الإطار جميع الوظائف الممكنة.حسنًا، يمكنك تصحيح الغلاف فوقه لتوفير الأمان الذي يعرض قائمة ثابتة من الكائنات الوظيفية المقبولة.

ALLOWED_FUNCS = [ operator.mul, operator.add, ...] # List of allowed funcs

def select_secure(keys=None, from_=None, where=None):
    if keys is not None and isinstance(keys, dict):
       for v, fun keys.values:
           assert fun in ALLOWED_FUNCS
    if where is not None:
       assert_composition_of_allowed_funcs(where, ALLOWED_FUNCS)
    return select(keys=keys, from_=from_, where=where)

كيفية كتابة assert_composition_of_allowed_funcs.من الصعب جدًا القيام بذلك في بايثون ولكنه سهل في اللثغة.لنفترض أنه توجد قائمة بالوظائف التي سيتم تقييمها بتنسيق يشبه الشفاه، على سبيل المثال. where=(operator.add, (operator.getitem, row, v1), 2) أو where=(operator.mul, (operator.add, (opreator.getitem, row, v2), 2), 3).

هذا يجعل من الممكن كتابة أ apply_lisp وظيفة تتأكد من أن وظيفة حيث تتكون فقط من ALLOWED_FUNCS أو ثوابت مثل float، int، str.

def apply_lisp(where, rowsym, rowval, ALLOWED_FUNCS):
    assert where[0] in ALLOWED_FUNCS
    return apply(where[0],
          [ (apply_lisp(w, rowsym, rowval, ALLOWED_FUNCS)
            if isinstance(w, tuple)
            else rowval if w is rowsym
            else w if isinstance(w, (float, int, str))
            else None ) for w in where[1:] ])

بجانب ذلك، ستحتاج أيضًا إلى التحقق من الأنواع الدقيقة، لأنك لا تريد تجاوز أنواعك.لذلك لا تستخدم isinstance, ، يستخدم type in (float, int, str).يا ولد وصلنا إلى:

القاعدة العاشرة للبرمجة عند جرينسبان:يحتوي أي برنامج C أو Fortran معقدًا بما فيه الكفاية على تطبيق بطيء محدد بشكل غير محدد بشكل غير محدد بشكل غير رسمي لنصف LISP الشائع.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top