1use crate::encoding::{Tag, INT_OFFSET, PAYLOAD_BITS};
6use crate::sql::{Request, Statement};
7
8pub const SCHEMA_VERSION: &str = "2";
12
13pub const TBOX_TABLE: &str = "CREATE TABLE IF NOT EXISTS tbox_closure (\
15 kind INTEGER NOT NULL, scope INTEGER NOT NULL, sub INTEGER NOT NULL, sup INTEGER NOT NULL, \
16 PRIMARY KEY (kind, scope, sup, sub)) WITHOUT ROWID, STRICT";
17pub const TBOX_INDEX: &str =
18 "CREATE INDEX IF NOT EXISTS tbox_closure_sub ON tbox_closure(kind, scope, sub, sup)";
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23#[cfg_attr(feature = "serde", serde(default, rename_all = "camelCase"))]
24pub struct StoreOptions {
25 pub graph_index: bool,
27 pub text_index: bool,
29 pub versioning: crate::version::Versioning,
33 pub stamp_index: bool,
36 pub as_of_index: bool,
39 pub system_graphs: bool,
43}
44
45impl Default for StoreOptions {
46 fn default() -> Self {
47 Self {
48 graph_index: true,
49 text_index: false,
50 versioning: crate::version::Versioning::Off,
51 stamp_index: false,
52 as_of_index: false,
53 system_graphs: false,
54 }
55 }
56}
57
58impl StoreOptions {
59 pub fn level_change(&self) -> crate::version::LevelChange {
61 crate::version::LevelChange {
62 as_of_index: self.as_of_index.then_some(true),
63 stamp_index: self.stamp_index.then_some(true),
64 ..Default::default()
65 }
66 }
67}
68
69pub fn schema_sql(options: &StoreOptions) -> String {
71 schema_sql_with(options, &[])
72}
73
74pub fn schema_sql_with(options: &StoreOptions, extra: &[Statement]) -> String {
76 let mut out =
77 String::from("-- oxilite schema (generated by oxilite_core::schema::schema_sql)\n");
78 for s in create_schema(options).statements.iter().chain(extra) {
79 out.push_str(&s.sql);
80 out.push_str(";\n");
81 }
82 out
83}
84
85pub fn create_schema(options: &StoreOptions) -> Request {
89 let mut r = base_schema(options);
90 if options.versioning > crate::version::Versioning::Off {
91 let change = crate::version::change_statements(
92 &crate::version::VersionState::default(),
93 options.versioning,
94 &options.level_change(),
95 )
96 .expect("versioning from off is always possible");
97 r.statements.extend(change);
98 }
99 if options.system_graphs {
100 r.statements
101 .extend(system_graph_statements(&crate::sql::Capabilities::d1()));
102 }
103 r
104}
105
106pub fn system_graph_statements(caps: &crate::sql::Capabilities) -> Vec<Statement> {
109 let quads = crate::registry::system_quads();
110 let mut s = crate::writer::EncodedQuads::new(quads.iter().map(oxrdf::Quad::as_ref))
111 .insert_statements(caps);
112 s.extend(crate::reason::closure_statements());
113 s.extend(crate::shapes::refresh_statements());
114 s
115}
116
117pub fn base_schema(options: &StoreOptions) -> Request {
119 let mut s = vec![
120 "CREATE TABLE IF NOT EXISTS oxilite_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL) STRICT",
121 "CREATE TABLE IF NOT EXISTS terms (\
123 id INTEGER PRIMARY KEY, \
124 lex TEXT NOT NULL, \
125 dt TEXT, \
126 lang TEXT, \
127 dir INTEGER, \
128 num REAL, \
129 nt INTEGER, \
130 ts REAL) STRICT",
131 "CREATE INDEX IF NOT EXISTS terms_num ON terms(num) WHERE num IS NOT NULL",
132 "CREATE INDEX IF NOT EXISTS terms_ts ON terms(ts) WHERE ts IS NOT NULL",
133 "CREATE TRIGGER IF NOT EXISTS terms_collision BEFORE INSERT ON terms \
135 WHEN EXISTS (SELECT 1 FROM terms t WHERE t.id = NEW.id AND \
136 (t.lex IS NOT NEW.lex OR t.dt IS NOT NEW.dt OR t.lang IS NOT NEW.lang OR t.dir IS NOT NEW.dir)) \
137 BEGIN SELECT RAISE(ABORT, 'oxilite: term hash collision'); END",
138 "CREATE TABLE IF NOT EXISTS triple_terms (\
139 id INTEGER PRIMARY KEY, s INTEGER NOT NULL, p INTEGER NOT NULL, o INTEGER NOT NULL, vk TEXT NOT NULL, sk TEXT NOT NULL) STRICT",
140 "CREATE TABLE IF NOT EXISTS quads (\
143 s INTEGER NOT NULL, p INTEGER NOT NULL, o INTEGER NOT NULL, g INTEGER NOT NULL DEFAULT 0, \
144 PRIMARY KEY (s, p, o, g)) WITHOUT ROWID, STRICT",
145 "CREATE INDEX IF NOT EXISTS quads_posg ON quads(p, o, s, g)",
146 "CREATE INDEX IF NOT EXISTS quads_ospg ON quads(o, s, p, g)",
147 "CREATE TABLE IF NOT EXISTS graphs (id INTEGER PRIMARY KEY) STRICT",
148 "CREATE TABLE IF NOT EXISTS stats_pred (\
149 p INTEGER PRIMARY KEY, triples INTEGER NOT NULL, distinct_s INTEGER NOT NULL, distinct_o INTEGER NOT NULL) STRICT",
150 "CREATE TABLE IF NOT EXISTS stats_class (o INTEGER PRIMARY KEY, instances INTEGER NOT NULL) STRICT",
151 "CREATE TABLE IF NOT EXISTS stats_po (p INTEGER NOT NULL, o INTEGER NOT NULL, n INTEGER NOT NULL, PRIMARY KEY (p, o)) WITHOUT ROWID, STRICT",
153 TBOX_TABLE,
157 TBOX_INDEX,
158 "CREATE TABLE IF NOT EXISTS quads_inf (\
159 s INTEGER NOT NULL, p INTEGER NOT NULL, o INTEGER NOT NULL, g INTEGER NOT NULL DEFAULT 0, \
160 PRIMARY KEY (s, p, o, g)) WITHOUT ROWID, STRICT",
161 "CREATE INDEX IF NOT EXISTS quads_inf_posg ON quads_inf(p, o, s, g)",
162 "CREATE INDEX IF NOT EXISTS quads_inf_ospg ON quads_inf(o, s, p, g)",
163 "CREATE TABLE IF NOT EXISTS quads_inf_src (\
166 src INTEGER NOT NULL, s INTEGER NOT NULL, p INTEGER NOT NULL, o INTEGER NOT NULL, g INTEGER NOT NULL DEFAULT 0, \
167 PRIMARY KEY (s, p, o, g, src)) WITHOUT ROWID, STRICT",
168 "CREATE TABLE IF NOT EXISTS inf_producers (id INTEGER PRIMARY KEY, name TEXT NOT NULL) STRICT",
169 "CREATE TABLE IF NOT EXISTS shapes_index (\
172 target TEXT NOT NULL, path TEXT NOT NULL, datatype TEXT, min_count INTEGER, \
173 max_count INTEGER, pattern TEXT, relationship INTEGER NOT NULL DEFAULT 0, \
174 PRIMARY KEY (target, path)) WITHOUT ROWID, STRICT",
175 "CREATE TABLE IF NOT EXISTS shapes_in (\
176 target TEXT NOT NULL, path TEXT NOT NULL, id INTEGER NOT NULL, \
177 lex TEXT, dt TEXT, lang TEXT, dir INTEGER, \
178 PRIMARY KEY (target, path, id)) WITHOUT ROWID, STRICT",
179 "CREATE TABLE IF NOT EXISTS datalog_work (\
184 run INTEGER NOT NULL, rel INTEGER NOT NULL, \
185 c0 INTEGER NOT NULL DEFAULT 0, c1 INTEGER NOT NULL DEFAULT 0, \
186 c2 INTEGER NOT NULL DEFAULT 0, c3 INTEGER NOT NULL DEFAULT 0, \
187 c4 INTEGER NOT NULL DEFAULT 0, c5 INTEGER NOT NULL DEFAULT 0, \
188 PRIMARY KEY (run, rel, c0, c1, c2, c3, c4, c5)) WITHOUT ROWID, STRICT",
189 "CREATE TABLE IF NOT EXISTS update_buffer (\
191 op INTEGER NOT NULL, s INTEGER NOT NULL, p INTEGER NOT NULL, o INTEGER NOT NULL, g INTEGER NOT NULL) STRICT",
192 "CREATE TABLE IF NOT EXISTS oxilite_guard (\
195 graph_does_not_exist INTEGER CHECK (graph_does_not_exist IS NULL), \
196 graph_already_exists INTEGER CHECK (graph_already_exists IS NULL), \
197 computed_value_not_storable INTEGER CHECK (computed_value_not_storable IS NULL)) STRICT",
198 ]
199 .into_iter()
200 .map(Statement::from)
201 .collect::<Vec<_>>();
202 if options.graph_index {
203 s.push("CREATE INDEX IF NOT EXISTS quads_gspo ON quads(g, s, p, o)".into());
204 }
205 if options.text_index {
206 s.extend(crate::text::schema_statements());
207 }
208 s.push(
209 format!(
210 "INSERT OR IGNORE INTO oxilite_meta(key, value) VALUES ('schema_version', '{SCHEMA_VERSION}'), ('graph_index', '{}'), ('int_offset', '{INT_OFFSET}'), ('payload_bits', '{PAYLOAD_BITS}'), ('integer_tag', '{}')",
211 u8::from(options.graph_index),
212 Tag::Integer as u8
213 )
214 .into(),
215 );
216 Request::atomic(s)
217}