// Copyright 2016 Mozilla // // Licensed under the Apache License, Version 2.0 (the "License"); you may not use // this file except in compliance with the License. You may obtain a copy of the // License at http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. #![allow(dead_code)] use {to_namespaced_keyword}; use edn; use edn::types::Value; use entids; use errors::*; use mentat_tx::entities::Entity; use mentat_tx_parser; use types::{IdentMap, Partition, PartitionMap, Schema, TypedValue}; use values; lazy_static! { static ref V1_IDENTS: Vec<(&'static str, i64)> = { vec![(":db/ident", entids::DB_IDENT), (":db.part/db", entids::DB_PART_DB), (":db/txInstant", entids::DB_TX_INSTANT), (":db.install/partition", entids::DB_INSTALL_PARTITION), (":db.install/valueType", entids::DB_INSTALL_VALUETYPE), (":db.install/attribute", entids::DB_INSTALL_ATTRIBUTE), (":db/valueType", entids::DB_VALUE_TYPE), (":db/cardinality", entids::DB_CARDINALITY), (":db/unique", entids::DB_UNIQUE), (":db/isComponent", entids::DB_IS_COMPONENT), (":db/index", entids::DB_INDEX), (":db/fulltext", entids::DB_FULLTEXT), (":db/noHistory", entids::DB_NO_HISTORY), (":db/add", entids::DB_ADD), (":db/retract", entids::DB_RETRACT), (":db.part/user", entids::DB_PART_USER), (":db.part/tx", entids::DB_PART_TX), (":db/excise", entids::DB_EXCISE), (":db.excise/attrs", entids::DB_EXCISE_ATTRS), (":db.excise/beforeT", entids::DB_EXCISE_BEFORE_T), (":db.excise/before", entids::DB_EXCISE_BEFORE), (":db.alter/attribute", entids::DB_ALTER_ATTRIBUTE), (":db.type/ref", entids::DB_TYPE_REF), (":db.type/keyword", entids::DB_TYPE_KEYWORD), (":db.type/long", entids::DB_TYPE_LONG), (":db.type/double", entids::DB_TYPE_DOUBLE), (":db.type/string", entids::DB_TYPE_STRING), (":db.type/boolean", entids::DB_TYPE_BOOLEAN), (":db.type/instant", entids::DB_TYPE_INSTANT), (":db.type/bytes", entids::DB_TYPE_BYTES), (":db.cardinality/one", entids::DB_CARDINALITY_ONE), (":db.cardinality/many", entids::DB_CARDINALITY_MANY), (":db.unique/value", entids::DB_UNIQUE_VALUE), (":db.unique/identity", entids::DB_UNIQUE_IDENTITY), (":db/doc", entids::DB_DOC), ] }; static ref V2_IDENTS: Vec<(&'static str, i64)> = { [(*V1_IDENTS).clone(), vec![(":db.schema/version", entids::DB_SCHEMA_VERSION), (":db.schema/attribute", entids::DB_SCHEMA_ATTRIBUTE), ]].concat() }; static ref V1_PARTS: Vec<(&'static str, i64, i64)> = { vec![(":db.part/db", 0, (1 + V1_IDENTS.len()) as i64), (":db.part/user", 0x10000, 0x10000), (":db.part/tx", 0x10000000, 0x10000000), ] }; static ref V2_PARTS: Vec<(&'static str, i64, i64)> = { vec![(":db.part/db", 0, (1 + V2_IDENTS.len()) as i64), (":db.part/user", 0x10000, 0x10000), (":db.part/tx", 0x10000000, 0x10000000), ] }; static ref V1_SYMBOLIC_SCHEMA: Value = { let s = r#" {:db/ident {:db/valueType :db.type/keyword :db/cardinality :db.cardinality/one :db/unique :db.unique/identity} :db.install/partition {:db/valueType :db.type/ref :db/cardinality :db.cardinality/many} :db.install/valueType {:db/valueType :db.type/ref :db/cardinality :db.cardinality/many} :db.install/attribute {:db/valueType :db.type/ref :db/cardinality :db.cardinality/many} ;; TODO: support user-specified functions in the future. ;; :db.install/function {:db/valueType :db.type/ref ;; :db/cardinality :db.cardinality/many} :db/txInstant {:db/valueType :db.type/long :db/cardinality :db.cardinality/one :db/index true} :db/valueType {:db/valueType :db.type/ref :db/cardinality :db.cardinality/one} :db/cardinality {:db/valueType :db.type/ref :db/cardinality :db.cardinality/one} :db/doc {:db/valueType :db.type/string :db/cardinality :db.cardinality/one} :db/unique {:db/valueType :db.type/ref :db/cardinality :db.cardinality/one} :db/isComponent {:db/valueType :db.type/boolean :db/cardinality :db.cardinality/one} :db/index {:db/valueType :db.type/boolean :db/cardinality :db.cardinality/one} :db/fulltext {:db/valueType :db.type/boolean :db/cardinality :db.cardinality/one} :db/noHistory {:db/valueType :db.type/boolean :db/cardinality :db.cardinality/one}}"#; edn::parse::value(s) .map_err(|_| ErrorKind::BadBootstrapDefinition("Unable to parse V1_SYMBOLIC_SCHEMA".into())) .unwrap() }; static ref V2_SYMBOLIC_SCHEMA: Value = { let s = r#" {:db.alter/attribute {:db/valueType :db.type/ref :db/cardinality :db.cardinality/many} :db.schema/version {:db/valueType :db.type/long :db/cardinality :db.cardinality/one} ;; unique-value because an attribute can only belong to a single ;; schema fragment. :db.schema/attribute {:db/valueType :db.type/ref :db/unique :db.unique/value :db/cardinality :db.cardinality/many}}"#; let right = edn::parse::value(s) .map_err(|_| ErrorKind::BadBootstrapDefinition("Unable to parse V2_SYMBOLIC_SCHEMA".into())) .unwrap(); edn::utils::merge(&V1_SYMBOLIC_SCHEMA, &right) .ok_or(ErrorKind::BadBootstrapDefinition("Unable to parse V2_SYMBOLIC_SCHEMA".into())) .unwrap() }; } /// Convert (ident, entid) pairs into [:db/add IDENT :db/ident IDENT] `Value` instances. fn idents_to_assertions(idents: &[(&str, i64)]) -> Vec { idents .into_iter() .map(|&(ident, _)| { let value = Value::NamespacedKeyword(to_namespaced_keyword(&ident).unwrap()); Value::Vector(vec![values::DB_ADD.clone(), value.clone(), values::DB_IDENT.clone(), value.clone()]) }) .collect() } /// Convert {:ident {:key :value ...} ...} to vec![(String(:ident), String(:key), TypedValue(:value)), ...]. /// /// Such triples are closer to what the transactor will produce when processing /// :db.install/attribute assertions. fn symbolic_schema_to_triples(ident_map: &IdentMap, symbolic_schema: &Value) -> Result> { // Failure here is a coding error, not a runtime error. let mut triples: Vec<(String, String, TypedValue)> = vec![]; // TODO: Consider `flat_map` and `map` rather than loop. match *symbolic_schema { Value::Map(ref m) => { for (ident, mp) in m { let ident = match ident { &Value::NamespacedKeyword(ref ident) => ident.to_string(), _ => bail!(ErrorKind::BadBootstrapDefinition(format!("Expected namespaced keyword for ident but got '{:?}'", ident))) }; match *mp { Value::Map(ref mpp) => { for (attr, value) in mpp { let attr = match attr { &Value::NamespacedKeyword(ref attr) => attr.to_string(), _ => bail!(ErrorKind::BadBootstrapDefinition(format!("Expected namespaced keyword for attr but got '{:?}'", attr))) }; // We have symbolic idents but the transactor handles entids. Ad-hoc // convert right here. This is a fundamental limitation on the // bootstrap symbolic schema format; we can't represent "real" keywords // at this time. // // TODO: remove this limitation, perhaps by including a type tag in the // bootstrap symbolic schema, or by representing the initial bootstrap // schema directly as Rust data. let typed_value = match TypedValue::from_edn_value(value) { Some(TypedValue::Keyword(ref s)) => TypedValue::Ref(*ident_map.get(s).ok_or(ErrorKind::UnrecognizedIdent(s.clone()))?), Some(v) => v, _ => bail!(ErrorKind::BadBootstrapDefinition(format!("Expected Mentat typed value for value but got '{:?}'", value))) }; triples.push((ident.clone(), attr.clone(), typed_value)); } }, _ => bail!(ErrorKind::BadBootstrapDefinition("Expected {:db/ident {:db/attr value ...} ...}".into())) } } }, _ => bail!(ErrorKind::BadBootstrapDefinition("Expected {...}".into())) } Ok(triples) } /// Convert {IDENT {:key :value ...} ...} to [[:db/add IDENT :key :value] ...]. /// In addition, add [:db.add :db.part/db :db.install/attribute IDENT] installation assertions. fn symbolic_schema_to_assertions(symbolic_schema: &Value) -> Result> { // Failure here is a coding error, not a runtime error. let mut assertions: Vec = vec![]; match *symbolic_schema { Value::Map(ref m) => { for (ident, mp) in m { assertions.push(Value::Vector(vec![values::DB_ADD.clone(), values::DB_PART_DB.clone(), values::DB_INSTALL_ATTRIBUTE.clone(), ident.clone()])); match *mp { Value::Map(ref mpp) => { for (attr, value) in mpp { assertions.push(Value::Vector(vec![values::DB_ADD.clone(), ident.clone(), attr.clone(), value.clone()])); } }, _ => bail!(ErrorKind::BadBootstrapDefinition("Expected {:db/ident {:db/attr value ...} ...}".into())) } } }, _ => bail!(ErrorKind::BadBootstrapDefinition("Expected {...}".into())) } Ok(assertions) } pub fn bootstrap_partition_map() -> PartitionMap { V2_PARTS[..].iter() .map(|&(part, start, index)| (part.to_string(), Partition::new(start, index))) .collect() } pub fn bootstrap_ident_map() -> IdentMap { V2_IDENTS[..].iter() .map(|&(ident, entid)| (ident.to_string(), entid)) .collect() } pub fn bootstrap_schema() -> Schema { let ident_map = bootstrap_ident_map(); let bootstrap_triples = symbolic_schema_to_triples(&ident_map, &V2_SYMBOLIC_SCHEMA).unwrap(); Schema::from_ident_map_and_triples(ident_map, bootstrap_triples).unwrap() } pub fn bootstrap_entities() -> Vec { let bootstrap_assertions: Value = Value::Vector([ symbolic_schema_to_assertions(&V2_SYMBOLIC_SCHEMA).unwrap(), idents_to_assertions(&V2_IDENTS[..]), ].concat()); // Failure here is a coding error (since the inputs are fixed), not a runtime error. // TODO: represent these bootstrap data errors rather than just panicing. let bootstrap_entities: Vec = mentat_tx_parser::Tx::parse(&[bootstrap_assertions][..]).unwrap(); return bootstrap_entities; }