Merge pull request #24 from mwatts/feature/blobs

add blob/bytes as a type
This commit is contained in:
Mark Watts 2021-08-23 21:19:24 -04:00 committed by GitHub
commit bd818ba1f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 161 additions and 30 deletions

View file

@ -16,6 +16,7 @@ ordered-float = { version = "~2.7", features = ["serde"] }
uuid = { version = "~0.8", features = ["v4", "serde"] } uuid = { version = "~0.8", features = ["v4", "serde"] }
serde = { version = "~1.0", features = ["rc"] } serde = { version = "~1.0", features = ["rc"] }
serde_derive = "~1.0" serde_derive = "~1.0"
bytes = { version = "1.0.1", features = ["serde"] }
[dependencies.edn] [dependencies.edn]
path = "../edn" path = "../edn"

View file

@ -16,6 +16,7 @@ extern crate ordered_float;
extern crate serde_derive; extern crate serde_derive;
extern crate edn; extern crate edn;
extern crate uuid; extern crate uuid;
extern crate bytes;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
@ -33,6 +34,7 @@ use std::sync::Arc;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use bytes::Bytes;
use indexmap::IndexMap; use indexmap::IndexMap;
use enum_set::EnumSet; use enum_set::EnumSet;
@ -280,6 +282,7 @@ pub enum ValueType {
String, String,
Keyword, Keyword,
Uuid, Uuid,
Bytes,
} }
impl ValueType { impl ValueType {
@ -294,6 +297,7 @@ impl ValueType {
s.insert(ValueType::String); s.insert(ValueType::String);
s.insert(ValueType::Keyword); s.insert(ValueType::Keyword);
s.insert(ValueType::Uuid); s.insert(ValueType::Uuid);
s.insert(ValueType::Bytes);
s s
} }
} }
@ -321,6 +325,7 @@ impl ValueType {
ValueType::String => "string", ValueType::String => "string",
ValueType::Keyword => "keyword", ValueType::Keyword => "keyword",
ValueType::Uuid => "uuid", ValueType::Uuid => "uuid",
ValueType::Bytes => "bytes",
}, },
) )
} }
@ -338,6 +343,7 @@ impl ValueType {
"string" => Some(ValueType::String), "string" => Some(ValueType::String),
"keyword" => Some(ValueType::Keyword), "keyword" => Some(ValueType::Keyword),
"uuid" => Some(ValueType::Uuid), "uuid" => Some(ValueType::Uuid),
"bytes" => Some(ValueType::Bytes),
_ => None, _ => None,
} }
} }
@ -355,6 +361,7 @@ impl ValueType {
ValueType::String => "string", ValueType::String => "string",
ValueType::Keyword => "keyword", ValueType::Keyword => "keyword",
ValueType::Uuid => "uuid", ValueType::Uuid => "uuid",
ValueType::Bytes => "bytes",
}, },
) )
} }
@ -369,6 +376,7 @@ impl ValueType {
ValueType::String => values::DB_TYPE_STRING.clone(), ValueType::String => values::DB_TYPE_STRING.clone(),
ValueType::Keyword => values::DB_TYPE_KEYWORD.clone(), ValueType::Keyword => values::DB_TYPE_KEYWORD.clone(),
ValueType::Uuid => values::DB_TYPE_UUID.clone(), ValueType::Uuid => values::DB_TYPE_UUID.clone(),
ValueType::Bytes => values::DB_TYPE_BYTES.clone(),
} }
} }
@ -391,6 +399,7 @@ impl fmt::Display for ValueType {
ValueType::String => ":db.type/string", ValueType::String => ":db.type/string",
ValueType::Keyword => ":db.type/keyword", ValueType::Keyword => ":db.type/keyword",
ValueType::Uuid => ":db.type/uuid", ValueType::Uuid => ":db.type/uuid",
ValueType::Bytes => ":db.type/bytes",
} }
) )
} }
@ -414,6 +423,7 @@ pub enum TypedValue {
String(ValueRc<String>), String(ValueRc<String>),
Keyword(ValueRc<Keyword>), Keyword(ValueRc<Keyword>),
Uuid(Uuid), // It's only 128 bits, so this should be acceptable to clone. Uuid(Uuid), // It's only 128 bits, so this should be acceptable to clone.
Bytes(Bytes),
} }
impl From<KnownEntid> for TypedValue { impl From<KnownEntid> for TypedValue {
@ -445,6 +455,7 @@ impl TypedValue {
TypedValue::String(_) => ValueType::String, TypedValue::String(_) => ValueType::String,
TypedValue::Keyword(_) => ValueType::Keyword, TypedValue::Keyword(_) => ValueType::Keyword,
TypedValue::Uuid(_) => ValueType::Uuid, TypedValue::Uuid(_) => ValueType::Uuid,
TypedValue::Bytes(_) => ValueType::Bytes,
} }
} }
@ -596,6 +607,13 @@ impl TypedValue {
_ => None, _ => None,
} }
} }
pub fn into_bytes(self) -> Option<Bytes> {
match self {
TypedValue::Bytes(b) => Some(b),
_ => None,
}
}
} }
// We don't do From<i64> or From<Entid> 'cos it's ambiguous. // We don't do From<i64> or From<Entid> 'cos it's ambiguous.
@ -686,6 +704,12 @@ impl From<f64> for TypedValue {
} }
} }
impl From<&[u8]> for TypedValue {
fn from(bslice: &[u8]) -> Self {
TypedValue::Bytes(Bytes::copy_from_slice(bslice))
}
}
trait MicrosecondPrecision { trait MicrosecondPrecision {
/// Truncate the provided `DateTime` to microsecond precision. /// Truncate the provided `DateTime` to microsecond precision.
fn microsecond_precision(self) -> Self; fn microsecond_precision(self) -> Self;

View file

@ -58,6 +58,7 @@ lazy_static_namespaced_keyword_value!(DB_TYPE_REF, "db.type", "ref");
lazy_static_namespaced_keyword_value!(DB_TYPE_STRING, "db.type", "string"); lazy_static_namespaced_keyword_value!(DB_TYPE_STRING, "db.type", "string");
lazy_static_namespaced_keyword_value!(DB_TYPE_URI, "db.type", "uri"); lazy_static_namespaced_keyword_value!(DB_TYPE_URI, "db.type", "uri");
lazy_static_namespaced_keyword_value!(DB_TYPE_UUID, "db.type", "uuid"); lazy_static_namespaced_keyword_value!(DB_TYPE_UUID, "db.type", "uuid");
lazy_static_namespaced_keyword_value!(DB_TYPE_BYTES, "db.type", "bytes");
lazy_static_namespaced_keyword_value!(DB_UNIQUE, "db", "unique"); lazy_static_namespaced_keyword_value!(DB_UNIQUE, "db", "unique");
lazy_static_namespaced_keyword_value!(DB_UNIQUE_IDENTITY, "db.unique", "identity"); lazy_static_namespaced_keyword_value!(DB_UNIQUE_IDENTITY, "db.unique", "identity");
lazy_static_namespaced_keyword_value!(DB_UNIQUE_VALUE, "db.unique", "value"); lazy_static_namespaced_keyword_value!(DB_UNIQUE_VALUE, "db.unique", "value");

View file

@ -51,6 +51,7 @@ impl SQLValueType for ValueType {
ValueType::String => (10, None), ValueType::String => (10, None),
ValueType::Uuid => (11, None), ValueType::Uuid => (11, None),
ValueType::Keyword => (13, None), ValueType::Keyword => (13, None),
ValueType::Bytes => (15, Some(SQLTypeAffinity::Blob)),
} }
} }
@ -71,6 +72,7 @@ impl SQLValueType for ValueType {
ValueType::String => false, ValueType::String => false,
Keyword => false, Keyword => false,
Uuid => false, Uuid => false,
Bytes => false,
} }
} }
} }

View file

@ -118,10 +118,10 @@ impl ::std::fmt::Display for InputError {
match self { match self {
BadDbId => { BadDbId => {
writeln!(f, ":db/id in map notation must either not be present or be an entid, an ident, or a tempid") writeln!(f, ":db/id in map notation must either not be present or be an entid, an ident, or a tempid")
}, }
BadEntityPlace => { BadEntityPlace => {
writeln!(f, "cannot convert value place into entity place") writeln!(f, "cannot convert value place into entity place")
}, }
} }
} }
} }

View file

@ -61,7 +61,7 @@ use std::iter::Peekable;
use failure::ResultExt; use failure::ResultExt;
use rusqlite; use rusqlite;
use rusqlite::{params_from_iter}; use rusqlite::params_from_iter;
use core_traits::{Binding, Entid, TypedValue}; use core_traits::{Binding, Entid, TypedValue};
@ -1072,7 +1072,9 @@ impl AttributeCaches {
replacing: bool, replacing: bool,
) -> Result<()> { ) -> Result<()> {
let mut aev_factory = AevFactory::new(); let mut aev_factory = AevFactory::new();
let rows = statement.query_map(params_from_iter(&args), |row| Ok(aev_factory.row_to_aev(row)))?; let rows = statement.query_map(params_from_iter(&args), |row| {
Ok(aev_factory.row_to_aev(row))
})?;
let aevs = AevRows { rows }; let aevs = AevRows { rows };
self.accumulate_into_cache( self.accumulate_into_cache(
None, None,

View file

@ -22,9 +22,9 @@ use itertools;
use itertools::Itertools; use itertools::Itertools;
use rusqlite; use rusqlite;
use rusqlite::limits::Limit; use rusqlite::limits::Limit;
use rusqlite::params_from_iter;
use rusqlite::types::{ToSql, ToSqlOutput}; use rusqlite::types::{ToSql, ToSqlOutput};
use rusqlite::TransactionBehavior; use rusqlite::TransactionBehavior;
use rusqlite::{params_from_iter};
use crate::bootstrap; use crate::bootstrap;
use crate::{repeat_values, to_namespaced_keyword}; use crate::{repeat_values, to_namespaced_keyword};
@ -434,6 +434,9 @@ impl TypedSQLValue for TypedValue {
Ok(TypedValue::Uuid(u)) Ok(TypedValue::Uuid(u))
} }
(13, rusqlite::types::Value::Text(x)) => to_namespaced_keyword(&x).map(|k| k.into()), (13, rusqlite::types::Value::Text(x)) => to_namespaced_keyword(&x).map(|k| k.into()),
(15, rusqlite::types::Value::Blob(x)) => {
Ok(TypedValue::Bytes(x.into()))
}
(_, value) => bail!(DbErrorKind::BadSQLValuePair(value, value_type_tag)), (_, value) => bail!(DbErrorKind::BadSQLValuePair(value, value_type_tag)),
} }
} }
@ -454,6 +457,7 @@ impl TypedSQLValue for TypedValue {
Value::Float(ref x) => Some(TypedValue::Double(*x)), Value::Float(ref x) => Some(TypedValue::Double(*x)),
Value::Text(ref x) => Some(x.clone().into()), Value::Text(ref x) => Some(x.clone().into()),
Value::Keyword(ref x) => Some(x.clone().into()), Value::Keyword(ref x) => Some(x.clone().into()),
Value::Bytes(b) => Some(TypedValue::Bytes(b.clone())),
_ => None, _ => None,
} }
} }
@ -470,6 +474,7 @@ impl TypedSQLValue for TypedValue {
TypedValue::String(ref x) => (x.as_str().into(), 10), TypedValue::String(ref x) => (x.as_str().into(), 10),
TypedValue::Uuid(ref u) => (u.as_bytes().to_vec().into(), 11), TypedValue::Uuid(ref u) => (u.as_bytes().to_vec().into(), 11),
TypedValue::Keyword(ref x) => (x.to_string().into(), 13), TypedValue::Keyword(ref x) => (x.to_string().into(), 13),
TypedValue::Bytes(b) => (b.to_vec().into(), 15),
} }
} }
@ -484,6 +489,7 @@ impl TypedSQLValue for TypedValue {
TypedValue::String(ref x) => (Value::Text(x.as_ref().clone()), ValueType::String), TypedValue::String(ref x) => (Value::Text(x.as_ref().clone()), ValueType::String),
TypedValue::Uuid(ref u) => (Value::Uuid(*u), ValueType::Uuid), TypedValue::Uuid(ref u) => (Value::Uuid(*u), ValueType::Uuid),
TypedValue::Keyword(ref x) => (Value::Keyword(x.as_ref().clone()), ValueType::Keyword), TypedValue::Keyword(ref x) => (Value::Keyword(x.as_ref().clone()), ValueType::Keyword),
TypedValue::Bytes(b) => (Value::Bytes(b.clone()), ValueType::Bytes),
} }
} }
} }

View file

@ -75,7 +75,7 @@ impl TransactableValue for ValueAndSpan {
} }
} }
Nil | Boolean(_) | Instant(_) | BigInteger(_) | Float(_) | Uuid(_) | PlainSymbol(_) Nil | Boolean(_) | Instant(_) | BigInteger(_) | Float(_) | Uuid(_) | PlainSymbol(_)
| NamespacedSymbol(_) | Vector(_) | Set(_) | Map(_) => { | NamespacedSymbol(_) | Vector(_) | Set(_) | Map(_) | Bytes(_) => {
bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)) bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace))
} }
} }
@ -105,7 +105,8 @@ impl TransactableValue for TypedValue {
| TypedValue::Long(_) | TypedValue::Long(_)
| TypedValue::Double(_) | TypedValue::Double(_)
| TypedValue::Instant(_) | TypedValue::Instant(_)
| TypedValue::Uuid(_) => { | TypedValue::Uuid(_)
| TypedValue::Bytes(_) => {
bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)) bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace))
} }
} }

View file

@ -248,6 +248,7 @@ pub fn update_attribute_map_from_entid_triples(
TypedValue::Ref(entids::DB_TYPE_REF) => { builder.value_type(ValueType::Ref); }, TypedValue::Ref(entids::DB_TYPE_REF) => { builder.value_type(ValueType::Ref); },
TypedValue::Ref(entids::DB_TYPE_STRING) => { builder.value_type(ValueType::String); }, TypedValue::Ref(entids::DB_TYPE_STRING) => { builder.value_type(ValueType::String); },
TypedValue::Ref(entids::DB_TYPE_UUID) => { builder.value_type(ValueType::Uuid); }, TypedValue::Ref(entids::DB_TYPE_UUID) => { builder.value_type(ValueType::Uuid); },
TypedValue::Ref(entids::DB_TYPE_BYTES) => { builder.value_type(ValueType::Bytes); },
_ => bail!(DbErrorKind::BadSchemaAssertion(format!("Expected [... :db/valueType :db.type/*] but got [... :db/valueType {:?}] for entid {} and attribute {}", value, entid, attr))) _ => bail!(DbErrorKind::BadSchemaAssertion(format!("Expected [... :db/valueType :db.type/*] but got [... :db/valueType {:?}] for entid {} and attribute {}", value, entid, attr)))
} }
}, },

View file

@ -362,6 +362,7 @@ impl SchemaTypeChecking for Schema {
(ValueType::Uuid, tv @ TypedValue::Uuid(_)) => Ok(tv), (ValueType::Uuid, tv @ TypedValue::Uuid(_)) => Ok(tv),
(ValueType::Instant, tv @ TypedValue::Instant(_)) => Ok(tv), (ValueType::Instant, tv @ TypedValue::Instant(_)) => Ok(tv),
(ValueType::Keyword, tv @ TypedValue::Keyword(_)) => Ok(tv), (ValueType::Keyword, tv @ TypedValue::Keyword(_)) => Ok(tv),
(ValueType::Bytes, tv @ TypedValue::Bytes(_)) => Ok(tv),
// Ref coerces a little: we interpret some things depending on the schema as a Ref. // 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::Long(x)) => Ok(TypedValue::Ref(x)),
(ValueType::Ref, TypedValue::Keyword(ref x)) => { (ValueType::Ref, TypedValue::Keyword(ref x)) => {
@ -379,6 +380,7 @@ impl SchemaTypeChecking for Schema {
| (vt @ ValueType::Uuid, _) | (vt @ ValueType::Uuid, _)
| (vt @ ValueType::Instant, _) | (vt @ ValueType::Instant, _)
| (vt @ ValueType::Keyword, _) | (vt @ ValueType::Keyword, _)
| (vt @ ValueType::Bytes, _)
| (vt @ ValueType::Ref, _) => { | (vt @ ValueType::Ref, _) => {
bail!(DbErrorKind::BadValuePair(format!("{}", value), vt)) bail!(DbErrorKind::BadValuePair(format!("{}", value), vt))
} }

View file

@ -81,7 +81,7 @@ fn move_transactions_to(
new_timeline, new_timeline,
crate::repeat_values(tx_ids.len(), 1) crate::repeat_values(tx_ids.len(), 1)
), ),
params_from_iter(tx_ids.iter()) params_from_iter(tx_ids.iter()),
)?; )?;
Ok(()) Ok(())
} }

View file

@ -67,6 +67,11 @@ fn test_from_sql_value_pair() {
.unwrap(), .unwrap(),
TypedValue::typed_ns_keyword("db", "keyword") TypedValue::typed_ns_keyword("db", "keyword")
); );
assert_eq!(
TypedValue::from_sql_value_pair(rusqlite::types::Value::Blob(vec![1,2,3,42]), 15)
.unwrap(),
TypedValue::Bytes((vec![1,2,3,42]).into())
);
} }
#[test] #[test]

View file

@ -19,6 +19,8 @@ uuid = { version = "~0.8", features = ["v4", "serde"] }
serde = { version = "~1.0", optional = true } serde = { version = "~1.0", optional = true }
serde_derive = { version = "~1.0", optional = true } serde_derive = { version = "~1.0", optional = true }
peg = "~0.7" peg = "~0.7"
bytes = "1.0.1"
hex = "0.4.3"
[dev-dependencies] [dev-dependencies]
serde_test = "~1.0" serde_test = "~1.0"

View file

@ -8,7 +8,9 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
extern crate bytes;
extern crate chrono; extern crate chrono;
extern crate hex;
extern crate itertools; extern crate itertools;
extern crate num; extern crate num;
extern crate ordered_float; extern crate ordered_float;
@ -38,7 +40,9 @@ pub mod value_rc;
pub use crate::value_rc::{Cloned, FromRc, ValueRc}; pub use crate::value_rc::{Cloned, FromRc, ValueRc};
// Re-export the types we use. // Re-export the types we use.
use bytes::Bytes;
pub use chrono::{DateTime, Utc}; pub use chrono::{DateTime, Utc};
use hex::decode;
pub use num::BigInt; pub use num::BigInt;
pub use ordered_float::OrderedFloat; pub use ordered_float::OrderedFloat;
pub use uuid::Uuid; pub use uuid::Uuid;
@ -172,6 +176,14 @@ peg::parser!(pub grammar parse() for str {
pub rule uuid() -> SpannedValue = "#uuid" whitespace()+ u:uuid_string() pub rule uuid() -> SpannedValue = "#uuid" whitespace()+ u:uuid_string()
{ SpannedValue::Uuid(u) } { SpannedValue::Uuid(u) }
rule byte_buffer() -> Bytes =
u:$( hex()+ ) {
let b = decode(u).expect("this is a valid hex byte string");
Bytes::copy_from_slice(&b)
}
pub rule bytes() -> SpannedValue = "#bytes" whitespace()+ u:byte_buffer()
{ SpannedValue::Bytes(u) }
rule namespace_divider() = "." rule namespace_divider() = "."
rule namespace_separator() = "/" rule namespace_separator() = "/"
@ -219,7 +231,7 @@ peg::parser!(pub grammar parse() for str {
// Note: It's important that float comes before integer or the parser assumes that floats are integers and fails to parse. // Note: It's important that float comes before integer or the parser assumes that floats are integers and fails to parse.
pub rule value() -> ValueAndSpan = pub rule value() -> ValueAndSpan =
__ start:position!() v:(nil() / nan() / infinity() / boolean() / number() / inst() / uuid() / text() / keyword() / symbol() / list() / vector() / map() / set()) end:position!() __ { __ start:position!() v:(nil() / nan() / infinity() / boolean() / number() / inst() / uuid() / bytes() / text() / keyword() / symbol() / list() / vector() / map() / set() ) end:position!() __ {
ValueAndSpan { ValueAndSpan {
inner: v, inner: v,
span: Span::new(start, end) span: Span::new(start, end)

View file

@ -58,9 +58,7 @@ impl Value {
let open = open.into(); let open = open.into();
let n = open.len() as isize; let n = open.len() as isize;
let i = { let i = {
let this = vs let this = vs.into_iter().map(|v| v.as_doc(allocator));
.into_iter()
.map(|v| v.as_doc(allocator));
let element = allocator.line(); let element = allocator.line();
Itertools::intersperse(this, element) Itertools::intersperse(this, element)
}; };
@ -86,9 +84,9 @@ impl Value {
Value::Map(ref vs) => { Value::Map(ref vs) => {
let xs = { let xs = {
let this = vs let this = vs
.iter() .iter()
.rev() .rev()
.map(|(k, v)| k.as_doc(pp).append(pp.line()).append(v.as_doc(pp)).group()); .map(|(k, v)| k.as_doc(pp).append(pp.line()).append(v.as_doc(pp)).group());
let element = pp.line(); let element = pp.line();
Itertools::intersperse(this, element) Itertools::intersperse(this, element)
}; };

View file

@ -233,7 +233,7 @@ impl FromValue<FnArg> for FnArg {
{ {
Some(FnArg::Constant(x.clone().into())) Some(FnArg::Constant(x.clone().into()))
} }
Nil | NamespacedSymbol(_) | Vector(_) | List(_) | Set(_) | Map(_) => None, Nil | NamespacedSymbol(_) | Vector(_) | List(_) | Set(_) | Map(_) | Bytes(_) => None,
} }
} }
} }
@ -410,6 +410,7 @@ impl FromValue<PatternValuePlace> for PatternValuePlace {
crate::SpannedValue::List(_) => None, crate::SpannedValue::List(_) => None,
crate::SpannedValue::Set(_) => None, crate::SpannedValue::Set(_) => None,
crate::SpannedValue::Vector(_) => None, crate::SpannedValue::Vector(_) => None,
crate::SpannedValue::Bytes(_) => None,
} }
} }
} }

View file

@ -27,6 +27,8 @@ use uuid::Uuid;
use crate::symbols; use crate::symbols;
use bytes::Bytes;
use hex::encode;
/// Value represents one of the allowed values in an EDN string. /// Value represents one of the allowed values in an EDN string.
#[derive(PartialEq, Eq, Hash, Clone, Debug)] #[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub enum Value { pub enum Value {
@ -52,6 +54,7 @@ pub enum Value {
// See https://internals.rust-lang.org/t/implementing-hash-for-hashset-hashmap/3817/1 // See https://internals.rust-lang.org/t/implementing-hash-for-hashset-hashmap/3817/1
Set(BTreeSet<Value>), Set(BTreeSet<Value>),
Map(BTreeMap<Value, Value>), Map(BTreeMap<Value, Value>),
Bytes(Bytes),
} }
/// `SpannedValue` is the parallel to `Value` but used in `ValueAndSpan`. /// `SpannedValue` is the parallel to `Value` but used in `ValueAndSpan`.
@ -73,6 +76,7 @@ pub enum SpannedValue {
List(LinkedList<ValueAndSpan>), List(LinkedList<ValueAndSpan>),
Set(BTreeSet<ValueAndSpan>), Set(BTreeSet<ValueAndSpan>),
Map(BTreeMap<ValueAndSpan, ValueAndSpan>), Map(BTreeMap<ValueAndSpan, ValueAndSpan>),
Bytes(Bytes),
} }
/// Span represents the current offset (start, end) into the input string. /// Span represents the current offset (start, end) into the input string.
@ -172,6 +176,7 @@ impl From<SpannedValue> for Value {
.map(|(x, y)| (x.without_spans(), y.without_spans())) .map(|(x, y)| (x.without_spans(), y.without_spans()))
.collect(), .collect(),
), ),
SpannedValue::Bytes(b) => Value::Bytes(b),
} }
} }
} }
@ -328,6 +333,7 @@ macro_rules! def_common_value_methods {
def_is!(is_list, $t::List(_)); def_is!(is_list, $t::List(_));
def_is!(is_set, $t::Set(_)); def_is!(is_set, $t::Set(_));
def_is!(is_map, $t::Map(_)); def_is!(is_map, $t::Map(_));
def_is!(is_bytes, $t::Bytes(_));
pub fn is_keyword(&self) -> bool { pub fn is_keyword(&self) -> bool {
match self { match self {
@ -360,6 +366,7 @@ macro_rules! def_common_value_methods {
def_as_ref!(as_uuid, $t::Uuid, Uuid); def_as_ref!(as_uuid, $t::Uuid, Uuid);
def_as_ref!(as_symbol, $t::PlainSymbol, symbols::PlainSymbol); def_as_ref!(as_symbol, $t::PlainSymbol, symbols::PlainSymbol);
def_as_ref!(as_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol); def_as_ref!(as_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol);
def_as_ref!(as_bytes, $t::Bytes, Bytes);
pub fn as_keyword(&self) -> Option<&symbols::Keyword> { pub fn as_keyword(&self) -> Option<&symbols::Keyword> {
match self { match self {
@ -397,6 +404,7 @@ macro_rules! def_common_value_methods {
def_into!(into_uuid, $t::Uuid, Uuid,); def_into!(into_uuid, $t::Uuid, Uuid,);
def_into!(into_symbol, $t::PlainSymbol, symbols::PlainSymbol,); def_into!(into_symbol, $t::PlainSymbol, symbols::PlainSymbol,);
def_into!(into_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol,); def_into!(into_namespaced_symbol, $t::NamespacedSymbol, symbols::NamespacedSymbol,);
def_into!(into_bytes, $t::Bytes, Bytes,);
pub fn into_keyword(self) -> Option<symbols::Keyword> { pub fn into_keyword(self) -> Option<symbols::Keyword> {
match self { match self {
@ -467,6 +475,7 @@ macro_rules! def_common_value_methods {
$t::List(_) => 13, $t::List(_) => 13,
$t::Set(_) => 14, $t::Set(_) => 14,
$t::Map(_) => 15, $t::Map(_) => 15,
$t::Bytes(_) => 16,
} }
} }
@ -487,6 +496,7 @@ macro_rules! def_common_value_methods {
$t::List(_) => true, $t::List(_) => true,
$t::Set(_) => true, $t::Set(_) => true,
$t::Map(_) => true, $t::Map(_) => true,
$t::Bytes(_) => false,
} }
} }
@ -524,6 +534,7 @@ macro_rules! def_common_value_ord {
(&$t::List(ref a), &$t::List(ref b)) => b.cmp(a), (&$t::List(ref a), &$t::List(ref b)) => b.cmp(a),
(&$t::Set(ref a), &$t::Set(ref b)) => b.cmp(a), (&$t::Set(ref a), &$t::Set(ref b)) => b.cmp(a),
(&$t::Map(ref a), &$t::Map(ref b)) => b.cmp(a), (&$t::Map(ref a), &$t::Map(ref b)) => b.cmp(a),
(&$t::Bytes(ref a), &$t::Bytes(ref b)) => b.cmp(a),
_ => $value.precedence().cmp(&$other.precedence()), _ => $value.precedence().cmp(&$other.precedence()),
} }
}; };
@ -590,6 +601,10 @@ macro_rules! def_common_value_display {
} }
write!($f, " }}") write!($f, " }}")
} }
$t::Bytes(ref v) => {
let s = encode(v);
write!($f, "#bytes {}", s)
}
} }
}; };
} }

View file

@ -82,6 +82,7 @@ fn_parse_into_value!(vector);
fn_parse_into_value!(set); fn_parse_into_value!(set);
fn_parse_into_value!(map); fn_parse_into_value!(map);
fn_parse_into_value!(value); fn_parse_into_value!(value);
fn_parse_into_value!(bytes);
#[test] #[test]
fn test_nil() { fn test_nil() {
@ -316,6 +317,39 @@ fn test_uuid() {
assert_eq!(value.to_pretty(100).unwrap(), s); assert_eq!(value.to_pretty(100).unwrap(), s);
} }
#[test]
fn test_bytes() {
assert!(parse::bytes("#bytes01 ").is_err()); // No whitespace.
assert!(parse::bytes("#bytes _ZZ").is_err()); // No whitespace.
assert!(parse::bytes("#bytes 01 ").is_err()); // No whitespace.
assert!(parse::bytes("#01 ").is_err()); // No whitespace.
let expected = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let s = format!("{} {}", "#bytes", hex::encode(expected.clone()));
let actual: Value = parse::bytes(&s).expect("parse success").into();
assert!(actual.is_bytes());
assert_eq!(expected, actual.as_bytes().unwrap().to_vec());
assert_eq!(
self::bytes("#bytes 010203050403022a").unwrap(),
Value::Bytes(bytes::Bytes::copy_from_slice(&vec!(
1, 2, 3, 5, 4, 3, 2, 42
)))
);
let data = r#"[ { :test/instant #inst "2018-01-01T11:00:00Z" :test/bytes #bytes 010203050403022a } ]"#;
let result = parse::value(data).unwrap().without_spans().to_string();
assert_eq!(data, result);
}
#[test]
fn test_entities() {
let d2 = r#"[ { :test/boolean true :test/long 33 :test/double 1.4 :test/string "foo" :test/keyword :foo/bar :test/uuid #uuid "12341234-1234-1234-1234-123412341234" :test/instant #inst "2018-01-01T11:00:00Z" :test/ref 1 :test/bytes #bytes 010203050403022a } ]"#;
let r2 = parse::entities(d2);
assert!(r2.is_ok());
}
#[test] #[test]
fn test_inst() { fn test_inst() {
assert!(parse::value("#inst\"2016-01-01T11:00:00.000Z\"").is_err()); // No whitespace. assert!(parse::value("#inst\"2016-01-01T11:00:00.000Z\"").is_err()); // No whitespace.
@ -584,6 +618,12 @@ fn test_value() {
value("#inst \"2017-04-28T20:23:05.187Z\"").unwrap(), value("#inst \"2017-04-28T20:23:05.187Z\"").unwrap(),
Instant(Utc.timestamp(1493410985, 187000000)) Instant(Utc.timestamp(1493410985, 187000000))
); );
assert_eq!(
value("#bytes 010203050403022a").unwrap(),
Bytes(bytes::Bytes::copy_from_slice(&vec!(
1, 2, 3, 5, 4, 3, 2, 42
)))
);
} }
#[test] #[test]

View file

@ -1499,7 +1499,7 @@ pub unsafe extern "C" fn query_builder_bind_ref_kw(
let kw = kw_from_string(c_char_to_string(value)); let kw = kw_from_string(c_char_to_string(value));
let query_builder = &mut *query_builder; let query_builder = &mut *query_builder;
if let Some(err) = query_builder.bind_ref_from_kw(&var, kw).err() { if let Some(err) = query_builder.bind_ref_from_kw(&var, kw).err() {
panic!(err); std::panic::panic_any(err);
} }
} }

View file

@ -34,6 +34,7 @@ fn prepopulated_schema() -> Schema {
.define_simple_attr("test", "uuid", ValueType::Uuid, false) .define_simple_attr("test", "uuid", ValueType::Uuid, false)
.define_simple_attr("test", "instant", ValueType::Instant, false) .define_simple_attr("test", "instant", ValueType::Instant, false)
.define_simple_attr("test", "ref", ValueType::Ref, false) .define_simple_attr("test", "ref", ValueType::Ref, false)
.define_simple_attr("test", "bytes", ValueType::Bytes, false)
.schema .schema
} }

View file

@ -110,7 +110,7 @@ impl SimpleAggregationOp {
String => Ok(the_type), String => Ok(the_type),
// Unordered types. // Unordered types.
Keyword | Ref | Uuid => { Keyword | Ref | Uuid | Bytes => {
bail!(ProjectorError::CannotApplyAggregateOperationToTypes( bail!(ProjectorError::CannotApplyAggregateOperationToTypes(
self, self,
possibilities possibilities

View file

@ -181,6 +181,18 @@ impl QueryBuilder for SQLiteQueryBuilder {
let v = Rc::new(rusqlite::types::Value::Text(s.as_ref().to_string())); let v = Rc::new(rusqlite::types::Value::Text(s.as_ref().to_string()));
self.push_static_arg(v); self.push_static_arg(v);
} }
Bytes(b) => {
let bytes = b.to_vec();
if let Some(arg) = self.byte_args.get(&bytes).cloned() {
// Why, borrow checker, why?!
self.push_named_arg(arg.as_str());
} else {
let arg = self.next_argument_name();
self.push_named_arg(arg.as_str());
self.byte_args.insert(bytes, arg);
}
},
} }
Ok(()) Ok(())
} }

View file

@ -835,6 +835,7 @@ fn test_type_reqs() {
{:db/ident :test/uuid :db/valueType :db.type/uuid :db/cardinality :db.cardinality/one} {:db/ident :test/uuid :db/valueType :db.type/uuid :db/cardinality :db.cardinality/one}
{:db/ident :test/instant :db/valueType :db.type/instant :db/cardinality :db.cardinality/one} {:db/ident :test/instant :db/valueType :db.type/instant :db/cardinality :db.cardinality/one}
{:db/ident :test/ref :db/valueType :db.type/ref :db/cardinality :db.cardinality/one} {:db/ident :test/ref :db/valueType :db.type/ref :db/cardinality :db.cardinality/one}
{:db/ident :test/bytes :db/valueType :db.type/bytes :db/cardinality :db.cardinality/one}
]"#, ]"#,
) )
.unwrap(); .unwrap();
@ -849,7 +850,8 @@ fn test_type_reqs() {
:test/keyword :foo/bar :test/keyword :foo/bar
:test/uuid #uuid "12341234-1234-1234-1234-123412341234" :test/uuid #uuid "12341234-1234-1234-1234-123412341234"
:test/instant #inst "2018-01-01T11:00:00.000Z" :test/instant #inst "2018-01-01T11:00:00.000Z"
:test/ref 1} :test/ref 1
:test/bytes #bytes 010203050403022a }
]"#, ]"#,
) )
.unwrap(); .unwrap();

View file

@ -10,7 +10,7 @@
#![allow(dead_code)] #![allow(dead_code)]
use hyper::{body, header, Client, Body, Method, Request, StatusCode}; use hyper::{body, header, Body, Client, Method, Request, StatusCode};
use hyper_tls::HttpsConnector; use hyper_tls::HttpsConnector;
// TODO: https://github.com/mozilla/mentat/issues/570 // TODO: https://github.com/mozilla/mentat/issues/570
// use serde_cbor; // use serde_cbor;

View file

@ -741,13 +741,14 @@ impl Syncer {
// Since we've "merged" with the remote bootstrap, the "no-op" and // Since we've "merged" with the remote bootstrap, the "no-op" and
// "local fast-forward" cases are reported as merges. // "local fast-forward" cases are reported as merges.
match Syncer::what_do(remote_state, local_state) { match Syncer::what_do(remote_state, local_state) {
SyncAction::NoOp => { SyncAction::NoOp => Ok(SyncReport::Merge(SyncFollowup::None)),
Ok(SyncReport::Merge(SyncFollowup::None))
}
SyncAction::PopulateRemote => { SyncAction::PopulateRemote => {
// This is a programming error. // This is a programming error.
bail!(TolstoyError::UnexpectedState("Remote state can't be empty on first sync against non-empty remote".to_string())) bail!(TolstoyError::UnexpectedState(
"Remote state can't be empty on first sync against non-empty remote"
.to_string()
))
} }
SyncAction::RemoteFastForward => { SyncAction::RemoteFastForward => {
@ -761,12 +762,11 @@ impl Syncer {
SyncAction::CombineChanges => { SyncAction::CombineChanges => {
let local_txs = Processor::process( let local_txs = Processor::process(
&ip.transaction, Some(local_metadata.root), LocalTxSet::new())?; &ip.transaction,
Syncer::merge( Some(local_metadata.root),
ip, LocalTxSet::new(),
incoming_txs[1..].to_vec(), )?;
local_txs, Syncer::merge(ip, incoming_txs[1..].to_vec(), local_txs)
)
} }
} }
} }

View file

@ -32,6 +32,8 @@ tabwriter = "~1.2"
tempfile = "~3.2" tempfile = "~3.2"
termion = "~1.5" termion = "~1.5"
time = "~0.3" time = "~0.3"
bytes = { version = "1.0.1", features = ["serde"] }
hex = "0.4.3"
[dependencies.rusqlite] [dependencies.rusqlite]
version = "~0.25" version = "~0.25"

View file

@ -613,6 +613,7 @@ impl Repl {
Ref(r) => format!("{}", r), Ref(r) => format!("{}", r),
String(ref s) => format!("{:?}", s.to_string()), String(ref s) => format!("{:?}", s.to_string()),
Uuid(ref u) => format!("{}", u), Uuid(ref u) => format!("{}", u),
Bytes(b) => format!("#bytes {:?}", b.to_vec()),
} }
} }
} }