Я пишу сервер, которым, как я ожидаю, будет управлять много разных людей, не со всеми из которых я буду иметь прямой контакт. Серверы будут взаимодействовать друг с другом в кластере. Часть функциональных возможностей сервера включает выбор небольшого подмножества строк из потенциально очень большой таблицы. Точный выбор выбранных строк потребует некоторой настройки, и важно, чтобы человек, запускающий кластер (например, я), мог обновить критерии выбора, не заставляя каждого администратора сервера развертывать новую версию сервера. .
Простое написание функции на Python на самом деле не вариант, поскольку никто не захочет устанавливать сервер, который загружает и выполняет произвольный код Python во время выполнения.
Мне нужны предложения по простейшему способу реализации предметно-ориентированного языка для достижения этой цели. Язык должен иметь возможность выполнять простую оценку выражений, а также запрашивать индексы таблиц и выполнять итерацию по возвращаемым строкам. Легкость написания и чтения языка вторична по сравнению с простотой его реализации. Я также предпочел бы не писать весь оптимизатор запросов, поэтому идеально было бы то, что явно указывает, какие индексы запрашивать.
Интерфейс, для которого это должно быть скомпилировано, будет аналогичен возможностям того, что экспортирует хранилище данных App Engine: вы можете запрашивать последовательные диапазоны по любому индексу в таблице (например, запросы меньше, больше, диапазона и равенства) , затем отфильтруйте возвращенную строку по любому логическому выражению. Вы также можете объединить несколько независимых наборов результатов вместе.
Я понимаю, что этот вопрос звучит так, как будто я прошу SQL. Однако я не хочу требовать, чтобы хранилище данных, поддерживающее эти данные, было реляционной базой данных, и мне не нужны накладные расходы, связанные с попыткой переопределить SQL самостоятельно. Я также имею дело только с одной таблицей с известной схемой. Наконец, никаких соединений не потребуется. Было бы намного предпочтительнее что-нибудь более простое.
Обновлено: расширенное описание, чтобы прояснить некоторые заблуждения.






Почему бы не создать язык, который при "компиляции" генерирует SQL или любой другой язык запросов, который требуется вашему хранилищу данных?
Вы бы в основном создавали абстракцию над своим постоянным слоем.
Архитектура сервера допускает несколько возможных реализаций хранилища данных. Самый простой из возможных - это простая таблица в памяти, поэтому язык запросов, который требуется моему хранилищу данных, - это Python.
Вы упомянули Python. Почему бы не использовать Python? Если кто-то может «напечатать» выражение в вашем DSL, он может напечатать его на Python.
Вам понадобятся некоторые правила по структуре выражения, но это намного проще, чем реализовать что-то новое.
Я уже сказал, почему: никто не захочет запускать сервер, который загружает и выполняет произвольный код во время выполнения. А синтаксический анализатор, который может достаточно тщательно проверять Python, чтобы убедиться, что он не делает ничего вредоносного, в любом случае, вероятно, будет почти так же сложен, как написание правильного DSL.
Но это проблема безопасности, с которой можно справиться. Например, администратор баз данных может загружать произвольный код во время выполнения. Учитывая, насколько редким кажется этот процесс, нет причин, по которым его не может выполнить кто-то, кому доверено выполнение работы.
Все дело в том, что он не может требовать ручного вмешательства со стороны каждого администратора сервера. Мне нужен DSL, способный запрашивать / фильтровать таким образом, но которому администраторы сервера могут доверять, чтобы обновляться без их вмешательства или надзора.
Вы сказали, что никто не захочет устанавливать сервер, который загружает и выполняет произвольный код во время выполнения. Однако это именно то, что будет делать ваш DSL (в конечном итоге), поэтому, вероятно, нет такой большой разницы. Если вы не делаете что-то очень конкретное с данными, я не думаю, что DSL купит вам столько, и это расстроит пользователей, которые уже разбираются в SQL. Не стоит недооценивать масштаб задачи, которую вы возьмете на себя.
Однако, чтобы ответить на ваш вопрос, вам нужно будет придумать грамматику для вашего языка, что-то для анализа текста и обхода дерева, испускания кода или вызова API, который вы написали (вот почему мой комментарий, что вы все равно придется отправить код).
Есть множество учебных текстов по грамматике математических выражений, на которые вы можете ссылаться в сети, это довольно просто. У вас может быть инструмент-генератор парсера, такой как ANTLR или Yacc, который вы можете использовать, чтобы помочь вам сгенерировать парсер (или используйте язык, такой как Lisp / Scheme, и объедините их вместе). Придумать разумную грамматику SQL будет непросто. Но погуглите "BNF SQL" и посмотрите, что у вас получится.
Удачи.
Да, он будет загружать «произвольный» код, но когда он выражается в терминах этого DSL, он мало что может сделать. Существует огромная разница между произвольным кодом Python и произвольным выражением фильтра (или подобным).
Думаю, здесь нам понадобится немного больше информации. Сообщите мне, если какое-либо из следующих утверждений основано на неверных предположениях.
Прежде всего, как вы сами отметили, уже существует DSL для выбора строк из произвольных таблиц - он называется «SQL». Поскольку вы не хотите изобретать SQL заново, я предполагаю, что вам нужно запрашивать только одну таблицу с фиксированным форматом.
В этом случае вам, вероятно, не нужно внедрять DSL (хотя это, безусловно, один из возможных вариантов); Если вы привыкли к объектной ориентации, может быть проще создать объект фильтра.
Более конкретно, коллекция «Фильтр», которая будет содержать один или несколько объектов SelectionCriterion. Вы можете реализовать их для наследования от одного или нескольких базовых классов, представляющих типы выбора (Range, LessThan, ExactMatch, Like и т. д.). После того, как эти базовые классы созданы, вы можете создать унаследованные версии для конкретного столбца, соответствующие этому столбцу. . Наконец, в зависимости от сложности запросов, которые вы хотите поддерживать, вы захотите реализовать какой-то связующий клей для обработки связей И, ИЛИ и НЕ между различными критериями.
Если вы хотите, вы можете создать простой графический интерфейс для загрузки коллекции; Я бы посмотрел на фильтрацию в Excel как на модель, если вы больше ничего не имеете в виду.
Наконец, должно быть тривиально преобразовать содержимое этой коллекции в соответствующий SQL и передать его в базу данных.
Однако: если вам нужна простота и ваши пользователи понимают SQL, вы можете просто попросить их ввести содержимое предложения WHERE и программно создать остальную часть запроса. С точки зрения безопасности, если ваш код контролирует выбранные столбцы и предложение FROM, а разрешения вашей базы данных установлены правильно, и вы выполняете некоторую проверку работоспособности строки, поступающей от пользователей, это будет относительно безопасный вариант.
В основном звучит, но мне все еще нужен способ указать набор фильтров и т. д. Имейте в виду, что цель здесь состоит в том, чтобы я мог указать обновленную функцию и распространить ее на все серверы в кластере.
Это действительно похоже на SQL, но, может быть, стоит попробовать использовать SQLite, если вы хотите, чтобы это было просто?
«реализовать язык, специфичный для предметной области»
«никто не захочет устанавливать сервер, который загружает и выполняет произвольный код Python во время выполнения»
Я хочу DSL, но не хочу, чтобы Python был таким DSL. Хорошо. Как вы будете выполнять этот DSL? Какая среда выполнения является приемлема, если не Python?
Что, если у меня есть программа на C, в которую встроен интерпретатор Python? Это приемлемо?
И - если Python не является приемлемой средой выполнения - почему у него есть тег Python?
Я выполню DSL, написав интерпретатор на Python. Вот почему у него есть тег Python. Весь смысл использования DSL в этом случае заключается в том, что он не позволит написанному на нем коду вырваться из песочницы и повлиять на систему в целом.
Похоже, вы хотите создать грамматику, а не DSL. Я бы посмотрел на ANTLR, который позволит вам создать конкретный парсер, который будет интерпретировать текст и переводить на определенные команды. ANTLR предоставляет библиотеки для Python, SQL, Java, C++, C, C# и т. д.
Кроме того, вот прекрасный пример ANTLR вычислительная машина, созданного на C#.
Это больше, чем просто грамматика - мне нужно не просто анализировать, но и выполнять, что делает его полным DSL. Пример «вычислительной машины» просто имеет код выполнения, встроенный в синтаксический анализатор.
Создание DSL для интерпретации Python.
Шаг 1. Создайте классы и объекты времени выполнения. Эти классы будут иметь все циклы курсора и операторы SQL, а также всю эту алгоритмическую обработку, спрятанную в их методах. Вы будете активно использовать шаблоны проектирования Командование и Стратегия для создания этих классов. Большинство вещей - это команды, опции и варианты - это стратегии плагинов. Посмотрите на дизайн API Задача Apache Ant - это хороший пример.
Шаг 2. Убедитесь, что эта система объектов действительно работает. Убедитесь, что дизайн простой и законченный. Ваши тесты построят объекты Command и Strategy, а затем выполнят объект Command верхнего уровня. Объекты Command сделают всю работу.
На этом вы в основном закончили. Ваша среда выполнения - это просто конфигурация объектов, созданных из вышеуказанного домена. [Это не так просто, как кажется. Требуется некоторая осторожность, чтобы определить набор классов, которые могут быть созданы, а затем «разговаривать между собой», чтобы выполнять работу вашего приложения.]
Обратите внимание: то, что у вас будет, не потребует ничего, кроме деклараций. Что не так с процедурным? Как только вы начнете писать DSL с процедурными элементами, вы обнаружите, что вам нужно все больше и больше функций, пока вы не напишете Python с другим синтаксисом. Нехорошо.
Кроме того, сложно писать интерпретаторы процедурного языка. Состояние выполнения и объем ссылок просто трудно контролировать.
Вы можете использовать собственный Python - и не беспокоиться о том, чтобы «выйти из песочницы». В самом деле, именно так вы будете все модульно тестировать, используя короткий скрипт Python для создания ваших объектов. Python будет DSL.
[«Но подождите», - скажете вы: «Если я просто буду использовать Python как DSL, люди смогут выполнять произвольные вещи». Зависит от того, что находится в PYTHONPATH и sys.path. Посмотрите на модуль сайт, чтобы узнать о способах управления тем, что доступно.]
Декларативный DSL - самый простой. Это целиком и полностью упражнение в представлении. Блок Python, который просто устанавливает значения некоторых переменных, хорош. Это то, что использует Django.
Вы можете использовать ConfigParser как язык для представления конфигурации объектов во время выполнения.
Вы можете использовать JSON или YAML в качестве языка для представления конфигурации объектов во время выполнения. Готовые парсеры полностью доступны.
Вы также можете использовать XML. Проектировать и разбирать сложнее, но работает нормально. Людям это нравится. Вот как Ant и Maven (и множество других инструментов) используют декларативный синтаксис для описания процедур. Не рекомендую, потому что это многословная боль в шее. Я рекомендую просто использовать Python.
Или вы можете выйти из глубин и изобрести свой собственный синтаксис и написать свой собственный синтаксический анализатор.
Все хорошие предложения, но я все еще ищу предложение по эффективному синтаксису или формату. Процедурные или декларативные? Как лучше обрабатывать курсоры? И т.п.
Отредактировано, чтобы ответить на эти два пункта. Декларативная. Циклы курсора SQL находятся в основных классах.
Контекстно-свободная грамматика обычно имеет древовидную структуру, и функциональные программы тоже имеют древовидную структуру. Я не утверждаю, что следующее могло бы решить все ваши проблемы, но это хороший шаг в этом направлении, если вы уверены, что не хотите использовать что-то вроде 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). О, мальчик, с которым мы столкнулись:
Greenspun's Tenth Rule of Programming: any sufficiently complicated C or Fortran program contains an ad hoc informally-specified bug-ridden slow implementation of half of Common Lisp.
Это было сделано для реализации GQL в App Engine? :П