Парсинг плейсхолдеров strikes back

Вурдалак

Продвинутый новичок
fixxxer, неправильно ты, дядя Федор, бутерброд держишь. У тебя должен быть INTERVAL ?s или как у Sad Spirit'а, что есть по сути свой тип плейсхолдера для INTERVAL.
 
  • Like
Реакции: AmdY

Вурдалак

Продвинутый новичок
Sad Spirit, а INTERVAL '2 hours' как-то написать можно? По идее можно передавать строкой:
PHP:
'age' => sprintf('%d hours', $age)
а там уже должна быть какая-то валидация значения плейхолдера.
 
Последнее редактирование:

Фанат

oncle terrible
Команда форума
SELECT * FROM t WHERE modified_ts >= CURRENT_TIMESTAMP - INTERVAL '?i seconds'
В интервале нет кОвычек, :-Р

А вот всякие геовычисления таки должны передаваться целиком в запрос.
Точно так же, как %$search%
Код:
$point = 'POINT(-0.93961472 43.52843475)';
$sql = "SELECT GeomFromText(?s)"
 

Sad Spirit

мизантроп (Старожил PHPClub)
Команда форума
Sad Spirit, а INTERVAL '2 hours' как-то написать можно? По идее можно передавать строкой:
PHP:
'age' => sprintf('%d hours', $age)
а там уже должна быть какая-то валидация значения плейхолдера.
Можно либо написать '2 hours' строкой, либо передать объект DateInterval (что, вообще-то, проще всего, но тогда не было бы смысла в демонстрации "типизированных" плейсхолдеров). Строка, впрочем, валидироваться не будет: у меня и так там 400 строчек разбора того, что Postgres может выдать как строковое представление интервала, а принять он может ещё больше форматов.
 

Sad Spirit

мизантроп (Старожил PHPClub)
Команда форума
Sad Spirit, а твоя либа - она встраиваемая, в принципе, во фреймворк?
Уж очень круто выглядит.
Ну она отдельно лежащая, из зависимостей PHP 5.3+ и расширение pgsql, PSR-0 соответствует.

Сложность может быть в том, что работа построителя запросов заточена под использование pg_query_params() или обёртки вокруг него (ну или prepare / execute, но вспоминаем, что писал Котеров), плюс желательно использовать для работы с базой именно объект подключения из библиотеки, ибо там приведение типов. Можно, конечно, заменять параметры на значения в AST и собирать запрос, но тогда мы теряем уровень кэширования готового запроса, кэшироваться будут только (куски) AST.

Так-то я планирую её куда-нибудь в сторону github'а метнуть, но это уже где-то в феврале будет, после того, как перепишем на неё один наш проектик и посмотрим, насколько оно в работе реально удобное.
 

Sad Spirit

мизантроп (Старожил PHPClub)
Команда форума
не вижу проблемы: пишем лексер под mysql, если кто-нибудь напишет под другие базы - хорошо,
Ваще да, Фанат, вот тебе идея по структуре:
Класс Lexer (ну и имеющийся изначально MySQLLexer), который принимает на вход строку и выдаёт на выход объект TokenStream (см. Twig).
TokenStream, который содержит массив с токенами и какие-то методы для работы с ним. Его можно класть в кэш, и в следующий раз гонять Lexer не придётся.
PlaceholderMangler, на вход получает TokenStream с подстановками и список их значений, на выходе TokenStream, но уже с константами.
Builder (ну и имеющийся изначально MySQLBuilder), на входе TokenStream, на выходе строка SQL.

Соответственно желающие работать с другой СУБД пишут свой Lexer и Builder, остальные потроха трогать не обязательно.
 

fixxxer

К.О.
Партнер клуба
Остается взять embedded innodb (забыл как щас называется, riak на нем тот же), написать экстешнен с асинхронным api и юзать phpdaemon :D
 

fixxxer

К.О.
Партнер клуба
fixxxer, неправильно ты, дядя Федор, бутерброд держишь. У тебя должен быть INTERVAL ?s или как у Sad Spirit'а, что есть по сути свой тип плейсхолдера для INTERVAL.
Как бы да, к тому и троллфейс:)

С фигурными скобками, кстати, есть массивы, но они внутри кавычек.
 

fixxxer

К.О.
Партнер клуба
Вообще, нет, правда, это всё решение какой-то странной задачи - распарсить SQL в некое AST, а потом собирать обратно, чтобы SQL-сервер его опять распарсил.

Типа как вместо наследования брать token_get_all, добавлять методы в класс, собирать обратно и делать eval.

Если так глобально задуматься, давно уже нужен более низкоуровневый интерфейс общения с СУБД, когда на вход дается уже формализованная сериализованная структура запроса. Что-то типа handlersocket, но не такое примитивное, но и не настолько низкоуровневое, как bdb.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
fixxxer, если задуматься глобально, то писать на языке другой язык, на котром пишется макро-язык, и на нем писать структуру, на основе которой генерится HTML, чтобы отдать его браузеру, чтобы тот потом его распарсил, построил AST, по которому и отрендерит картинку ... :) почему-то считается очень правильной архитектурой
 
Последнее редактирование:

fixxxer

К.О.
Партнер клуба
Если рассуждать о веб-приложениях (а не о сайтах, которым нужна поисковая индексация, до сих пор ориентированная на статические страницы), то как раз популярен подход рендеринга на клиенте (angularjs, knockout итд) и jsonrpc между браузером и сервером - типичная трехзвенка.

Вот у Оракла была лет 10 назад приблуда, которая умела работать веб-сервером со скриптингом на pl/sql. Тогда, конечно, был примитив asp style, но мне кажется это направление (с учетом общения по jsonrpc) должно быть реанимировано. Ну например в виде веб-сервера + субд + виртуальной машины, и все это в райнтайме по типу Erlang, с неявной асинхронкой ;)
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
как говорится, если бы мир жил по логике, мужчины бы ездили в женском седле ))

сама по себе идея "веб-сервер + субд + виртуальная машина" - конечно, было бы неплохо, только с утечками памяти сложно бороться
 

riff

Новичок
В какой-то момент я начал уже с опаской ожидать появление "subj"а. Разговоры становятся всё более и более "суровыми".
 

fixxxer

К.О.
Партнер клуба
только с утечками памяти сложно бороться
Ну или с тормозами (точнее их непредсказуемостью и соответствующим latency) GC, если это все в виртуальной машине, да. Это вечная проблема :)
 

fixxxer

К.О.
Партнер клуба
В какой-то момент я начал уже с опаской ожидать появление "subj"а. Разговоры становятся всё более и более "суровыми".
Ну потому что задача такая - если отойти от простейших вариаций на тему str_replace, то неминуемо вылазит то, что на самом деле все, с чем мы работаем, это криво склеенные соплями куски кода ;)
 

Sad Spirit

мизантроп (Старожил PHPClub)
Команда форума
Вообще, нет, правда, это всё решение какой-то странной задачи - распарсить SQL в некое AST, а потом собирать обратно, чтобы SQL-сервер его опять распарсил.
Если это камень в мой огород, то поясню. Задача-то как раз не странная: сделать Query Builder, но не write only, а с возможностью доступа к кускам запроса и их изменения. Потому что вся вот эта х-ня с замыканиями, х-ня с трейтами и т.д. и т.п. --- это кнэшно красиво смотрится в документации, но не позволяет дорабатывать "построенный" запрос.

Честно это всё решается только через построение более-менее полноценного AST с дальнейшей его обработкой. Так сделано в том же питоновском SQLAlchemy.

То есть, во-первых, понятно: нужно AST. Во-вторых, понятно, что свой SQLAlchemy на похапэ не написать, т.к. языку похапэ недостаёт выразительных средств.

ОК, будем передавать куски запроса как строки, потому что собирать AST руками из объектов получается крайне многословно (пример из моих unit-тестов):
PHP:
    public function testLogicalExpression()
    {
        $expr = $this->parser->parseExpression(<<<QRY
    a and not b or not not c and d or e
QRY
        );
        $this->assertEquals(
            new LogicalExpression(
                array(
                    new LogicalExpression(
                        array(
                            new ColumnReference(array(new Identifier('a'))),
                            new OperatorExpression('not', null, new ColumnReference(array(new Identifier('b'))))
                        ),
                        'and'
                    ),
                    new LogicalExpression(
                        array(
                            new OperatorExpression('not', null, new OperatorExpression(
                                'not', null, new ColumnReference(array(new Identifier('c')))
                            )),
                            new ColumnReference(array(new Identifier('d')))
                        )
                    ),
                    new ColumnReference(array(new Identifier('e')))
                ),
                'or'
            ),
            $expr
        );
    }
Типа как вместо наследования брать token_get_all, добавлять методы в класс, собирать обратно и делать eval.
Наследование --- вещь статическая, тащемта. Аналогом наследования можно считать создание в базе представления, например. А запросы нам тащемта надо собирать динамически, в зависимости от пользовательского ввода, например.

То есть, по аналогии, тут мы решаем вопрос "как бы добавить метод существующему экземпляру класса", а в похапэ до 5.4.x ответом на этот вопрос было радостное причмокивание.

Ну и чё-то помнится, eval() с добавлением методов используется в том же PHPUnit'е для Mock'ов (с наследованием, впрочем).

Если так глобально задуматься, давно уже нужен более низкоуровневый интерфейс общения с СУБД, когда на вход дается уже формализованная сериализованная структура запроса. Что-то типа handlersocket, но не такое примитивное, но и не настолько низкоуровневое, как bdb.
SQL ещё худо-бедно переносимый, а этот ужас явно переносимым не будет, плюс писать его руками будет невозможно. Ну и парсинг запроса (если его делать не на похапэ, а на Ц) --- копейки по сравнению со всеми остальными накладными расходами.
 

fixxxer

К.О.
Партнер клуба
Не, не надо воспринимать как камень в твою сторону. Это абстрактные рассуждения, скорее, о том, что естественный, но притом весьма сложный с точки зрения грамматики язык запросов, придуманный для человека и удобный для написания вручную, стал некоторой проблемой в плане его генерации. Я не призываю же отказаться от SQL, он прекрасен в своей изначальной роли. Я вообще о проблеме модификации запроса. Дополнительный протокол, хоть бы даже и в виде AST, сериализованного в XML, уже куда удобнее (как минимум, можно применить XSL трансформации). Избавиться от двойной работы в этом случае можно выносом парсинга в либу бд-клиента. Но вообще интереснее свести уровень model на уровень базы целиком. Но для этого нужно что-то более пригодное для промышленного применения, чем plsql, в котором, несмотря на интеграцию с базой, нет никаких нормальных средств модификации запроса - чуть что все сваливается в конкатенации строк. Не говоря уж об отсутствии средств отладки, управления зависимости и деплоинга
 

Фанат

oncle terrible
Команда форума
Там всё еще остаётся косяк с плейсхолдерами внутри кавычек/бэктиков...
Вот, Никита Попов ответил на стаковерфлое,
PHP:
$pattern = <<<'NOW'
/(?:
    '[^'\\\\]*(?:(?:\\\\.|'')[^'\\\\]*)*'
  | "[^"\\\\]*(?:(?:\\\\.|"")[^"\\\\]*)*"
  | `[^`\\\\]*(?:(?:\\\\.|``)[^`\\\\]*)*`
)(*SKIP)(*F)| (\?)
/x
NOW;
- чтобы плейсхолдеры не парсились внутри строк/идентификаторов.
Кажись, работает. Я, правда, с трудом понимаю - как.
Особенно последнее слово на букву F
 
Сверху