Перейти к основному содержимому
Перейти к основному содержимому

Структура словаря «дерево регулярных выражений»

Обзор

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

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

Not supported in ClickHouse Cloud

Словари дерева регулярных выражений определяются в открытой версии ClickHouse с использованием источника YAMLRegExpTree, которому передаётся путь к YAML-файлу с деревом регулярных выражений.

CREATE DICTIONARY regexp_dict
(
    regexp String,
    name String,
    version String
)
PRIMARY KEY(regexp)
SOURCE(YAMLRegExpTree(PATH '/var/lib/clickhouse/user_files/regexp_tree.yaml'))
LAYOUT(regexp_tree)
...

Источник словаря YAMLRegExpTree представляет собой структуру дерева регулярных выражений. Например:

- regexp: 'Linux/(\d+[\.\d]*).+tlinux'
  name: 'TencentOS'
  version: '\1'

- regexp: '\d+/tclwebkit(?:\d+[\.\d]*)'
  name: 'Android'
  versions:
    - regexp: '33/tclwebkit'
      version: '13'
    - regexp: '3[12]/tclwebkit'
      version: '12'
    - regexp: '30/tclwebkit'
      version: '11'
    - regexp: '29/tclwebkit'
      version: '10'

Эта конфигурация состоит из списка узлов дерева регулярных выражений. Каждый узел имеет следующую структуру:

  • regexp: регулярное выражение узла.
  • attributes: список пользовательских атрибутов словаря. В этом примере есть два атрибута: name и version. Первый узел задаёт оба атрибута. Второй узел задаёт только атрибут name. Атрибут version задаётся дочерними узлами второго узла.
    • Значение атрибута может содержать обратные ссылки, ссылающиеся на группы захвата сопоставленного регулярного выражения. В примере значение атрибута version в первом узле состоит из обратной ссылки \1 на группу захвата (\d+[\.\d]*) в регулярном выражении. Номера обратных ссылок находятся в диапазоне от 1 до 9 и записываются как $1 или \1 (для числа 1). Во время выполнения запроса обратная ссылка заменяется сопоставленной группой захвата.
  • child nodes: список дочерних узлов узла дерева regexp, каждый из которых имеет свои собственные атрибуты и (потенциально) дочерние узлы. Сопоставление строки выполняется в порядке обхода в глубину. Если строка соответствует узлу regexp, словарь проверяет, соответствует ли она также дочерним узлам этого узла. Если это так, назначаются атрибуты самого глубокого совпавшего узла. Атрибуты дочернего узла переопределяют одноимённые атрибуты родительских узлов. Имя дочерних узлов в YAML-файлах может быть произвольным, например versions в приведённом выше примере.

Словари на основе дерева регулярных выражений допускают доступ только с использованием функций dictGet, dictGetOrDefault и dictGetAll. Например:

SELECT dictGet('regexp_dict', ('name', 'version'), '31/tclwebkit1024');
┌─dictGet('regexp_dict', ('name', 'version'), '31/tclwebkit1024')─┐
│ ('Android','12')                                                │
└─────────────────────────────────────────────────────────────────┘

В этом случае мы сначала сопоставляем регулярное выражение \d+/tclwebkit(?:\d+[\.\d]*) со вторым узлом на верхнем уровне. Затем словарь продолжает просматривать дочерние узлы и обнаруживает, что строка также соответствует 3[12]/tclwebkit. В результате значение атрибута name равно Android (определено на первом уровне), а значение атрибута version равно 12 (определено в дочернем узле).

С помощью сложного конфигурационного файла YAML вы можете использовать словари в виде дерева регулярных выражений в качестве парсера строки user agent. ClickHouse поддерживает uap-core, и вы можете увидеть, как его использовать, в функциональном тесте 02504_regexp_dictionary_ua_parser

Сбор значений атрибутов

Иногда бывает полезно возвращать значения из нескольких регулярных выражений, которые сработали, а не только значение листового узла. В таких случаях можно использовать специализированную функцию dictGetAll. Если узел имеет значение атрибута типа T, dictGetAll вернёт Array(T), содержащий ноль или более значений.

По умолчанию количество совпадений, возвращаемых для каждого ключа, не ограничено. Ограничение можно передать в качестве необязательного четвертого аргумента функции dictGetAll. Массив заполняется в топологическом порядке, что означает, что дочерние узлы идут перед родительскими, а одноуровневые (соседние) узлы следуют порядку в исходном описании.

Пример:

CREATE DICTIONARY regexp_dict
(
    regexp String,
    tag String,
    topological_index Int64,
    captured Nullable(String),
    parent String
)
PRIMARY KEY(regexp)
SOURCE(YAMLRegExpTree(PATH '/var/lib/clickhouse/user_files/regexp_tree.yaml'))
LAYOUT(regexp_tree)
LIFETIME(0)
# /var/lib/clickhouse/user_files/regexp_tree.yaml
- regexp: 'clickhouse\.com'
  tag: 'ClickHouse'
  topological_index: 1
  paths:
    - regexp: 'clickhouse\.com/docs(.*)'
      tag: 'ClickHouse Documentation'
      topological_index: 0
      captured: '\1'
      parent: 'ClickHouse'

- regexp: '/docs(/|$)'
  tag: 'Documentation'
  topological_index: 2

- regexp: 'github.com'
  tag: 'GitHub'
  topological_index: 3
  captured: 'NULL'
CREATE TABLE urls (url String) ENGINE=MergeTree ORDER BY url;
INSERT INTO urls VALUES ('clickhouse.com'), ('clickhouse.com/docs/en'), ('github.com/clickhouse/tree/master/docs');
SELECT url, dictGetAll('regexp_dict', ('tag', 'topological_index', 'captured', 'parent'), url, 2) FROM urls;

Результат:

┌─url────────────────────────────────────┬─dictGetAll('regexp_dict', ('tag', 'topological_index', 'captured', 'parent'), url, 2)─┐
│ clickhouse.com                         │ (['ClickHouse'],[1],[],[])                                                            │
│ clickhouse.com/docs/en                 │ (['ClickHouse Documentation','ClickHouse'],[0,1],['/en'],['ClickHouse'])              │
│ github.com/clickhouse/tree/master/docs │ (['Documentation','GitHub'],[2,3],[NULL],[])                                          │
└────────────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────────────┘

Режимы сопоставления

Поведение сопоставления по шаблону можно изменить с помощью определённых настроек словаря:

  • regexp_dict_flag_case_insensitive: использовать регистронезависимое сопоставление (по умолчанию false). Можно переопределить для отдельных выражений с помощью (?i) и (?-i).
  • regexp_dict_flag_dotall: разрешить символу '.' сопоставлять также символы перевода строки (по умолчанию false).

Использование словаря дерева регулярных выражений в ClickHouse Cloud

Источник YAMLRegExpTree работает в ClickHouse Open Source, но не в ClickHouse Cloud. Чтобы использовать словари дерева регулярных выражений в ClickHouse Cloud, сначала локально в ClickHouse Open Source создайте такой словарь из YAML-файла, после чего выгрузите его в CSV-файл с помощью табличной функции dictionary и предложения INTO OUTFILE.

SELECT * FROM dictionary(regexp_dict) INTO OUTFILE('regexp_dict.csv')

Содержимое CSV‑файла:

1,0,"Linux/(\d+[\.\d]*).+tlinux","['version','name']","['\\1','TencentOS']"
2,0,"(\d+)/tclwebkit(\d+[\.\d]*)","['comment','version','name']","['test $1 and $2','$1','Android']"
3,2,"33/tclwebkit","['version']","['13']"
4,2,"3[12]/tclwebkit","['version']","['12']"
5,2,"3[12]/tclwebkit","['version']","['11']"
6,2,"3[12]/tclwebkit","['version']","['10']"

Структура дамп-файла:

  • id UInt64: идентификатор узла RegexpTree.
  • parent_id UInt64: идентификатор родительского узла.
  • regexp String: строка с регулярным выражением.
  • keys Array(String): имена пользовательских атрибутов.
  • values Array(String): значения пользовательских атрибутов.

Чтобы создать словарь в ClickHouse Cloud, сначала создайте таблицу regexp_dictionary_source_table со следующей структурой:

CREATE TABLE regexp_dictionary_source_table
(
    id UInt64,
    parent_id UInt64,
    regexp String,
    keys   Array(String),
    values Array(String)
) ENGINE=Memory;

Затем обновите локальный CSV‑файл с помощью

clickhouse client \
    --host MY_HOST \
    --secure \
    --password MY_PASSWORD \
    --query "
    INSERT INTO regexp_dictionary_source_table
    SELECT * FROM input ('id UInt64, parent_id UInt64, regexp String, keys Array(String), values Array(String)')
    FORMAT CSV" < regexp_dict.csv

Вы можете ознакомиться с разделом Insert Local Files для получения более подробной информации. После инициализации исходной таблицы мы можем создать RegexpTree на основе этой таблицы:

CREATE DICTIONARY regexp_dict
(
    regexp String,
    name String,
    version String
PRIMARY KEY(regexp)
SOURCE(CLICKHOUSE(TABLE 'regexp_dictionary_source_table'))
LIFETIME(0)
LAYOUT(regexp_tree);