# Guide des backends — Catrust

Ce guide explique comment utiliser les backends SQL (PostgreSQL, Snowflake, Trino) et Graph (Neo4j) pour déployer et migrer des données réelles.

---

## Architecture des backends

```
CQL (Schema + Instance + Mapping)
            │
            ▼
    trait Backend
    ┌─────────────────────┐
    │  deploy_schema()    │  → CREATE TABLE / CREATE CONSTRAINT
    │  export_instance()  │  → INSERT INTO / CREATE (node)
    │  migrate_delta()    │  → SELECT ... JOIN (vue)
    │  migrate_sigma()    │  → INSERT INTO ... SELECT (migration)
    └─────────────────────┘
            │
    ┌───────┴───────────────────┐
    │                           │
SqlBackend<D>             Neo4jBackend
(PostgreSQL, Snowflake,   (Cypher)
 Trino)
```

---

## Backend PostgreSQL

### Générer le DDL (schéma)

```rust
use catrust::backend::sql::{SqlBackend, PostgresDialect};
use catrust::backend::Backend;

let backend = SqlBackend::new(PostgresDialect);
let statements = backend.deploy_schema(&schema);

for stmt in &statements {
    println!("{}", stmt);
}
```

**Sortie pour un schéma `Employee → Department`** :
```sql
CREATE TABLE "Department" (
    "catrust_id" SERIAL PRIMARY KEY,
    "dept_name" TEXT
);

CREATE TABLE "Employee" (
    "catrust_id" SERIAL PRIMARY KEY,
    "emp_name" TEXT,
    "salary" INTEGER,
    "works_in_id" INTEGER REFERENCES "Department"("catrust_id")
);
```

**Règles de traduction** :

| CQL | PostgreSQL |
|-----|------------|
| `Node` | `CREATE TABLE` avec `SERIAL PRIMARY KEY` |
| `Attribute(String)` | `TEXT` |
| `Attribute(Integer)` | `INTEGER` |
| `Attribute(Float)` | `DOUBLE PRECISION` |
| `Attribute(Boolean)` | `BOOLEAN` |
| `ForeignKey` | Colonne `{fk_name}_id INTEGER REFERENCES ...` |

### Générer le DML (données)

```rust
let statements = backend.export_instance(&schema, &instance);
```

**Sortie** :
```sql
INSERT INTO "Department" ("catrust_id", "dept_name") VALUES (1, 'Engineering');
INSERT INTO "Department" ("catrust_id", "dept_name") VALUES (2, 'Marketing');
INSERT INTO "Employee" ("catrust_id", "emp_name", "salary", "works_in_id")
    VALUES (1, 'Alice', 90000, 1);
INSERT INTO "Employee" ("catrust_id", "emp_name", "salary", "works_in_id")
    VALUES (2, 'Bob', 75000, 1);
```

> **Note** : Les tables référencées (Department) sont générées avant les tables qui les référencent (Employee) pour respecter les contraintes FK.

---

## Backend Snowflake

```rust
use catrust::backend::sql::{SqlBackend, SnowflakeDialect};

let backend = SqlBackend::new(SnowflakeDialect);
let ddl = backend.deploy_schema(&schema);
```

**Différences par rapport à PostgreSQL** :

| Type CQL | PostgreSQL | Snowflake |
|----------|------------|-----------|
| PK auto | `SERIAL` | `NUMBER AUTOINCREMENT` |
| String | `TEXT` | `VARCHAR` |
| Integer | `INTEGER` | `NUMBER(38,0)` |
| Float | `DOUBLE PRECISION` | `FLOAT` |
| Boolean | `BOOLEAN` | `BOOLEAN` |

**Sortie typique** :
```sql
CREATE TABLE "Department" (
    "catrust_id" NUMBER AUTOINCREMENT PRIMARY KEY,
    "dept_name" VARCHAR
);
```

---

## Backend Trino (ex-Presto)

Trino est un moteur de requêtes fédéré. Le backend Catrust génère du SQL compatible avec le catalogue Iceberg.

```rust
use catrust::backend::sql::{SqlBackend, TrinoDialect};

let trino = SqlBackend::new(TrinoDialect::new("iceberg", "default"));
let ddl = trino.deploy_schema(&schema);
```

**Différences** :

| Type CQL | Trino |
|----------|-------|
| PK auto | `BIGINT` (pas d'auto-increment natif en Trino) |
| String | `VARCHAR` |
| Integer | `BIGINT` |
| Float | `DOUBLE` |
| Boolean | `BOOLEAN` |

**Sortie typique** (catalogue `iceberg`, schéma `default`) :
```sql
CREATE TABLE iceberg.default."Department" (
    "catrust_id" BIGINT,
    "dept_name" VARCHAR
) WITH (format = 'PARQUET');
```

---

## Backend Neo4j (Cypher)

Dans Neo4j, la correspondance CQL → graphe est très naturelle :

| CQL | Neo4j |
|-----|-------|
| `Node` (entité) | Label de nœud (`:Employee`) |
| `ForeignKey` | Relation (`-[:WORKS_IN]->`) |
| `Attribute` | Propriété du nœud (`emp_name: "Alice"`) |
| `Path` | Pattern Cypher `(a)-[:FK1]->(b)-[:FK2]->(c)` |
| `PathEquation` | Contrainte sémantique |

### Utilisation

```rust
use catrust::backend::graph::Neo4jBackend;
use catrust::backend::Backend;

let backend = Neo4jBackend::new();

// DDL : contraintes d'unicité
let schema_cypher = backend.deploy_schema(&schema);
for stmt in &schema_cypher {
    println!("{}", stmt);
}

// DML : nœuds et relations
let data_cypher = backend.export_instance(&schema, &instance);
for stmt in &data_cypher {
    println!("{}", stmt);
}
```

### Sortie DDL

```cypher
CREATE CONSTRAINT ON (n:Department) ASSERT n.catrust_id IS UNIQUE;
CREATE CONSTRAINT ON (n:Employee) ASSERT n.catrust_id IS UNIQUE;
```

### Sortie DML

```cypher
CREATE (:Department { catrust_id: 1, dept_name: "Engineering" });
CREATE (:Department { catrust_id: 2, dept_name: "Marketing" });
CREATE (:Employee { catrust_id: 1, emp_name: "Alice", salary: 90000 });
CREATE (:Employee { catrust_id: 2, emp_name: "Bob", salary: 75000 });

MATCH (src:Employee { catrust_id: 1 }), (tgt:Department { catrust_id: 1 })
CREATE (src)-[:WORKS_IN]->(tgt);

MATCH (src:Employee { catrust_id: 2 }), (tgt:Department { catrust_id: 1 })
CREATE (src)-[:WORKS_IN]->(tgt);
```

---

## Le planificateur SQL (`SqlPlanner`)

Le `SqlPlanner` génère des requêtes `SELECT` optimisées à partir de `CqlQuery`, en appliquant automatiquement le `PathOptimizer`.

```rust
use catrust::backend::sql::planner::SqlPlanner;
use catrust::backend::sql::PostgresDialect;
use catrust::core::query::*;

let planner = SqlPlanner::new(&PostgresDialect, &schema);
let plans = planner.plan_query(&query);

for plan in &plans {
    println!("{}", plan);
}
```

### Exemple : requête avec optimisation automatique

**Schéma** avec path equation :
```
employee.department.manager = employee.direct_manager
```

**Requête CQL** :
```
FROM e : Employee
WHERE e.department.manager.emp_name = "Alice"   ← 2 JOINs
RETURN e.emp_name, e.salary
```

**SQL généré après optimisation** :
```sql
-- AVANT optimisation (sans path equations) :
SELECT e."emp_name", e."salary"
FROM "Employee" e
JOIN "Department" dept ON e."works_in_id" = dept."catrust_id"
JOIN "Employee"   mgr  ON dept."manager_id" = mgr."catrust_id"
WHERE mgr."emp_name" = 'Alice';

-- APRÈS optimisation (avec path equation) :
SELECT e."emp_name", e."salary"
FROM "Employee" e
JOIN "Employee" mgr ON e."direct_manager_id" = mgr."catrust_id"
WHERE mgr."emp_name" = 'Alice';
-- → 1 JOIN éliminé
```

---

## Migration multi-backend

Une des forces de Catrust est de générer le code pour **plusieurs backends simultanément** à partir du même mapping.

```rust
use catrust::backend::sql::{SqlBackend, PostgresDialect, SnowflakeDialect, TrinoDialect};
use catrust::backend::graph::Neo4jBackend;
use catrust::backend::Backend;

// Même instance, quatre backends
let pg    = SqlBackend::new(PostgresDialect);
let sf    = SqlBackend::new(SnowflakeDialect);
let trino = SqlBackend::new(TrinoDialect::new("iceberg", "default"));
let neo   = Neo4jBackend::new();

// DDL pour chaque backend
let pg_ddl    = pg.deploy_schema(&schema_new);
let sf_ddl    = sf.deploy_schema(&schema_new);
let trino_ddl = trino.deploy_schema(&schema_new);
let neo_ddl   = neo.deploy_schema(&schema_new);

// DML pour chaque backend
let pg_dml    = pg.export_instance(&schema_new, &instance_new);
let neo_dml   = neo.export_instance(&schema_new, &instance_new);
```

---

## Ajouter un backend custom

Pour supporter un nouveau backend (ex : DuckDB, ClickHouse, BigQuery), implémenter le trait `SqlDialect` :

```rust
use catrust::backend::sql::{SqlDialect, SqlBackend};
use catrust::core::typeside::BaseType;

pub struct DuckDbDialect;

impl SqlDialect for DuckDbDialect {
    fn type_to_sql(&self, ty: &BaseType) -> String {
        match ty {
            BaseType::String  => "VARCHAR".to_string(),
            BaseType::Integer => "BIGINT".to_string(),
            BaseType::Float   => "DOUBLE".to_string(),
            BaseType::Boolean => "BOOLEAN".to_string(),
            BaseType::Custom(s) => s.clone(),
        }
    }

    fn auto_id_type(&self) -> String {
        "INTEGER".to_string()  // DuckDB supporte SEQUENCE séparément
    }

    fn dialect_name(&self) -> String {
        "DuckDB".to_string()
    }
}

// Utilisation immédiate
let backend = SqlBackend::new(DuckDbDialect);
```

Pour un backend entièrement différent (ex : un format de fichier), implémenter directement le trait `Backend`.

---

## Connexions réelles aux bases de données

Aujourd'hui, les backends génèrent des **chaînes SQL/Cypher**. L'exécution réelle sur les bases de données n'est pas encore intégrée (voir `Cargo.toml` — zéro dépendance externe).

Pour connecter Catrust à une vraie base, on peut utiliser les crates suivantes avec les statements générés :

| Base | Crate recommandée |
|------|-------------------|
| PostgreSQL | `sqlx` ou `tokio-postgres` |
| Snowflake | `snowflake-connector-rs` ou API HTTP |
| Trino | Client REST Trino |
| Neo4j | `neo4rs` |

```rust
// Exemple d'intégration avec sqlx (PostgreSQL)
use sqlx::PgPool;

let pool = PgPool::connect("postgresql://user:pass@localhost/db").await?;
let backend = SqlBackend::new(PostgresDialect);

for stmt in backend.deploy_schema(&schema) {
    sqlx::query(&stmt).execute(&pool).await?;
}

for stmt in backend.export_instance(&schema, &instance) {
    sqlx::query(&stmt).execute(&pool).await?;
}
```
