Pire UDF

Список функций

  • Pire::Grep(String) -> (String?) -> Bool
  • Pire::Match(String) -> (String?) -> Bool
  • Pire::MultiGrep(String) -> (String?) -> Tuple<Bool, Bool, ...>
  • Pire::MultiMatch(String) -> (String?) -> Tuple<Bool, Bool, ...>
  • Pire::Capture(String) -> (String?) -> String?
  • Pire::Replace(String) -> (String?, String) -> String?

Одной из опций для поиска по регулярным выражениям в YQL является библиотека Pire (Perl Incompatible Regular Expressions). Это разработанная в Яндексе очень быстрая библиотека регулярных выражений: на нижнем уровне она просматривает входную строку один раз, подряд, без откатов, и тратит (на x86 и x86_64) по 5 инструкций на символ.

Скорость работы достигается за счет некоторых разумных ограничений:

  • Библиотека Pire ориентирована прежде всего на проверку на совпадение строки с регулярным выражением (Match).
  • Возможность вернуть совпавшую подстроку тоже поддерживается (Capture), но с ограничениями (возвращает совпадение только с одной группой).

По умолчанию все функции работают в однобайтовом режиме, но если регулярное выражение является валидной UTF-8 строкой, но не является валидной ASCII строкой, — автоматически включается режим UTF-8.

Чтобы включить Unicode-режим, можно вставить в выражение один символ за пределами ASCII с оператором ?, например \\w+я?.

Синтаксис вызова

Чтобы избежать компиляции регулярного выражения на каждой строке таблицы, необходимо обернуть вызов функции в именованное выражение:

$re = Pire::Grep("\\d+"); -- создаем вызываемое значение для проверки конкретного регулярного выражения
SELECT * FROM table WHERE $re(key); -- используем его для фильтрации таблицы

Обратите внимание на экранирование спецсимволов в регулярном выражении. Второй слеш нужен, так как все стандартные строковые литералы в SQL могут принимать С-escaped строки, а последовательность \d не является валидной последовательностью, и даже если бы являлась — не приводила бы к ожидаемому эффекту поиска чисел. Есть возможность отключить чувствительность к регистру (то есть включить case-insensitive режим), указав в начале регулярного выражения флаг (?i).

Примеры

$value = "xaaxaaxaa";
$match = Pire::Match("a.*");
$grep = Pire::Grep("axa");
$insensitive_grep = Pire::Grep("(?i)axa");
$multi_match = Pire::MultiMatch(@@a.*
.*a.*
.*a
.*axa.*@@);
$capture = Pire::Capture(".*x(a).*");
$capture_many = Pire::Capture(".*x(a+).*");
$replace = Pire::Replace(".*x(a).*");

SELECT
  $match($value) AS match,
  $grep($value) AS grep,
  $insensitive_grep($value) AS insensitive_grep,
  $multi_match($value) AS multi_match,
  $multi_match($value).0 AS some_multi_match,
  $capture($value) AS capture,
  $capture_many($value) AS capture_many,
  $replace($value, "b") AS replace;

/*
- match: `false`
- grep: `true`
- multi_match: `(false, true, true, true)`
- some_multi_match: `false`
- capture: `"a"`
- capture_many: `"a"`
- replace: `"xbaxaaxaa"`
*/

Grep

Проверяет совпадение регулярного выражения с частью строки (произвольной подстрокой).

Match

Проверяет совпадение регулярного выражения со строкой целиком. Чтобы получить результат, аналогиный Grep (где учитывается совпадание с подстрокой), нужно обрамлять регулярное выражение с обоих сторон в .*, например .*foo.* вместо foo.

MultiGrep / MultiMatch

Библиотека Pire предоставляет возможность за один проход по тексту проверить несколько регулярных выражений и получить по каждому из них отдельный ответ. С помощью функций MultiGrep/MultiMatch можно оптимизировать скорость выполнения запроса, но это нужно делать осмотрительно, поскольку размер используемого для проверки конечного автомата растет экспоненциально с числом регулярных выражений:

  • Если вас интересует совпадение строки с любым из перечисленных выражений (результаты объединяются через «или»), то намного эффективнее сделать одно регулярное выражение, объединив части с помощью оператора |, и использовать для поиска обычный Grep или Match.
  • В библиотеке Pire установлен лимит на размер конечного автомата (в YQL используется значение этого лимита, установленное по умолчанию в библиотеке). Если лимит превышен, при запуске запроса вернется ошибка Failed to glue up regexes, probably the finite state machine appeared to be too large. При вызове функций MultiGrep/MultiMatch регулярные выражения передаются по одному на строку с использованием многострочных строковых литералов:

Примеры

$multi_match = Pire::MultiMatch(@@a.*
.*x.*
.*axa.*@@);

SELECT
$multi_match("a") AS a,
$multi_match("axa") AS axa;

/*
- a: `(true, false, false)`
- axa: `(true, true, true)`
*/

Capture

При совпадении строки с указанным регулярным выраженим возвращает подстроку, совпавшую с группой, отмеченной в регулярном выражении круглыми скобками. Capture работает НЕ в жадном режиме, то есть возвращается самая короткая подстрока из возможных.

Предупреждение

В выражении должна быть ровно одна группа, обозначенная круглыми скобками. В случае отсутствия совпадения возвращается NULL (пустой Optional).

Если описанные выше ограничения и особенности по каким-либо причинам выглядят неприемлемыми, рекомендуется рассмотреть возможность использования Re2::Capture.

Replace UDF

В библиотеке Pire отсутствует функция замены по регулярному выражению. Функция Pire::Replace в YQL представляет собой упрощенную эмуляцию, реализованную с помощью Capture. Может работать некорректно, если найденная подстрока встречается в исходной более одного раза.

Как правило, лучше воспользоваться Re2::Replace вместо неё.