KeiSeiKit-1.0/_primitives/_rust/kei-crossdomain/src/schema.rs
Parfii-bot ec205d5ee5 feat(w10a): engine TextPairWithMetadata extra_columns + kei-crossdomain re-migrated
EdgeKeyKind::TextPairWithMetadata extended with:
- from_col / to_col (custom column names, default src_path/dst_path)
- extra_columns: &[(name, FieldKind)] for domain-specific edge metadata

kei-crossdomain fully re-migrated via engine:
- edge_table: Some('cross_edges') + TextPairWithMetadata variant with
  from_col='from_uri', to_col='to_uri', has_id/has_weight/has_created_at,
  extra_columns=[evidence, metadata]
- Custom edges DDL dropped from custom_migrations (engine owns it now)
- edges.rs query_edges SELECT uses edge_id (engine-emitted PK)

Tests: 42/42 kei-entity-store (+2), 5/5 kei-crossdomain preserved.
Sister crates (task/chat/content/social/sage) no regression.

Closes HANDOFF-WAKE deferred item #2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:59:06 +08:00

81 lines
3.5 KiB
Rust

//! kei-crossdomain EntitySchema — declarative spec consumed by
//! `kei_entity_store::Store` for migrations + user_version pragma.
//!
//! **Architectural note (2026-04-23 re-migration, Option B):**
//! kei-crossdomain is an edges-only graph store — URIs (`domain://path`)
//! are the only identifiers; there is no primary "node" entity row. The
//! engine's `EntitySchema` contract requires exactly one `IntegerPk`
//! field, so we declare a minimal synthetic `cross_nodes` table purely
//! to satisfy the DDL contract. No code writes to this table; every
//! query still runs against `cross_edges`.
//!
//! The rich `cross_edges` DDL is now generated by `kei-entity-store` via
//! `EdgeKeyKind::TextPairWithMetadata { from_col: "from_uri", to_col:
//! "to_uri", has_id, has_weight, has_created_at, extra_columns }`. The
//! legacy hand-rolled `custom_migrations` DDL was dropped; see git
//! history for the prior version.
use kei_entity_store::schema::{EdgeKeyKind, EntitySchema, FieldDef, FieldKind};
use rusqlite::{Connection, Result};
/// Synthetic primary table — exists solely to satisfy the engine's
/// `IntegerPk` requirement. Not used by any verb or caller.
static FIELDS: &[FieldDef] = &[FieldDef::pk("id")];
/// Extra columns on `cross_edges` beyond the standard metadata
/// (id / weight / created_at / edge_type). Defaults (`E4`, `{}`) are
/// applied by kei-crossdomain callers at INSERT time since the engine's
/// edge DDL only emits `TEXT DEFAULT ''` for `FieldKind::Text`; existing
/// databases keep their original `DEFAULT 'E4' / '{}'` column attributes
/// via SQLite's `CREATE TABLE IF NOT EXISTS` no-op.
static EDGE_EXTRAS: &[(&str, FieldKind)] = &[
("evidence", FieldKind::Text),
("metadata", FieldKind::Text),
];
pub static CROSSDOMAIN_SCHEMA: EntitySchema = EntitySchema {
name: "crossdomain",
table: "cross_nodes",
fields: FIELDS,
// Empty verb set: every kei-crossdomain op is bespoke (rich typed
// edges with evidence/metadata — engine's `link` verb does dispatch
// extras now, but kei-crossdomain keeps its own typed wrappers in
// `edges.rs`/`bfs.rs`/`auto_link.rs` for the strongly-typed API).
enabled_verbs: &[],
fts_columns: None,
edge_table: Some("cross_edges"),
edge_key_kind: EdgeKeyKind::TextPairWithMetadata {
from_col: "from_uri",
to_col: "to_uri",
has_id: true,
has_weight: true,
has_created_at: true,
extra_columns: EDGE_EXTRAS,
},
archived_field: None,
// Legacy hand-rolled DDL dropped — engine now emits it. Only the
// kei-crossdomain-specific indexes (`idx_ce_from`, `idx_ce_type`)
// live here; the engine auto-emits `idx_cross_edges_dst` on `to_uri`.
custom_migrations: &[
"CREATE INDEX IF NOT EXISTS idx_ce_from ON cross_edges(from_uri);",
"CREATE INDEX IF NOT EXISTS idx_ce_type ON cross_edges(edge_type);",
],
};
/// Kept for backward compatibility with any external caller that
/// imported `schema::create_schema` directly. New code should open via
/// `Store::open` / `Store::open_memory`, which invokes the engine's
/// migration runner with `CROSSDOMAIN_SCHEMA`.
pub fn create_schema(conn: &Connection) -> Result<()> {
// Delegate to the engine's DDL generator so the one-shot path stays
// byte-identical to the engine-driven migration.
let ddl = kei_entity_store::ddl::edge_table_for(
"cross_edges",
CROSSDOMAIN_SCHEMA.edge_key_kind,
);
conn.execute_batch(&ddl)?;
for stmt in CROSSDOMAIN_SCHEMA.custom_migrations {
conn.execute_batch(stmt)?;
}
Ok(())
}