1use std::collections::BTreeMap;
8use std::path::{Path, PathBuf};
9
10use concept_graph::ordinal::Ordinal;
11use redb::{Database, TableHandle};
12
13use crate::record::{Concept, Designation, PropertyValue};
14use crate::tables;
15
16#[derive(Debug, thiserror::Error)]
18pub enum BuildError {
19 #[error("cannot create the artifact at {path}")]
21 Create {
22 path: PathBuf,
24 #[source]
26 source: redb::DatabaseError,
27 },
28 #[error("transaction failed")]
30 Transaction(#[from] redb::TransactionError),
31 #[error("cannot open table")]
33 Table(#[from] redb::TableError),
34 #[error("storage write failed")]
36 Storage(#[from] redb::StorageError),
37 #[error("commit failed")]
39 Commit(#[from] redb::CommitError),
40 #[error("compaction failed")]
42 Compaction(#[from] redb::CompactionError),
43 #[error("{kind} {name:?} is already ordinal {existing}, not {requested}")]
45 Vocabulary {
46 kind: String,
48 name: String,
50 existing: u32,
52 requested: u32,
54 },
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct PreferredRule {
62 pub preferred: u32,
64}
65
66pub struct StoreBuilder {
73 path: PathBuf,
74 db: Database,
75 system: String,
76 version: String,
77 vocabularies: BTreeMap<&'static str, BTreeMap<u32, String>>,
78 codes: BTreeMap<String, u32>,
79 concepts: BTreeMap<u32, Vec<u8>>,
80 designations: BTreeMap<(u32, u32), Vec<u8>>,
81 acceptability: BTreeMap<(u32, u32, u32), u32>,
82 designation_uses: BTreeMap<(u32, u32), u32>,
83 properties: BTreeMap<(u32, u32), Vec<u8>>,
84}
85
86impl std::fmt::Debug for StoreBuilder {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 f.debug_struct("StoreBuilder")
89 .field("path", &self.path)
90 .field("concepts", &self.concepts.len())
91 .field("designations", &self.designations.len())
92 .finish_non_exhaustive()
93 }
94}
95
96impl StoreBuilder {
97 pub fn create(path: &Path, system: &str, version: &str) -> Result<Self, BuildError> {
104 if path.exists() {
105 std::fs::remove_file(path).map_err(|source| BuildError::Create {
106 path: path.to_path_buf(),
107 source: redb::DatabaseError::Storage(redb::StorageError::Io(source)),
108 })?;
109 }
110 let db = Database::create(path).map_err(|source| BuildError::Create {
111 path: path.to_path_buf(),
112 source,
113 })?;
114 Ok(Self {
115 path: path.to_path_buf(),
116 db,
117 system: system.to_owned(),
118 version: version.to_owned(),
119 vocabularies: BTreeMap::new(),
120 codes: BTreeMap::new(),
121 concepts: BTreeMap::new(),
122 designations: BTreeMap::new(),
123 acceptability: BTreeMap::new(),
124 designation_uses: BTreeMap::new(),
125 properties: BTreeMap::new(),
126 })
127 }
128
129 pub fn vocabulary(
136 &mut self,
137 kind: crate::store::Vocabulary,
138 ordinal: u32,
139 name: &str,
140 ) -> Result<(), BuildError> {
141 let table_name = match kind {
142 crate::store::Vocabulary::PropertyKeys => tables::PROPERTY_KEYS.name(),
143 crate::store::Vocabulary::DesignationUses => tables::DESIGNATION_USES.name(),
144 crate::store::Vocabulary::LanguageRefsets => tables::LANGUAGE_REFSETS.name(),
145 crate::store::Vocabulary::Acceptabilities => tables::ACCEPTABILITIES.name(),
146 };
147 let entries = self.vocabularies.entry(table_name).or_default();
148 if let Some((existing, _)) = entries
149 .iter()
150 .find(|(key, value)| value.as_str() == name && **key != ordinal)
151 {
152 return Err(BuildError::Vocabulary {
153 kind: table_name.to_owned(),
154 name: name.to_owned(),
155 existing: *existing,
156 requested: ordinal,
157 });
158 }
159 entries.insert(ordinal, name.to_owned());
160 Ok(())
161 }
162
163 pub fn concept(&mut self, ordinal: Ordinal, concept: &Concept) -> Result<(), BuildError> {
169 self.codes.insert(concept.code.clone(), ordinal.index());
170 self.concepts.insert(ordinal.index(), concept.encode());
171 Ok(())
172 }
173
174 pub fn designation(
180 &mut self,
181 ordinal: Ordinal,
182 index: u32,
183 designation: &Designation,
184 ) -> Result<(), BuildError> {
185 self.designations
186 .insert((ordinal.index(), index), designation.encode());
187 self.designation_uses
188 .insert((ordinal.index(), index), designation.use_ordinal);
189 Ok(())
190 }
191
192 pub fn acceptability(
198 &mut self,
199 ordinal: Ordinal,
200 index: u32,
201 language_refset: u32,
202 acceptability: u32,
203 ) -> Result<(), BuildError> {
204 self.acceptability
205 .insert((ordinal.index(), index, language_refset), acceptability);
206 Ok(())
207 }
208
209 pub fn properties(
215 &mut self,
216 ordinal: Ordinal,
217 key: u32,
218 values: &[PropertyValue],
219 ) -> Result<(), BuildError> {
220 self.properties
221 .insert((ordinal.index(), key), PropertyValue::encode_list(values));
222 Ok(())
223 }
224
225 pub fn finish(self, rule: &PreferredRule) -> Result<PathBuf, BuildError> {
233 let txn = self.db.begin_write()?;
234 {
235 let mut meta = txn.open_table(tables::META)?;
236 meta.insert(tables::META_LAYOUT, tables::LAYOUT_VERSION)?;
237 meta.insert(tables::META_SYSTEM, self.system.as_str())?;
238 meta.insert(tables::META_VERSION, self.version.as_str())?;
239 meta.insert(
240 tables::META_CONCEPTS,
241 self.concepts.len().to_string().as_str(),
242 )?;
243 }
244 {
245 let mut codes = txn.open_table(tables::CODES)?;
246 for (code, ordinal) in &self.codes {
247 codes.insert(code.as_str(), *ordinal)?;
248 }
249 }
250 {
251 let mut concepts = txn.open_table(tables::CONCEPTS)?;
252 for (ordinal, bytes) in &self.concepts {
253 concepts.insert(*ordinal, bytes.as_slice())?;
254 }
255 }
256 {
257 let mut designations = txn.open_table(tables::DESIGNATIONS)?;
258 for (key, bytes) in &self.designations {
259 designations.insert(*key, bytes.as_slice())?;
260 }
261 }
262 {
263 let mut acceptability = txn.open_table(tables::ACCEPTABILITY)?;
264 for (key, value) in &self.acceptability {
265 acceptability.insert(*key, *value)?;
266 }
267 }
268 {
269 let mut properties = txn.open_table(tables::PROPERTIES)?;
270 for (key, bytes) in &self.properties {
271 properties.insert(*key, bytes.as_slice())?;
272 }
273 }
274 for table_def in [
275 tables::PROPERTY_KEYS,
276 tables::DESIGNATION_USES,
277 tables::LANGUAGE_REFSETS,
278 tables::ACCEPTABILITIES,
279 ] {
280 let mut table = txn.open_table(table_def)?;
281 if let Some(entries) = self.vocabularies.get(table_def.name()) {
282 for (ordinal, name) in entries {
283 table.insert(*ordinal, name.as_str())?;
284 }
285 }
286 }
287 {
288 let mut chosen: BTreeMap<(u32, u32, u32), u32> = BTreeMap::new();
291 for ((concept, index, refset), acceptability) in &self.acceptability {
292 if *acceptability != rule.preferred {
293 continue;
294 }
295 let Some(use_ordinal) = self.designation_uses.get(&(*concept, *index)) else {
296 continue;
297 };
298 chosen
299 .entry((*concept, *refset, *use_ordinal))
300 .and_modify(|existing| *existing = (*existing).min(*index))
301 .or_insert(*index);
302 }
303 let mut preferred = txn.open_table(tables::PREFERRED)?;
304 for (key, index) in &chosen {
305 preferred.insert(*key, *index)?;
306 }
307 }
308 txn.commit()?;
309 let mut db = self.db;
313 while db.compact()? {}
314 drop(db);
315 Ok(self.path)
316 }
317}