Базовые операции с базами данных и неймспейсами
Этот раздел описывает базовые операции с базами данных и неймспейсами при взаимодействии с Reindexer:
- через Go-коннектор,
- через Python-коннектор,
- через Java-коннектор,
- по HTTP.
Операции с базами данных
Создание базы данных. Подключение к БД
Примеры создания БД:
Фрагмент простой программы на Go, выполняющей подключение к серверу Reindexer и создание БД с именем testdb, будет выглядеть так (см. пояснения в комментариях в коде):
package main
// Импорт необходимых пакетов
import (
"fmt"
"math/rand"
"github.com/restream/reindexer"
// Выбор способа подключения к Reindexer, в зависимости от варианта использования СУБД:
// Выберите этот вариант, если Reindexer встраивается в приложение, которое он обслуживает, как статическая библиотека (`Built-in`, `Embedded`).
_ "github.com/restream/reindexer/bindings/builtin"
// Выберите этот вариант, если Reindexer встраивается в приложение как статическая библиотека с возможностью подключения других приложений-клиентов по сети (`Built-in server`):
// _ "github.com/restream/reindexer/bindings/builtinserver"
// "github.com/restream/reindexer/bindings/builtinserver/config"
)
func main() {
// Инициализация базы данных и подключение к ней (для варианта использования Reindexer `Builtin`)
db := reindexer.NewReindex("builtin:///tmp/reindex/testdb")
// Инициализация базы данных и подключение к ней (для варианта использования Reindexer `Standalone`)
// База данных должна быть создана явно через Reindexer_tool или через опцию WithCreateDBIfMissing:
// Если включен режим безопасности сервера (security, --security), имя пользователя и пароль являются обязательными
// db := reindexer.NewReindex("cproto://user:pass@127.0.0.1:6534/testdb", reindexer.WithCreateDBIfMissing())
// Инициализация базы данных и подключение к ней (для варианта использования Reindexer `Standalone`)
// с поддержкой TLS, используя cprotos-протокол и пакет tls из стандартной библиотеки Go.
// База данных должна быть создана явно через Reindexer_tool или через опцию WithCreateDBIfMissing,
// Предполагается, что по этому адресу запущен сервер RPCs(RPC с поддержкой TLS). По умолчанию для него используется порт 6535.
// tlsConfig := tls.Config{
// /*required options*/
// }
// db := reindexer.NewReindex("cprotos://user:pass@127.0.0.1:6535/testdb", reindexer.WithCreateDBIfMissing(), reindexer.WithTLSConfig(&tlsConfig))
// Инициализация базы данных и подключение к ней (для варианта использования Reindexer `Built-in server`)
// serverConfig := config.DefaultServerConfig ()
// Если включен режим безопасности сервера (security, --security), имя пользователя и пароль являются обязательными
// db := reindexer.NewReindex("builtinserver://user:pass@testdb",reindexer.WithServerConfig(100*time.Second, serverConfig))
// Ваш код для работы с базой данных
// ...
}
Пример программы для создания новой БД testdb и неймспейса items:
from pyreindexer.rx_connector import RxConnector
if __name__ == "__main__":
# Инициализация базы данных и подключение к ней (для варианта использования Reindexer `Builtin`)
# db = ConnectorApi(
# "builtin://tmp/testdb"
# )
# Инициализация базы данных и подключение к ней (для варианта использования Reindexer `Standalone`)
db = ConnectorApi(
"cproto://127.0.0.1:6534/testdb"
)
# Новая БД будет создана автоматически, если БД с таким именем не существовало
# Если включен режим безопасности сервера (security, --security), имя пользователя и пароль являются обязательными
# db = ConnectorApi(
# "cproto://user:pass@127.0.0.1:6534/testdb"
# )
# Python-коннектор не поддерживает вариант подключения builtinserver
# Ваш код для работы с базой данных
# ...
Примечание
Python-коннектор создает БД только после создания неймспейса в новой базе данных.
package com.mycompany.connector;
// Импорт необходимых пакетов
import java.time.Duration;
import ru.rt.restream.reindexer.Reindexer;
import ru.rt.restream.reindexer.ReindexerConfiguration;
public class App {
public static void main(String[] args) throws Exception {
// Инициализация базы данных и подключение к ней (для варианта использования Reindexer `Builtin`). Для запуска необходимо собрать отдельно builtin-adapter и поместить собранную динамическую библиотеку в корневую Java-проекта, указав путь к ней в файле конфигурации (pom.xml) в качестве источника:
// Reindexer db = ReindexerConfiguration.builder()
// .url("builtin:///tmp/reindex/testdb")
// .getReindexer();
// Инициализация базы данных и подключение к ней (для варианта использования Reindexer `Standalone`)
Reindexer db = ReindexerConfiguration.builder()
.url("cproto://127.0.0.1:6534/testdb")
.getReindexer();
// Для подключения к Reindexer с использованием TLS применяйте протокол cprotos:// с портом по умолчанию 6535.
// Используйте ReindexerConfiguration.sslSocketFactory() для предоставления кастомного SSLSocketFactory.
// Reindexer db = ReindexerConfiguration.builder()
// .url("cprotos://127.0.0.1:6534/testdb")
// .getReindexer();
// Если включен режим безопасности сервера (security, --security), имя пользователя и пароль являются обязательными
// Reindexer db = ReindexerConfiguration.builder()
// .url("cproto://user:pass@127.0.0.1:6534/testdb")
// .getReindexer();
// Инициализация базы данных и подключение к ней (для варианта использования Reindexer `Built-in server`)
// Reindexer db = ReindexerConfiguration.builder()
// .url("builtinserver:///tmp/reindex")
// .serverConfigFile("config.yml") // задание конфиг-файла для разворачиваемого сервера (необязательно). По умолчанию будет использоваться default-builtin-server-config.yml, если он существует
// .getReindexer();
// Ваш код для работы с базой данных
// ...
}
}
Пример команды для создания новой БД testdb по HTTP:
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "testdb"
}'
Удаление базы данных
Пример команды для удаления базы данных testdb по HTTP:
curl --location --request DELETE 'http://127.0.0.1:9088/api/v1/db/testdb'
Рекомендованный способ удаления базы данных — напрямую через файловую систему, когда нет активных подключений к этой БД.
Операции с неймспейсами
Создание неймспейса, подключение к неймспейсу, добавление индексов
Примеры создания неймспейса с указанными индексными полями:
Для создания нового неймспейса с именем items воспользуйтесь функцией OpenNamespace:
// Объявление структуры с индексными полями
type Item struct {
ID int64 `reindex:"id,,pk"` // Поле 'id' — первичный ключ
Name string `reindex:"name"` // Текстовое индексное поле
Articles []int `reindex:"articles"` // Массив 'articles' — множество записей (документов, статей) в составе индекса
Year int `reindex:"year,tree"` // Добавление сортируемого индекса по полю 'year'
}
// Создание нового неймспейса
db.OpenNamespace("items", reindexer.DefaultNamespaceOptions(), Item{})
При отсутствии в БД неймспейса с указанным именем он будет добавлен, после чего произойдет подключение к нему.
При наличии неймспейса items в базе данных будет создано подключение к нему.
Также будут созданы индексы и задана JSON-схема в соответствии со структурой Item.
Индексы неймспейса добавляются в существующий неймспейc функцией index_add:
db.namespace_open("items")
index_definitions = [
{
"name": "id",
"json_paths": ["id"],
"field_type": "int64",
'is_pk': True,
},
{
"name": "name",
"json_paths": ["name"],
"field_type": "string",
},
{
"name": "articles",
"json_paths": ["articles"],
"field_type": "int",
"is_array": True,
},
{
"name": "year",
"json_paths": ["year"],
"field_type": "int",
"index_type": "tree",
}
]
for index in index_definitions:
db.index_add("items", index)
import java.util.List;
import ru.rt.restream.reindexer.NamespaceOptions;
import ru.rt.restream.reindexer.annotations.Reindex;
import static ru.rt.restream.reindexer.IndexType.TREE;
// Объявление структуры с индексными полями
public static class Item {
// Поле 'id' — первичный ключ
@Reindex(name = "id", isPrimaryKey = true)
private Integer id;
// Текстовое индексное поле
@Reindex(name = "name")
private String name;
// Массив 'articles' — множество записей (документов, статей) в составе индекса
@Reindex(name = "articles")
private List<Integer> articles;
// Добавление сортируемого индекса по полю 'year'
@Reindex(name = "year", type = TREE)
private Integer year;
public Item() {
}
public Item(Integer id, String name, List<Integer> articles, Integer year) {
this.id = id;
this.name = name;
this.articles = articles;
this.year = year;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Integer> getArticles() {
return articles;
}
public void setArticles(List<Integer> articles) {
this.articles = articles;
}
public Integer getYear() {
return year;
}
public void setYear(Integer year) {
this.year = year;
}
@Override
public String toString() {
return "Item{" +
"id=" + id +
", name='" + name + '\'' +
", articles=" + articles +
", year=" + year +
'}';
}
}
// Создание нового неймспейса
Namespace<Item> itemNamespace = db.openNamespace(
"items",
NamespaceOptions.defaultOptions(),
Item.class
);
Пример запроса для создания в БД testdb нового неймспейса name с добавлением в него индекса id по HTTP:
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/namespaces' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "name",
"storage": {
"enabled": true
},
"indexes": [
{
"name": "id",
"field_type": "int64",
"index_type": "hash",
"is_pk": true,
"is_array": false,
"is_dense": false,
"is_sparse": false,
"collate_mode": "none",
"sort_order_letters": "",
"expire_after": 0,
"config": {},
"json_paths": [
"id"
]
}
],
"temporary": false,
"schema": "{}"
}'
Пример команды для добавления в неймспейс name базы данных testdb нового индекса year:
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/namespaces/name/indexes' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "year",
"json_paths": [
"year"
],
"field_type": "int",
"index_type": "hash",
"expire_after": 0,
"is_pk": false,
"is_array": false,
"is_dense": false,
"is_sparse": false,
"rtree_type": "linear",
"is_simple_tag": false,
"collate_mode": "none",
"sort_order_letters": "",
"config": {
"enable_translit": true,
"enable_numbers_search": false,
"enable_warmup_on_ns_copy": false,
"enable_kb_layout": true,
"log_level": 0,
"merge_limit": 20000,
"extra_word_symbols": "-/+",
"stop_words": [
"string"
],
"stemmers": [
"en",
"ru"
],
"synonyms": [
{
"tokens": [
"string"
],
"alternatives": [
"string"
]
}
],
"terms_boost": [
{
"terms": [
"string"
],
"boost": 1
}
],
"bm25_boost": 1,
"bm25_weight": 0.1,
"distance_boost": 1,
"distance_weight": 0.5,
"term_len_boost": 1,
"term_len_weight": 0.3,
"position_boost": 1,
"position_weight": 0.1,
"full_match_boost": 1.1,
"partial_match_decrease": 15,
"min_relevancy": 0.05,
"max_typos": 2,
"max_typo_len": 15,
"max_rebuild_steps": 50,
"max_step_size": 4000,
"sum_ranks_by_fields_ratio": 0,
"fields": [
{
"field_name": "string",
"bm25_boost": 1,
"bm25_weight": 0.1,
"term_len_boost": 1,
"term_len_weight": 0.3,
"position_boost": 1,
"position_weight": 0.1
}
]
}
}'
Переименование неймспейса
Примеры команд для переименования неймспейса:
Примечание
Python-коннектор не поддерживает переименование неймспейсов.
Пример команды для изменения имени неймспейса с items на newitems на Go:
db.RenameNamespace("items", "newitems")
Пример команды для изменения имени неймспейса с items на newitems по HTTP:
curl --location --request GET 'http://127.0.0.1:9088/api/v1/db/testdb/namespaces/items/rename/newitems'
Удаление неймспейса
Операция полностью удаляет неймспейс из памяти и с диска, включая индексы и метаданные.
Примеры команд для удаления неймспейса items из базы данных testdb:
db.DropNamespace("items")
db.namespace_drop(namespace)
db.dropNamespace("items");
curl --location --request DELETE 'http://127.0.0.1:9088/api/v1/db/testdb/namespaces/items'
Операции с документами (записями) в неймспейсе
Выбор одной записи из неймспейса
Примеры команд для выбора записи с id = 40 из неймспейса items:
elem, found := db.Query("items").
Where("id", reindexer.EQ, 40).
Get()
query = (
db
.new_query(namespace)
.where('id', CondType.CondEq, 40)
)
result = list(query)
import ru.rt.restream.reindexer.Namespace;
import static ru.rt.restream.reindexer.Query.Condition.EQ;
// ...
Item elem = namespace.query()
.where("id", EQ, 40)
.getOne();
curl --location --request GET 'http://127.0.0.1:9088/api/v1/db/testdb/namespaces/items/items?filter=id%3D40'
Добавление новой записи в неймспейс
Для добавления записей предусмотрено 2 метода: Insert и Upsert.
При использовании Insert запись добавляется, только если в неймспейсе не было записи с таким же первичным ключом. Upsert либо вставляет запись, либо обновляет существующую.
Примеры добавления записи в неймспейс:
db.Insert("items", &Item{
ID: 1,
Name: "Article",
Articles: []int{1, 2},
Year: 2022,
})
db.Upsert("items", &Item{
ID: 1,
Name: "Article 2",
Articles: []int{1, 2},
Year: 2023,
})
item = {
"id": 1,
"name": "Article",
"articles": [
1,
2
],
"year": 2022
}
db.item_insert("items", item)
item = {
"id": 1,
"name": "Article 2",
"articles": [
1,
2
],
"year": 2023
}
db.item_upsert("items", item)
db.insert(
"items",
new Item(1, "Article", Arrays.asList(1, 2), 2000)
);
Команда из примера добавляет в неймспейс items новую запись с первичным ключом ID = 1, name = Article, массив Articles заполняется значениями 1 и 2, значение поля Year = 2022.
Если запись с ID = 1 уже есть в неймспейсе, никаких изменений с ней не произойдет.
db.upsert(
"items",
new Item(1, "Article", Arrays.asList(1, 2), 2000)
);
Команда из примера добавляет в неймспейс items новую запись с первичным ключом ID = 1, name = Article, массив Articles заполняется значениями 1 и 2, значение поля Year = 2022.
Если запись с ID = 1 уже есть в неймспейсе, она обновится.
Для реализации вставки Insert используется POST-метод HTTP REST API:
curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/namespaces/items/items' \
--header 'Content-Type: application/json' \
--data-raw '{
"ID": 5,
"Name": "New_Article",
"Articles": 0,
"Year": 2022
}'
Для реализации вставки Upsert используется PATCH-метод HTTP REST API:
curl --location --request PATCH 'http://127.0.0.1:9088/api/v1/db/testdb/namespaces/items/items' \
--header 'Content-Type: application/json' \
--data-raw '{
"ID": 5,
"Name": "New_Article 2",
"Articles": 0,
"Year": 2023
}'
Команда Insert добавляет в неймспейс items новую запись с первичным ключом ID = 5, name = New_Article, массив Articles остается пустым, значение поля Year = 2022.
Если запись с ID = 1 уже есть в неймспейсе, никаких изменений с ней не произойдет.
Команда Upsert добавляет в неймспейс items новую запись с первичным ключом ID = 5.
Если запись с таким ID уже есть в неймспейсе, произойдет ее обновление. При отсутствии записи с таким id в неймспейсе она будет добавлена.
Обновление/изменение записи
Пример команды для обновления записи с ID = 6 (изменение значения поля Name на New_name) из неймспейса newitems:
db.Update("newitems", &Item{
ID: 6,
Name: "New_name",
})
db.item_update(
"newitems",
{
"id": 6,
"name": "New_name"
}
)
db.query("newitems", Item.class)
.where("id", EQ, 6)
.set("name", "New_name")
.update();
curl --location --request PUT 'http://127.0.0.1:9088/api/v1/db/testdb/namespaces/newitems/items' \
--header 'Content-Type: application/json' \
--data-raw '{
"ID": 6,
"name": "New_name"
}'
При использовании этой команды запись полностью перезаписывается. Поэтому она должна передаваться в тело запроса полностью, как объект JSON, включая значение полей, которые не будут меняться (в противном случае они будут удалены).
При использовании этого способа обновления записи невозможно изменить ее первичный ключ, поскольку поиск документа для изменения производится по первичному ключу.
Использование прецептов при добавлении и обновлении записей
Reindexer поддерживает атомарные функции, выполнение которых происходит при условии блокировки неймспейса. Это гарантирует согласованность данных.
В Insert, Upsert и Update запросах вы можете использовать функции:
serial()— реализуется с помощью механизма последовательностей целых чисел. Будет полезной для автоматического увеличения значения полей с автоинкрементом.now()— текущая отметка времени (см. Определение текущего времени).
Генерация значения поля id с помощью функции serial():
db.Insert("items", &item, "id=serial()")
db.item_insert("items", item, ["id=serial()"])
UPDATE items SET id = now(nsec), updated_at = serial()
curl \
--location \
--request POST \
http://127.0.0.1:9088/api/v1/db/testdb/namespaces/items/items \
--header 'Content-Type: application/json' \
--data-raw \
'
{
"ID": 5,
"Name": "New_Article",
"Articles": 0,
"Year": 2022
}
'
Генерация значения поля id с помощью функции serial() и добавление текущей отметки времени в наносекундах в поле updated_at:
db.Upsert("items", &item, "updated_at=now(nsec)", "id=serial()")
db.item_upsert("items", item, ["updated_at=now(nsec)", "id=serial()"])
UPDATE items SET id = now(nsec), updated_at = serial()
curl \
--location \
--request PATCH \
http://127.0.0.1:9088/api/v1/db/testdb/query \
--header 'Content-Type: application/json' \
--data-raw \
'
{
"namespace": "items",
"type": "update",
"update_fields": [
{
"name": "id",
"type": "expression",
"values": [
"now(nsec)"
]
},
{
"name": "updated_at",
"type": "expression",
"values": [
"serial()"
]
}
]
}
'
Удаление записи из неймспейса
Примеры команд для удаления записи с первичным ключом ID = 1 из неймспейса newitems:
db.Delete("newitems", &Item{ID: 1})
db.item_delete("newitems", { "id": 1 })
curl --location --request DELETE 'http://127.0.0.1:9088/api/v1/db/testdb/namespaces/newitems/items' \
--header 'Content-Type: application/json' \
--data-raw '{"ID":1}
'
Если нужно одной командой удалить несколько записей, первичный ключ каждой из них должен передаваться в тело запроса как отдельный JSON-объект. Пример:
curl --location --request DELETE 'http://127.0.0.1:9088/api/v1/db/testdb/namespaces/newitems/items' \
--header 'Content-Type: application/json' \
--data-raw '{"ID":3}
{"ID":4}
'