mentat/vocabulary.rs
1// Copyright 2016-2018 Mozilla
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4// this file except in compliance with the License. You may obtain a copy of the
5// License at http://www.apache.org/licenses/LICENSE-2.0
6// Unless required by applicable law or agreed to in writing, software distributed
7// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
8// CONDITIONS OF ANY KIND, either express or implied. See the License for the
9// specific language governing permissions and limitations under the License.
10
11//! This module exposes an interface for programmatic management of vocabularies.
12//!
13//! A vocabulary is defined by a name, a version number, and a collection of attribute definitions.
14//!
15//! Operations on vocabularies can include migrations between versions. These are defined
16//! programmatically as a pair of functions, `pre` and `post`, that are invoked prior to
17//! an upgrade.
18//!
19//! A Mentat store exposes, via the `HasSchema` trait, operations to read
20//! vocabularies by name or in bulk.
21//!
22//! An in-progress transaction (`InProgress`) further exposes a trait,
23//! `VersionedStore`, which allows for a vocabulary definition to be
24//! checked for existence in the store, and transacted if needed.
25//!
26//! Typical use is the following:
27//!
28//! ```
29//! #[macro_use(kw)]
30//! extern crate mentat;
31//!
32//! use mentat::{
33//! Store,
34//! ValueType,
35//! };
36//!
37//! use mentat::vocabulary;
38//! use mentat::vocabulary::{
39//! Definition,
40//! HasVocabularies,
41//! VersionedStore,
42//! VocabularyOutcome,
43//! };
44//!
45//! fn main() {
46//! let mut store = Store::open("").expect("connected");
47//!
48//! {
49//! // Read the list of installed vocabularies.
50//! let reader = store.begin_read().expect("began read");
51//! let vocabularies = reader.read_vocabularies().expect("read");
52//! for (name, vocabulary) in vocabularies.iter() {
53//! println!("Vocab {} is at version {}.", name, vocabulary.version);
54//! for &(ref name, ref attr) in vocabulary.attributes().iter() {
55//! println!(" >> {} ({})", name, attr.value_type);
56//! }
57//! }
58//! }
59//!
60//! {
61//! let mut in_progress = store.begin_transaction().expect("began transaction");
62//!
63//! // Make sure the core vocabulary exists.
64//! in_progress.verify_core_schema().expect("verified");
65//!
66//! // Make sure our vocabulary is installed, and install if necessary.
67//! in_progress.ensure_vocabulary(&Definition {
68//! name: kw!(:example/links),
69//! version: 1,
70//! attributes: vec![
71//! (kw!(:link/title),
72//! vocabulary::AttributeBuilder::helpful()
73//! .value_type(ValueType::String)
74//! .multival(false)
75//! .fulltext(true)
76//! .build()),
77//! ],
78//! pre: Definition::no_op,
79//! post: Definition::no_op,
80//! }).expect("ensured");
81//!
82//! // Now we can do stuff.
83//! in_progress.transact("[{:link/title \"Title\"}]").expect("transacts");
84//! in_progress.commit().expect("commits");
85//! }
86//! }
87//! ```
88//!
89//! A similar approach is taken using the
90//! `VocabularyProvider` trait to handle migrations across
91//! multiple vocabularies.
92
93use std::collections::BTreeMap;
94
95use core_traits::KnownEntid;
96
97use core_traits::attribute::Unique;
98
99use super::{
100 Attribute, Binding, Entid, HasSchema, IntoResult, Keyword, TypedValue, ValueType,
101 CORE_SCHEMA_VERSION,
102};
103
104use super::errors::{MentatError, Result};
105
106use mentat_transaction::{InProgress, Queryable};
107
108use mentat_transaction::entity_builder::{BuildTerms, TermBuilder, Terms};
109
110/// AttributeBuilder is how you build vocabulary definitions to apply to a store.
111pub use mentat_db::AttributeBuilder;
112
113pub type Version = u32;
114pub type Datom = (Entid, Entid, TypedValue);
115
116/// A definition of an attribute that is independent of a particular store.
117///
118/// `Attribute` instances not only aren't named, but don't even have entids.
119///
120/// We need two kinds of structure: an abstract definition of a vocabulary in terms of names,
121/// and a concrete instance of a vocabulary in a particular store.
122///
123/// `Definition` is the former, and `Vocabulary` is the latter.
124///
125/// Note that, because it's possible to 'flesh out' a vocabulary with attributes without bumping
126/// its version number, we need to track the attributes that the application cares about — it's
127/// not enough to know the name and version. Indeed, we even care about the details of each attribute,
128/// because that's how we'll detect errors.
129///
130/// `Definition` includes two additional fields: functions to run if this vocabulary is being
131/// upgraded. `pre` and `post` are run before and after the definition is transacted against the
132/// store. Each is called with the existing `Vocabulary` instance so that they can do version
133/// checks or employ more fine-grained logic.
134#[derive(Clone)]
135pub struct Definition {
136 pub name: Keyword,
137 pub version: Version,
138 pub attributes: Vec<(Keyword, Attribute)>,
139 pub pre: fn(&mut InProgress<'_, '_>, &Vocabulary) -> Result<()>,
140 pub post: fn(&mut InProgress<'_, '_>, &Vocabulary) -> Result<()>,
141}
142
143/// ```
144/// #[macro_use(kw)]
145/// extern crate mentat;
146///
147/// use mentat::{
148/// HasSchema,
149/// IntoResult,
150/// Queryable,
151/// Store,
152/// TypedValue,
153/// ValueType,
154/// };
155///
156/// use mentat::entity_builder::{
157/// BuildTerms,
158/// TermBuilder,
159/// };
160///
161/// use mentat::vocabulary;
162/// use mentat::vocabulary::{
163/// AttributeBuilder,
164/// Definition,
165/// HasVocabularies,
166/// VersionedStore,
167/// };
168///
169/// fn main() {
170/// let mut store = Store::open("").expect("connected");
171/// let mut in_progress = store.begin_transaction().expect("began transaction");
172///
173/// // Make sure the core vocabulary exists.
174/// in_progress.verify_core_schema().expect("verified");
175///
176/// // Make sure our vocabulary is installed, and install if necessary.
177/// in_progress.ensure_vocabulary(&Definition {
178/// name: kw!(:example/links),
179/// version: 2,
180/// attributes: vec![
181/// (kw!(:link/title),
182/// AttributeBuilder::helpful()
183/// .value_type(ValueType::String)
184/// .multival(false)
185/// .fulltext(true)
186/// .build()),
187/// ],
188/// pre: |ip, from| {
189/// // Version one allowed multiple titles; version two
190/// // doesn't. Retract any duplicates we find.
191/// if from.version < 2 {
192/// let link_title = ip.get_entid(&kw!(:link/title)).unwrap();
193///
194/// let results = ip.q_once(r#"
195/// [:find ?e ?t2
196/// :where [?e :link/title ?t1]
197/// [?e :link/title ?t2]
198/// [(unpermute ?t1 ?t2)]]
199/// "#, None).into_rel_result()?;
200///
201/// if !results.is_empty() {
202/// let mut builder = TermBuilder::new();
203/// for row in results.into_iter() {
204/// let mut r = row.into_iter();
205/// let e = r.next().and_then(|e| e.into_known_entid()).expect("entity");
206/// let obsolete = r.next().expect("value").into_scalar().expect("typed value");
207/// builder.retract(e, link_title, obsolete)?;
208/// }
209/// ip.transact_builder(builder)?;
210/// }
211/// }
212/// Ok(())
213/// },
214/// post: |_ip, from| {
215/// println!("We migrated :example/links from version {}", from.version);
216/// Ok(())
217/// },
218/// }).expect("ensured");
219///
220/// // Now we can do stuff.
221/// in_progress.transact("[{:link/title \"Title\"}]").expect("transacts");
222/// in_progress.commit().expect("commits");
223/// }
224/// ```
225impl Definition {
226 pub fn no_op(_ip: &mut InProgress<'_, '_>, _from: &Vocabulary) -> Result<()> {
227 Ok(())
228 }
229
230 pub fn new<N, A>(name: N, version: Version, attributes: A) -> Definition
231 where
232 N: Into<Keyword>,
233 A: Into<Vec<(Keyword, Attribute)>>,
234 {
235 Definition {
236 name: name.into(),
237 version,
238 attributes: attributes.into(),
239 pre: Definition::no_op,
240 post: Definition::no_op,
241 }
242 }
243
244 /// Called with an in-progress transaction and the previous vocabulary version
245 /// if the definition's version is later than that of the vocabulary in the store.
246 fn pre(&self, ip: &mut InProgress<'_, '_>, from: &Vocabulary) -> Result<()> {
247 (self.pre)(ip, from)
248 }
249
250 /// Called with an in-progress transaction and the previous vocabulary version
251 /// if the definition's version is later than that of the vocabulary in the store.
252 fn post(&self, ip: &mut InProgress<'_, '_>, from: &Vocabulary) -> Result<()> {
253 (self.post)(ip, from)
254 }
255}
256
257/// A definition of a vocabulary as retrieved from a particular store.
258///
259/// A `Vocabulary` is just like `Definition`, but concrete: its name and attributes are identified
260/// by `Entid`, not `Keyword`.
261#[derive(Clone, Debug, Eq, PartialEq)]
262pub struct Vocabulary {
263 pub entity: Entid,
264 pub version: Version,
265 attributes: Vec<(Entid, Attribute)>,
266}
267
268impl Vocabulary {
269 pub fn attributes(&self) -> &Vec<(Entid, Attribute)> {
270 &self.attributes
271 }
272}
273
274/// A collection of named `Vocabulary` instances, as retrieved from the store.
275#[derive(Debug, Default, Clone)]
276pub struct Vocabularies(pub BTreeMap<Keyword, Vocabulary>); // N.B., this has a copy of the attributes in Schema!
277
278impl Vocabularies {
279 pub fn len(&self) -> usize {
280 self.0.len()
281 }
282
283 pub fn is_empty(&self) -> bool {
284 self.0.len() == 0
285 }
286
287 pub fn get(&self, name: &Keyword) -> Option<&Vocabulary> {
288 self.0.get(name)
289 }
290
291 pub fn iter(&self) -> ::std::collections::btree_map::Iter<'_, Keyword, Vocabulary> {
292 self.0.iter()
293 }
294}
295
296lazy_static! {
297 static ref DB_SCHEMA_CORE: Keyword = kw!(:db.schema/core);
298 static ref DB_SCHEMA_ATTRIBUTE: Keyword = kw!(:db.schema/attribute);
299 static ref DB_SCHEMA_VERSION: Keyword = kw!(:db.schema/version);
300 static ref DB_IDENT: Keyword = kw!(:db/ident);
301 static ref DB_UNIQUE: Keyword = kw!(:db/unique);
302 static ref DB_UNIQUE_VALUE: Keyword = kw!(:db.unique/value);
303 static ref DB_UNIQUE_IDENTITY: Keyword = kw!(:db.unique/identity);
304 static ref DB_IS_COMPONENT: Keyword = Keyword::namespaced("db", "isComponent");
305 static ref DB_VALUE_TYPE: Keyword = Keyword::namespaced("db", "valueType");
306 static ref DB_INDEX: Keyword = kw!(:db/index);
307 static ref DB_FULLTEXT: Keyword = kw!(:db/fulltext);
308 static ref DB_CARDINALITY: Keyword = kw!(:db/cardinality);
309 static ref DB_CARDINALITY_ONE: Keyword = kw!(:db.cardinality/one);
310 static ref DB_CARDINALITY_MANY: Keyword = kw!(:db.cardinality/many);
311 static ref DB_NO_HISTORY: Keyword = Keyword::namespaced("db", "noHistory");
312}
313
314trait HasCoreSchema {
315 /// Return the entity ID for a type. On failure, return `MissingCoreVocabulary`.
316 fn core_type(&self, t: ValueType) -> Result<KnownEntid>;
317
318 /// Return the entity ID for an ident. On failure, return `MissingCoreVocabulary`.
319 fn core_entid(&self, ident: &Keyword) -> Result<KnownEntid>;
320
321 /// Return the entity ID for an attribute's keyword. On failure, return
322 /// `MissingCoreVocabulary`.
323 fn core_attribute(&self, ident: &Keyword) -> Result<KnownEntid>;
324}
325
326impl<T> HasCoreSchema for T
327where
328 T: HasSchema,
329{
330 fn core_type(&self, t: ValueType) -> Result<KnownEntid> {
331 self.entid_for_type(t)
332 .ok_or_else(|| MentatError::MissingCoreVocabulary(DB_SCHEMA_VERSION.clone()))
333 }
334
335 fn core_entid(&self, ident: &Keyword) -> Result<KnownEntid> {
336 self.get_entid(ident)
337 .ok_or_else(|| MentatError::MissingCoreVocabulary(DB_SCHEMA_VERSION.clone()))
338 }
339
340 fn core_attribute(&self, ident: &Keyword) -> Result<KnownEntid> {
341 self.attribute_for_ident(ident)
342 .ok_or_else(|| MentatError::MissingCoreVocabulary(DB_SCHEMA_VERSION.clone()))
343 .map(|(_, e)| e)
344 }
345}
346
347impl Definition {
348 fn description_for_attributes<T, R>(
349 &self,
350 attributes: &[R],
351 via: &T,
352 diff: Option<BTreeMap<Keyword, Attribute>>,
353 ) -> Result<Terms>
354 where
355 T: HasCoreSchema,
356 R: ::std::borrow::Borrow<(Keyword, Attribute)>,
357 {
358 // The attributes we'll need to describe this vocabulary.
359 let a_version = via.core_attribute(&DB_SCHEMA_VERSION)?;
360 let a_ident = via.core_attribute(&DB_IDENT)?;
361 let a_attr = via.core_attribute(&DB_SCHEMA_ATTRIBUTE)?;
362
363 let a_cardinality = via.core_attribute(&DB_CARDINALITY)?;
364 let a_fulltext = via.core_attribute(&DB_FULLTEXT)?;
365 let a_index = via.core_attribute(&DB_INDEX)?;
366 let a_is_component = via.core_attribute(&DB_IS_COMPONENT)?;
367 let a_value_type = via.core_attribute(&DB_VALUE_TYPE)?;
368 let a_unique = via.core_attribute(&DB_UNIQUE)?;
369
370 let a_no_history = via.core_attribute(&DB_NO_HISTORY)?;
371
372 let v_cardinality_many = via.core_entid(&DB_CARDINALITY_MANY)?;
373 let v_cardinality_one = via.core_entid(&DB_CARDINALITY_ONE)?;
374 let v_unique_identity = via.core_entid(&DB_UNIQUE_IDENTITY)?;
375 let v_unique_value = via.core_entid(&DB_UNIQUE_VALUE)?;
376
377 // The properties of the vocabulary itself.
378 let name: TypedValue = self.name.clone().into();
379 let version: TypedValue = TypedValue::Long(self.version as i64);
380
381 // Describe the vocabulary.
382 let mut entity = TermBuilder::new().describe_tempid("s");
383 entity.add(a_version, version)?;
384 entity.add(a_ident, name)?;
385 let (mut builder, schema) = entity.finish();
386
387 // Describe each of its attributes.
388 // This is a lot like Schema::to_edn_value; at some point we should tidy this up.
389 for r in attributes.iter() {
390 let (kw, attr) = r.borrow();
391
392 let tempid = builder.named_tempid(kw.to_string());
393 let name: TypedValue = kw.clone().into();
394 builder.add(tempid.clone(), a_ident, name)?;
395 builder.add(schema.clone(), a_attr, tempid.clone())?;
396
397 let value_type = via.core_type(attr.value_type)?;
398 builder.add(tempid.clone(), a_value_type, value_type)?;
399
400 let c = if attr.multival {
401 v_cardinality_many
402 } else {
403 v_cardinality_one
404 };
405 builder.add(tempid.clone(), a_cardinality, c)?;
406
407 // These are all unconditional because we use attribute descriptions to _alter_, not
408 // just to _add_, and so absence is distinct from negation!
409 builder.add(tempid.clone(), a_index, TypedValue::Boolean(attr.index))?;
410 builder.add(
411 tempid.clone(),
412 a_fulltext,
413 TypedValue::Boolean(attr.fulltext),
414 )?;
415 builder.add(
416 tempid.clone(),
417 a_is_component,
418 TypedValue::Boolean(attr.component),
419 )?;
420 builder.add(
421 tempid.clone(),
422 a_no_history,
423 TypedValue::Boolean(attr.no_history),
424 )?;
425
426 if let Some(u) = attr.unique {
427 let uu = match u {
428 Unique::Identity => v_unique_identity,
429 Unique::Value => v_unique_value,
430 };
431 builder.add(tempid.clone(), a_unique, uu)?;
432 } else {
433 let existing_unique = if let Some(ref diff) = diff {
434 diff.get(kw).and_then(|a| a.unique)
435 } else {
436 None
437 };
438 match existing_unique {
439 None => {
440 // Nothing to do.
441 }
442 Some(Unique::Identity) => {
443 builder.retract(tempid.clone(), a_unique, v_unique_identity)?;
444 }
445 Some(Unique::Value) => {
446 builder.retract(tempid.clone(), a_unique, v_unique_value)?;
447 }
448 }
449 }
450 }
451
452 builder.build()
453 }
454
455 /// Return a sequence of terms that describes this vocabulary definition and its attributes.
456 fn description_diff<T>(&self, via: &T, from: &Vocabulary) -> Result<Terms>
457 where
458 T: HasSchema,
459 {
460 let relevant = self
461 .attributes
462 .iter()
463 .filter_map(|(keyword, _)|
464 // Look up the keyword to see if it's currently in use.
465 via.get_entid(keyword)
466
467 // If so, map it to the existing attribute.
468 .and_then(|e| from.find(e).cloned())
469
470 // Collect enough that we can do lookups.
471 .map(|e| (keyword.clone(), e)))
472 .collect();
473 self.description_for_attributes(self.attributes.as_slice(), via, Some(relevant))
474 }
475
476 /// Return a sequence of terms that describes this vocabulary definition and its attributes.
477 fn description<T>(&self, via: &T) -> Result<Terms>
478 where
479 T: HasSchema,
480 {
481 self.description_for_attributes(self.attributes.as_slice(), via, None)
482 }
483}
484
485/// This enum captures the various relationships between a particular vocabulary pair — one
486/// `Definition` and one `Vocabulary`, if present.
487#[derive(Debug, Eq, PartialEq)]
488pub enum VocabularyCheck<'definition> {
489 /// The provided definition is not already present in the store.
490 NotPresent,
491
492 /// The provided definition is present in the store, and all of its attributes exist.
493 Present,
494
495 /// The provided definition is present in the store with an earlier version number.
496 PresentButNeedsUpdate { older_version: Vocabulary },
497
498 /// The provided definition is present in the store with a more recent version number.
499 PresentButTooNew { newer_version: Vocabulary },
500
501 /// The provided definition is present in the store, but some of its attributes are not.
502 PresentButMissingAttributes {
503 attributes: Vec<&'definition (Keyword, Attribute)>,
504 },
505}
506
507/// This enum captures the outcome of attempting to ensure that a vocabulary definition is present
508/// and up-to-date in the store.
509#[derive(Debug, Eq, PartialEq)]
510pub enum VocabularyOutcome {
511 /// The vocabulary was absent and has been installed.
512 Installed,
513
514 /// The vocabulary was present with this version, but some attributes were absent.
515 /// They have been installed.
516 InstalledMissingAttributes,
517
518 /// The vocabulary was present, at the correct version, and all attributes were present.
519 Existed,
520
521 /// The vocabulary was present, at an older version, and it has been upgraded. Any
522 /// missing attributes were installed.
523 Upgraded,
524}
525
526/// This trait captures the ability to retrieve and describe stored vocabularies.
527pub trait HasVocabularies {
528 fn read_vocabularies(&self) -> Result<Vocabularies>;
529 fn read_vocabulary_named(&self, name: &Keyword) -> Result<Option<Vocabulary>>;
530}
531
532/// This trait captures the ability of a store to check and install/upgrade vocabularies.
533pub trait VersionedStore: HasVocabularies + HasSchema {
534 /// Check whether the vocabulary described by the provided metadata is present in the store.
535 fn check_vocabulary<'definition>(
536 &self,
537 definition: &'definition Definition,
538 ) -> Result<VocabularyCheck<'definition>> {
539 if let Some(vocabulary) = self.read_vocabulary_named(&definition.name)? {
540 // The name is present.
541 // Check the version.
542 if vocabulary.version == definition.version {
543 // Same version. Check that all of our attributes are present.
544 let mut missing: Vec<&'definition (Keyword, Attribute)> = vec![];
545 for pair in definition.attributes.iter() {
546 if let Some(entid) = self.get_entid(&pair.0) {
547 if let Some(existing) = vocabulary.find(entid) {
548 if *existing == pair.1 {
549 // Same. Phew.
550 continue;
551 } else {
552 // We have two vocabularies with the same name, same version, and
553 // different definitions for an attribute. That's a coding error.
554 // We can't accept this vocabulary.
555 bail!(MentatError::ConflictingAttributeDefinitions(
556 definition.name.to_string(),
557 definition.version,
558 pair.0.to_string(),
559 existing.clone(),
560 pair.1.clone()
561 ));
562 }
563 }
564 }
565 // It's missing. Collect it.
566 missing.push(pair);
567 }
568 if missing.is_empty() {
569 Ok(VocabularyCheck::Present)
570 } else {
571 Ok(VocabularyCheck::PresentButMissingAttributes {
572 attributes: missing,
573 })
574 }
575 } else if vocabulary.version < definition.version {
576 // Ours is newer. Upgrade.
577 Ok(VocabularyCheck::PresentButNeedsUpdate {
578 older_version: vocabulary,
579 })
580 } else {
581 // The vocabulary in the store is newer. We are outdated.
582 Ok(VocabularyCheck::PresentButTooNew {
583 newer_version: vocabulary,
584 })
585 }
586 } else {
587 // The vocabulary isn't present in the store. Install it.
588 Ok(VocabularyCheck::NotPresent)
589 }
590 }
591
592 /// Check whether the provided vocabulary is present in the store. If it isn't, make it so.
593 fn ensure_vocabulary(&mut self, definition: &Definition) -> Result<VocabularyOutcome>;
594
595 /// Check whether the provided vocabularies are present in the store at the correct
596 /// version and with all defined attributes. If any are not, invoke the `pre`
597 /// function on the provided `VocabularySource`, install or upgrade the necessary vocabularies,
598 /// then invoke `post`. Returns `Ok` if all of these steps succeed.
599 ///
600 /// Use this function instead of calling `ensure_vocabulary` if you need to have pre/post
601 /// functions invoked when vocabulary changes are necessary.
602 fn ensure_vocabularies(
603 &mut self,
604 vocabularies: &mut dyn VocabularySource,
605 ) -> Result<BTreeMap<Keyword, VocabularyOutcome>>;
606
607 /// Make sure that our expectations of the core vocabulary — basic types and attributes — are met.
608 fn verify_core_schema(&self) -> Result<()> {
609 if let Some(core) = self.read_vocabulary_named(&DB_SCHEMA_CORE)? {
610 if core.version != CORE_SCHEMA_VERSION {
611 bail!(MentatError::UnexpectedCoreSchema(
612 CORE_SCHEMA_VERSION,
613 Some(core.version)
614 ));
615 }
616
617 // TODO: check things other than the version.
618 } else {
619 // This would be seriously messed up.
620 bail!(MentatError::UnexpectedCoreSchema(CORE_SCHEMA_VERSION, None));
621 }
622 Ok(())
623 }
624}
625
626/// `VocabularyStatus` is passed to `pre` function when attempting to add or upgrade vocabularies
627/// via `ensure_vocabularies`. This is how you can find the status and versions of existing
628/// vocabularies — you can retrieve the requested definition and the resulting `VocabularyCheck`
629/// by name.
630pub trait VocabularyStatus {
631 fn get(&self, name: &Keyword) -> Option<(&Definition, &VocabularyCheck<'_>)>;
632 fn version(&self, name: &Keyword) -> Option<Version>;
633}
634
635#[derive(Default)]
636struct CheckedVocabularies<'a> {
637 items: BTreeMap<Keyword, (&'a Definition, VocabularyCheck<'a>)>,
638}
639
640impl<'a> CheckedVocabularies<'a> {
641 fn add(&mut self, definition: &'a Definition, check: VocabularyCheck<'a>) {
642 self.items
643 .insert(definition.name.clone(), (definition, check));
644 }
645
646 fn is_empty(&self) -> bool {
647 self.items.is_empty()
648 }
649}
650
651impl VocabularyStatus for CheckedVocabularies<'_> {
652 fn get(&self, name: &Keyword) -> Option<(&Definition, &VocabularyCheck<'_>)> {
653 self.items.get(name).map(|&(d, ref c)| (d, c))
654 }
655
656 fn version(&self, name: &Keyword) -> Option<Version> {
657 self.items.get(name).map(|&(d, _)| d.version)
658 }
659}
660
661trait VocabularyMechanics {
662 fn install_vocabulary(&mut self, definition: &Definition) -> Result<VocabularyOutcome>;
663 fn install_attributes_for<'definition>(
664 &mut self,
665 definition: &'definition Definition,
666 attributes: Vec<&'definition (Keyword, Attribute)>,
667 ) -> Result<VocabularyOutcome>;
668 fn upgrade_vocabulary(
669 &mut self,
670 definition: &Definition,
671 from_version: Vocabulary,
672 ) -> Result<VocabularyOutcome>;
673}
674
675impl Vocabulary {
676 // TODO: don't do linear search!
677 fn find<T>(&self, entid: T) -> Option<&Attribute>
678 where
679 T: Into<Entid>,
680 {
681 let to_find = entid.into();
682 self.attributes
683 .iter()
684 .find(|&&(e, _)| e == to_find)
685 .map(|(_, a)| a)
686 }
687}
688
689impl VersionedStore for InProgress<'_, '_> {
690 fn ensure_vocabulary(&mut self, definition: &Definition) -> Result<VocabularyOutcome> {
691 match self.check_vocabulary(definition)? {
692 VocabularyCheck::Present => Ok(VocabularyOutcome::Existed),
693 VocabularyCheck::NotPresent => self.install_vocabulary(definition),
694 VocabularyCheck::PresentButNeedsUpdate { older_version } => {
695 self.upgrade_vocabulary(definition, older_version)
696 }
697 VocabularyCheck::PresentButMissingAttributes { attributes } => {
698 self.install_attributes_for(definition, attributes)
699 }
700 VocabularyCheck::PresentButTooNew { newer_version } => {
701 Err(MentatError::ExistingVocabularyTooNew(
702 definition.name.to_string(),
703 newer_version.version,
704 definition.version,
705 ))
706 }
707 }
708 }
709
710 fn ensure_vocabularies(
711 &mut self,
712 vocabularies: &mut dyn VocabularySource,
713 ) -> Result<BTreeMap<Keyword, VocabularyOutcome>> {
714 let definitions = vocabularies.definitions();
715
716 let mut update = Vec::new();
717 let mut missing = Vec::new();
718 let mut out = BTreeMap::new();
719
720 let mut work = CheckedVocabularies::default();
721
722 for definition in definitions.iter() {
723 match self.check_vocabulary(definition)? {
724 VocabularyCheck::Present => {
725 out.insert(definition.name.clone(), VocabularyOutcome::Existed);
726 }
727 VocabularyCheck::PresentButTooNew { newer_version } => {
728 bail!(MentatError::ExistingVocabularyTooNew(
729 definition.name.to_string(),
730 newer_version.version,
731 definition.version
732 ));
733 }
734
735 c @ VocabularyCheck::NotPresent
736 | c @ VocabularyCheck::PresentButNeedsUpdate { .. }
737 | c @ VocabularyCheck::PresentButMissingAttributes { .. } => {
738 work.add(definition, c);
739 }
740 }
741 }
742
743 if work.is_empty() {
744 return Ok(out);
745 }
746
747 // If any work needs to be done, run pre/post.
748 vocabularies.pre(self, &work)?;
749
750 for (name, (definition, check)) in work.items.into_iter() {
751 match check {
752 VocabularyCheck::NotPresent => {
753 // Install it directly.
754 out.insert(name, self.install_vocabulary(definition)?);
755 }
756 VocabularyCheck::PresentButNeedsUpdate { older_version } => {
757 // Save this: we'll do it later.
758 update.push((definition, older_version));
759 }
760 VocabularyCheck::PresentButMissingAttributes { attributes } => {
761 // Save this: we'll do it later.
762 missing.push((definition, attributes));
763 }
764 VocabularyCheck::Present | VocabularyCheck::PresentButTooNew { .. } => {
765 unreachable!();
766 }
767 }
768 }
769
770 for (d, v) in update {
771 out.insert(d.name.clone(), self.upgrade_vocabulary(d, v)?);
772 }
773 for (d, a) in missing {
774 out.insert(d.name.clone(), self.install_attributes_for(d, a)?);
775 }
776
777 vocabularies.post(self)?;
778 Ok(out)
779 }
780}
781
782/// Implement `VocabularySource` to have full programmatic control over how a set of `Definition`s
783/// are checked against and transacted into a store.
784pub trait VocabularySource {
785 /// Called to obtain the list of `Definition`s to install. This will be called before `pre`.
786 fn definitions(&mut self) -> Vec<Definition>;
787
788 /// Called before the supplied `Definition`s are transacted. Do not commit the `InProgress`.
789 /// If this function returns `Err`, the entire vocabulary operation will fail.
790 fn pre(
791 &mut self,
792 _in_progress: &mut InProgress<'_, '_>,
793 _checks: &dyn VocabularyStatus,
794 ) -> Result<()> {
795 Ok(())
796 }
797
798 /// Called after the supplied `Definition`s are transacted. Do not commit the `InProgress`.
799 /// If this function returns `Err`, the entire vocabulary operation will fail.
800 fn post(&mut self, _in_progress: &mut InProgress<'_, '_>) -> Result<()> {
801 Ok(())
802 }
803}
804
805/// A convenience struct to package simple `pre` and `post` functions with a collection of
806/// vocabulary `Definition`s.
807pub struct SimpleVocabularySource {
808 pub definitions: Vec<Definition>,
809 pub pre: Option<fn(&mut InProgress<'_, '_>) -> Result<()>>,
810 pub post: Option<fn(&mut InProgress<'_, '_>) -> Result<()>>,
811}
812
813impl SimpleVocabularySource {
814 pub fn new(
815 definitions: Vec<Definition>,
816 pre: Option<fn(&mut InProgress<'_, '_>) -> Result<()>>,
817 post: Option<fn(&mut InProgress<'_, '_>) -> Result<()>>,
818 ) -> SimpleVocabularySource {
819 SimpleVocabularySource {
820 pre,
821 post,
822 definitions,
823 }
824 }
825
826 pub fn with_definitions(definitions: Vec<Definition>) -> SimpleVocabularySource {
827 Self::new(definitions, None, None)
828 }
829}
830
831impl VocabularySource for SimpleVocabularySource {
832 fn pre(
833 &mut self,
834 in_progress: &mut InProgress<'_, '_>,
835 _checks: &dyn VocabularyStatus,
836 ) -> Result<()> {
837 self.pre.map(|pre| (pre)(in_progress)).unwrap_or(Ok(()))
838 }
839
840 fn post(&mut self, in_progress: &mut InProgress<'_, '_>) -> Result<()> {
841 self.post.map(|pre| (pre)(in_progress)).unwrap_or(Ok(()))
842 }
843
844 fn definitions(&mut self) -> Vec<Definition> {
845 self.definitions.clone()
846 }
847}
848
849impl VocabularyMechanics for InProgress<'_, '_> {
850 /// Turn the vocabulary into datoms, transact them, and on success return the outcome.
851 fn install_vocabulary(&mut self, definition: &Definition) -> Result<VocabularyOutcome> {
852 let (terms, _tempids) = definition.description(self)?;
853 self.transact_entities(terms)?;
854 Ok(VocabularyOutcome::Installed)
855 }
856
857 fn install_attributes_for<'definition>(
858 &mut self,
859 definition: &'definition Definition,
860 attributes: Vec<&'definition (Keyword, Attribute)>,
861 ) -> Result<VocabularyOutcome> {
862 let (terms, _tempids) = definition.description_for_attributes(&attributes, self, None)?;
863 self.transact_entities(terms)?;
864 Ok(VocabularyOutcome::InstalledMissingAttributes)
865 }
866
867 /// Turn the declarative parts of the vocabulary into alterations. Run the 'pre' steps.
868 /// Transact the changes. Run the 'post' steps. Return the result and the new `InProgress`!
869 fn upgrade_vocabulary(
870 &mut self,
871 definition: &Definition,
872 from_version: Vocabulary,
873 ) -> Result<VocabularyOutcome> {
874 // It's sufficient for us to generate the datom form of each attribute and transact that.
875 // We trust that the vocabulary will implement a 'pre' function that cleans up data for any
876 // failable conversion (e.g., cardinality-many to cardinality-one).
877
878 definition.pre(self, &from_version)?;
879
880 // TODO: don't do work for attributes that are unchanged. Here we rely on the transactor
881 // to elide duplicate datoms.
882 let (terms, _tempids) = definition.description_diff(self, &from_version)?;
883 self.transact_entities(terms)?;
884
885 definition.post(self, &from_version)?;
886 Ok(VocabularyOutcome::Upgraded)
887 }
888}
889
890impl<T> HasVocabularies for T
891where
892 T: HasSchema + Queryable,
893{
894 fn read_vocabulary_named(&self, name: &Keyword) -> Result<Option<Vocabulary>> {
895 if let Some(entid) = self.get_entid(name) {
896 match self.lookup_value_for_attribute(entid, &DB_SCHEMA_VERSION)? {
897 None => Ok(None),
898 Some(TypedValue::Long(version)) if version > 0 && (version < u32::MAX as i64) => {
899 let version = version as u32;
900 let attributes = self
901 .lookup_values_for_attribute(entid, &DB_SCHEMA_ATTRIBUTE)?
902 .into_iter()
903 .filter_map(|a| {
904 if let TypedValue::Ref(a) = a {
905 self.attribute_for_entid(a).cloned().map(|attr| (a, attr))
906 } else {
907 None
908 }
909 })
910 .collect();
911 Ok(Some(Vocabulary {
912 entity: entid.into(),
913 version,
914 attributes,
915 }))
916 }
917 Some(_) => bail!(MentatError::InvalidVocabularyVersion),
918 }
919 } else {
920 Ok(None)
921 }
922 }
923
924 fn read_vocabularies(&self) -> Result<Vocabularies> {
925 // This would be way easier with pull expressions. #110.
926 let versions: BTreeMap<Entid, u32> = self
927 .q_once(
928 r#"[:find ?vocab ?version
929 :where [?vocab :db.schema/version ?version]]"#,
930 None,
931 )
932 .into_rel_result()?
933 .into_iter()
934 .filter_map(|v| match (&v[0], &v[1]) {
935 (
936 &Binding::Scalar(TypedValue::Ref(vocab)),
937 &Binding::Scalar(TypedValue::Long(version)),
938 ) if version > 0 && (version < u32::MAX as i64) => Some((vocab, version as u32)),
939 (_, _) => None,
940 })
941 .collect();
942
943 let mut attributes = BTreeMap::<Entid, Vec<(Entid, Attribute)>>::new();
944 let pairs = self
945 .q_once(
946 "[:find ?vocab ?attr :where [?vocab :db.schema/attribute ?attr]]",
947 None,
948 )
949 .into_rel_result()?
950 .into_iter()
951 .filter_map(|v| match (&v[0], &v[1]) {
952 (
953 &Binding::Scalar(TypedValue::Ref(vocab)),
954 &Binding::Scalar(TypedValue::Ref(attr)),
955 ) => Some((vocab, attr)),
956 (_, _) => None,
957 });
958
959 // TODO: validate that attributes.keys is a subset of versions.keys.
960 for (vocab, attr) in pairs {
961 if let Some(attribute) = self.attribute_for_entid(attr).cloned() {
962 attributes.entry(vocab).or_default().push((attr, attribute));
963 }
964 }
965
966 // TODO: return more errors?
967
968 // We walk versions first in order to support vocabularies with no attributes.
969 Ok(Vocabularies(
970 versions
971 .into_iter()
972 .filter_map(|(vocab, version)| {
973 // Get the name.
974 self.get_ident(vocab).cloned().map(|name| {
975 let attrs = attributes.remove(&vocab).unwrap_or_default();
976 (
977 name,
978 Vocabulary {
979 entity: vocab,
980 version,
981 attributes: attrs,
982 },
983 )
984 })
985 })
986 .collect(),
987 ))
988 }
989}
990
991#[cfg(test)]
992mod tests {
993 use crate::Store;
994
995 use super::HasVocabularies;
996
997 #[test]
998 fn test_read_vocabularies() {
999 let mut store = Store::open("").expect("opened");
1000 let vocabularies = store
1001 .begin_read()
1002 .expect("in progress")
1003 .read_vocabularies()
1004 .expect("OK");
1005 assert_eq!(vocabularies.len(), 1);
1006 let core = vocabularies.get(&kw!(:db.schema/core)).expect("exists");
1007 assert_eq!(core.version, 1);
1008 }
1009
1010 #[test]
1011 fn test_core_schema() {
1012 let mut store = Store::open("").expect("opened");
1013 let in_progress = store.begin_transaction().expect("in progress");
1014 let vocab = in_progress.read_vocabularies().expect("vocabulary");
1015 assert_eq!(1, vocab.len());
1016 assert_eq!(
1017 1,
1018 vocab
1019 .get(&kw!(:db.schema/core))
1020 .expect("core vocab")
1021 .version
1022 );
1023 }
1024}