1use std::path::{Path, PathBuf};
7
8use concept_graph::ordinal::Ordinal;
9use redb::{ReadOnlyDatabase, ReadableDatabase, ReadableTable, TableHandle};
10
11use crate::record::{Concept, Designation, PropertyValue, RecordError};
12use crate::tables;
13
14#[derive(Debug, thiserror::Error)]
16pub enum StoreError {
17 #[error("cannot open the artifact at {path}")]
19 Open {
20 path: PathBuf,
22 #[source]
24 source: redb::DatabaseError,
25 },
26 #[error("cannot begin a read transaction")]
28 Transaction(#[from] redb::TransactionError),
29 #[error("cannot open table {table}")]
31 Table {
32 table: String,
34 #[source]
36 source: redb::TableError,
37 },
38 #[error("storage read failed")]
40 Storage(#[from] redb::StorageError),
41 #[error("artifact layout {found:?}, expected {expected:?}")]
43 Layout {
44 found: Option<String>,
46 expected: &'static str,
48 },
49 #[error("damaged record in table {table} at key {key}")]
51 Record {
52 table: String,
54 key: String,
56 #[source]
58 source: RecordError,
59 },
60}
61
62pub struct Store {
64 path: PathBuf,
65 db: ReadOnlyDatabase,
66}
67
68impl std::fmt::Debug for Store {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 f.debug_struct("Store")
71 .field("path", &self.path)
72 .finish_non_exhaustive()
73 }
74}
75
76macro_rules! open_table {
77 ($txn:expr, $def:expr) => {
78 $txn.open_table($def).map_err(|source| StoreError::Table {
79 table: $def.name().to_owned(),
80 source,
81 })
82 };
83}
84
85impl Store {
86 pub fn open(path: &Path) -> Result<Self, StoreError> {
93 let db = ReadOnlyDatabase::open(path).map_err(|source| StoreError::Open {
94 path: path.to_path_buf(),
95 source,
96 })?;
97 let store = Self {
98 path: path.to_path_buf(),
99 db,
100 };
101 let layout = store.meta(tables::META_LAYOUT)?;
102 if layout.as_deref() != Some(tables::LAYOUT_VERSION) {
103 return Err(StoreError::Layout {
104 found: layout,
105 expected: tables::LAYOUT_VERSION,
106 });
107 }
108 Ok(store)
109 }
110
111 #[must_use]
113 pub fn path(&self) -> &Path {
114 &self.path
115 }
116
117 pub fn meta(&self, key: &str) -> Result<Option<String>, StoreError> {
123 let txn = self.db.begin_read()?;
124 let table = open_table!(txn, tables::META)?;
125 Ok(table.get(key)?.map(|v| v.value().to_owned()))
126 }
127
128 pub fn ordinal(&self, code: &str) -> Result<Option<Ordinal>, StoreError> {
134 let txn = self.db.begin_read()?;
135 let table = open_table!(txn, tables::CODES)?;
136 Ok(table.get(code)?.map(|v| Ordinal::new(v.value())))
137 }
138
139 pub fn concept(&self, ordinal: Ordinal) -> Result<Option<Concept>, StoreError> {
145 let txn = self.db.begin_read()?;
146 let table = open_table!(txn, tables::CONCEPTS)?;
147 table
148 .get(ordinal.index())?
149 .map(|v| {
150 Concept::decode(v.value()).map_err(|source| StoreError::Record {
151 table: tables::CONCEPTS.name().to_owned(),
152 key: ordinal.to_string(),
153 source,
154 })
155 })
156 .transpose()
157 }
158
159 pub fn designations(&self, ordinal: Ordinal) -> Result<Vec<Designation>, StoreError> {
165 let txn = self.db.begin_read()?;
166 let table = open_table!(txn, tables::DESIGNATIONS)?;
167 let mut out = Vec::new();
168 for entry in table.range((ordinal.index(), 0)..(ordinal.index(), u32::MAX))? {
169 let (key, value) = entry?;
170 out.push(
171 Designation::decode(value.value()).map_err(|source| StoreError::Record {
172 table: tables::DESIGNATIONS.name().to_owned(),
173 key: format!("{:?}", key.value()),
174 source,
175 })?,
176 );
177 }
178 Ok(out)
179 }
180
181 pub fn acceptability(
187 &self,
188 ordinal: Ordinal,
189 designation: u32,
190 language_refset: u32,
191 ) -> Result<Option<u32>, StoreError> {
192 let txn = self.db.begin_read()?;
193 let table = open_table!(txn, tables::ACCEPTABILITY)?;
194 Ok(table
195 .get((ordinal.index(), designation, language_refset))?
196 .map(|v| v.value()))
197 }
198
199 pub fn preferred(
206 &self,
207 ordinal: Ordinal,
208 language_refset: u32,
209 use_ordinal: u32,
210 ) -> Result<Option<Designation>, StoreError> {
211 let txn = self.db.begin_read()?;
212 let preferred = open_table!(txn, tables::PREFERRED)?;
213 let Some(index) = preferred.get((ordinal.index(), language_refset, use_ordinal))? else {
214 return Ok(None);
215 };
216 let designations = open_table!(txn, tables::DESIGNATIONS)?;
217 designations
218 .get((ordinal.index(), index.value()))?
219 .map(|v| {
220 Designation::decode(v.value()).map_err(|source| StoreError::Record {
221 table: tables::DESIGNATIONS.name().to_owned(),
222 key: format!("({ordinal}, {})", index.value()),
223 source,
224 })
225 })
226 .transpose()
227 }
228
229 pub fn properties(
235 &self,
236 ordinal: Ordinal,
237 ) -> Result<Vec<(u32, Vec<PropertyValue>)>, StoreError> {
238 let txn = self.db.begin_read()?;
239 let table = open_table!(txn, tables::PROPERTIES)?;
240 let mut out = Vec::new();
241 for entry in table.range((ordinal.index(), 0)..(ordinal.index(), u32::MAX))? {
242 let (key, value) = entry?;
243 let values =
244 PropertyValue::decode_list(value.value()).map_err(|source| StoreError::Record {
245 table: tables::PROPERTIES.name().to_owned(),
246 key: format!("{:?}", key.value()),
247 source,
248 })?;
249 out.push((key.value().1, values));
250 }
251 Ok(out)
252 }
253
254 pub fn vocabulary(&self, kind: Vocabulary, ordinal: u32) -> Result<Option<String>, StoreError> {
261 let txn = self.db.begin_read()?;
262 let table = open_table!(txn, kind.table())?;
263 Ok(table.get(ordinal)?.map(|v| v.value().to_owned()))
264 }
265
266 pub fn vocabulary_ordinal(
272 &self,
273 kind: Vocabulary,
274 name: &str,
275 ) -> Result<Option<u32>, StoreError> {
276 let txn = self.db.begin_read()?;
277 let table = open_table!(txn, kind.table())?;
278 for entry in table.iter()? {
279 let (key, value) = entry?;
280 if value.value() == name {
281 return Ok(Some(key.value()));
282 }
283 }
284 Ok(None)
285 }
286}
287
288#[derive(Debug, Clone, Copy, PartialEq, Eq)]
290pub enum Vocabulary {
291 PropertyKeys,
293 DesignationUses,
295 LanguageRefsets,
297 Acceptabilities,
299}
300
301impl Vocabulary {
302 fn table(self) -> redb::TableDefinition<'static, u32, &'static str> {
303 match self {
304 Self::PropertyKeys => tables::PROPERTY_KEYS,
305 Self::DesignationUses => tables::DESIGNATION_USES,
306 Self::LanguageRefsets => tables::LANGUAGE_REFSETS,
307 Self::Acceptabilities => tables::ACCEPTABILITIES,
308 }
309 }
310}