# armdb: интеграция с armour (feature `armour`)
## Обзор
Feature `armour` добавляет интеграцию armdb с экосистемой armour:
- **Db** — менеджер именованных коллекций с метаданными и генерацией ID
- **TreeMeta** — описание коллекции (имя, тип ключа, тип значения, версия)
- **Миграции** — автоматическое применение миграций при открытии дерева
- **RPC-сервер** — TCP/UDS сервер с бинарным протоколом armour-rpc
## Зависимости
```toml
armour = [
"dep:armour-core", # общие типы (Typ, KeyScheme, Persist, ...)
"dep:armour-rpc", # протокол и клиент
"dep:compio", # async runtime (io_uring / kqueue)
"dep:futures-util", # Stream/Sink utilities
"dep:async-broadcast", # shutdown broadcast
"dep:ctrlc", # Ctrl-C handler
"dep:serde", # DbInfo serialization
"dep:serde_json", # Persist format
"typed-tree", # TypedTree/TypedMap (Codec)
]
```
## Db
Менеджер базы данных — управляет директорией, метаданными коллекций и генерацией
последовательных ID.
```rust
use armdb::armour::Db;
let db = Db::open("data/myapp")?;
// Генерация ID для коллекции
let id = db.next_id("users")?; // 1, 2, 3, ...
// Путь для дерева
let path = db.tree_path("users", 1); // "data/myapp/users:v1"
db.close()?;
```
### Структура директории
```
data/myapp/
db.info # JSON — метаданные коллекций (Persist<DbInfo>)
__seq/ # ConstTree<[u8;8], 8> — счётчики последовательностей
users:v1/ # данные коллекции "users" версии 1
posts:v2/ # данные коллекции "posts" версии 2
```
### DbInfo / CollectionInfo
```rust
// Персистентные метаданные — версия и хеш типа для каждой коллекции
struct DbInfo {
collections: BTreeMap<String, CollectionInfo>,
}
struct CollectionInfo {
version: u16, // текущая версия схемы
typ_hash: u64, // Typ::h() — хеш типа значения
}
```
## TreeMeta и CollectionMeta
`TreeMeta` — статическое описание коллекции. Используется при открытии дерева.
Можно создать вручную:
```rust
use armour_core::{KeyScheme, KeyType, Typ};
use armdb::armour::TreeMeta;
const USERS_META: TreeMeta = TreeMeta {
name: "users",
key_scheme: KeyScheme::Typed(&[KeyType::Fuid]),
ty: <User as GetType>::TYPE,
group_bits: 10,
version: 2,
};
```
Или через трейт `CollectionMeta` — тип описывает свою коллекцию:
```rust
use armdb::armour::{CollectionMeta, TreeMeta};
use armour_core::{GetType, KeyScheme, KeyType};
#[derive(GetType, Rapira)]
struct User {
name: String,
email: String,
}
impl CollectionMeta for User {
const NAME: &'static str = "users";
const VERSION: u16 = 2;
const KEY_SCHEME: KeyScheme = KeyScheme::Typed(&[KeyType::Fuid]);
const GROUP_BITS: u32 = 10;
}
// Typ берётся из GetType автоматически
const USERS_META: TreeMeta = TreeMeta::of::<User>();
```
## Миграции
### TypedTree
```rust
use armdb::armour::{open_typed_tree, Db, TreeMeta, TypedMigration};
use armdb::{Config, MigrateAction};
// Функция миграции: вызывается для каждой записи при смене версии
fn migrate_v1_to_v2(key: &[u8; 16], val: &User) -> MigrateAction<User> {
MigrateAction::Update(User { /* ... */ })
}
let migrations: &[TypedMigration<[u8; 16], User>] = &[
(1, migrate_v1_to_v2), // from_version=1 -> текущая версия
];
let config = Config::new(db.tree_path("users", 2));
let tree = open_typed_tree(&db, &USERS_META, config, RapiraCodec, migrations)?;
```
### ZeroTree
```rust
use armdb::armour::{open_zero_tree, ZeroMigration};
let migrations: &[ZeroMigration<[u8; 8], Session>] = &[];
let tree = open_zero_tree(&db, &meta, config, migrations)?;
```
### Логика миграции
1. Читается `CollectionInfo` из `db.info` для имени коллекции
2. Если `stored.version != meta.version`:
- Ищется миграция с `from_version == stored.version`
- Вызывается `tree.migrate(migration_fn)`
- Для каждой записи callback возвращает `Keep`, `Update(T)` или `Delete`
3. Обновляется `CollectionInfo` (version + typ_hash) в `db.info`
## RPC-сервер
Бинарный протокол поверх length-delimited framing. Поддерживает TCP и Unix Domain Sockets.
### Запуск
```rust
use std::collections::HashMap;
use std::sync::Arc;
use armdb::armour::{Db, TreeMap, TypedTreeHandler, RpcHandler};
let db = Db::open("data/myapp")?;
let tree = Arc::new(open_typed_tree(&db, &meta, config, codec, &[])?);
// Регистрация деревьев по hashname (xxh3 от имени)
let mut map = HashMap::new();
let handler: Arc<dyn RpcHandler> = Arc::new(TypedTreeHandler {
name: "users".into(),
typ_hash: meta.ty.h(),
version: meta.version,
tree: tree.clone(),
codec: Arc::new(RapiraCodec),
seq_tree: Arc::new(db.seq_tree),
});
let hashname = xxhash_rust::xxh3::xxh3_64(b"users");
map.insert(hashname, handler);
let trees: TreeMap = Arc::new(map);
// TCP
db.listen_tcp(trees.clone(), "127.0.0.1:9000");
// Unix socket
db.listen_uds(trees, "/tmp/myapp.sock");
```
### RpcHandler trait
```rust
pub trait RpcHandler: Send + Sync {
fn name(&self) -> &str;
fn info(&self) -> (u64, u16); // (typ_hash, version)
fn get(&self, key: &[u8]) -> DbResult<Option<Vec<u8>>>;
fn contains(&self, key: &[u8]) -> DbResult<Option<u32>>;
fn first(&self) -> DbResult<Option<(Vec<u8>, Vec<u8>)>>;
fn last(&self) -> DbResult<Option<(Vec<u8>, Vec<u8>)>>;
fn range(start, end) -> DbResult<Vec<(Vec<u8>, Vec<u8>)>>;
fn range_keys(start, end) -> DbResult<Vec<Vec<u8>>>;
fn upsert(key, flag, value) -> DbResult<Vec<u8>>;
fn remove(&self, key: &[u8]) -> DbResult<()>;
fn count(&self) -> DbResult<u64>;
fn apply_batch(items) -> DbResult<()>;
}
```
### TypedTreeHandler
Реализация `RpcHandler` для `TypedTree<K, T, C>`. Мост между байтовым RPC-протоколом
и типизированным API дерева:
- **get/contains** — `K::read_from_bytes(key)` -> `tree.get(&key)` -> `codec.encode_to(&val)`
- **upsert** — `codec.decode_from(value)` -> `tree.put(&key, typed_value)`
- **upsert с `UpsertKey::Sequence`** — генерирует ID через `seq_tree`
- **upsert с `flag`** — `Some(true)` = только update, `Some(false)` = только insert
- **range/first/last** — итерация через `tree.range()`/`tree.iter()`
- **apply_batch** — серия put/delete в цикле (не атомарна)
### Протокол
Каждый запрос: `opcode(u8) + hashname(u64) + payload`. Ответ: `status(u8) + payload`.
| Get | Получить значение по ключу |
| Contains | Проверить наличие (возвращает длину значения) |
| First / Last | Первый / последний элемент |
| Range / RangeKeys | Диапазонный запрос (ключи+значения / только ключи) |
| Count | Количество элементов |
| Upsert | Вставка/обновление с опциональным флагом |
| Remove | Удаление |
| ApplyBatch | Пакетная операция |
| ListCollections | Список всех коллекций с метаданными |
### Shutdown
Ctrl-C отправляет broadcast через `async_broadcast`. Все accept-loop'ы и активные
соединения завершаются. `listen_tcp`/`listen_uds` запускают compio runtime в отдельном
потоке.
## Пользовательские RpcHandler
`TypedTreeHandler` работает только с `TypedTree`. Для других типов деревьев
(ConstTree, VarTree, ZeroTree) нужно реализовать `RpcHandler` вручную.
```rust
struct MyConstHandler {
tree: Arc<ConstTree<[u8; 16], 64>>,
}
impl RpcHandler for MyConstHandler {
fn name(&self) -> &str { "my_const" }
fn get(&self, key: &[u8]) -> DbResult<Option<Vec<u8>>> {
let k: [u8; 16] = key.try_into().map_err(|_| DbError::KeyNotFound)?;
Ok(self.tree.get(&k).map(|v| v.to_vec()))
}
// ... остальные методы
}
```