1use rusqlite::Connection;
3
4use crate::errors::Result;
5use crate::events::sql_error;
6use crate::graph::TxGuard;
7
8pub fn initialize_database(conn: &Connection) -> Result<()> {
16 let tx = TxGuard::begin(conn)?;
17 let relation_existed_before_bootstrap: bool = conn
18 .query_row(
19 "SELECT EXISTS(SELECT 1 FROM sqlite_schema WHERE type='table' AND name='relation')",
20 [],
21 |row| row.get(0),
22 )
23 .map_err(sql_error)?;
24 conn.execute_batch(
25 "CREATE TABLE IF NOT EXISTS entity (
26 id INTEGER PRIMARY KEY,
27 name_hash INTEGER NOT NULL,
28 name TEXT NOT NULL,
29 type_id INTEGER NOT NULL,
30 obs_count INTEGER NOT NULL DEFAULT 0,
31 out_deg INTEGER NOT NULL DEFAULT 0,
32 in_deg INTEGER NOT NULL DEFAULT 0,
33 created_us INTEGER NOT NULL,
34 updated_us INTEGER NOT NULL,
35 flags INTEGER NOT NULL DEFAULT 0
36 ) STRICT;
37
38 CREATE INDEX IF NOT EXISTS entity_by_hash
39 ON entity(name_hash, type_id, obs_count, out_deg, in_deg)
40 WHERE flags = 0;
41
42 CREATE INDEX IF NOT EXISTS entity_name_ci
43 ON entity(lower(name))
44 WHERE flags = 0;
45
46 CREATE TABLE IF NOT EXISTS observation (
47 id INTEGER PRIMARY KEY,
48 entity_id INTEGER NOT NULL,
49 idx INTEGER NOT NULL,
50 body TEXT NOT NULL,
51 created_us INTEGER NOT NULL
52 ) STRICT;
53
54 CREATE INDEX IF NOT EXISTS obs_by_entity
55 ON observation(entity_id, idx);
56
57 CREATE TABLE IF NOT EXISTS relation (
58 from_id INTEGER NOT NULL,
59 to_id INTEGER NOT NULL,
60 type_id INTEGER NOT NULL,
61 created_us INTEGER NOT NULL
62 ) STRICT;
63
64 CREATE INDEX IF NOT EXISTS rel_out
65 ON relation(from_id, type_id, to_id);
66
67 CREATE INDEX IF NOT EXISTS rel_in
68 ON relation(to_id, type_id, from_id);
69
70 CREATE VIRTUAL TABLE IF NOT EXISTS name_fts
71 USING fts5(name, content='entity', content_rowid='id',
72 tokenize='unicode61 remove_diacritics 2');
73
74 CREATE VIRTUAL TABLE IF NOT EXISTS obs_fts
75 USING fts5(body, content='observation', content_rowid='id',
76 tokenize='unicode61 remove_diacritics 2');
77
78 CREATE TRIGGER IF NOT EXISTS obs_fts_ai AFTER INSERT ON observation BEGIN
79 INSERT INTO obs_fts(rowid, body) VALUES (new.id, new.body);
80 END;
81
82 CREATE TRIGGER IF NOT EXISTS obs_fts_bd BEFORE DELETE ON observation BEGIN
83 INSERT INTO obs_fts(obs_fts, rowid, body) VALUES ('delete', old.id, old.body);
84 END;
85
86 CREATE TABLE IF NOT EXISTS type_dict (
87 id INTEGER PRIMARY KEY,
88 kind INTEGER NOT NULL,
89 name TEXT NOT NULL,
90 count INTEGER NOT NULL DEFAULT 0
91 ) STRICT;
92
93 CREATE INDEX IF NOT EXISTS type_by_name
94 ON type_dict(kind, name);
95
96 CREATE TABLE IF NOT EXISTS graph_stat (
97 key TEXT NOT NULL PRIMARY KEY,
98 value INTEGER NOT NULL
99 ) STRICT, WITHOUT ROWID;
100
101 CREATE TABLE IF NOT EXISTS hub_degree (
102 entity_id INTEGER PRIMARY KEY,
103 out_deg INTEGER NOT NULL,
104 in_deg INTEGER NOT NULL
105 ) STRICT;
106
107 CREATE TABLE IF NOT EXISTS partition_map (
108 table_name TEXT NOT NULL PRIMARY KEY,
109 role INTEGER NOT NULL,
110 type_id INTEGER,
111 row_count INTEGER NOT NULL DEFAULT 0
112 ) STRICT, WITHOUT ROWID;",
113 )
114 .map_err(sql_error)?;
115 if !relation_existed_before_bootstrap {
116 conn.execute_batch(
117 "CREATE UNIQUE INDEX relation_unique_triple
118 ON relation(from_id, to_id, type_id);",
119 )
120 .map_err(sql_error)?;
121 }
122 let has_stat: bool = conn
123 .query_row("SELECT EXISTS(SELECT 1 FROM graph_stat)", [], |row| {
124 row.get(0)
125 })
126 .map_err(sql_error)?;
127 if !has_stat {
128 conn.execute_batch(
129 "INSERT INTO graph_stat(key, value) VALUES
130 ('entities', 0), ('relations', 0), ('observations', 0),
131 ('entity_seq', 0), ('obs_seq', 0);",
132 )
133 .map_err(sql_error)?;
134 }
135 tx.commit()?;
136 crate::events::migrate(conn)
137}