recall_echo/graph/
store.rs1use std::path::Path;
4
5use surrealdb::engine::local::SurrealKv;
6use surrealdb::Surreal;
7
8use super::error::GraphError;
9
10pub type Db = surrealdb::engine::local::Db;
11
12const NAMESPACE: &str = "recall";
13const DATABASE: &str = "graph";
14
15pub async fn open(path: &Path) -> Result<Surreal<Db>, GraphError> {
17 let surreal_path = path.join("surreal");
18 std::fs::create_dir_all(&surreal_path)?;
19
20 let db: Surreal<Db> = Surreal::new::<SurrealKv>(surreal_path.to_str().unwrap()).await?;
21 db.use_ns(NAMESPACE).use_db(DATABASE).await?;
22
23 Ok(db)
24}
25
26pub async fn init_schema(db: &Surreal<Db>) -> Result<(), GraphError> {
28 db.query(
29 r#"
30 DEFINE TABLE IF NOT EXISTS entity SCHEMAFULL;
31 DEFINE FIELD IF NOT EXISTS name ON entity TYPE string;
32 DEFINE FIELD IF NOT EXISTS entity_type ON entity TYPE string;
33 DEFINE FIELD IF NOT EXISTS abstract ON entity TYPE string;
34 DEFINE FIELD IF NOT EXISTS overview ON entity TYPE string;
35 DEFINE FIELD IF NOT EXISTS content ON entity TYPE option<string>;
36 DEFINE FIELD IF NOT EXISTS attributes ON entity TYPE option<object> FLEXIBLE;
37 DEFINE FIELD IF NOT EXISTS embedding ON entity TYPE option<array<float>>;
38 DEFINE FIELD IF NOT EXISTS mutable ON entity TYPE bool DEFAULT true;
39 DEFINE FIELD IF NOT EXISTS access_count ON entity TYPE int DEFAULT 0;
40 DEFINE FIELD IF NOT EXISTS created_at ON entity TYPE datetime DEFAULT time::now();
41 DEFINE FIELD IF NOT EXISTS updated_at ON entity TYPE datetime DEFAULT time::now();
42 DEFINE FIELD IF NOT EXISTS source ON entity TYPE option<string>;
43
44 DEFINE INDEX IF NOT EXISTS entity_name ON entity FIELDS name;
45 DEFINE INDEX IF NOT EXISTS entity_type ON entity FIELDS entity_type;
46 DEFINE INDEX IF NOT EXISTS entity_vector ON entity FIELDS embedding HNSW DIMENSION 384 DIST COSINE;
47
48 -- Pipeline attribute indexes
49 DEFINE INDEX IF NOT EXISTS entity_pipeline_stage ON entity FIELDS attributes.pipeline_stage;
50 DEFINE INDEX IF NOT EXISTS entity_pipeline_status ON entity FIELDS attributes.pipeline_status;
51
52 DEFINE TABLE IF NOT EXISTS relates_to SCHEMAFULL TYPE RELATION;
53 DEFINE FIELD IF NOT EXISTS rel_type ON relates_to TYPE string;
54 DEFINE FIELD IF NOT EXISTS description ON relates_to TYPE option<string>;
55 DEFINE FIELD IF NOT EXISTS valid_from ON relates_to TYPE datetime DEFAULT time::now();
56 DEFINE FIELD IF NOT EXISTS valid_until ON relates_to TYPE option<datetime>;
57 DEFINE FIELD IF NOT EXISTS confidence ON relates_to TYPE float DEFAULT 1.0;
58 DEFINE FIELD IF NOT EXISTS source ON relates_to TYPE option<string>;
59
60 DEFINE INDEX IF NOT EXISTS rel_type_idx ON relates_to FIELDS rel_type;
61
62 DEFINE TABLE IF NOT EXISTS episode SCHEMAFULL;
63 DEFINE FIELD IF NOT EXISTS session_id ON episode TYPE string;
64 DEFINE FIELD IF NOT EXISTS timestamp ON episode TYPE datetime DEFAULT time::now();
65 DEFINE FIELD IF NOT EXISTS abstract ON episode TYPE string;
66 DEFINE FIELD IF NOT EXISTS overview ON episode TYPE option<string>;
67 DEFINE FIELD IF NOT EXISTS content ON episode TYPE option<string>;
68 DEFINE FIELD IF NOT EXISTS embedding ON episode TYPE option<array<float>>;
69 DEFINE FIELD IF NOT EXISTS log_number ON episode TYPE option<int>;
70 DEFINE FIELD IF NOT EXISTS extracted ON episode TYPE bool DEFAULT false;
71
72 -- Backfill: set extracted = false on episodes that predate the field
73 UPDATE episode SET extracted = false WHERE extracted IS NONE;
74
75 DEFINE INDEX IF NOT EXISTS episode_session ON episode FIELDS session_id;
76 DEFINE INDEX IF NOT EXISTS episode_time ON episode FIELDS timestamp;
77 DEFINE INDEX IF NOT EXISTS episode_vector ON episode FIELDS embedding HNSW DIMENSION 384 DIST COSINE;
78 "#,
79 )
80 .await?
81 .check()?;
82
83 Ok(())
84}