2017-01-26 00:13:56 +00:00
// 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) ]
Extract partial storage abstraction; use error-chain throughout. Fixes #328. r=rnewman (#341)
* Pre: Drop unneeded tx0 from search results.
* Pre: Don't require a schema in some of the DB code.
The idea is to separate the transaction applying code, which is
schema-aware, from the concrete storage code, which is just concerned
with getting bits onto disk.
* Pre: Only reference Schema, not DB, in debug module.
This is part of a larger separation of the volatile PartitionMap,
which is modified every transaction, from the stable Schema, which is
infrequently modified.
* Pre: Fix indentation.
* Extract part of DB to new SchemaTypeChecking trait.
* Extract part of DB to new PartitionMapping trait.
* Pre: Don't expect :db.part/tx partition to advance when tx fails.
This fails right now, because we allocate tx IDs even when we shouldn't.
* Sketch a db interface without DB.
* Add ValueParseError; use error-chain in tx-parser.
This can be simplified when
https://github.com/Marwes/combine/issues/86 makes it to a published
release, but this unblocks us for now. This converts the `combine`
error type `ParseError<&'a [edn::Value]>` to a type with owned
`Vec<edn::Value>` collections, re-using `edn::Value::Vector` for
making them `Display`.
* Pre: Accept Borrow<Schema> instead of just &Schema in debug module.
This makes it easy to use Rc<Schema> or Arc<Schema> without inserting
&* sigils throughout the code.
* Use error-chain in query-parser.
There are a few things to point out here:
- the fine grained error types have been flattened into one crate-wide
error type; it's pretty easy to regain the granularity as needed.
- edn::ParseError is automatically lifted to
mentat_query_parser::errors::Error;
- we use mentat_parser_utils::ValueParser to maintain parsing error
information from `combine`.
* Patch up top-level.
* Review comment: Only `borrow()` once.
2017-02-24 23:32:41 +00:00
use db ::TypedSQLValue ;
use edn ;
2017-01-26 00:13:56 +00:00
use entids ;
2017-02-18 00:10:34 +00:00
use errors ::{ ErrorKind , Result } ;
2017-02-17 21:55:36 +00:00
use edn ::symbols ;
2017-02-14 03:20:20 +00:00
use mentat_core ::{
Attribute ,
Entid ,
EntidMap ,
IdentMap ,
Schema ,
SchemaMap ,
TypedValue ,
ValueType ,
} ;
2017-01-26 00:13:56 +00:00
/// Return `Ok(())` if `schema_map` defines a valid Mentat schema.
fn validate_schema_map ( entid_map : & EntidMap , schema_map : & SchemaMap ) -> Result < ( ) > {
for ( entid , attribute ) in schema_map {
let ident = entid_map . get ( entid ) . ok_or ( ErrorKind ::BadSchemaAssertion ( format! ( " Could not get ident for entid: {} " , entid ) ) ) ? ;
2017-02-08 23:26:45 +00:00
if attribute . unique_value & & ! attribute . index {
bail! ( ErrorKind ::BadSchemaAssertion ( format! ( " :db/unique :db/unique_value true without :db/index true for entid: {} " , ident ) ) )
}
2017-01-26 00:13:56 +00:00
if attribute . unique_identity & & ! attribute . unique_value {
bail! ( ErrorKind ::BadSchemaAssertion ( format! ( " :db/unique :db/unique_identity without :db/unique :db/unique_value for entid: {} " , ident ) ) )
}
if attribute . fulltext & & attribute . value_type ! = ValueType ::String {
bail! ( ErrorKind ::BadSchemaAssertion ( format! ( " :db/fulltext true without :db/valueType :db.type/string for entid: {} " , ident ) ) )
}
if attribute . component & & attribute . value_type ! = ValueType ::Ref {
bail! ( ErrorKind ::BadSchemaAssertion ( format! ( " :db/isComponent true without :db/valueType :db.type/ref for entid: {} " , ident ) ) )
}
// TODO: consider warning if we have :db/index true for :db/valueType :db.type/string,
// since this may be inefficient. More generally, we should try to drive complex
// :db/valueType (string, uri, json in the future) users to opt-in to some hash-indexing
// scheme, as discussed in https://github.com/mozilla/mentat/issues/69.
}
Ok ( ( ) )
}
2017-02-14 03:20:20 +00:00
pub trait SchemaBuilding {
2017-02-17 21:55:36 +00:00
fn require_ident ( & self , entid : Entid ) -> Result < & symbols ::NamespacedKeyword > ;
fn require_entid ( & self , ident : & symbols ::NamespacedKeyword ) -> Result < Entid > ;
2017-02-14 03:20:20 +00:00
fn require_attribute_for_entid ( & self , entid : Entid ) -> Result < & Attribute > ;
fn from_ident_map_and_schema_map ( ident_map : IdentMap , schema_map : SchemaMap ) -> Result < Schema > ;
fn from_ident_map_and_triples < U > ( ident_map : IdentMap , assertions : U ) -> Result < Schema >
2017-02-17 21:55:36 +00:00
where U : IntoIterator < Item = ( symbols ::NamespacedKeyword , symbols ::NamespacedKeyword , TypedValue ) > ;
2017-02-14 03:20:20 +00:00
}
2017-01-26 00:13:56 +00:00
2017-02-14 03:20:20 +00:00
impl SchemaBuilding for Schema {
2017-02-17 21:55:36 +00:00
fn require_ident ( & self , entid : Entid ) -> Result < & symbols ::NamespacedKeyword > {
2017-02-14 03:20:20 +00:00
self . get_ident ( entid ) . ok_or ( ErrorKind ::UnrecognizedEntid ( entid ) . into ( ) )
2017-01-26 00:13:56 +00:00
}
2017-02-17 21:55:36 +00:00
fn require_entid ( & self , ident : & symbols ::NamespacedKeyword ) -> Result < Entid > {
self . get_entid ( & ident ) . ok_or ( ErrorKind ::UnrecognizedIdent ( ident . to_string ( ) ) . into ( ) )
2017-01-26 00:13:56 +00:00
}
2017-02-14 03:20:20 +00:00
fn require_attribute_for_entid ( & self , entid : Entid ) -> Result < & Attribute > {
self . attribute_for_entid ( entid ) . ok_or ( ErrorKind ::UnrecognizedEntid ( entid ) . into ( ) )
2017-01-26 00:13:56 +00:00
}
/// Create a valid `Schema` from the constituent maps.
2017-02-14 03:20:20 +00:00
fn from_ident_map_and_schema_map ( ident_map : IdentMap , schema_map : SchemaMap ) -> Result < Schema > {
2017-01-26 00:13:56 +00:00
let entid_map : EntidMap = ident_map . iter ( ) . map ( | ( k , v ) | ( v . clone ( ) , k . clone ( ) ) ) . collect ( ) ;
validate_schema_map ( & entid_map , & schema_map ) ? ;
Ok ( Schema {
ident_map : ident_map ,
entid_map : entid_map ,
schema_map : schema_map ,
} )
}
2017-02-17 21:55:36 +00:00
/// Turn vec![(NamespacedKeyword(:ident), NamespacedKeyword(:key), TypedValue(:value)), ...] into a Mentat `Schema`.
2017-02-14 03:20:20 +00:00
fn from_ident_map_and_triples < U > ( ident_map : IdentMap , assertions : U ) -> Result < Schema >
2017-02-17 21:55:36 +00:00
where U : IntoIterator < Item = ( symbols ::NamespacedKeyword , symbols ::NamespacedKeyword , TypedValue ) > {
2017-01-26 00:13:56 +00:00
let mut schema_map = SchemaMap ::new ( ) ;
for ( ref symbolic_ident , ref symbolic_attr , ref value ) in assertions . into_iter ( ) {
2017-02-17 21:55:36 +00:00
let ident : i64 = * ident_map . get ( symbolic_ident ) . ok_or ( ErrorKind ::UnrecognizedIdent ( symbolic_ident . to_string ( ) ) ) ? ;
let attr : i64 = * ident_map . get ( symbolic_attr ) . ok_or ( ErrorKind ::UnrecognizedIdent ( symbolic_attr . to_string ( ) ) ) ? ;
2017-01-26 00:13:56 +00:00
let attributes = schema_map . entry ( ident ) . or_insert ( Attribute ::default ( ) ) ;
// TODO: improve error messages throughout.
match attr {
entids ::DB_VALUE_TYPE = > {
match * value {
TypedValue ::Ref ( entids ::DB_TYPE_REF ) = > { attributes . value_type = ValueType ::Ref ; } ,
TypedValue ::Ref ( entids ::DB_TYPE_BOOLEAN ) = > { attributes . value_type = ValueType ::Boolean ; } ,
TypedValue ::Ref ( entids ::DB_TYPE_LONG ) = > { attributes . value_type = ValueType ::Long ; } ,
TypedValue ::Ref ( entids ::DB_TYPE_STRING ) = > { attributes . value_type = ValueType ::String ; } ,
TypedValue ::Ref ( entids ::DB_TYPE_KEYWORD ) = > { attributes . value_type = ValueType ::Keyword ; } ,
_ = > bail! ( ErrorKind ::BadSchemaAssertion ( format! ( " Expected [... :db/valueType :db.type/*] but got [... :db/valueType {:?} ] for ident ' {} ' and attribute ' {} ' " , value , ident , attr ) ) )
}
} ,
entids ::DB_CARDINALITY = > {
match * value {
TypedValue ::Ref ( entids ::DB_CARDINALITY_MANY ) = > { attributes . multival = true ; } ,
TypedValue ::Ref ( entids ::DB_CARDINALITY_ONE ) = > { attributes . multival = false ; } ,
_ = > bail! ( ErrorKind ::BadSchemaAssertion ( format! ( " Expected [... :db/cardinality :db.cardinality/many|:db.cardinality/one] but got [... :db/cardinality {:?} ] " , value ) ) )
}
} ,
entids ::DB_UNIQUE = > {
match * value {
2017-02-08 23:26:45 +00:00
TypedValue ::Ref ( entids ::DB_UNIQUE_VALUE ) = > {
attributes . unique_value = true ;
attributes . index = true ;
} ,
2017-01-26 00:13:56 +00:00
TypedValue ::Ref ( entids ::DB_UNIQUE_IDENTITY ) = > {
attributes . unique_value = true ;
attributes . unique_identity = true ;
2017-02-08 23:26:45 +00:00
attributes . index = true ;
2017-01-26 00:13:56 +00:00
} ,
_ = > bail! ( ErrorKind ::BadSchemaAssertion ( format! ( " Expected [... :db/unique :db.unique/value|:db.unique/identity] but got [... :db/unique {:?} ] " , value ) ) )
}
} ,
entids ::DB_INDEX = > {
match * value {
TypedValue ::Boolean ( x ) = > { attributes . index = x } ,
_ = > bail! ( ErrorKind ::BadSchemaAssertion ( format! ( " Expected [... :db/index true|false] but got [... :db/index {:?} ] " , value ) ) )
}
} ,
entids ::DB_FULLTEXT = > {
match * value {
TypedValue ::Boolean ( x ) = > {
attributes . fulltext = x ;
if attributes . fulltext {
attributes . index = true ;
}
} ,
_ = > bail! ( ErrorKind ::BadSchemaAssertion ( format! ( " Expected [... :db/fulltext true|false] but got [... :db/fulltext {:?} ] " , value ) ) )
}
} ,
entids ::DB_IS_COMPONENT = > {
match * value {
TypedValue ::Boolean ( x ) = > { attributes . component = x } ,
_ = > bail! ( ErrorKind ::BadSchemaAssertion ( format! ( " Expected [... :db/isComponent true|false] but got [... :db/isComponent {:?} ] " , value ) ) )
}
} ,
entids ::DB_DOC = > {
// Nothing for now.
} ,
entids ::DB_IDENT = > {
// Nothing for now.
} ,
entids ::DB_INSTALL_ATTRIBUTE = > {
// Nothing for now.
} ,
_ = > {
bail! ( ErrorKind ::BadSchemaAssertion ( format! ( " Do not recognize attribute ' {} ' for ident ' {} ' " , attr , ident ) ) )
}
}
} ;
2017-02-14 03:20:20 +00:00
Schema ::from_ident_map_and_schema_map ( ident_map . clone ( ) , schema_map )
2017-01-26 00:13:56 +00:00
}
}
Extract partial storage abstraction; use error-chain throughout. Fixes #328. r=rnewman (#341)
* Pre: Drop unneeded tx0 from search results.
* Pre: Don't require a schema in some of the DB code.
The idea is to separate the transaction applying code, which is
schema-aware, from the concrete storage code, which is just concerned
with getting bits onto disk.
* Pre: Only reference Schema, not DB, in debug module.
This is part of a larger separation of the volatile PartitionMap,
which is modified every transaction, from the stable Schema, which is
infrequently modified.
* Pre: Fix indentation.
* Extract part of DB to new SchemaTypeChecking trait.
* Extract part of DB to new PartitionMapping trait.
* Pre: Don't expect :db.part/tx partition to advance when tx fails.
This fails right now, because we allocate tx IDs even when we shouldn't.
* Sketch a db interface without DB.
* Add ValueParseError; use error-chain in tx-parser.
This can be simplified when
https://github.com/Marwes/combine/issues/86 makes it to a published
release, but this unblocks us for now. This converts the `combine`
error type `ParseError<&'a [edn::Value]>` to a type with owned
`Vec<edn::Value>` collections, re-using `edn::Value::Vector` for
making them `Display`.
* Pre: Accept Borrow<Schema> instead of just &Schema in debug module.
This makes it easy to use Rc<Schema> or Arc<Schema> without inserting
&* sigils throughout the code.
* Use error-chain in query-parser.
There are a few things to point out here:
- the fine grained error types have been flattened into one crate-wide
error type; it's pretty easy to regain the granularity as needed.
- edn::ParseError is automatically lifted to
mentat_query_parser::errors::Error;
- we use mentat_parser_utils::ValueParser to maintain parsing error
information from `combine`.
* Patch up top-level.
* Review comment: Only `borrow()` once.
2017-02-24 23:32:41 +00:00
pub trait SchemaTypeChecking {
/// Do schema-aware typechecking and coercion.
///
/// Either assert that the given value is in the attribute's value set, or (in limited cases)
/// coerce the given value into the attribute's value set.
fn to_typed_value ( & self , value : & edn ::Value , attribute : & Attribute ) -> Result < TypedValue > ;
}
impl SchemaTypeChecking for Schema {
fn to_typed_value ( & self , value : & edn ::Value , attribute : & Attribute ) -> Result < TypedValue > {
// TODO: encapsulate entid-ident-attribute for better error messages.
match TypedValue ::from_edn_value ( value ) {
// We don't recognize this EDN at all. Get out!
None = > bail! ( ErrorKind ::BadEDNValuePair ( value . clone ( ) , attribute . value_type . clone ( ) ) ) ,
Some ( typed_value ) = > match ( & attribute . value_type , typed_value ) {
// Most types don't coerce at all.
( & ValueType ::Boolean , tv @ TypedValue ::Boolean ( _ ) ) = > Ok ( tv ) ,
( & ValueType ::Long , tv @ TypedValue ::Long ( _ ) ) = > Ok ( tv ) ,
( & ValueType ::Double , tv @ TypedValue ::Double ( _ ) ) = > Ok ( tv ) ,
( & ValueType ::String , tv @ TypedValue ::String ( _ ) ) = > Ok ( tv ) ,
( & ValueType ::Keyword , tv @ TypedValue ::Keyword ( _ ) ) = > Ok ( tv ) ,
// Ref coerces a little: we interpret some things depending on the schema as a Ref.
( & ValueType ::Ref , TypedValue ::Long ( x ) ) = > Ok ( TypedValue ::Ref ( x ) ) ,
( & ValueType ::Ref , TypedValue ::Keyword ( ref x ) ) = > self . require_entid ( & x ) . map ( | entid | TypedValue ::Ref ( entid ) ) ,
// Otherwise, we have a type mismatch.
( value_type , _ ) = > bail! ( ErrorKind ::BadEDNValuePair ( value . clone ( ) , value_type . clone ( ) ) ) ,
}
}
}
}