armdb 0.2.0

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

## Зависимости

### 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`.

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