Skip to main content

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}