Специфичный для домена язык запросов с Flask-SQLAlchemy - PullRequest
0 голосов
/ 04 октября 2018

Я пишу приложение, используя Flask и Flask-SQLAlchemy.Я хочу, чтобы пользователь мог запрашивать базу данных, используя специфичный для домена язык запросов, например parent.name = "foo" AND (name = "bar" OR age = 11).

. Я написал анализатор для этого языка с помощью Pyparsing:

import pyparsing as pp

query = 'parent.name = "foo" AND (name = "bar" OR age = 11)'

and_operator = pp.oneOf(['and', '&'], caseless=True)
or_operator = pp.oneOf(['or', '|'], caseless=True)

identifier = pp.Word(pp.alphas + '_', pp.alphas + '_.')
comparison_operator = pp.oneOf(['=','!=','>','>=','<', '<='])

integer = pp.Regex(r'[+-]?\d+').setParseAction(lambda t: int(t[0]))
float_ = pp.Regex(r'[+-]?\d+\.\d*').setParseAction(lambda t: float(t[0]))
string = pp.QuotedString('"')

comparison_operand = string | identifier | float_ | integer
comparison_expr = pp.Group(comparison_operand +
                           comparison_operator +
                           comparison_operand)

grammar = pp.operatorPrecedence(comparison_expr,
                                [
                                    (and_operator, 2, pp.opAssoc.LEFT),
                                    (or_operator, 2, pp.opAssoc.LEFT)
                                ])

result = grammar.parseString(query)
print(result.asList())

Это дает мне следующий вывод:

[[['parent.name', '=', 'foo'], 'and', [['name', '=', 'bar'], 'or', ['age', '=', 11]]]]

Теперь я не знаю, что делать.Как я могу динамически генерировать запрос SQLAlchemy?Есть ли библиотеки, которые могли бы помочь с этим?Будет ли проще генерировать сырой SQL?

1 Ответ

0 голосов
/ 04 октября 2018

Получение написанного парсера - первый шаг.Отсюда, я предлагаю вам улучшить ваш operatorPrecedence вызов (имя является старым и устаревшим, кстати, теперь он называется infixNotation), чтобы pyparsing создавал вложенный набор узлов, который соответствует построению абстрактного синтаксического дерева (AST).Несмотря на то, что вам удалось запустить парсер, я должен сказать вам, что этот следующий шаг довольно большой.

Идея состоит в том, чтобы синтаксический анализатор возвращал вам не только строки или преобразованные целые и плавающие числа, но ифактические экземпляры классов.Это выглядело бы примерно так:

class AndOperation:
    def __init__(self, tokens):
        # tokens will look like [operand1, 'AND', operand2, 'AND', operand3, ...]
        self._operands = tokens[::2]

class OrOperation:
    def __init__(self, tokens):
        # tokens will look like [operand1, 'OR', operand2, 'OR', operand3, ...]
        self._operands = tokens[::2]

class NotOperation:
    def __init__(self, tokens):
        # tokens will look like ['NOT', operand]
        self._operands = tokens[-1]

Затем вы добавили бы их в infixNotation как:

AND, OR, TRUE, FALSE = map(pp.Keyword, "AND OR TRUE FALSE".split())
boolean_term = TRUE | FALSE | ~(AND | OR) + pp.pyparsing_common.identifier
boolean_expr = pp.infixNotation(boolean_term,
    [
    ('NOT', 1, pp.opAssoc.RIGHT, NotOperation),
    ('AND', 2, pp.opAssoc.LEFT, AndOperation),
    ('OR', 2, pp.opAssoc.LEFT, OrOperation),
    ])

Без добавленных действий разбора класса, разбор "P AND NOT Q" вернул бы:

[['P', 'AND', ['NOT', 'Q']]]

С добавленными классами анализ "P AND NOT Q" даст вам что-то вроде:

[AndOperation('P', NotOperation('Q'))]

На этом этапе вы можете выбрать, хотите ли вы добавить какую-либо формуevaluate() или execute метода для каждого класса xxxOperation для оценки выражения или, возможно, render метода, если вы хотите просто вывести предложение SQL WHERE.

Например, render для AndOperationдля создания SQL синтаксис WHERE может выглядеть следующим образом:

def render(self):
    return ' AND '.join("'" + oper + "'" if isinstance(oper, str) else oper.render()
                                   for oper in self.operands)

(Как отмечает Илья Эверила в своем комментарии, остерегайтесь проблем с внедрением SQL, когда работает , например, такое предложение WHERE напрямую - render()в основном для визуализации и отладки)

В каталоге pyparsing repo examples есть несколько примеров (https://github.com/pyparsing/pyparsing/tree/master/examples) - поиск вариантов использования infixNotation, чтобы увидеть, как они выполняются.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...