# armdb: интеграция с armour (feature `armour`)
## Обзор
Feature `armour` добавляет интеграцию armdb с экосистемой armour:
- **Db** — менеджер именованных коллекций с метаданными и генерацией ID
- **TreeMeta** — описание коллекции (имя, тип ключа, тип значения, версия)
- **Миграции** — автоматическое применение миграций при открытии дерева
- **RPC-сервер** — TCP/UDS сервер с бинарным протоколом armour-rpc
## Зависимости
### Feature `armour`
```toml
armour = [
"dep:serde", # DbInfo serialization
"dep:serde_json", # Persist format
"dep:rend", # rkyv portable endian types
"dep:arrayvec", # stack-allocated collections
"typed-tree", # TypedTree/TypedMap (Codec)
]
```
### Feature `rpc` (implies `armour`)
```toml
rpc = [
"armour",
"dep:armour-rpc", # протокол и клиент
"armour-rpc/schema", # schema introspection
"dep:compio", # async runtime (io_uring / kqueue)
"dep:futures-util", # Stream/Sink utilities
"dep:async-broadcast", # shutdown broadcast
"dep:ctrlc", # Ctrl-C handler
]
```
## 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::{Db, TypedMigration};
use armdb::{Config, MigrateAction, NoHook, RapiraCodec};
// Функция миграции: вызывается для каждой записи при смене версии
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 tree = db.open_typed_tree::<User, RapiraCodec, NoHook>(
Config::default(),
NoHook,
migrations,
)?;
```
### ZeroTree
```rust
use armdb::{Config, NoHook};
let tree = db.open_zero_tree::<Session, { size_of::<Session>() }, NoHook>(
Config::default(),
NoHook,
&[], // 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 armdb::armour::Db;
use armdb::{Config, NoHook, RapiraCodec};
let db = Db::open("data/myapp")?;
// open_typed_tree auto-registers RPC handler when `rpc` feature is enabled
let tree = db.open_typed_tree::<User, RapiraCodec, NoHook>(
Config::default(),
NoHook,
&[], // migrations
)?;
// Start TCP and/or Unix socket listener
db.listen_tcp("127.0.0.1:9000");
db.listen_uds("/tmp/myapp.sock");
```
### RpcHandler trait
```rust
pub trait RpcHandler: Send + Sync {
fn name(&self) -> &str;
fn info(&self) -> (u64, u16); // (typ_hash, version)
fn schema(&self) -> armour_rpc::SchemaResponse;
fn get(&self, key: &[u8]) -> DbResult<Option<ValueBytes>>;
fn contains(&self, key: &[u8]) -> DbResult<bool>;
fn entry_len(&self, key: &[u8]) -> DbResult<Option<u32>>;
fn first(&self) -> DbResult<Option<(KeyBytes, ValueBytes)>>;
fn last(&self) -> DbResult<Option<(KeyBytes, ValueBytes)>>;
fn range(&self, start: Bound<KeyBytes>, end: Bound<KeyBytes>)
-> DbResult<Vec<(KeyBytes, ValueBytes)>>;
fn range_keys(&self, start: Bound<KeyBytes>, end: Bound<KeyBytes>)
-> DbResult<Vec<ValueBytes>>;
fn upsert(&self, key: UpsertKey, flag: Option<bool>, value: ValueBytes)
-> DbResult<ValueBytes>;
fn remove(&self, key: &[u8], soft: bool) -> DbResult<()>;
fn take(&self, key: &[u8], soft: bool) -> DbResult<Option<ValueBytes>>;
fn count(&self, exact: bool) -> DbResult<u64>;
fn apply_batch(&self, items: Vec<(KeyBytes, Option<ValueBytes>)>) -> 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()))
}
// ... остальные методы
}
```