Написание предметно-ориентированного языка для выбора строк из таблицы - PullRequest
5 голосов
/ 26 сентября 2008

Я пишу сервер, который, как я ожидаю, будет запущен многими разными людьми, не со всеми, с кем я буду иметь прямой контакт. Серверы будут связываться друг с другом в кластере. Часть функциональности сервера включает в себя выбор небольшого подмножества строк из потенциально очень большой таблицы. Точный выбор выбранных строк потребует некоторой настройки, и важно, чтобы человек, управляющий кластером (например, я), мог обновить критерии выбора, не заставляя каждого администратора сервера развертывать новую версию сервера. .

Простое написание функции на Python на самом деле не вариант, так как никто не захочет устанавливать сервер, который загружает и выполняет произвольный код Python во время выполнения.

Что мне нужно, так это предложения по простейшему способу реализации языка, специфичного для домена, для достижения этой цели. Язык должен быть способен к простой оценке выражений, а также к запросам индексов таблиц и итерации по возвращаемым строкам. Легкость написания и чтения языка вторична по отношению к легкости его реализации. Я также предпочел бы не писать целый оптимизатор запросов, поэтому что-то, что явно указывает, какие индексы запрашивать, будет идеальным.

Интерфейс, с которым это нужно будет скомпилировать, будет аналогичен по возможностям тому, что экспортирует хранилище данных App Engine: вы можете запрашивать последовательные диапазоны для любого индекса в таблице (например, диапазон меньше, больше, и запросы на равенство), затем отфильтруйте возвращенную строку по любому логическому выражению. Вы также можете объединить несколько независимых наборов результатов вместе.

Я понимаю, что этот вопрос звучит так, как будто я прошу SQL. Тем не менее, я не хочу требовать, чтобы хранилище данных, поддерживающее эти данные, было реляционной базой данных, и я не хочу лишних затрат пытаться переопределить сам SQL. Я также имею дело только с одной таблицей с известной схемой. Наконец, никаких объединений не потребуется. Что-то намного проще было бы гораздо предпочтительнее.

Редактировать: Расширенное описание для устранения некоторых заблуждений.

Ответы [ 9 ]

4 голосов
/ 27 сентября 2008

Создание DSL для интерпретации Python.

Шаг 1. Создайте классы и объекты времени выполнения. Эти классы будут иметь все циклы курсора и операторы SQL и всю эту алгоритмическую обработку, спрятанную в их методах. Вы будете интенсивно использовать шаблоны проектирования Command и Strategy для создания этих классов. Большинство вещей - это команды, опции и варианты - стратегии плагинов. Посмотрите на дизайн API Apache Ant Task - это хороший пример.

Шаг 2. Убедитесь, что эта система объектов действительно работает. Будьте уверены, что дизайн прост и завершен. Ваши тесты построят объекты Command и Strategy, а затем выполнят объект Command верхнего уровня. Объекты Command выполнят эту работу.

На данный момент вы в основном сделали. Ваше время выполнения - это просто конфигурация объектов, созданных из вышеуказанного домена. [Это не так просто, как кажется. Требуется некоторая осторожность, чтобы определить набор классов, которые можно создать, а затем «поговорить между собой», чтобы выполнить работу вашего приложения.]

Обратите внимание, что то, что у вас будет, не потребует ничего, кроме объявлений. Что не так с процедурным? Когда вы начинаете писать DSL с процедурными элементами, вы обнаруживаете, что вам нужно все больше и больше функций, пока вы не напишите Python с другим синтаксисом. Не хорошо.

Кроме того, переводчиков процедурного языка просто сложно написать. Состояние исполнения и объем ссылок просто сложны для управления.

Вы можете использовать собственный Python - и перестать беспокоиться о «выходе из песочницы». Действительно, именно так вы будете тестировать все, используя короткий скрипт Python для создания ваших объектов. Python будет DSL.

[«Но подождите», вы говорите: «Если я просто использую Python в качестве DSL, люди могут выполнять произвольные вещи». Зависит от того, что находится на PYTHONPATH и sys.path. Посмотрите в модуле site способы контроля того, что доступно.]

Декларативный DSL является самым простым. Это полностью упражнение в представлении. Блок Python, который просто устанавливает значения некоторых переменных, это хорошо. Это то, что использует Джанго.

Вы можете использовать ConfigParser в качестве языка для представления конфигурации объектов во время выполнения.

Вы можете использовать JSON или YAML в качестве языка для представления конфигурации объектов во время выполнения. Готовые парсеры полностью доступны.

Вы также можете использовать XML. Его сложнее спроектировать и разобрать, но он отлично работает. Люди любят это. Вот как Ant и Maven (и множество других инструментов) используют декларативный синтаксис для описания процедур. Я не рекомендую это, потому что это многословная боль в шее. Я рекомендую просто использовать Python.

Или вы можете выйти из глубины и придумать свой собственный синтаксис и написать свой собственный синтаксический анализатор.

1 голос
/ 27 сентября 2008

"реализовать язык, специфичный для домена"

"никто не захочет устанавливать сервер, который загружает и выполняет произвольный код Python во время выполнения"

Я хочу DSL, но я не хочу, чтобы Python был таким DSL. Хорошо. Как вы будете выполнять этот DSL? Какое время выполнения является приемлемым, если не Python?

Что если у меня есть программа на C, которая встраивает интерпретатор Python? Это приемлемо?

И - если Python не является приемлемым временем выполнения - почему у него есть тег Python?

1 голос
/ 26 сентября 2008

Я думаю, нам здесь понадобится немного больше информации. Дайте мне знать, если любое из следующего основано на неправильных предположениях.

Прежде всего, как вы сами указали, уже существует DSL для выбора строк из произвольных таблиц - он называется «SQL». Поскольку вы не хотите заново изобретать SQL, я предполагаю, что вам нужно запрашивать только из одной таблицы с фиксированным форматом.

Если это так, вам, вероятно, не нужно реализовывать DSL (хотя это, безусловно, один из способов); может быть проще, если вы привыкли к объектной ориентации, создать объект фильтра.

Точнее говоря, коллекция "Filter", которая будет содержать один или несколько объектов SelectionCriterion. Вы можете реализовать их для наследования от одного или нескольких базовых классов, представляющих типы выборок (Range, LessThan, ExactMatch, Like и т. Д.). После того, как эти базовые классы созданы, вы можете создавать специфичные для столбца унаследованные версии, которые соответствуют этому столбцу. , Наконец, в зависимости от сложности запросов, которые вы хотите поддерживать, вы захотите внедрить некоторый тип связующего клея для обработки связей И и ИЛИ и НЕ между различными критериями.

Если вам так хочется, вы можете создать простой графический интерфейс для загрузки коллекции; Я бы посмотрел на фильтрацию в Excel как на модель, если вы не имеете в виду ничего другого.

Наконец, должно быть тривиально преобразовать содержимое этой Коллекции в соответствующий SQL и передать его в базу данных.

Однако: если вам нужна простота и ваши пользователи понимают SQL, вы можете просто попросить их ввести содержимое предложения WHERE и программно создать остальную часть запроса. С точки зрения безопасности, если ваш код контролирует выбранные столбцы и предложение FROM, а права доступа к базе данных установлены правильно, и вы выполняете некоторую проверку работоспособности строки, поступающей от пользователей, это будет относительно безопасным вариантом.

0 голосов
/ 16 октября 2017

У контекстно-свободной грамматики обычно есть древовидная структура, а у функциональных программ - древовидная структура. Я не утверждаю, что следующее решит все ваши проблемы, но это хороший шаг в этом направлении, если вы уверены, что не хотите использовать что-то вроде 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. Это очень трудно сделать это в python, но легко в lisp. Давайте предположим, что где - список функций, которые должны оцениваться в виде губ, например, в формате where=(operator.add, (operator.getitem, row, v1), 2) или where=(operator.mul, (operator.add, (opreator.getitem, row, v2), 2), 3).

Это позволяет написать функцию apply_lisp, которая гарантирует, что функция where состоит только из 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 содержит специальное неофициально указанное медленная реализация половины Common Lisp.

0 голосов
/ 27 сентября 2008

Звучит так, будто вы хотите создать грамматику, а не DSL. Я бы посмотрел на ANTLR , который позволит вам создать определенный синтаксический анализатор, который будет интерпретировать текст и переводить в конкретные команды. ANTLR предоставляет библиотеки для Python, SQL, Java, C ++, C, C # и т. Д.

Кроме того, вот прекрасный пример механизма вычисления ANTLR , созданного в C #

0 голосов
/ 26 сентября 2008

Это действительно звучит как SQL, но, возможно, стоит попробовать использовать SQLite, если вы хотите сохранить простоту?

0 голосов
/ 26 сентября 2008

Вы сказали, что никто не захочет устанавливать сервер, который загружает и выполняет произвольный код во время выполнения. Тем не менее, это именно то, что ваш DSL будет делать (в конце концов), так что, вероятно, не так уж много различий. Если вы не делаете что-то очень специфичное с данными, тогда я не думаю, что DSL купит вас так сильно, и это разочарует пользователей, которые уже разбираются в SQL. Не стоит недооценивать размер задачи, которую вы будете выполнять.

Однако, чтобы ответить на ваш вопрос, вам нужно придумать грамматику для вашего языка, что-то для анализа текста и обхода дерева, выдачи кода или вызова API, который вы написали (поэтому мой комментарий таков: вам все еще придется отправить некоторый код).

Существует множество учебных текстов по грамматике для математических выражений, на которые вы можете ссылаться в сети, это довольно просто. У вас может быть инструмент генератора синтаксических анализаторов, такой как ANTLR или Yacc, который вы можете использовать, чтобы помочь вам сгенерировать синтаксический анализатор (или использовать такой язык, как Lisp / Scheme и объединить их). Придумать разумную грамматику SQL будет непросто. Но посмотрите «BNF SQL» и посмотрите, что у вас получится.

Удачи.

0 голосов
/ 26 сентября 2008

Вы упомянули Python. Почему бы не использовать Python? Если кто-то может «напечатать» выражение в вашем DSL, он может напечатать на Python.

Вам понадобятся некоторые правила для структуры выражения, но это намного проще, чем реализовать что-то новое.

0 голосов
/ 26 сентября 2008

Почему бы не создать язык, который при "компиляции" генерирует SQL или любой другой язык запросов, необходимый вашему хранилищу данных?

Вы бы в основном создавали абстракцию над своим постоянным слоем.

...