UDF и плагины

Manticore можно расширять с помощью пользовательских функций, или сокращенно UDF, например так:

SELECT id, attr1, myudf (attr2, attr3+attr4) ...

Вы можете динамически загружать и выгружать UDF в searchd без необходимости перезапуска сервера и использовать их в выражениях при поиске, ранжировании и т.д. Краткий обзор возможностей UDF:

  • UDF могут принимать целочисленные (как 32-битные, так и 64-битные), вещественные, строковые аргументы, аргументы типа MVA или PACKEDFACTORS().
  • UDF могут возвращать целочисленные, вещественные или строковые значения.
  • UDF могут проверять количество, типы и имена аргументов на этапе настройки запроса и вызывать ошибки.

Мы пока не поддерживаем агрегатные функции. Другими словами, ваши UDF будут вызываться только для одного документа за раз и должны возвращать некоторое значение для этого документа. Написание функции, которая может вычислять агрегированное значение, такое как AVG(), для всей группы документов, имеющих одинаковый ключ GROUP BY, пока невозможно. Однако вы можете использовать UDF внутри встроенных агрегатных функций: то есть, даже если MYCUSTOMAVG() пока не поддерживается, AVG(MYCUSTOMFUNC()) должен работать отлично!

UDF предлагают широкий спектр применений, таких как:

  • включение пользовательских математических или строковых функций;
  • доступ к базам данных или файлам изнутри Manticore;
  • создание сложных функций ранжирования.

Плагины

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

Вот полный список типов плагинов:

  • Плагины UDF (по сути, UDF, но поскольку они подключаются, их также называют 'плагинами UDF')
  • Плагины ранкера
  • Плагины фильтров токенов во время индексации
  • Плагины фильтров токенов во время выполнения запроса

В этом разделе рассматривается общий процесс написания и управления плагинами; специфика, связанная с созданием различных типов плагинов, обсуждается в соответствующих подразделах.

Итак, как написать и использовать плагин? Вот краткое руководство из четырех шагов:

  • создайте динамическую библиотеку (либо .so, либо .dll), скорее всего, используя C или C++;
  • загрузите плагин в searchd с помощью CREATE PLUGIN;
  • используйте плагин с помощью специфичных для плагина вызовов (обычно через определенные OPTIONS).
  • выгрузите или перезагрузите плагин с помощью DROP PLUGIN и RELOAD PLUGINS соответственно.

Обратите внимание, что хотя UDF являются полноценными плагинами, они устанавливаются с помощью отдельного оператора CREATE FUNCTION. Это позволяет аккуратно указать тип возвращаемого значения, не жертвуя обратной совместимостью и не меняя синтаксис.

Динамические плагины поддерживаются в потоках и воркерах thread_pool. Несколько плагинов (и/или UDF) могут содержаться в одном файле библиотеки. Вы можете выбрать либо сгруппировать все плагины, специфичные для проекта, в одной большой библиотеке, либо создать отдельную библиотеку для каждой UDF и плагина; это на ваше усмотрение.

Как и в случае с UDF, вы должны включить заголовочный файл src/sphinxudf.h. Как минимум, вам понадобится константа SPH_UDF_VERSION для реализации соответствующей функции версии. В зависимости от конкретного типа плагина, вам может понадобиться или не понадобиться линковать ваш плагин с src/sphinxudf.c. Однако все функции, реализованные в sphinxudf.c, связаны с распаковкой бинарных данных PACKEDFACTORS(), и ни один тип плагинов не имеет доступа к этим данным. Поэтому в настоящее время должно быть достаточно линковки только с заголовочным файлом. (Фактически, если вы скопируете номер версии UDF, вам даже не понадобится заголовочный файл для некоторых типов плагинов.)

Формально плагины — это просто наборы функций на языке C, которые соответствуют определенному шаблону именования. Обычно требуется определить одну ключевую функцию для основной задачи, но можно также определить дополнительные функции. Например, чтобы реализовать ранкер с именем "myrank", вы должны определить функцию myrank_finalize(), которая возвращает значение релевантности. Однако вы также можете определить функции myrank_init(), myrank_update() и myrank_deinit(). Конкретные наборы известных суффиксов и аргументов вызова различаются в зависимости от типа плагина, но _init() и _deinit() являются общими, и они есть у каждого плагина. Подсказка: для быстрого ознакомления с известными суффиксами и их типами аргументов обратитесь к sphinxplugin.h, где прототипы вызовов определены в начале файла.

Хотя публичный интерфейс определен на чистом C, наши плагины по сути следуют объектно-ориентированной модели. Действительно, каждая функция _init() получает выходной параметр void ** userdata, и затем значение указателя, сохраненное в (*userdata), передается в качестве первого аргумента всем остальным функциям плагина. Таким образом, вы можете думать о плагине как о классе, который создается каждый раз, когда для обработки запроса требуется объект этого класса: указатель userdata служит указателем this; функции действуют как методы, а функции _init() и _deinit() работают как конструктор и деструктор соответственно.

Эта небольшая сложность с ООП на C возникает потому, что плагины работают в многопоточной среде, и некоторым необходимо сохранять состояние. Вы не можете хранить это состояние в глобальной переменной в вашем плагине, поэтому мы передаем параметр userdata, что естественным образом приводит к ООП-модели. Если ваш плагин простой и не имеет состояния, интерфейс позволяет опустить _init(), _deinit() и любые другие функции.

Подводя итог, вот самый простой полный плагин ранкера всего в трех строках кода на C:

// gcc -fPIC -shared -o myrank.so myrank.c
#include "sphinxudf.h"
int myrank_ver() { return SPH_UDF_VERSION; }
int myrank_finalize(void *u, int w) { return 123; }

Вот как использовать простой плагин ранкера:

mysql> CREATE PLUGIN myrank TYPE 'ranker' SONAME 'myrank.dll';
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT id, weight() FROM test1 WHERE MATCH('test') OPTION ranker=myrank('');
+------+----------+
| id   | weight() |
+------+----------+
|    1 |      123 |
|    2 |      123 |
+------+----------+
2 rows in set (0.01 sec)
Last modified: August 28, 2025