# CATRUSTDB — Guide utilisateur

> Version 0.8 · Avril 2026

---

## Table des matières

1. [Installation](#1-installation)
2. [Concepts essentiels](#2-concepts-essentiels)
3. [Écrire un schéma CQL](#3-écrire-un-schéma-cql)
4. [Écrire une instance CQL](#4-écrire-une-instance-cql)
5. [Faire des requêtes](#5-faire-des-requêtes)
6. [Migrer des données avec Σ](#6-migrer-des-données-avec-σ)
7. [Le serveur TCP NDJSON](#7-le-serveur-tcp-ndjson)
8. [Persistence WAL](#8-persistence-wal)
9. [Snapshots binaires V2 (fast)](#9-snapshots-binaires-v2-fast)
10. [Les index O(1) et lazy indexing](#10-les-index-o1-et-lazy-indexing)
11. [Intégration Rust](#11-intégration-rust)
12. [Référence des commandes serveur](#12-référence-des-commandes-serveur)
13. [Dépannage](#13-dépannage)

---

## 1. Installation

### Depuis les sources

```bash
git clone https://github.com/sirgudu/Catrust
cd Catrust

# Installation globale (catrust + catrustdb dans ~/.cargo/bin) :
cargo install --path . --locked
cargo install --path catrustdb-server --locked

# Ou compilation locale uniquement :
cargo build --release -p catrustdb-server
# Binaire disponible dans :
./target/release/catrustdb
```

### Vérification

```bash
catrustdb --help
# Usage: catrustdb [--host <HOST>] [--port <PORT>]

catrustdb --port 7474 &
echo '{"cmd":"ping"}' | nc localhost 7474
# → {"ok":true,"msg":"pong"}
```

### Docker (si disponible)

```bash
docker run -p 7474:7474 catrust/catrustdb:latest
```

---

## 2. Concepts essentiels

CATRUSTDB est fondé sur la **théorie des catégories**. Voici la traduction pratique :

| Terme mathématique | Ce que c'est en pratique |
|--------------------|--------------------------|
| **Schéma** (catégorie) | La structure de vos données : tables, colonnes, FK |
| **Instance** (foncteur) | Les données elles-mêmes : les lignes |
| **Mapping** (foncteur) | La règle de restructuration d'un schéma vers un autre |
| **Δ (delta)** | Restriction / filtre (le WHERE de CQL) |
| **Σ (sigma)** | Migration = déplacer les données d'un schéma dans un autre |

### Le cycle de vie des données

```
Schéma CQL  →  Instance CQL  →  Requête CQL  →  Résultat
    ↓               ↓
  Mapping  →   Σ (migration)  →  Nouvelle Instance
```

Dans CATRUSTDB, tout cela se passe **en mémoire, en Rust pur, sans SQL**.

---

## 3. Écrire un schéma CQL

Un schéma décrit la **structure** de vos données. Il ressemble à du DDL SQL mais en plus lisible.

### Syntaxe minimale

```cql
schema NomDuSchéma {
  entities
    NomEntite1
    NomEntite2

  foreign_keys
    nom_fk : EntitéSource -> EntitéCible

  attributes
    nom_attr : EntitéSource -> TypeBase
}
```

### Types de base disponibles

| Type CQL | Exemple | Rust interne |
|----------|---------|--------------|
| `String` | `"Alice"` | `Val::Str(String)` |
| `Float`  | `90000.0` | `Val::Float(f64)` |
| `Integer`| `42` | `Val::Int(i64)` |
| `Boolean`| `true` | `Val::Bool(bool)` |

### Exemple complet

```cql
schema Company {
  entities
    Employee
    Department

  foreign_keys
    dept : Employee -> Department

  attributes
    emp_name  : Employee   -> String
    salary    : Employee   -> Float
    dept_name : Department -> String
    budget    : Department -> Float
}
```

**Règles importantes :**
- Les noms d'entités commencent par une majuscule
- Une FK pointe d'une entité **source** vers une entité **cible**
- Un attribut est une FK vers un type de base (String, Float…) — c'est la vision catégorique

---

## 4. Écrire une instance CQL

Une instance contient les **données** conformes à un schéma.

### Syntaxe

```cql
instance NomInstance : NomSchéma {
  NomEntité {
    symbole  attr1="valeur"  attr2=42.0  fk=symbole_cible
  }
}
```

- **symbole** : étiquette locale pour les références croisées (ne va pas en base)
- Les entités cibles (ex : Department) doivent être déclarées **avant** les entités qui les référencent (Employee)

### Exemple

```cql
instance SampleData : Company {
  Department {
    d1  dept_name="Engineering"  budget=150000.0
    d2  dept_name="Marketing"    budget=80000.0
    d3  dept_name="Research"     budget=200000.0
  }

  Employee {
    e1  dept=d1  emp_name="Alice"    salary=90000.0
    e2  dept=d1  emp_name="Bob"      salary=75000.0
    e3  dept=d2  emp_name="Charlie"  salary=70000.0
    e4  dept=d2  emp_name="Diana"    salary=82000.0
    e5  dept=d3  emp_name="Eve"      salary=95000.0
  }
}
```

---

## 5. Faire des requêtes

### Syntaxe CQL

```cql
query NomRequête : NomSchéma {
  from    alias : Entité
  where   alias.attribut > valeur
  select  alias.attribut1  alias.attribut2  alias.fk.attribut_distant
}
```

### Exemples

**Tous les employés :**
```cql
query AllEmployees : Company {
  from    e : Employee
  select  e.emp_name  e.salary  e.dept.dept_name
}
```

**Employés bien payés :**
```cql
query HighEarners : Company {
  from    e : Employee
  where   e.salary > 80000
  select  e.emp_name  e.salary
}
```

**Filtre sur un département (via FK) :**
```cql
query EngineeringTeam : Company {
  from    e : Employee
  where   e.dept.dept_name = "Engineering"
  select  e.emp_name  e.salary
}
```

### Opérateurs WHERE supportés

| Opérateur | Signification |
|-----------|--------------|
| `=`       | Égalité exacte |
| `!=`      | Différent |
| `>`       | Strictement supérieur |
| `<`       | Strictement inférieur |
| `>=`      | Supérieur ou égal |
| `<=`      | Inférieur ou égal |

> **Coercions numériques :** `Float` et `Integer` se comparent automatiquement.

### Via le serveur

```bash
echo '{
  "cmd": "query",
  "cql_query": "query Q : Company { from e : Employee where e.salary > 80000 select e.emp_name e.salary }"
}' | nc localhost 7474
```

Réponse :
```json
{
  "ok": true,
  "name": "Q",
  "columns": ["emp_name", "salary"],
  "rows": [["Alice", "90000.0"], ["Diana", "82000.0"], ["Eve", "95000.0"]],
  "row_count": 3
}
```

---

## 6. Migrer des données avec Σ

Σ (sigma) est l'opération de **migration catégorique** : elle prend des données dans un schéma source et les transforme dans un schéma cible selon un Mapping.

### 1. Définir le schéma cible

```cql
schema Org {
  entities
    Staff
    Team

  foreign_keys
    member_of : Staff -> Team

  attributes
    full_name : Staff -> String
    pay       : Staff -> Float
    team_name : Team  -> String
}
```

### 2. Définir le Mapping

```cql
mapping Rename : Company -> Org {
  entities
    Employee   -> Staff
    Department -> Team

  foreign_keys
    dept -> member_of

  attributes
    emp_name  -> full_name
    salary    -> pay
    dept_name -> team_name
}
```

### 3. Exécuter via le serveur

```bash
# Charger la source
echo '{"cmd":"load","schema_cql":"...Company CQL...","instance_cql":"...SampleData CQL..."}' | nc localhost 7474

# Migrer vers Org
echo '{
  "cmd":        "sigma",
  "target_cql": "...Org CQL...",
  "mapping_cql":"...Rename CQL..."
}' | nc localhost 7474
```

Réponse :
```json
{
  "ok": true,
  "schema": "Org",
  "entities": [
    {"name": "Staff",  "count": 5, "columns": ["full_name","pay"], "rows": [...]},
    {"name": "Team",   "count": 3, "columns": ["team_name"],       "rows": [...]}
  ]
}
```

### Ce que Σ garantit

- **Toutes les FK sont remappées** : aucune clé orpheline possible
- **L'ordre est préservé** : les lignes arrivent dans le même ordre relatif
- **Les attributs non mappés sont NULL** : comportement explicite, pas silencieux

---

## 7. Le serveur TCP NDJSON

### Démarrage

```bash
catrustdb --host 127.0.0.1 --port 7474
# [catrustdb] tcp://127.0.0.1:7474 — prêt
```

Options :
- `--host` : adresse d'écoute (défaut : `127.0.0.1`)
- `--port` : port TCP (défaut : `7474`)

### Protocole

Le protocole est **NDJSON** (Newline-Delimited JSON) :
- Chaque **requête** = une ligne JSON terminée par `\n`
- Chaque **réponse** = une ligne JSON terminée par `\n`
- La connexion reste ouverte — vous pouvez envoyer plusieurs commandes

```bash
# Session interactive avec nc (netcat)
nc localhost 7474
{"cmd":"ping"}
→ {"ok":true,"msg":"pong"}
{"cmd":"stats"}
→ {"ok":true,"loaded":false}
```

### Connexion depuis Python

```python
import socket, json

def catrustdb(host="127.0.0.1", port=7474):
    s = socket.socket()
    s.connect((host, port))
    return s

def send(s, cmd: dict) -> dict:
    s.sendall((json.dumps(cmd) + "\n").encode())
    return json.loads(s.makefile().readline())

with catrustdb() as s:
    print(send(s, {"cmd": "ping"}))
    # → {'ok': True, 'msg': 'pong'}
    print(send(s, {"cmd": "stats"}))
```

### Connexion depuis Node.js

```javascript
const net = require('net');
const { Readable } = require('stream');

const client = net.createConnection(7474, '127.0.0.1', () => {
  client.write(JSON.stringify({ cmd: 'ping' }) + '\n');
});

client.on('data', (data) => {
  const resp = JSON.parse(data.toString().trim());
  console.log(resp); // { ok: true, msg: 'pong' }
  client.end();
});
```

---

## 8. Persistence WAL

CATRUSTDB utilise un **Write-Ahead Log** (WAL) au format NDJSON pour la persistence.

### Sauvegarder

```bash
echo '{"cmd":"persist","path":"/var/data/mydb.wal"}' | nc localhost 7474
# → {"ok":true,"path":"/var/data/mydb.wal"}
```

Le fichier WAL contient une entrée par ligne :
```
{"Schema":{"cql":"schema Company { ... }"}}
{"Insert":{"entity":"Department","attrs":[["dept_name","Engineering"],["budget",150000.0]],"fks":[]}}
{"Insert":{"entity":"Employee","attrs":[["emp_name","Alice"],["salary",90000.0]],"fks":[["dept",0]]}}
{"Checkpoint":null}
```

### Recharger après un crash

```bash
echo '{"cmd":"from_wal","path":"/var/data/mydb.wal"}' | nc localhost 7474
# → {"ok":true,"entities":2,"rows":8}
```

### Avantages du WAL NDJSON

- **Lisible par un humain** — vous pouvez inspecter le fichier avec `cat` ou `less`
- **Diffable** — `git diff db_v1.wal db_v2.wal` montre exactement ce qui a changé
- **Append-only** — écriture séquentielle, pas de réécriture de pages
- **Rejouable** — en cas de corruption partielle, tout ce qui précède la coupure est récupérable

### Sauvegarde automatique (exemple shell)

```bash
# Cronjob toutes les heures
0 * * * * echo '{"cmd":"persist","path":"/backups/db_$(date +\%Y\%m\%d_\%H).wal"}' | nc localhost 7474
```

---

## 9. Snapshots binaires V2 (fast)

Depuis la v0.8, CATRUSTDB dispose d'un format de snapshot **colonnaire** (V2) basé sur `memmap2`. C'est la méthode recommandée pour sauvegarder et restaurer des bases volumineuses.

### Performances à 1 M lignes

| Opération | V1 (bincode) | V2 (colonnaire) | Gain |
|-----------|-------------|-----------------|------|
| Sauvegarde | 1.26 s | **93 ms** | **13.5×** |
| Chargement lazy (sans index) | 3.66 s | **~5 ms** | **~700×** |
| Chargement eager (index complet) | 3.66 s | 2.31 s | 1.6× |

### Sauvegarder (V2)

```bash
catrust snapshot --schema examples/company.cql --out /backups/snapshot.v2
```

```rust
db.persist_binary_v2(std::path::Path::new("/backups/snapshot.v2"))?;
```

### Restaurer (V2)

```bash
catrust rollback --snapshot /backups/snapshot.v2
```

```rust
// Chargement lazy — indexes construits à la demande
let mut db = Database::from_binary_v2(std::path::Path::new("/backups/snapshot.v2"))?;
```

### Lazy indexing au chargement

`from_binary_v2` n'applique **aucun** rebuild d'index à l'ouverture.
Les index sont construits colonne par colonne au premier filtre sur cette colonne.

```rust
use catrustdb_eval::filter_entity_lazy;

// Premier appel sur "salary" : construit l'index en O(n) puis filtre O(1)
let rich = filter_entity_lazy(&mut db, "Employee", &Pred::Eq {
    path: vec!["salary".into()],
    val:  Val::Float(90000.0),
});
// Appels suivants sur la même colonne → O(1) direct
```

> **Quand utiliser V2 ?** Dès que votre base dépasse ~10 000 lignes ou que le temps de redémarrage compte.

---

## 10. Les index O(1) et lazy indexing

Par défaut, CATRUSTDB fait des scans O(n) sur les colonnes. Vous pouvez construire des **index d'égalité** pour passer en O(1) sur les `WHERE attr = val`.

### Via l'API Rust (mode eager)

```rust
use catrustdb_store::Database;

let store = db.entity_mut("Employee").unwrap();

// Construire un index sur salary
store.build_index("salary");

// Lookup O(1) — retourne les RowIds directement
let rows = store.lookup_eq("salary", &Val::Float(90000.0));
// → &[0] (Alice est à la ligne 0)

// Supprimer l'index
store.drop_index("salary");
```

### Via l'API Rust (mode lazy — après from_binary_v2)

```rust
use catrustdb_eval::filter_entity_lazy;

// db chargé via from_binary_v2 → aucun index en mémoire
let rows = filter_entity_lazy(&mut db, "Employee", &Pred::Eq {
    path: vec!["salary".into()],
    val:  Val::Float(90000.0),
});
// L'index d'égalité sur "salary" vient d'être construit.
// La prochaine requête sur "salary" sera O(1) directement.
```

### Comportement automatique

Une fois `build_index("attr")` appelé, **chaque `insert_row` maintient l'index automatiquement**.

Le filtre `filter_entity` / `filter_entity_lazy` détecte automatiquement si un index existe :

```rust
// Si salary est indexé → O(1) lookup via HashMap
// Sinon               → O(n) scan séquentiel
let rows = filter_entity(&db, "Employee", &Pred::Eq {
    path: vec!["salary".into()],
    val:  Val::Float(90000.0),
});
```

### Quand utiliser un index ?

| Situation | Conseil |
|-----------|----------|
| Requêtes répétées sur la même colonne | Indexez |
| Colonne de haute cardinalité (IDs, emails) | Indexez |
| Chargement V2 + premières requêtes | Utilisez `filter_entity_lazy` |
| Colonne booléenne peu sélective | Inutile |
| Données insérées 1 fois, jamais requêtées | Inutile |

---

## 11. Intégration Rust

CATRUSTDB est une bibliothèque Rust en premier lieu. Le serveur TCP n'est qu'un wrapper.

### Cargo.toml

```toml
[dependencies]
catrustdb-store = { path = "../catrustdb-store" }
catrustdb-eval  = { path = "../catrustdb-eval" }
catrust-core    = { path = "../catrust-core", features = ["serde"] }
catrust-parser  = { path = "../catrust-parser" }
```

### Exemple complet en Rust

```rust
use std::collections::HashMap;
use catrust_core::schema::Schema;
use catrust_core::typeside::BaseType;
use catrust_core::instance::Instance;
use catrust_core::typeside::Value;
use catrustdb_store::Database;
use catrustdb_eval::{Pred, Val, filter_entity, filter_entity_lazy};

fn main() {
    // 1. Créer un schéma
    let mut schema = Schema::new("Company");
    schema.add_node("Employee");
    schema.add_node("Department");
    schema.add_fk("dept", "Employee", "Department");
    schema.add_attribute("emp_name", "Employee",   BaseType::String);
    schema.add_attribute("salary",   "Employee",   BaseType::Float);
    schema.add_attribute("dept_name","Department", BaseType::String);

    // 2. Créer une instance
    let mut inst = Instance::new("demo", &schema);
    let d1 = inst.insert("Department",
        [("dept_name".to_string(), Value::String("Engineering".into()))].into_iter().collect(),
        HashMap::new());
    inst.insert("Employee",
        [("emp_name".into(), Value::String("Alice".into())),
         ("salary".into(),   Value::Float(90000.0))].into_iter().collect(),
        [("dept".to_string(), d1)].into_iter().collect());

    // 3. Charger dans CATRUSTDB
    let mut db = Database::new(schema);
    db.load_instance(&inst).unwrap();

    // 4. Requête avec index O(1) (eager)
    db.entity_mut("Employee").unwrap().build_index("salary");
    let rich = filter_entity(&db, "Employee", &Pred::Eq {
        path: vec!["salary".into()],
        val:  Val::Float(90000.0),
    });
    println!("Lignes avec salary=90000: {:?}", rich); // → [0]

    // 5. Sauvegarder en V2 (13.5× plus rapide que WAL pour 1 M lignes)
    db.schema_cql = "schema Company { ... }".to_string();
    db.persist_binary_v2(std::path::Path::new("/tmp/demo.v2")).unwrap();

    // 6. Recharger avec lazy indexing (~700× plus rapide que V1)
    let mut db2 = Database::from_binary_v2(std::path::Path::new("/tmp/demo.v2")).unwrap();
    // Premier filtre → construit l'index à la demande
    let rich2 = filter_entity_lazy(&mut db2, "Employee", &Pred::Eq {
        path: vec!["salary".into()],
        val:  Val::Float(90000.0),
    });
    println!("Lignes rechargées : {}", db2.total_rows()); // → 2
    println!("Employes riches : {:?}", rich2); // → [0]
}
```

---

## 12. Référence des commandes serveur

Toutes les commandes s'envoient en JSON avec le champ `"cmd"`.

### `ping`

```json
{"cmd": "ping"}
```
```json
{"ok": true, "msg": "pong"}
```

---

### `load`

Charge un schéma CQL (et optionnellement une instance) en mémoire.

```json
{
  "cmd":         "load",
  "schema_cql":  "<texte CQL du schéma>",
  "instance_cql": "<texte CQL de l'instance>"
}
```

`instance_cql` est optionnel.

```json
{"ok": true, "entities": 2, "rows": 8}
```

---

### `from_wal`

Recharge la base depuis un fichier WAL.

```json
{"cmd": "from_wal", "path": "/chemin/vers/fichier.wal"}
```
```json
{"ok": true, "entities": 2, "rows": 8}
```

---

### `query`

Exécute une requête CQL.

```json
{"cmd": "query", "cql_query": "query Q : Company { from e:Employee select e.emp_name }"}
```
```json
{
  "ok":        true,
  "name":      "Q",
  "columns":   ["emp_name"],
  "rows":      [["Alice"], ["Bob"], ["Charlie"]],
  "row_count": 3
}
```

---

### `sigma`

Migre les données chargées vers un nouveau schéma via un Mapping.

```json
{
  "cmd":         "sigma",
  "target_cql":  "<CQL du schéma cible>",
  "mapping_cql": "<CQL du mapping>"
}
```
```json
{
  "ok":       true,
  "schema":   "Org",
  "entities": [
    {"name": "Staff", "count": 5, "columns": ["full_name","pay"], "rows": [...]}
  ]
}
```

---

### `stats`

Retourne des statistiques sur la base chargée.

```json
{"cmd": "stats"}
```
```json
{
  "ok":         true,
  "loaded":     true,
  "schema":     "Company",
  "entities":   [{"name": "Department","rows": 3}, {"name": "Employee","rows": 5}],
  "total_rows": 8
}
```

---

### `persist`

Sauvegarde la base dans un fichier WAL.

```json
{"cmd": "persist", "path": "/chemin/vers/fichier.wal"}
```
```json
{"ok": true, "path": "/chemin/vers/fichier.wal"}
```

---

## 13. Dépannage

### `"aucune base chargée"`

Vous avez envoyé une commande `query`, `sigma`, `stats` ou `persist` sans avoir au préalable fait un `load` ou `from_wal`.

```bash
echo '{"cmd":"load","schema_cql":"..."}' | nc localhost 7474
# PUIS :
echo '{"cmd":"stats"}' | nc localhost 7474
```

---

### `"Schéma : ..."`  / erreur de parsing

Le CQL envoyé contient une erreur de syntaxe. Vérifiez :
- Les accolades `{` `}` sont fermées
- `entities`, `foreign_keys`, `attributes` ont des blocs séparés
- Les noms d'entités commencent par une majuscule

---

### `"entité cible '...' absente du schéma cible"` pendant sigma

Le Mapping référence une entité qui n'existe pas dans le schéma cible. Vérifiez que le `target_cql` et le `mapping_cql` sont cohérents.

---

### Le serveur ne répond plus

Le mutex tokio est peut-être bloqué par une connexion zombie. Redémarrez le serveur — la base peut être rechargée depuis le WAL :

```bash
pkill catrustdb
catrustdb --port 7474 &
echo '{"cmd":"from_wal","path":"/chemin/vers/derniere.wal"}' | nc localhost 7474
```

---

### Performance : scans lents sur grande table

Construisez un index sur la colonne filtrée :

```rust
db.entity_mut("Employee").unwrap().build_index("salary");
```

Le filtre passe alors de O(n) à O(1) pour les requêtes d'égalité.

---

*CATRUSTDB — Zéro SQL. Zéro injection. Garantie mathématique.*
