armdb 0.1.14

sharded bitcask key-value storage optimized for NVMe
Documentation
# 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`.

| OpCode | Описание |
|--------|----------|
| 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()))
    }
    // ... остальные методы
}
```