Типы данных Manticore можно разделить на две категории: полнотекстовые поля и атрибуты.
Имена полей в Manticore должны соответствовать следующим правилам:
- Могут содержать буквы (a-z, A-Z), цифры (0-9) и дефисы (-)
- Должны начинаться с буквы
- Цифры могут появляться только после букв
- Подчеркивание (
_) — единственный разрешённый специальный символ - Имена полей не чувствительны к регистру
Например:
- Допустимые имена полей:
title,product_id,user_name_2 - Недопустимые имена полей:
2title,-price,user@name
Полнотекстовые поля:
- могут индексироваться с помощью алгоритмов обработки естественного языка, поэтому по ним можно искать ключевые слова
- не могут использоваться для сортировки или группировки
- можно получить исходное содержимое документа
- исходное содержимое документа можно использовать для подсветки
Полнотекстовые поля представлены типом данных text. Все остальные типы данных называются «атрибутами».
Атрибуты — это не полнотекстовые значения, связанные с каждым документом, которые можно использовать для фильтрации, сортировки и группировки по неполнотекстовым критериям во время поиска.
Часто требуется обрабатывать результаты полнотекстового поиска не только на основе совпадения ID документа и его ранга, но и на основе ряда других значений, связанных с документом. Например, может понадобиться отсортировать результаты поиска новостей по дате, а затем по релевантности, или искать товары в заданном ценовом диапазоне, или ограничить поиск по блогу постами, сделанными выбранными пользователями, или сгруппировать результаты по месяцу. Для эффективного выполнения таких задач Manticore позволяет добавлять к каждому документу не только полнотекстовые поля, но и дополнительные атрибуты. Эти атрибуты можно использовать для фильтрации, сортировки или группировки полнотекстовых совпадений, а также для поиска только по атрибутам.
Атрибуты, в отличие от полнотекстовых полей, не индексируются полнотекстово. Они хранятся в таблице, но по ним нельзя выполнять полнотекстовый поиск.
Хорошим примером атрибутов может служить таблица сообщений форума. Предположим, что полнотекстовым поиском должны быть доступны только поля title и content — но иногда требуется ограничить поиск определённым автором или подфорумом (то есть искать только те строки, у которых есть конкретные значения author_id или forum_id); или сортировать совпадения по столбцу post_date; или группировать совпадающие сообщения по месяцу из post_date и подсчитывать количество совпадений в каждой группе.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- config
CREATE TABLE forum(title text, content text, author_id int, forum_id int, post_date timestamp);POST /cli -d "CREATE TABLE forum(title text, content text, author_id int, forum_id int, post_date timestamp)"$index = new \Manticoresearch\Index($client);
$index->setName('forum');
$index->create([
'title'=>['type'=>'text'],
'content'=>['type'=>'text'],
'author_id'=>['type'=>'int'],
'forum_id'=>['type'=>'int'],
'post_date'=>['type'=>'timestamp']
]);utilsApi.sql('CREATE TABLE forum(title text, content text, author_id int, forum_id int, post_date timestamp)')await utilsApi.sql('CREATE TABLE forum(title text, content text, author_id int, forum_id int, post_date timestamp)')res = await utilsApi.sql('CREATE TABLE forum(title text, content text, author_id int, forum_id int, post_date timestamp)');utilsApi.sql("CREATE TABLE forum(title text, content text, author_id int, forum_id int, post_date timestamp)");utilsApi.Sql("CREATE TABLE forum(title text, content text, author_id int, forum_id int, post_date timestamp)");utils_api.sql("CREATE TABLE forum(title text, content text, author_id int, forum_id int, post_date timestamp)", Some(true)).await;table forum
{
type = rt
path = forum
# when configuring fields via config, they are indexed (and not stored) by default
rt_field = title
rt_field = content
# this option needs to be specified for the field to be stored
stored_fields = title, content
rt_attr_uint = author_id
rt_attr_uint = forum_id
rt_attr_timestamp = post_date
}В этом примере показан запуск полнотекстового запроса с фильтрацией по author_id, forum_id и сортировкой по post_date.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select * from forum where author_id=123 and forum_id in (1,3,7) order by post_date descPOST /search
{
"table": "forum",
"query":
{
"match_all": {},
"bool":
{
"must":
[
{ "equals": { "author_id": 123 } },
{ "in": { "forum_id": [1,3,7] } }
]
}
},
"sort": [ { "post_date": "desc" } ]
}$client->search([
'table' => 'forum',
'query' =>
[
'match_all' => [],
'bool' => [
'must' => [
'equals' => ['author_id' => 123],
'in' => [
'forum_id' => [
1,3,7
]
]
]
]
],
'sort' => [
['post_date' => 'desc']
]
]);searchApi.search({"table":"forum","query":{"match_all":{},"bool":{"must":[{"equals":{"author_id":123}},{"in":{"forum_id":[1,3,7]}}]}},"sort":[{"post_date":"desc"}]})await searchApi.search({"table":"forum","query":{"match_all":{},"bool":{"must":[{"equals":{"author_id":123}},{"in":{"forum_id":[1,3,7]}}]}},"sort":[{"post_date":"desc"}]})res = await searchApi.search({"table":"forum","query":{"match_all":{},"bool":{"must":[{"equals":{"author_id":123}},{"in":{"forum_id":[1,3,7]}}]}},"sort":[{"post_date":"desc"}]});HashMap<String,Object> filters = new HashMap<String,Object>(){{
put("must", new HashMap<String,Object>(){{
put("equals",new HashMap<String,Integer>(){{
put("author_id",123);
}});
put("in",
new HashMap<String,Object>(){{
put("forum_id",new int[] {1,3,7});
}});
}});
}};
Map<String,Object> query = new HashMap<String,Object>();
query.put("match_all",null);
query.put("bool",filters);
SearchRequest searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
searchRequest.setQuery(query);
searchRequest.setSort(new ArrayList<Object>(){{
add(new HashMap<String,String>(){{ put("post_date","desc");}});
}});
SearchResponse searchResponse = searchApi.search(searchRequest);object query = new { match_all=null };
var searchRequest = new SearchRequest("forum", query);
var boolFilter = new BoolFilter();
boolFilter.Must = new List<Object> {
new EqualsFilter("author_id", 123),
new InFilter("forum_id", new List<Object> {1,3,7})
};
searchRequest.AttrFilter = boolFilter;
searchRequest.Sort = new List<Object> { new SortOrder("post_date", SortOrder.OrderEnum.Desc) };
var searchResponse = searchApi.Search(searchRequest);let query = SearchQuery::new();
let mut sort = HashMap::new();
sort.insert("post_date".to_string(), serde_json::json!("desc"));
let search_request = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
sort: serde_json::json!(sort)
..Default::default()
};
let search_res = search_api.search(search_req).await;Manticore поддерживает два типа хранения атрибутов:
- строковое — традиционное хранение, доступное в Manticore Search из коробки
- колонковое — предоставляется Manticore Columnar Library
Как можно понять из названий, они хранят данные по-разному. Традиционное строковое хранение:
- хранит атрибуты без сжатия
- все атрибуты одного документа хранятся в одной строке рядом друг с другом
- строки хранятся одна за другой
- доступ к атрибутам осуществляется просто умножением ID строки на шаг (длину одного вектора) и получением нужного атрибута из рассчитанного адреса в памяти. Это обеспечивает очень низкую задержку случайного доступа.
- атрибуты должны находиться в памяти для достижения приемлемой производительности, иначе из-за строковой природы хранения Manticore может читать с диска слишком много ненужных данных, что во многих случаях не оптимально.
В случае колонкового хранения:
- каждый атрибут хранится независимо от всех остальных в отдельной «колонке»
- хранилище разбито на блоки по 65536 записей
- блоки хранятся в сжатом виде. Это часто позволяет хранить всего несколько различных значений вместо хранения всех значений, как в строковом хранении. Высокое сжатие позволяет быстрее читать с диска и значительно снижает требования к памяти
- при индексировании данных схема хранения выбирается для каждого блока отдельно. Например, если все значения в блоке одинаковы, он получает «const» хранение, и для всего блока хранится только одно значение. Если в блоке менее 256 уникальных значений, он получает «table» хранение и хранит индексы в таблицу значений вместо самих значений
- поиск в блоке может быть досрочно отклонён, если ясно, что запрашиваемое значение отсутствует в блоке.
Колонковое хранение было разработано для обработки больших объёмов данных, которые не помещаются в ОЗУ, поэтому рекомендации таковы:
- если у вас достаточно памяти для всех атрибутов, вы получите выгоду от строкового хранения
- в противном случае колонковое хранение всё равно может обеспечить достойную производительность с гораздо меньшим потреблением памяти, что позволит хранить гораздо больше документов в таблице
Традиционное построчное хранение является значением по умолчанию, поэтому если вы хотите, чтобы всё хранилось построчно, вам не нужно ничего делать при создании таблицы.
Чтобы включить колонковое хранение, нужно:
- указать
engine='columnar'в CREATE TABLE, чтобы все атрибуты таблицы были колонковыми. Если вы хотите, чтобы конкретный атрибут оставался построчным, нужно добавитьengine='rowwise'при его объявлении. Например:create table tbl(title text, type int, price float engine='rowwise') engine='columnar' - указать
engine='columnar'для конкретного атрибута вCREATE TABLE, чтобы сделать его колонковым. Например:create table tbl(title text, type int, price float engine='columnar');или
create table tbl(title text, type int, price float engine='columnar') engine='rowwise'; - в plain режиме нужно перечислить атрибуты, которые вы хотите сделать колонковыми, в columnar_attrs.
Ниже приведён список типов данных, поддерживаемых Manticore Search:
Идентификатор документа — обязательный атрибут, который должен быть уникальным 64-битным беззнаковым целым числом. Идентификаторы документов могут быть явно указаны при создании таблицы, но они всегда включены, даже если не указаны. Идентификаторы документов нельзя обновлять.
При создании таблицы вы можете явно указать ID, но независимо от используемого типа данных он всегда будет вести себя, как описано выше — храниться как беззнаковое 64-битное число, но отображаться как знаковое 64-битное целое.
mysql> CREATE TABLE tbl(id bigint, content text);
DESC tbl;
+---------+--------+----------------+
| Field | Type | Properties |
+---------+--------+----------------+
| id | bigint | |
| content | text | indexed stored |
+---------+--------+----------------+
2 rows in set (0.00 sec)
Вы также можете вообще не указывать ID, он будет включён автоматически.
mysql> CREATE TABLE tbl(content text);
DESC tbl;
+---------+--------+----------------+
| Field | Type | Properties |
+---------+--------+----------------+
| id | bigint | |
| content | text | indexed stored |
+---------+--------+----------------+
2 rows in set (0.00 sec)
При работе с идентификаторами документов важно знать, что они хранятся внутренне как беззнаковые 64-битные целые, но обрабатываются по-разному в зависимости от интерфейса:
Интерфейс MySQL/SQL:
- Идентификаторы больше 2^63-1 будут отображаться как отрицательные числа.
- При фильтрации по таким большим ID нужно использовать их знаковое представление.
- Используйте функцию UINT64(), чтобы увидеть фактическое беззнаковое значение.
Интерфейс JSON/HTTP:
- ID всегда отображаются как их исходные беззнаковые значения, независимо от размера.
- Для фильтрации можно использовать как знаковое, так и беззнаковое представление.
- Операции вставки принимают полный диапазон беззнаковых 64-битных чисел.
Например, создадим таблицу и вставим несколько значений около 2^63:
mysql> create table t(id_text string);
Query OK, 0 rows affected (0.01 sec)
mysql> insert into t values(9223372036854775807, '2 ^ 63 - 1'),(9223372036854775808, '2 ^ 63');
Query OK, 2 rows affected (0.00 sec)
Некоторые ID в результатах отображаются как отрицательные числа, потому что они превышают 2^63-1. Однако использование UINT64(id) позволяет увидеть их фактические беззнаковые значения:
mysql> select *, uint64(id) from t;
+----------------------+------------+---------------------+
| id | id_text | uint64(id) |
+----------------------+------------+---------------------+
| 9223372036854775807 | 2 ^ 63 - 1 | 9223372036854775807 |
| -9223372036854775808 | 2 ^ 63 | 9223372036854775808 |
+----------------------+------------+---------------------+
2 rows in set (0.00 sec)
--- 2 out of 2 results in 0ms ---
Для запросов документов с ID меньше 2^63 можно использовать беззнаковое значение напрямую:
mysql> select * from t where id = 9223372036854775807;
+---------------------+------------+
| id | id_text |
+---------------------+------------+
| 9223372036854775807 | 2 ^ 63 - 1 |
+---------------------+------------+
1 row in set (0.00 sec)
--- 1 out of 1 results in 0ms ---
Однако для ID, начиная с 2^63, нужно использовать знаковое значение:
mysql> select * from t where id = -9223372036854775808;
+----------------------+---------+
| id | id_text |
+----------------------+---------+
| -9223372036854775808 | 2 ^ 63 |
+----------------------+---------+
1 row in set (0.00 sec)
--- 1 out of 1 results in 0ms ---
Если использовать беззнаковое значение вместо этого, будет ошибка:
mysql> select * from t where id = 9223372036854775808;
ERROR 1064 (42000): number 9223372036854775808 is out of range [-9223372036854775808..9223372036854775807]
Значения, не помещающиеся в 64 бита, вызовут аналогичную ошибку:
mysql> select * from t where id = -9223372036854775809;
ERROR 1064 (42000): number -9223372036854775809 is out of range [-9223372036854775808..9223372036854775807]
Различия в поведении между интерфейсами MySQL/SQL и JSON/HTTP становятся более заметными при очень больших идентификаторах документов. Вот полный пример:
Интерфейс MySQL/SQL:
mysql> drop table if exists t; create table t; insert into t values(17581446260360033510);
Query OK, 0 rows affected (0.01 sec)
mysql> select * from t;
+---------------------+
| id |
+---------------------+
| -865297813349518106 |
+---------------------+
mysql> select *, uint64(id) from t;
+---------------------+----------------------+
| id | uint64(id) |
+---------------------+----------------------+
| -865297813349518106 | 17581446260360033510 |
+---------------------+----------------------+
mysql> select * from t where id = -865297813349518106;
+---------------------+
| id |
+---------------------+
| -865297813349518106 |
+---------------------+
mysql> select * from t where id = 17581446260360033510;
ERROR 1064 (42000): number 17581446260360033510 is out of range [-9223372036854775808..9223372036854775807]
Интерфейс JSON/HTTP:
# Search returns the original unsigned value
curl -s 0:9308/search -d '{"table": "t"}'
{
"took": 0,
"timed_out": false,
"hits": {
"total": 1,
"total_relation": "eq",
"hits": [
{
"_id": 17581446260360033510,
"_score": 1,
"_source": {}
}
]
}
}
# Both signed and unsigned values work for filtering
curl -s 0:9308/search -d '{"table": "t", "query": {"equals": {"id": 17581446260360033510}}}'
curl -s 0:9308/search -d '{"table": "t", "query": {"equals": {"id": -865297813349518106}}}'
# Insert with maximum unsigned 64-bit value
curl -s 0:9308/insert -d '{"table": "t", "id": 18446744073709551615, "doc": {}}'
Это означает, что при работе с большими идентификаторами документов:
- Интерфейс MySQL требует использования знакового представления для запросов, но может отображать беззнаковое значение с помощью
UINT64() - Интерфейс JSON последовательно использует беззнаковые значения для отображения и принимает оба представления для фильтрации
Общий синтаксис:
string|text [stored|attribute] [indexed]
Свойства:
indexed— полнотекстовый индекс (можно использовать в полнотекстовых запросах)stored— хранится в docstore (хранится на диске, не в ОЗУ, ленивое чтение)attribute— делает его строковым атрибутом (можно сортировать/группировать по нему)
Указание хотя бы одного свойства отменяет все свойства по умолчанию (см. ниже), то есть если вы решите использовать пользовательскую комбинацию свойств, нужно перечислить все свойства, которые хотите.
Если свойства не указаны:
string и text — это синонимы, но если не указать свойства, они по умолчанию означают разное:
- просто
stringпо умолчанию означаетattribute(подробности ниже). - просто
textпо умолчанию означаетstored+indexed(подробности ниже).
Тип данных текст (просто text или text/string indexed) формирует полнотекстовую часть таблицы. Текстовые поля индексируются и по ним можно искать ключевые слова.
Текст проходит через конвейер анализатора, который разбивает текст на слова, применяет морфологические преобразования и т. д. В итоге из этого текста строится полнотекстовая таблица (специальная структура данных, позволяющая быстро искать ключевое слово).
Полнотекстовые поля можно использовать только в условии MATCH() и нельзя использовать для сортировки или агрегации. Слова хранятся в обратном индексе вместе с ссылками на поля, которым они принадлежат, и позициями в поле. Это позволяет искать слово внутри каждого поля и использовать расширенные операторы, например, близость. По умолчанию исходный текст полей индексируется и хранится в хранилище документов. Это значит, что исходный текст можно вернуть в результатах запроса и использовать в подсветке результатов поиска.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
CREATE TABLE products(title text);POST /cli -d "CREATE TABLE products(title text)"$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text']
]);utilsApi.sql('CREATE TABLE products(title text)')await utilsApi.sql('CREATE TABLE products(title text)')res = await utilsApi.sql('CREATE TABLE products(title text)');utilsApi.sql("CREATE TABLE products(title text)");utilsApi.Sql("CREATE TABLE products(title text)");utils_api.sql("CREATE TABLE products(title text)", Some(true)).await;table products
{
type = rt
path = products
# when configuring fields via config, they are indexed (and not stored) by default
rt_field = title
# this option needs to be specified for the field to be stored
stored_fields = title
}Это поведение можно переопределить, явно указав, что текст индексируется только.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
CREATE TABLE products(title text indexed);POST /cli -d "CREATE TABLE products(title text indexed)"$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text','options'=>['indexed']]
]);utilsApi.sql('CREATE TABLE products(title text indexed)')await utilsApi.sql('CREATE TABLE products(title text indexed)')res = await utilsApi.sql('CREATE TABLE products(title text indexed)');utilsApi.sql("CREATE TABLE products(title text indexed)");utilsApi.Sql("CREATE TABLE products(title text indexed)");utils_api.sql("CREATE TABLE products(title text indexed)", Some(true)).await;table products
{
type = rt
path = products
# when configuring fields via config, they are indexed (and not stored) by default
rt_field = title
}Поля имеют имена, и вы можете ограничить поиск одним полем (например, искать только по "title") или подмножеством полей (например, только "title" и "abstract"). Вы можете иметь до 256 полнотекстовых полей.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select * from products where match('@title first');POST /search
{
"table": "products",
"query":
{
"match": { "title": "first" }
}
}$index->setName('products')->search('@title')->get();searchApi.search({"table":"products","query":{"match":{"title":"first"}}})await searchApi.search({"table":"products","query":{"match":{"title":"first"}}})res = await searchApi.search({"table":"products","query":{"match":{"title":"first"}}});utilsApi.sql("CREATE TABLE products(title text indexed)");utilsApi.Sql("CREATE TABLE products(title text indexed)");utils_api.sql("CREATE TABLE products(title text indexed)", Some(true)).await;В отличие от полнотекстовых полей, строковые атрибуты (просто string или string/text attribute) хранятся в том виде, в каком они получены, и не могут использоваться в полнотекстовом поиске. Вместо этого они возвращаются в результатах, могут использоваться в условии WHERE для фильтрации по сравнению или REGEX, а также для сортировки и агрегации. В общем, не рекомендуется хранить большие тексты в строковых атрибутах, лучше использовать строковые атрибуты для метаданных, таких как имена, заголовки, теги, ключи.
Если вы хотите также индексировать строковый атрибут, вы можете указать его как string attribute indexed. Это позволит полнотекстовый поиск и будет работать как атрибут.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
CREATE TABLE products(title text, keys string);POST /cli -d "CREATE TABLE products(title text, keys string)"$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'keys'=>['type'=>'string']
]);utilsApi.sql('CREATE TABLE products(title text, keys string)')await utilsApi.sql('CREATE TABLE products(title text, keys string)')res = await utilsApi.sql('CREATE TABLE products(title text, keys string)');utilsApi.sql("CREATE TABLE products(title text, keys string)");utilsApi.Sql("CREATE TABLE products(title text, keys string)");utils_api.sql("CREATE TABLE products(title text, keys string)", Some(true)).await;table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_string = keys
}В Manticore нет специального типа поля для бинарных данных, но вы можете безопасно хранить их, используя кодирование base64 и типы полей text stored или string stored (которые являются синонимами). Если не кодировать бинарные данные, их части могут потеряться — например, Manticore обрезает конец строки, если встречает нулевой байт.
Вот пример, где мы кодируем команду ls с помощью base64, сохраняем её в Manticore, а затем декодируем, чтобы проверить, что контрольная сумма MD5 осталась неизменной:
- Example
# md5sum /bin/ls
43d1b8a7ccda411118e2caba685f4329 /bin/ls
# encoded_data=`base64 -i /bin/ls `
# mysql -P9306 -h0 -e "drop table if exists test; create table test(data text stored); insert into test(data) values('$encoded_data')"
# mysql -P9306 -h0 -NB -e "select data from test" | base64 -d > /tmp/ls | md5sum
43d1b8a7ccda411118e2caba685f4329 -Тип integer позволяет хранить 32-битные беззнаковые целочисленные значения.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
CREATE TABLE products(title text, price int);POST /cli -d "CREATE TABLE products(title text, price int)"$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'int']
]);utilsApi.sql('CREATE TABLE products(title text, price int)')await utilsApi.sql('CREATE TABLE products(title text, price int)')res = await utilsApi.sql('CREATE TABLE products(title text, price int)');utilsApi.sql("CREATE TABLE products(title text, price int)");utilsApi.Sql("CREATE TABLE products(title text, price int)");utils_api.sql("CREATE TABLE products(title text, price int)", Some(true)).await;table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_uint = type
}Целые числа могут храниться в меньших размерах, чем 32-битные, путем указания количества бит. Например, если мы хотим сохранить числовое значение, которое, как мы знаем, не будет больше 8, тип можно определить как bit(3). Целые числа с битовой длиной работают медленнее, чем полноразмерные, но требуют меньше оперативной памяти. Они сохраняются в 32-битных блоках, поэтому для экономии места их следует группировать в конце определения атрибутов (иначе целое число с битовой длиной между двумя полноразмерными целыми также займет 32 бита).
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
CREATE TABLE products(title text, flags bit(3), tags bit(2) );POST /cli -d "CREATE TABLE products(title text, flags bit(3), tags bit(2))"$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'flags'=>['type'=>'bit(3)'],
'tags'=>['type'=>'bit(2)']
]);utilsApi.sql('CREATE TABLE products(title text, flags bit(3), tags bit(2) ')await utilsApi.sql('CREATE TABLE products(title text, flags bit(3), tags bit(2) ')res = await utilsApi.sql('CREATE TABLE products(title text, flags bit(3), tags bit(2) ');utilsApi.sql("CREATE TABLE products(title text, flags bit(3), tags bit(2)");utilsApi.Sql("CREATE TABLE products(title text, flags bit(3), tags bit(2)");utils_api.sql("CREATE TABLE products(title text, flags bit(3), tags bit(2)", Some(true)).await;table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_uint = flags:3
rt_attr_uint = tags:2
}Большие целые числа (bigint) — это 64-битные знаковые целые числа.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
CREATE TABLE products(title text, price bigint );POST /cli -d "CREATE TABLE products(title text, price bigint)"$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'bigint']
]);utilsApi.sql('CREATE TABLE products(title text, price bigint )')await utilsApi.sql('CREATE TABLE products(title text, price bigint )')res = await utilsApi.sql('CREATE TABLE products(title text, price bigint )');utilsApi.sql("CREATE TABLE products(title text, price bigint )");utilsApi.Sql("CREATE TABLE products(title text, price bigint )");utils_api.sql("CREATE TABLE products(title text, price bigint )", Some(true)).await;table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_bigint = type
}Объявляет булевый атрибут. Это эквивалентно целочисленному атрибуту с битовой длиной 1.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
CREATE TABLE products(title text, sold bool );POST /cli -d "CREATE TABLE products(title text, sold bool)"$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'sold'=>['type'=>'bool']
]);utilsApi.sql('CREATE TABLE products(title text, sold bool )')await utilsApi.sql('CREATE TABLE products(title text, sold bool )')res = await utilsApi.sql('CREATE TABLE products(title text, sold bool )');utilsApi.sql("CREATE TABLE products(title text, sold bool )");utilsApi.Sql("CREATE TABLE products(title text, sold bool )");utils_api.sql("CREATE TABLE products(title text, sold bool )", Some(true)).await;table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_bool = sold
}Тип timestamp представляет собой Unix-метки времени, которые хранятся как 32-битные целые числа. В отличие от базовых целых чисел, тип timestamp позволяет использовать функции времени и даты. Преобразование из строковых значений происходит по следующим правилам:
- Числа без разделителей, длиной не менее 10 символов, преобразуются в метки времени как есть.
%Y-%m-%dT%H:%M:%E*S%Z%Y-%m-%d'T'%H:%M:%S%Z%Y-%m-%dT%H:%M:%E*S%Y-%m-%dT%H:%M:%s%Y-%m-%dT%H:%M%Y-%m-%dT%H%Y-%m-%d%Y-%m%Y
Значения этих спецификаторов преобразования подробно описаны в руководстве strptime, за исключением %E*S, который обозначает миллисекунды.
Обратите внимание, что автоматическое преобразование меток времени не поддерживается в простых таблицах.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
CREATE TABLE products(title text, date timestamp);POST /cli -d "CREATE TABLE products(title text, date timestamp)"$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'date'=>['type'=>'timestamp']
]);utilsApi.sql('CREATE TABLE products(title text, date timestamp)')await utilsApi.sql('CREATE TABLE products(title text, date timestamp)')res = await utilsApi.sql('CREATE TABLE products(title text, date timestamp)');utilsApi.sql("CREATE TABLE products(title text, date timestamp)");utilsApi.Sql("CREATE TABLE products(title text, date timestamp)");utils_api.sql("CREATE TABLE products(title text, date timestamp)", Some(true)).await;table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_timestamp = date
}Вещественные числа хранятся как 32-битные числа с плавающей точкой одинарной точности по стандарту IEEE 754.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
CREATE TABLE products(title text, coeff float);POST /cli -d "CREATE TABLE products(title text, coeff float)"$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'coeff'=>['type'=>'float']
]);utilsApi.sql('CREATE TABLE products(title text, coeff float)')await utilsApi.sql('CREATE TABLE products(title text, coeff float)')res = await utilsApi.sql('CREATE TABLE products(title text, coeff float)');utilsApi.sql("CREATE TABLE products(title text, coeff float)");utilsApi.Sql("CREATE TABLE products(title text, coeff float)");utils_api.sql("CREATE TABLE products(title text, coeff float)", Some(true)).await;table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_float = coeff
}В отличие от целочисленных типов, сравнивать два числа с плавающей точкой на равенство не рекомендуется из-за возможных ошибок округления. Более надежный подход — использовать сравнение с допуском, проверяя абсолютную погрешность.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select abs(a-b)<=0.00001 from productsPOST /search
{
"table": "products",
"query": { "match_all": {} } },
"expressions": { "eps": "abs(a-b)" }
}$index->setName('products')->search('')->expression('eps','abs(a-b)')->get();searchApi.search({"table":"products","query":{"match_all":{}},"expressions":{"eps":"abs(a-b)"}})await searchApi.search({"table":"products","query":{"match_all":{}},"expressions":{"eps":"abs(a-b)"}})res = await searchApi.search({"table":"products","query":{"match_all":{}},"expressions":{"eps":"abs(a-b)"}});searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
query = new HashMap<String,Object>();
query.put("match_all",null);
searchRequest.setQuery(query);
Object expressions = new HashMap<String,Object>(){{
put("ebs","abs(a-b)");
}};
searchRequest.setExpressions(expressions);
searchResponse = searchApi.search(searchRequest);object query = new { match_all=null };
var searchRequest = new SearchRequest("forum", query);
searchRequest.Expressions = new List<Object>{
new Dictionary<string, string> { {"ebs", "abs(a-b)"} }
};
var searchResponse = searchApi.Search(searchRequest);let query = SearchQuery::new();
let mut expr = HashMap::new();
expr.insert("ebs".to_string(), serde_json::json!("abs(a-b)"));
let expressions: [HashMap; 1] = [expr];
let search_request = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
expressions: serde_json::json!(expressions),
..Default::default(),
};
let search_res = search_api.search(search_req).await;Еще одна альтернатива, которая также может использоваться для выполнения IN(attr,val1,val2,val3) — сравнивать числа с плавающей точкой как целые, выбирая множитель и преобразуя числа с плавающей точкой в целые в операциях. Следующий пример иллюстрирует изменение IN(attr,2.0,2.5,3.5) для работы с целочисленными значениями.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select in(ceil(attr*100),200,250,350) from productsPOST /search
{
"table": "products",
"query": { "match_all": {} } },
"expressions": { "inc": "in(ceil(attr*100),200,250,350)" }
}$index->setName('products')->search('')->expression('inc','in(ceil(attr*100),200,250,350)')->get();searchApi.search({"table":"products","query":{"match_all":{}}},"expressions":{"inc":"in(ceil(attr*100),200,250,350)"}})await searchApi.search({"table":"products","query":{"match_all":{}}},"expressions":{"inc":"in(ceil(attr*100),200,250,350)"}})res = await searchApi.search({"table":"products","query":{"match_all":{}}},"expressions":{"inc":"in(ceil(attr*100),200,250,350)"}});searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
query = new HashMap<String,Object>();
query.put("match_all",null);
searchRequest.setQuery(query);
Object expressions = new HashMap<String,Object>(){{
put("inc","in(ceil(attr*100),200,250,350)");
}};
searchRequest.setExpressions(expressions);
searchResponse = searchApi.search(searchRequest);object query = new { match_all=null };
var searchRequest = new SearchRequest("forum", query);
searchRequest.Expressions = new List<Object> {
new Dictionary<string, string> { {"ebs", "in(ceil(attr*100),200,250,350)"} }
};
var searchResponse = searchApi.Search(searchRequest);let query = SearchQuery::new();
let mut expr = HashMap::new();
expr.insert("ebs".to_string(), serde_json::json!("in(ceil(attr*100),200,250,350)"));
let expressions: [HashMap; 1] = [expr];
let search_request = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
expressions: serde_json::json!(expressions),
..Default::default(),
};
let search_res = search_api.search(search_req).await;Числа с плавающей точкой в Manticore отображаются с точностью, чтобы отражать точное сохраненное значение. Этот подход был введен для предотвращения потери точности, особенно в случаях, таких как географические координаты, где округление до 6 знаков после запятой приводило к неточностям.
Теперь Manticore сначала выводит число с 6 знаками, затем парсит и сравнивает его с исходным значением. Если они не совпадают, добавляются дополнительные знаки, пока не совпадут.
Например, если значение с плавающей точкой было вставлено как 19.45, Manticore отобразит его как 19.450001, чтобы точно представить сохраненное значение.
- Example
insert into t(id, f) values(1, 19.45)
--------------
Query OK, 1 row affected (0.02 sec)
--------------
select * from t
--------------
+------+-----------+
| id | f |
+------+-----------+
| 1 | 19.450001 |
+------+-----------+
1 row in set (0.00 sec)
--- 1 out of 1 results in 0ms ---Этот тип данных позволяет хранить JSON-объекты, что особенно полезно для работы с данными без схемы. При определении JSON-значений убедитесь, что включены открывающие и закрывающие фигурные скобки { и } для объектов или квадратные скобки [ и ] для массивов. Хотя JSON не поддерживается колонковым хранением, он может храниться в традиционном построчном хранении. Стоит отметить, что оба типа хранения могут комбинироваться в одной таблице.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
CREATE TABLE products(title text, data json);POST /cli -d "CREATE TABLE products(title text, data json)"$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'data'=>['type'=>'json']
]);utilsApi.sql('CREATE TABLE products(title text, data json)')await utilsApi.sql('CREATE TABLE products(title text, data json)')res = await utilsApi.sql('CREATE TABLE products(title text, data json)');utilsApi.sql("CREATE TABLE products(title text, data json)");utilsApi.Sql("CREATE TABLE products(title text, data json)");utils_api.sql("CREATE TABLE products(title text, data json)", Some(true)).await;table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_json = data
}Свойства JSON могут использоваться в большинстве операций. Также существуют специальные функции, такие как ALL(), ANY(), GREATEST(), LEAST() и INDEXOF(), которые позволяют обходить массивы свойств.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select indexof(x>2 for x in data.intarray) from productsPOST /search
{
"table": "products",
"query": { "match_all": {} } },
"expressions": { "idx": "indexof(x>2 for x in data.intarray)" }
}$index->setName('products')->search('')->expression('idx','indexof(x>2 for x in data.intarray)')->get();searchApi.search({"table":"products","query":{"match_all":{}},"expressions":{"idx":"indexof(x>2 for x in data.intarray)"}})await searchApi.search({"table":"products","query":{"match_all":{}},"expressions":{"idx":"indexof(x>2 for x in data.intarray)"}})res = await searchApi.search({"table":"products","query":{"match_all":{}},"expressions":{"idx":"indexof(x>2 for x in data.intarray)"}});searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
query = new HashMap<String,Object>();
query.put("match_all",null);
searchRequest.setQuery(query);
Object expressions = new HashMap<String,Object>(){{
put("idx","indexof(x>2 for x in data.intarray)");
}};
searchRequest.setExpressions(expressions);
searchResponse = searchApi.search(searchRequest);object query = new { match_all=null };
var searchRequest = new SearchRequest("forum", query);
searchRequest.Expressions = new List<Object> {
new Dictionary<string, string> { {"idx", "indexof(x>2 for x in data.intarray)"} }
};
var searchResponse = searchApi.Search(searchRequest);let query = SearchQuery::new();
let mut expr = HashMap::new();
expr.insert("idx".to_string(), serde_json::json!("indexof(x>2 for x in data.intarray)"));
let expressions: [HashMap; 1] = [expr];
let search_request = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
expressions: serde_json::json!(expressions),
..Default::default(),
};
let search_res = search_api.search(search_req).await;Текстовые свойства обрабатываются так же, как строки, поэтому их нельзя использовать в выражениях полнотекстового поиска. Однако можно использовать строковые функции, такие как REGEX().
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select regex(data.name, 'est') as c from products where c>0POST /search
{
"table": "products",
"query":
{
"match_all": {},
"range": { "c": { "gt": 0 } } }
},
"expressions": { "c": "regex(data.name, 'est')" }
}$index->setName('products')->search('')->expression('idx',"regex(data.name, 'est')")->filter('c','gt',0)->get();searchApi.search({"table":"products","query":{"match_all":{},"range":{"c":{"gt":0}}}},"expressions":{"c":"regex(data.name, 'est')"}})await searchApi.search({"table":"products","query":{"match_all":{},"range":{"c":{"gt":0}}}},"expressions":{"c":"regex(data.name, 'est')"}})res = await searchApi.search({"table":"products","query":{"match_all":{},"range":{"c":{"gt":0}}}},"expressions":{"c":"regex(data.name, 'est')"}}});searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
query = new HashMap<String,Object>();
query.put("match_all",null);
query.put("range", new HashMap<String,Object>(){{
put("c", new HashMap<String,Object>(){{
put("gt",0);
}});
}});
searchRequest.setQuery(query);
Object expressions = new HashMap<String,Object>(){{
put("idx","indexof(x>2 for x in data.intarray)");
}};
searchRequest.setExpressions(expressions);
searchResponse = searchApi.search(searchRequest);object query = new { match_all=null };
var searchRequest = new SearchRequest("forum", query);
var rangeFilter = new RangeFilter("c");
rangeFilter.Gt = 0;
searchRequest.AttrFilter = rangeFilter;
searchRequest.Expressions = new List<Object> {
new Dictionary<string, string> { {"idx", "indexof(x>2 for x in data.intarray)"} }
};
var searchResponse = searchApi.Search(searchRequest);let query = SearchQuery::new();
let mut expr = HashMap::new();
expr.insert("idx".to_string(), serde_json::json!("indexof(x>2 for x in data.intarray)"));
let expressions: [HashMap; 1] = [expr];
let search_request = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
expressions: serde_json::json!(expressions),
..Default::default(),
};
let search_res = search_api.search(search_req).await;В случае свойств JSON может потребоваться принудительное указание типа данных для правильной работы в некоторых ситуациях. Например, при работе с числами с плавающей точкой необходимо использовать DOUBLE() для правильной сортировки.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select * from products order by double(data.myfloat) descPOST /search
{
"table": "products",
"query": { "match_all": {} } },
"sort": [ { "double(data.myfloat)": { "order": "desc"} } ]
}$index->setName('products')->search('')->sort('double(data.myfloat)','desc')->get();searchApi.search({"table":"products","query":{"match_all":{}}},"sort":[{"double(data.myfloat)":{"order":"desc"}}]})await searchApi.search({"table":"products","query":{"match_all":{}}},"sort":[{"double(data.myfloat)":{"order":"desc"}}]})res = await searchApi.search({"table":"products","query":{"match_all":{}}},"sort":[{"double(data.myfloat)":{"order":"desc"}}]});searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
query = new HashMap<String,Object>();
query.put("match_all",null);
searchRequest.setQuery(query);
searchRequest.setSort(new ArrayList<Object>(){{
add(new HashMap<String,String>(){{ put("double(data.myfloat)",new HashMap<String,String>(){{ put("order","desc");}});}});
}});
searchResponse = searchApi.search(searchRequest);object query = new { match_all=null };
var searchRequest = new SearchRequest("forum", query);
searchRequest.Sort = new List<Object> {
new SortOrder("double(data.myfloat)", SortOrder.OrderEnum.Desc)
};
var searchResponse = searchApi.Search(searchRequest);let query = SearchQuery::new();
let mut sort = HashMap::new();
sort.insert("double(data.myfloat)".to_string(), serde_json::json!("desc"));
let search_request = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
sort: serde_json::json!(sort)
..Default::default()
};
let search_res = search_api.search(search_req).await;Атрибуты векторных чисел с плавающей запятой позволяют хранить списки переменной длины из чисел с плавающей запятой, преимущественно используемые для приложений машинного обучения и поиска по сходству. Этот тип отличается от мультизначных атрибутов (MVA) по нескольким важным аспектам:
- Сохраняет точный порядок значений (в отличие от MVA, которые могут менять порядок)
- Сохраняет дублирующиеся значения (в отличие от MVA, которые удаляют дубликаты)
- Нет дополнительной обработки при вставке (в отличие от MVA, которые сортируют и удаляют дубликаты)
Атрибуты векторных чисел с плавающей запятой позволяют хранить списки переменной длины из чисел с плавающей запятой, преимущественно используемые для приложений машинного обучения и поиска по сходству.
- В настоящее время поддерживаются только в таблицах реального времени
- Могут использоваться только в поисках KNN (k-ближайших соседей)
- Не поддерживаются в обычных таблицах или других функциях/выражениях
- При использовании с настройками KNN нельзя
UPDATEзначенияfloat_vector. ИспользуйтеREPLACE - При использовании без настроек KNN можно
UPDATEзначенияfloat_vector - Векторы с плавающей запятой нельзя использовать в обычных фильтрах или сортировке
- Единственный способ фильтровать по значениям
float_vector— через операции векторного поиска (KNN)
- Текстовые эмбеддинги для семантического поиска
- Векторы рекомендательных систем
- Эмбеддинги изображений для поиска по сходству
- Векторы признаков для машинного обучения
Имейте в виду, что тип данных float_vector несовместим с механизмом Auto schema.
Для получения подробной информации о настройке векторов с плавающей запятой и их использовании в поисках смотрите KNN search.
Самый удобный способ работы с векторами с плавающей запятой — использование автоматических эмбеддингов. Эта функция автоматически генерирует эмбеддинги из ваших текстовых данных с помощью моделей машинного обучения, устраняя необходимость вручную вычислять и вставлять векторы.
- Упрощённый рабочий процесс: Просто вставляйте текст, эмбеддинги генерируются автоматически
- Нет необходимости вручную вычислять векторы: Не нужно запускать отдельные модели эмбеддингов
- Последовательные эмбеддинги: Одна и та же модель обеспечивает согласованное представление векторов
- Поддержка нескольких моделей: Выбор из моделей sentence-transformers, OpenAI, Voyage и Jina
- Гибкий выбор полей: Контроль над тем, какие поля используются для генерации эмбеддингов
При создании таблицы с автоматическими эмбеддингами укажите следующие дополнительные параметры:
MODEL_NAME: модель эмбеддингов для автоматической генерации векторовFROM: какие поля использовать для генерации эмбеддингов (пустая строка означает все текстовые/строковые поля)
Поддерживаемые модели эмбеддингов:
- Sentence Transformers: Любая подходящая модель BERT на Hugging Face (например,
sentence-transformers/all-MiniLM-L6-v2) — API ключ не требуется. Manticore загружает модель при создании таблицы. - OpenAI: Модели эмбеддингов OpenAI, например
openai/text-embedding-ada-002— требует параметрAPI_KEY='<OPENAI_API_KEY>' - Voyage: Модели эмбеддингов Voyage AI — требует параметр
API_KEY='<VOYAGE_API_KEY>' - Jina: Модели эмбеддингов Jina AI — требует параметр
API_KEY='<JINA_API_KEY>'
- SQL
Использование модели sentence-transformers (API ключ не требуется)
CREATE TABLE products (
title TEXT,
description TEXT,
embedding_vector FLOAT_VECTOR KNN_TYPE='hnsw' HNSW_SIMILARITY='l2'
MODEL_NAME='sentence-transformers/all-MiniLM-L6-v2' FROM='title'
);Использование модели OpenAI (требуется параметр API_KEY)
CREATE TABLE products_openai (
title TEXT,
content TEXT,
embedding_vector FLOAT_VECTOR KNN_TYPE='hnsw' HNSW_SIMILARITY='cosine'
MODEL_NAME='openai/text-embedding-ada-002' FROM='title,content' API_KEY='<OPENAI_API_KEY>'
);Использование всех текстовых полей для эмбеддингов (FROM пустой)
CREATE TABLE products_all_fields (
title TEXT,
description TEXT,
tags TEXT,
embedding_vector FLOAT_VECTOR KNN_TYPE='hnsw' HNSW_SIMILARITY='l2'
MODEL_NAME='sentence-transformers/all-MiniLM-L6-v2' FROM=''
);Параметр FROM контролирует, какие поля используются для генерации эмбеддингов:
- Конкретные поля:
FROM='title'— используется только поле title - Несколько полей:
FROM='title,description'— объединяются и используются поля title и description - Все текстовые поля:
FROM=''(пусто) — используются все поля типаtext(полнотекстовые поля) иstring(строковые атрибуты) в таблице - Пустые векторы: Можно вставлять пустые векторы с помощью
(), чтобы исключить документы из векторного поиска
При использовании автоматических эмбеддингов не указывайте поле вектора в ваших операторах INSERT. Эмбеддинги автоматически генерируются из указанных текстовых полей:
-- Insert text data - embeddings generated automatically
INSERT INTO products (title, description) VALUES
('smartphone', 'latest mobile device with camera'),
('laptop computer', 'portable workstation for developers');
-- Insert with empty vector (excluded from vector search)
INSERT INTO products (title, description, embedding_vector) VALUES
('no-vector item', 'this item has no embedding', ());
В качестве альтернативы можно работать с вручную вычисленными векторами с плавающей запятой.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
CREATE TABLE products(title text, image_vector float_vector);POST /cli -d "CREATE TABLE products(title text, image_vector float_vector)"$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'image_vector'=>['type'=>'float_vector']
]);utilsApi.sql('CREATE TABLE products(title text, image_vector float_vector)')await utilsApi.sql('CREATE TABLE products(title text, image_vector float_vector)')res = await utilsApi.sql('CREATE TABLE products(title text, image_vector float_vector)');utilsApi.sql("CREATE TABLE products(title text, image_vector float_vector)");utilsApi.Sql("CREATE TABLE products(title text, image_vector float_vector)");utils_api.sql("CREATE TABLE products(title text, image_vector float_vector)", Some(true)).await;table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_float_vector = image_vector
}Мультизначные атрибуты позволяют хранить списки переменной длины из 32-битных беззнаковых целых чисел. Это может быть полезно для хранения числовых значений один-ко-многим, таких как теги, категории продуктов и свойства.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
CREATE TABLE products(title text, product_codes multi);POST /cli -d "CREATE TABLE products(title text, product_codes multi)"$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'product_codes'=>['type'=>'multi']
]);utilsApi.sql('CREATE TABLE products(title text, product_codes multi)')await utilsApi.sql('CREATE TABLE products(title text, product_codes multi)')res = await utilsApi.sql('CREATE TABLE products(title text, product_codes multi)');utilsApi.sql("CREATE TABLE products(title text, product_codes multi)");utilsApi.Sql("CREATE TABLE products(title text, product_codes multi)");utils_api.sql("CREATE TABLE products(title text, product_codes multi)", Some(true)).await;table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_multi = product_codes
}It supports filtering and aggregation, but not sorting. Filtering can be done using a condition that requires at least one element to pass (using ANY()) or all elements (ALL()) to pass.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select * from products where any(product_codes)=3POST /search
{
"table": "products",
"query":
{
"match_all": {},
"equals" : { "any(product_codes)": 3 }
}
}$index->setName('products')->search('')->filter('any(product_codes)','equals',3)->get();searchApi.search({"table":"products","query":{"match_all":{},"equals":{"any(product_codes)":3}}}})await searchApi.search({"table":"products","query":{"match_all":{},"equals":{"any(product_codes)":3}}}})res = await searchApi.search({"table":"products","query":{"match_all":{},"equals":{"any(product_codes)":3}}}})'searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
query = new HashMap<String,Object>();
query.put("match_all",null);
query.put("equals",new HashMap<String,Integer>(){{
put("any(product_codes)",3);
}});
searchRequest.setQuery(query);
searchResponse = searchApi.search(searchRequest);object query = new { match_all=null };
var searchRequest = new SearchRequest("forum", query);
searchRequest.AttrFilter = new EqualsFilter("any(product_codes)", 3);
var searchResponse = searchApi.Search(searchRequest);let query = SearchQuery::new();
let search_request = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
..Default::default(),
};
let search_res = search_api.search(search_req).await;Information like least or greatest element and length of the list can be extracted. An example shows ordering by the least element of a multi-value attribute.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
select least(product_codes) l from products order by l ascPOST /search
{
"table": "products",
"query":
{
"match_all": {},
"sort": [ { "product_codes":{ "order":"asc", "mode":"min" } } ]
}
}$index->setName('products')->search('')->sort('product_codes','asc','min')->get();searchApi.search({"table":"products","query":{"match_all":{},"sort":[{"product_codes":{"order":"asc","mode":"min"}}]}})await searchApi.search({"table":"products","query":{"match_all":{},"sort":[{"product_codes":{"order":"asc","mode":"min"}}]}})res = await searchApi.search({"table":"products","query":{"match_all":{},"sort":[{"product_codes":{"order":"asc","mode":"min"}}]}});searchRequest = new SearchRequest();
searchRequest.setIndex("forum");
query = new HashMap<String,Object>();
query.put("match_all",null);
searchRequest.setQuery(query);
searchRequest.setSort(new ArrayList<Object>(){{
add(new HashMap<String,String>(){{ put("product_codes",new HashMap<String,String>(){{ put("order","asc");put("mode","min");}});}});
}});
searchResponse = searchApi.search(searchRequest);object query = new { match_all=null };
var searchRequest = new SearchRequest("forum", query);
searchRequest.Sort = new List<Object> {
new SortMVA("product_codes", SortOrder.OrderEnum.Asc, SortMVA.ModeEnum.Min)
};
searchResponse = searchApi.Search(searchRequest);let query = SearchQuery::new();
let mut sort_opts = HashMap::new();
sort_opts.insert("order".to_string(), serde_json::json!("asc"));
sort_opts.insert("mode".to_string(), serde_json::json!("min"));
sort_expr.insert("product_codes".to_string(), serde_json::json!(sort_opts));
let sort: [HashMap; 1] = [sort_expr];
let search_req = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
sort: serde_json::json!(sort),
..Default::default(),
};
let search_res = search_api.search(search_req).await;When grouping by a multi-value attribute, a document will contribute to as many groups as there are different values associated with that document. For instance, if a collection contains exactly one document having a 'product_codes' multi-value attribute with values 5, 7, and 11, grouping on 'product_codes' will produce 3 groups with COUNT(*)equal to 1 and GROUPBY() key values of 5, 7, and 11, respectively. Also, note that grouping by multi-value attributes may lead to duplicate documents in the result set because each document can participate in many groups.
- SQL
insert into products values ( 1, 'doc one', (5,7,11) );
select id, count(*), groupby() from products group by product_codes;Query OK, 1 row affected (0.00 sec)
+------+----------+-----------+
| id | count(*) | groupby() |
+------+----------+-----------+
| 1 | 1 | 11 |
| 1 | 1 | 7 |
| 1 | 1 | 5 |
+------+----------+-----------+
3 rows in set (0.00 sec)The order of the numbers inserted as values of multivalued attributes is not preserved. Values are stored internally as a sorted set.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
insert into product values (1,'first',(4,2,1,3));
select * from products;POST /insert
{
"table":"products",
"id":1,
"doc":
{
"title":"first",
"product_codes":[4,2,1,3]
}
}
POST /search
{
"table": "products",
"query": { "match_all": {} }
}$index->addDocument([
"title"=>"first",
"product_codes"=>[4,2,1,3]
]);
$index->search('')-get();indexApi.insert({"table":"products","id":1,"doc":{"title":"first","product_codes":[4,2,1,3]}})
searchApi.search({"table":"products","query":{"match_all":{}}})await indexApi.insert({"table":"products","id":1,"doc":{"title":"first","product_codes":[4,2,1,3]}})
await searchApi.search({"table":"products","query":{"match_all":{}}})await indexApi.insert({"table":"products","id":1,"doc":{"title":"first","product_codes":[4,2,1,3]}});
res = await searchApi.search({"table":"products","query":{"match_all":{}}});InsertDocumentRequest newdoc = new InsertDocumentRequest();
HashMap<String,Object> doc = new HashMap<String,Object>(){{
put("title","first");
put("product_codes",new int[] {4,2,1,3});
}};
newdoc.index("products").id(1L).setDoc(doc);
sqlresult = indexApi.insert(newdoc);
Map<String,Object> query = new HashMap<String,Object>();
query.put("match_all",null);
SearchRequest searchRequest = new SearchRequest();
searchRequest.setIndex("products");
searchRequest.setQuery(query);
SearchResponse searchResponse = searchApi.search(searchRequest);
System.out.println(searchResponse.toString() );Dictionary<string, Object> doc = new Dictionary<string, Object>();
doc.Add("title", "first");
doc.Add("product_codes", new List<Object> {4,2,1,3});
InsertDocumentRequest newdoc = new InsertDocumentRequest(index: "products", id: 1, doc: doc);
var sqlresult = indexApi.Insert(newdoc);
object query = new { match_all=null };
var searchRequest = new SearchRequest("products", query);
var searchResponse = searchApi.Search(searchRequest);
Console.WriteLine(searchResponse.ToString())let mut doc = HashMap::new();
doc.insert("title".to_string(), serde_json::json!("first"));
doc.insert("product_codes".to_string(), serde_json::json!([4,2,1,3]));
let insert_req = InsertDocumentRequest::new("products".to_string(), serde_json::json!(doc));
let insert_res = index_api.insert(insert_req).await;
let query = SearchQuery::new();
let search_req = SearchRequest {
table: "forum".to_string(),
query: Some(Box::new(query)),
..Default::default(),
};
let search_res = search_api.search(search_req).await;
println!("{:?}", search_res);Query OK, 1 row affected (0.00 sec)
+------+---------------+-------+
| id | product_codes | title |
+------+---------------+-------+
| 1 | 1,2,3,4 | first |
+------+---------------+-------+
1 row in set (0.01 sec){
"table":"products",
"_id":1,
"created":true,
"result":"created",
"status":201
}
{
"took":0,
"timed_out":false,
"hits":{
"total":1,
"hits":[
{
"_id": 1,
"_score":1,
"_source":{
"product_codes":[
1,
2,
3,
4
],
"title":"first"
}
}
]
}
}Array
(
[_index] => products
[_id] => 1
[created] => 1
[result] => created
[status] => 201
)
Array
(
[took] => 0
[timed_out] =>
[hits] => Array
(
[total] => 1
[hits] => Array
(
[0] => Array
(
[_id] => 1
[_score] => 1
[_source] => Array
(
[product_codes] => Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
)
[title] => first
)
)
)
)
){'created': True,
'found': None,
'id': 1,
'table': 'products',
'result': 'created'}
{'hits': {'hits': [{u'_id': u'1',
u'_score': 1,
u'_source': {u'product_codes': [1, 2, 3, 4],
u'title': u'first'}}],
'total': 1},
'profile': None,
'timed_out': False,
'took': 29}{'created': True,
'found': None,
'id': 1,
'table': 'products',
'result': 'created'}
{'hits': {'hits': [{u'_id': u'1',
u'_score': 1,
u'_source': {u'product_codes': [1, 2, 3, 4],
u'title': u'first'}}],
'total': 1},
'profile': None,
'timed_out': False,
'took': 29}{"took":0,"timed_out":false,"hits":{"total":1,"hits":[{"_id": 1,"_score":1,"_source":{"product_codes":[1,2,3,4],"title":"first"}}]}}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
hits: [{_id=1, _score=1, _source={product_codes=[1, 2, 3, 4], title=first}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
hits: [{_id=1, _score=1, _source={product_codes=[1, 2, 3, 4], title=first}}]
aggregations: null
}
profile: null
}class SearchResponse {
took: 0
timedOut: false
hits: class SearchResponseHits {
total: 1
hits: [{_id=1, _score=1, _source={product_codes=[1, 2, 3, 4], title=first}}]
aggregations: null
}
profile: null
}A data type that allows storing variable-length lists of 64-bit signed integers. It has the same functionality as multi-value integer.
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- javascript
- java
- C#
- Rust
- config
CREATE TABLE products(title text, values multi64);POST /cli -d "CREATE TABLE products(title text, values multi64)"$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'values'=>['type'=>'multi64']
]);utilsApi.sql('CREATE TABLE products(title text, values multi64))')await utilsApi.sql('CREATE TABLE products(title text, values multi64))')res = await utilsApi.sql('CREATE TABLE products(title text, values multi64))');utilsApi.sql("CREATE TABLE products(title text, values multi64))");utilsApi.Sql("CREATE TABLE products(title text, values multi64))");utils_api.sql("CREATE TABLE products(title text, values multi64))", Some(true)).await;table products
{
type = rt
path = products
rt_field = title
stored_fields = title
rt_attr_multi_64 = values
}When you use the columnar storage you can specify the following properties for the attributes.
By default, Manticore Columnar storage stores all attributes in a columnar fashion, as well as in a special docstore row by row. This enables fast execution of queries like SELECT * FROM ..., especially when fetching a large number of records at once. However, if you are sure that you do not need it or wish to save disk space, you can disable it by specifying fast_fetch='0' when creating a table or (if you are defining a table in a config) by using columnar_no_fast_fetch as shown in the following example.
- RT mode
- Plain mode
create table t(a int, b int fast_fetch='0') engine='columnar'; desc t;source min {
type = mysql
sql_host = localhost
sql_user = test
sql_pass =
sql_db = test
sql_query = select 1, 1 a, 1 b
sql_attr_uint = a
sql_attr_uint = b
}
table tbl {
path = tbl/col
source = min
columnar_attrs = *
columnar_no_fast_fetch = b
}+-------+--------+---------------------+
| Field | Type | Properties |
+-------+--------+---------------------+
| id | bigint | columnar fast_fetch |
| a | uint | columnar fast_fetch |
| b | uint | columnar |
+-------+--------+---------------------+
3 rows in set (0.00 sec)+-------+--------+---------------------+
| Field | Type | Properties |
+-------+--------+---------------------+
| id | bigint | columnar fast_fetch |
| a | uint | columnar fast_fetch |
| b | uint | columnar |
+-------+--------+---------------------+В Manticore Search существует два способа управления таблицами:
Режим реального времени не требует определения таблицы в конфигурационном файле. Однако директива data_dir в секции searchd обязательна. Файлы таблиц хранятся внутри data_dir.
Репликация доступна только в этом режиме.
Вы можете использовать SQL-команды, такие как CREATE TABLE, ALTER TABLE и DROP TABLE для создания и изменения схемы таблицы, а также для её удаления. Этот режим особенно полезен для таблиц реального времени и перколяторов.
Имена таблиц при создании преобразуются в нижний регистр.
В этом режиме вы можете указать схему таблицы в конфигурационном файле. Manticore читает эту схему при запуске и создаёт таблицу, если она ещё не существует. Этот режим особенно полезен для plain таблиц, которые используют данные из внешнего хранилища.
Чтобы удалить таблицу, удалите её из конфигурационного файла или удалите настройку пути и отправьте серверу сигнал HUP или перезапустите его.
Имена таблиц в этом режиме чувствительны к регистру.
В этом режиме поддерживаются все типы таблиц.
| Тип таблицы | RT режим | Plain режим |
|---|---|---|
| Real-time | поддерживается | поддерживается |
| Plain | не поддерживается | поддерживается |
| Percolate | поддерживается | поддерживается |
| Distributed | поддерживается | поддерживается |
| Template | не поддерживается | поддерживается |
Таблица в реальном времени — это основной тип таблицы в Manticore. Она позволяет добавлять, обновлять и удалять документы, и вы можете видеть эти изменения сразу же. Вы можете настроить таблицу в реальном времени в конфигурационном файле или использовать команды, такие как CREATE, UPDATE, DELETE или ALTER.
Внутри таблица в реальном времени состоит из одной или нескольких простых таблиц, называемых чанками. Существуют два типа чанков:
- несколько дисковых чанков — они сохраняются на диске и структурированы как простая таблица.
- один RAM-чанк — хранится в памяти и собирает все изменения.
Размер RAM-чанка контролируется настройкой rt_mem_limit. Как только этот лимит достигается, RAM-чанк переносится на диск как дисковый чанк. Если дисковых чанков становится слишком много, Manticore объединяет некоторые из них для улучшения производительности.
Вы можете создать новую таблицу в реальном времени двумя способами: с помощью команды CREATE TABLE или через _mapping endpoint HTTP JSON API.
Вы можете использовать эту команду как через SQL, так и через HTTP протоколы:
- SQL
- JSON
- PHP
- Python
- Python-asyncio
- Javascript
- Java
- C#
- Rust
- Typescript
- Go
- CONFIG
CREATE TABLE products(title text, price float) morphology='stem_en';POST /cli -d "CREATE TABLE products(title text, price float) morphology='stem_en'"$index = new \Manticoresearch\Index($client);
$index->setName('products');
$index->create([
'title'=>['type'=>'text'],
'price'=>['type'=>'float'],
]);utilsApi.sql('CREATE TABLE forum(title text, price float)')await utilsApi.sql('CREATE TABLE forum(title text, price float)')res = await utilsApi.sql('CREATE TABLE forum(title text, price float)');utilsApi.sql("CREATE TABLE forum(title text, price float)");utilsApi.Sql("CREATE TABLE forum(title text, price float)");utils_api.sql("CREATE TABLE forum(title text, price float)", Some(true)).await;res = await utilsApi.sql('CREATE TABLE forum(title text, price float)');utilsAPI.Sql(context.Background()).Body("CREATE TABLE forum(title text, price float)").Execute()table products {
type = rt
path = tbl
rt_field = title
rt_attr_uint = price
stored_fields = title
}Query OK, 0 rows affected (0.00 sec){
"total":0,
"error":"",
"warning":""
}ПРИМЕЧАНИЕ: API
_mappingтребует Manticore Buddy. Если он не работает, убедитесь, что Buddy установлен.
В качестве альтернативы вы можете создать новую таблицу через endpoint _mapping. Этот endpoint позволяет определить структуру таблицы, похожую на Elasticsearch, которая будет преобразована в таблицу Manticore.
Тело вашего запроса должно иметь следующую структуру:
"properties"
{
"FIELD_NAME_1":
{
"type": "FIELD_TYPE_1"
},
"FIELD_NAME_2":
{
"type": "FIELD_TYPE_2"
},
...
"FIELD_NAME_N":
{
"type": "FIELD_TYPE_M"
}
}
При создании таблицы типы данных Elasticsearch будут сопоставлены с типами Manticore согласно следующим правилам:
- aggregate_metric => json
- binary => string
- boolean => bool
- byte => int
- completion => string
- date => timestamp
- date_nanos => bigint
- date_range => json
- dense_vector => json
- flattened => json
- flat_object => json
- float => float
- float_range => json
- geo_point => json
- geo_shape => json
- half_float => float
- histogram => json
- integer => int
- integer_range => json
- ip => string
- ip_range => json
- keyword => string
- knn_vector => float_vector
- long => bigint
- long_range => json
- match_only_text => text
- object => json
- point => json
- scaled_float => float
- search_as_you_type => text
- shape => json
- short => int
- text => text
- unsigned_long => int
- version => string
- JSON
POST /your_table_name/_mapping -d '
{
"properties": {
"price": {
"type": "float"
},
"title": {
"type": "text"
}
}
}
'{
"total":0,
"error":"",
"warning":""
}Вы можете создать копию таблицы в реальном времени, с данными или без них. Обратите внимание, что если таблица большая, копирование с данными может занять некоторое время. Копирование работает в синхронном режиме, но если соединение прервется, оно продолжится в фоновом режиме.
CREATE TABLE [IF NOT EXISTS] table_name LIKE old_table_name [WITH DATA]
ПРИМЕЧАНИЕ: Копирование таблицы требует Manticore Buddy. Если оно не работает, убедитесь, что Buddy установлен.
- SQL
- Example (WITH DATA)
create table products LIKE old_products;create table products LIKE old_products WITH DATA;- Добавлять документы.
- Обновлять атрибуты и полнотекстовые поля с помощью процесса Update.
- Удалять документы.
- Очищать таблицу.
- Изменять схему онлайн с помощью команды
ALTER, как описано в разделе Изменение схемы онлайн. - Определять таблицу в конфигурационном файле, как подробно описано в разделе Определение таблицы.
- Использовать функцию UUID для автоматического присвоения ID.
- Загружать данные с помощью функции indexer.
- Подключать её к источникам для удобного индексирования из внешнего хранилища.
- Обновите killlist_target, так как он автоматически управляется таблицей реального времени.
В следующей таблице приведены различные расширения файлов и их описания в таблице реального времени:
| Extension | Description |
|---|---|
.lock |
Файл блокировки, который гарантирует, что только один процесс может получить доступ к таблице одновременно. |
.ram |
RAM-чанк таблицы, хранящийся в памяти и используемый как аккумулятор изменений. |
.meta |
Заголовки таблицы реального времени, определяющие её структуру и настройки. |
.*.sp* |
Дисковые чанки, которые хранятся на диске в том же формате, что и обычные таблицы. Они создаются, когда размер RAM-чанка превышает rt_mem_limit. |
Для получения дополнительной информации о структуре дисковых чанков обратитесь к структуре файлов обычной таблицы.
Plain table — это базовый элемент для поиска без перколяции. Он может быть определён только в конфигурационном файле с использованием Plain mode и не поддерживается в RT mode. Обычно используется вместе с source для обработки данных из внешнего хранилища и может быть позже присоединена к real-time table.
Чтобы создать plain table, необходимо определить её в конфигурационном файле. Команда CREATE TABLE её не поддерживает.
Ниже приведён пример конфигурации plain table и source для получения данных из базы данных MySQL:
- Plain table example
source source {
type = mysql
sql_host = localhost
sql_user = myuser
sql_pass = mypass
sql_db = mydb
sql_query = SELECT id, title, description, category_id from mytable
sql_attr_uint = category_id
sql_field_string = title
}
table tbl {
type = plain
source = source
path = /path/to/table
}- Создавать её из внешнего хранилища с помощью source и indexer
- Выполнять обновление на месте для целочисленных, числовых, строковых и MVA атрибутов
- Обновлять его
killlist_target
- Вставлять дополнительные данные в таблицу после её создания
- Удалять данные из таблицы
- Создавать, удалять или изменять схему таблицы онлайн
- Использовать UUID для автоматической генерации ID (данные из внешнего хранилища должны содержать уникальный идентификатор)
Числовые атрибуты, включая MVA, — единственные элементы, которые можно обновлять в plain table. Все остальные данные в таблице неизменяемы. Если требуются обновления или новые записи, таблицу необходимо перестраивать. Во время перестройки существующая таблица остаётся доступной для обслуживания запросов, и выполняется процесс, называемый ротацией, когда новая версия готова, она выводится в онлайн, а старая версия удаляется.
Скорость индексирования plain table зависит от нескольких факторов, включая:
- Скорость получения данных из источника
- Настройки токенизации
- Аппаратные характеристики (например, CPU, RAM и производительность диска)
Для небольших наборов данных самым простым вариантом является одна plain table, которая полностью перестраивается по мере необходимости. Такой подход приемлем, когда:
- Данные в таблице не такие свежие, как данные в источнике
- Время построения таблицы увеличивается с ростом объёма данных
Для больших наборов данных plain table может использоваться вместо Real-Time. Сценарий main+delta включает:
- Создание меньшей таблицы для инкрементального индексирования
- Объединение двух таблиц с помощью distributed table
Этот подход позволяет реже перестраивать большую таблицу и чаще обрабатывать обновления из источника. Меньшую таблицу можно перестраивать чаще (например, каждую минуту или даже каждые несколько секунд).
Однако со временем время индексирования меньшей таблицы станет слишком долгим, что потребует перестройки большой таблицы и очистки меньшей.
Схема main+delta подробно объясняется в этом интерактивном курсе.
Механизм kill list и директива killlist_target используются для обеспечения приоритета документов из текущей таблицы над документами из другой таблицы.
Более подробную информацию по этой теме смотрите здесь.
В следующей таблице приведены различные расширения файлов, используемых в plain table, и их описания:
| Extension | Description |
|---|---|
.spa |
хранит атрибуты документов в построчном режиме |
.spb |
хранит blob-атрибуты в построчном режиме: строки, MVA, json |
.spc |
хранит атрибуты документов в колоночном режиме |
.spd |
хранит списки ID документов, соответствующих каждому ID слова |
.sph |
хранит информацию заголовка таблицы |
.sphi |
хранит гистограммы значений атрибутов |
.spi |
хранит списки слов (ID слов и указатели на файл .spd) |
.spidx |
хранит данные вторичных индексов |
.spjidx |
хранит данные вторичных индексов, сгенерированных для JSON-атрибутов |
.spk |
хранит kill-листы |
.spl |
файл блокировки |
.spm |
хранит битовую карту удалённых документов |
.spp |
хранит списки попаданий (также известные как posting, или вхождения слова) для каждого ID слова |
.spt |
хранит дополнительные структуры данных для ускорения поиска по ID документов |
.spe |
хранит skip-листы для ускорения фильтрации списков документов |
.spds |
хранит тексты документов |
.tmp* |
временные файлы во время index_settings_and_status |
.new.sp* |
новая версия простой таблицы перед ротацией |
.old.sp* |
старая версия простой таблицы после ротации |