Merge branch 'dev' of gitee.com:cozodb/cozo into dev

main
Ziyang Hu 2 years ago
commit 0b0bbef1cb

@ -108,10 +108,11 @@ list = { "[" ~ (expr ~ ",")* ~ expr? ~ "]" }
grouping = { "(" ~ expr ~ ")" }
option = _{(limit_option|offset_option|sort_option|relation_option|timeout_option|sleep_option|
assert_none_option|assert_some_option) ~ ";"?}
assert_none_option|assert_some_option|yield_option) ~ ";"?}
out_arg = @{var ~ ("(" ~ var ~ ")")?}
limit_option = {":limit" ~ expr}
offset_option = {":offset" ~ expr}
yield_option = {":yield" ~ ident}
sort_option = {(":sort" | ":order") ~ (sort_arg ~ ",")* ~ sort_arg }
relation_option = {relation_op ~ compound_ident ~ table_schema?}
relation_op = _{relation_create | relation_replace | relation_put | relation_rm | relation_ensure | relation_ensure_not}

@ -105,20 +105,4 @@ impl From<DataValue> for JsonValue {
}
}
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use crate::data::json::JsonValue;
use crate::data::value::DataValue;
#[test]
fn bad_values() {
println!("{}", json!(f64::INFINITY));
println!("{}", JsonValue::from(DataValue::from(f64::INFINITY)));
println!("{}", JsonValue::from(DataValue::from(f64::NEG_INFINITY)));
println!("{}", JsonValue::from(DataValue::from(f64::NAN)));
}
}
}

@ -301,136 +301,3 @@ impl DataValue {
}
impl<T: Write> MemCmpEncoder for T {}
#[cfg(test)]
mod tests {
use smartstring::SmartString;
use uuid::Uuid;
use crate::data::memcmp::{decode_bytes, MemCmpEncoder};
use crate::data::value::{DataValue, Num, UuidWrapper};
#[test]
fn encode_decode_num() {
use rand::prelude::*;
let n = i64::MAX;
let mut collected = vec![];
let mut test_num = |n: Num| {
let mut encoder = vec![];
encoder.encode_num(n);
let (decoded, rest) = Num::decode_from_key(&encoder);
assert_eq!(decoded, n);
assert!(rest.is_empty());
collected.push(encoder);
};
for i in 0..54 {
for j in 0..1000 {
let vb = (n >> i) - j;
for v in [vb, -vb - 1] {
test_num(Num::Int(v));
}
}
}
test_num(Num::Float(f64::INFINITY));
test_num(Num::Float(f64::NEG_INFINITY));
test_num(Num::Float(f64::NAN));
for _ in 0..100000 {
let f = (thread_rng().gen::<f64>() - 0.5) * 2.0;
test_num(Num::Float(f));
test_num(Num::Float(1. / f));
}
let mut collected_copy = collected.clone();
collected.sort();
collected_copy.sort_by_key(|c| Num::decode_from_key(c).0);
assert_eq!(collected, collected_copy);
}
#[test]
fn test_encode_decode_uuid() {
let uuid = DataValue::Uuid(UuidWrapper(
Uuid::parse_str("dd85b19a-5fde-11ed-a88e-1774a7698039").unwrap(),
));
let mut encoder = vec![];
encoder.encode_datavalue(&uuid);
let (decoded, remaining) = DataValue::decode_from_key(&encoder);
assert_eq!(decoded, uuid);
assert!(remaining.is_empty());
}
#[test]
fn encode_decode_bytes() {
let target = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit...";
for i in 0..target.len() {
let bs = &target[i..];
let mut encoder: Vec<u8> = vec![];
encoder.encode_bytes(bs);
let (decoded, remaining) = decode_bytes(&encoder);
assert!(remaining.is_empty());
assert_eq!(bs, decoded);
let mut encoder: Vec<u8> = vec![];
encoder.encode_bytes(target);
encoder.encode_bytes(bs);
encoder.encode_bytes(bs);
encoder.encode_bytes(target);
let (decoded, remaining) = decode_bytes(&encoder);
assert_eq!(&target[..], decoded);
let (decoded, remaining) = decode_bytes(remaining);
assert_eq!(bs, decoded);
let (decoded, remaining) = decode_bytes(remaining);
assert_eq!(bs, decoded);
let (decoded, remaining) = decode_bytes(remaining);
assert_eq!(&target[..], decoded);
assert!(remaining.is_empty());
}
}
#[test]
fn specific_encode() {
let mut encoder = vec![];
encoder.encode_datavalue(&DataValue::from(2095));
// println!("e1 {:?}", encoder);
encoder.encode_datavalue(&DataValue::Str(SmartString::from("MSS")));
// println!("e2 {:?}", encoder);
let (a, remaining) = DataValue::decode_from_key(&encoder);
// println!("r {:?}", remaining);
let (b, remaining) = DataValue::decode_from_key(remaining);
assert!(remaining.is_empty());
assert_eq!(a, DataValue::from(2095));
assert_eq!(b, DataValue::Str(SmartString::from("MSS")));
}
#[test]
fn encode_decode_datavalues() {
let mut dv = vec![
DataValue::Null,
DataValue::Bool(false),
DataValue::Bool(true),
DataValue::from(1),
DataValue::from(1.0),
DataValue::from(i64::MAX),
DataValue::from(i64::MAX - 1),
DataValue::from(i64::MAX - 2),
DataValue::from(i64::MIN),
DataValue::from(i64::MIN + 1),
DataValue::from(i64::MIN + 2),
DataValue::from(f64::INFINITY),
DataValue::from(f64::NEG_INFINITY),
DataValue::List(vec![]),
];
dv.push(DataValue::List(dv.clone()));
dv.push(DataValue::List(dv.clone()));
let mut encoded = vec![];
let v = DataValue::List(dv);
encoded.encode_datavalue(&v);
let (decoded, remaining) = DataValue::decode_from_key(&encoded);
assert!(remaining.is_empty());
assert_eq!(decoded, v);
}
}

@ -43,6 +43,7 @@ pub(crate) struct QueryOutOptions {
pub(crate) sorters: Vec<(Symbol, SortDir)>,
pub(crate) store_relation: Option<(InputRelationHandle, RelationOp)>,
pub(crate) assertion: Option<QueryAssertion>,
pub(crate) yield_const: Option<Symbol>,
}
impl Debug for QueryOutOptions {
@ -199,6 +200,21 @@ impl InputInlineRulesOrFixed {
InputInlineRulesOrFixed::Fixed { fixed, .. } => fixed.span,
}
}
pub(crate) fn used_rule(&self, rule_name: &Symbol) -> bool {
match self {
InputInlineRulesOrFixed::Rules { rules, .. } => rules
.iter()
.any(|rule| rule.body.iter().any(|atom| atom.used_rule(rule_name))),
InputInlineRulesOrFixed::Fixed { fixed, .. } => fixed.rule_args.iter().any(|arg| {
if let FixedRuleArg::InMem { name, .. } = arg {
if name == rule_name {
return true;
}
}
false
}),
}
}
}
pub(crate) struct FixedRuleApply {
@ -502,6 +518,10 @@ struct EntryHeadNotExplicitlyDefinedError(#[label] SourceSpan);
pub(crate) struct NoEntryError;
impl InputProgram {
pub(crate) fn used_rule(&self, rule_name: &Symbol) -> bool {
self.prog.values().any(|rule| rule.used_rule(rule_name))
}
pub(crate) fn get_entry_arity(&self) -> Result<usize> {
if let Some(entry) = self.prog.get(&Symbol::new(PROG_ENTRY, SourceSpan(0, 0))) {
return match entry {
@ -949,6 +969,16 @@ impl Display for InputAtom {
}
impl InputAtom {
pub(crate) fn used_rule(&self, rule_name: &Symbol) -> bool {
match self {
InputAtom::Rule { inner } => inner.name == *rule_name,
InputAtom::Negation { inner, .. } => inner.used_rule(rule_name),
InputAtom::Conjunction { inner, .. } | InputAtom::Disjunction { inner, .. } => {
inner.iter().any(|a| a.used_rule(rule_name))
}
_ => false,
}
}
pub(crate) fn span(&self) -> SourceSpan {
match self {
InputAtom::Negation { span, .. }

@ -81,6 +81,9 @@ impl Symbol {
pub(crate) fn is_prog_entry(&self) -> bool {
self.name == "?"
}
pub(crate) fn is_ignored_symbol(&self) -> bool {
self.name == "_"
}
pub(crate) fn ensure_valid_field(&self) -> Result<()> {
if self.name.contains('(') || self.name.contains(')') {
#[derive(Debug, Error, Diagnostic)]

@ -0,0 +1,21 @@
/*
* Copyright 2022, The Cozo Project Authors.
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/.
*
*/
use serde_json::json;
use crate::data::json::JsonValue;
use crate::data::value::DataValue;
#[test]
fn bad_values() {
println!("{}", json!(f64::INFINITY));
println!("{}", JsonValue::from(DataValue::from(f64::INFINITY)));
println!("{}", JsonValue::from(DataValue::from(f64::NEG_INFINITY)));
println!("{}", JsonValue::from(DataValue::from(f64::NAN)));
}

@ -0,0 +1,138 @@
/*
* Copyright 2022, The Cozo Project Authors.
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/.
*
*/
use smartstring::SmartString;
use uuid::Uuid;
use crate::data::memcmp::{decode_bytes, MemCmpEncoder};
use crate::data::value::{DataValue, Num, UuidWrapper};
#[test]
fn encode_decode_num() {
use rand::prelude::*;
let n = i64::MAX;
let mut collected = vec![];
let mut test_num = |n: Num| {
let mut encoder = vec![];
encoder.encode_num(n);
let (decoded, rest) = Num::decode_from_key(&encoder);
assert_eq!(decoded, n);
assert!(rest.is_empty());
collected.push(encoder);
};
for i in 0..54 {
for j in 0..1000 {
let vb = (n >> i) - j;
for v in [vb, -vb - 1] {
test_num(Num::Int(v));
}
}
}
test_num(Num::Float(f64::INFINITY));
test_num(Num::Float(f64::NEG_INFINITY));
test_num(Num::Float(f64::NAN));
for _ in 0..100000 {
let f = (thread_rng().gen::<f64>() - 0.5) * 2.0;
test_num(Num::Float(f));
test_num(Num::Float(1. / f));
}
let mut collected_copy = collected.clone();
collected.sort();
collected_copy.sort_by_key(|c| Num::decode_from_key(c).0);
assert_eq!(collected, collected_copy);
}
#[test]
fn test_encode_decode_uuid() {
let uuid = DataValue::Uuid(UuidWrapper(
Uuid::parse_str("dd85b19a-5fde-11ed-a88e-1774a7698039").unwrap(),
));
let mut encoder = vec![];
encoder.encode_datavalue(&uuid);
let (decoded, remaining) = DataValue::decode_from_key(&encoder);
assert_eq!(decoded, uuid);
assert!(remaining.is_empty());
}
#[test]
fn encode_decode_bytes() {
let target = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit...";
for i in 0..target.len() {
let bs = &target[i..];
let mut encoder: Vec<u8> = vec![];
encoder.encode_bytes(bs);
let (decoded, remaining) = decode_bytes(&encoder);
assert!(remaining.is_empty());
assert_eq!(bs, decoded);
let mut encoder: Vec<u8> = vec![];
encoder.encode_bytes(target);
encoder.encode_bytes(bs);
encoder.encode_bytes(bs);
encoder.encode_bytes(target);
let (decoded, remaining) = decode_bytes(&encoder);
assert_eq!(&target[..], decoded);
let (decoded, remaining) = decode_bytes(remaining);
assert_eq!(bs, decoded);
let (decoded, remaining) = decode_bytes(remaining);
assert_eq!(bs, decoded);
let (decoded, remaining) = decode_bytes(remaining);
assert_eq!(&target[..], decoded);
assert!(remaining.is_empty());
}
}
#[test]
fn specific_encode() {
let mut encoder = vec![];
encoder.encode_datavalue(&DataValue::from(2095));
// println!("e1 {:?}", encoder);
encoder.encode_datavalue(&DataValue::Str(SmartString::from("MSS")));
// println!("e2 {:?}", encoder);
let (a, remaining) = DataValue::decode_from_key(&encoder);
// println!("r {:?}", remaining);
let (b, remaining) = DataValue::decode_from_key(remaining);
assert!(remaining.is_empty());
assert_eq!(a, DataValue::from(2095));
assert_eq!(b, DataValue::Str(SmartString::from("MSS")));
}
#[test]
fn encode_decode_datavalues() {
let mut dv = vec![
DataValue::Null,
DataValue::Bool(false),
DataValue::Bool(true),
DataValue::from(1),
DataValue::from(1.0),
DataValue::from(i64::MAX),
DataValue::from(i64::MAX - 1),
DataValue::from(i64::MAX - 2),
DataValue::from(i64::MIN),
DataValue::from(i64::MIN + 1),
DataValue::from(i64::MIN + 2),
DataValue::from(f64::INFINITY),
DataValue::from(f64::NEG_INFINITY),
DataValue::List(vec![]),
];
dv.push(DataValue::List(dv.clone()));
dv.push(DataValue::List(dv.clone()));
let mut encoded = vec![];
let v = DataValue::List(dv);
encoded.encode_datavalue(&v);
let (decoded, remaining) = DataValue::decode_from_key(&encoded);
assert!(remaining.is_empty());
assert_eq!(decoded, v);
}

@ -8,4 +8,7 @@
mod functions;
mod aggrs;
mod validity;
mod validity;
mod json;
mod memcmp;
mod values;

@ -0,0 +1,59 @@
/*
* Copyright 2022, The Cozo Project Authors.
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/.
*
*/
use std::collections::{BTreeMap, HashMap};
use std::mem::size_of;
use smartstring::SmartString;
use crate::data::symb::Symbol;
use crate::data::value::DataValue;
#[test]
fn show_size() {
dbg!(size_of::<DataValue>());
dbg!(size_of::<Symbol>());
dbg!(size_of::<String>());
dbg!(size_of::<HashMap<String, String>>());
dbg!(size_of::<BTreeMap<String, String>>());
}
#[test]
fn utf8() {
let c = char::from_u32(0x10FFFF).unwrap();
let mut s = String::new();
s.push(c);
println!("{}", s);
println!(
"{:b} {:b} {:b} {:b}",
s.as_bytes()[0],
s.as_bytes()[1],
s.as_bytes()[2],
s.as_bytes()[3]
);
dbg!(s);
}
#[test]
fn display_datavalues() {
println!("{}", DataValue::Null);
println!("{}", DataValue::Bool(true));
println!("{}", DataValue::from(-1));
println!("{}", DataValue::from(-1121212121.331212121));
println!("{}", DataValue::from(f64::NAN));
println!("{}", DataValue::from(f64::NEG_INFINITY));
println!(
"{}",
DataValue::List(vec![
DataValue::Bool(false),
DataValue::Str(SmartString::from(r###"abc"你"好'啊👌"###)),
DataValue::from(f64::NEG_INFINITY),
])
);
}

@ -337,57 +337,3 @@ impl DataValue {
}
pub(crate) const LARGEST_UTF_CHAR: char = '\u{10ffff}';
#[cfg(test)]
mod tests {
use std::collections::{BTreeMap, HashMap};
use std::mem::size_of;
use smartstring::SmartString;
use crate::data::symb::Symbol;
use crate::data::value::DataValue;
#[test]
fn show_size() {
dbg!(size_of::<DataValue>());
dbg!(size_of::<Symbol>());
dbg!(size_of::<String>());
dbg!(size_of::<HashMap<String, String>>());
dbg!(size_of::<BTreeMap<String, String>>());
}
#[test]
fn utf8() {
let c = char::from_u32(0x10FFFF).unwrap();
let mut s = String::new();
s.push(c);
println!("{}", s);
println!(
"{:b} {:b} {:b} {:b}",
s.as_bytes()[0],
s.as_bytes()[1],
s.as_bytes()[2],
s.as_bytes()[3]
);
dbg!(s);
}
#[test]
fn display_datavalues() {
println!("{}", DataValue::Null);
println!("{}", DataValue::Bool(true));
println!("{}", DataValue::from(-1));
println!("{}", DataValue::from(-1121212121.331212121));
println!("{}", DataValue::from(f64::NAN));
println!("{}", DataValue::from(f64::NEG_INFINITY));
println!(
"{}",
DataValue::List(vec![
DataValue::Bool(false),
DataValue::Str(SmartString::from(r###"abc"你"好'啊👌"###)),
DataValue::from(f64::NEG_INFINITY),
])
);
}
}

@ -8,7 +8,7 @@
use std::cmp::Reverse;
use std::collections::btree_map::Entry;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::rc::Rc;
@ -62,6 +62,11 @@ struct MultipleRuleDefinitionError(String, Vec<SourceSpan>);
#[diagnostic(code(parser::multiple_out_assert))]
struct DuplicateQueryAssertion(#[label] SourceSpan);
#[derive(Debug, Error, Diagnostic)]
#[error("Multiple query yields defined")]
#[diagnostic(code(parser::multiple_yields))]
struct DuplicateYield(#[label] SourceSpan);
impl Error for MultipleRuleDefinitionError {}
impl Display for MultipleRuleDefinitionError {
@ -96,7 +101,7 @@ fn merge_spans(symbs: &[Symbol]) -> SourceSpan {
pub(crate) fn parse_query(
src: Pairs<'_>,
param_pool: &BTreeMap<String, DataValue>,
fixedrithms: &BTreeMap<String, Arc<Box<dyn FixedRule>>>,
fixed_rules: &BTreeMap<String, Arc<Box<dyn FixedRule>>>,
cur_vld: ValidityTs,
) -> Result<InputProgram> {
let mut progs: BTreeMap<Symbol, InputInlineRulesOrFixed> = Default::default();
@ -150,7 +155,7 @@ pub(crate) fn parse_query(
}
Rule::fixed_rule => {
let rule_span = pair.extract_span();
let (name, apply) = parse_fixed_rule(pair, param_pool, fixedrithms, cur_vld)?;
let (name, apply) = parse_fixed_rule(pair, param_pool, fixed_rules, cur_vld)?;
match progs.entry(name) {
Entry::Vacant(e) => {
@ -342,6 +347,14 @@ pub(crate) fn parse_query(
);
out_opts.assertion = Some(QueryAssertion::AssertSome(pair.extract_span()))
}
Rule::yield_option => {
ensure!(
out_opts.yield_const.is_none(),
DuplicateYield(pair.extract_span())
);
let p = pair.into_inner().next().unwrap();
out_opts.yield_const = Some(Symbol::new(p.as_str(), p.extract_span()));
}
Rule::EOI => break,
r => unreachable!("{:?}", r),
}
@ -451,8 +464,14 @@ fn parse_rule(
ensure!(!head.is_empty(), EmptyRuleHead(head_span));
let body = src.next().unwrap();
let mut body_clauses = vec![];
let mut ignored_counter = 0;
for atom_src in body.into_inner() {
body_clauses.push(parse_disjunction(atom_src, param_pool, cur_vld)?)
body_clauses.push(parse_disjunction(
atom_src,
param_pool,
cur_vld,
&mut ignored_counter,
)?)
}
Ok((
@ -470,11 +489,12 @@ fn parse_disjunction(
pair: Pair<'_>,
param_pool: &BTreeMap<String, DataValue>,
cur_vld: ValidityTs,
ignored_counter: &mut u32,
) -> Result<InputAtom> {
let span = pair.extract_span();
let res: Vec<_> = pair
.into_inner()
.map(|v| parse_atom(v, param_pool, cur_vld))
.map(|v| parse_atom(v, param_pool, cur_vld, ignored_counter))
.try_collect()?;
Ok(if res.len() == 1 {
res.into_iter().next().unwrap()
@ -487,23 +507,29 @@ fn parse_atom(
src: Pair<'_>,
param_pool: &BTreeMap<String, DataValue>,
cur_vld: ValidityTs,
ignored_counter: &mut u32,
) -> Result<InputAtom> {
Ok(match src.as_rule() {
Rule::rule_body => {
let span = src.extract_span();
let grouped: Vec<_> = src
.into_inner()
.map(|v| parse_disjunction(v, param_pool, cur_vld))
.map(|v| parse_disjunction(v, param_pool, cur_vld, ignored_counter))
.try_collect()?;
InputAtom::Conjunction {
inner: grouped,
span,
}
}
Rule::disjunction => parse_disjunction(src, param_pool, cur_vld)?,
Rule::disjunction => parse_disjunction(src, param_pool, cur_vld, ignored_counter)?,
Rule::negation => {
let span = src.extract_span();
let inner = parse_atom(src.into_inner().next().unwrap(), param_pool, cur_vld)?;
let inner = parse_atom(
src.into_inner().next().unwrap(),
param_pool,
cur_vld,
ignored_counter,
)?;
InputAtom::Negation {
inner: inner.into(),
span,
@ -517,10 +543,15 @@ fn parse_atom(
let span = src.extract_span();
let mut src = src.into_inner();
let var = src.next().unwrap();
let mut symb = Symbol::new(var.as_str(), var.extract_span());
if symb.is_ignored_symbol() {
symb.name = format!("*^*{}", *ignored_counter).into();
*ignored_counter += 1;
}
let expr = build_expr(src.next().unwrap(), param_pool)?;
InputAtom::Unification {
inner: Unification {
binding: Symbol::new(var.as_str(), var.extract_span()),
binding: symb,
expr,
one_many_unif: false,
span,
@ -531,10 +562,15 @@ fn parse_atom(
let span = src.extract_span();
let mut src = src.into_inner();
let var = src.next().unwrap();
let mut symb = Symbol::new(var.as_str(), var.extract_span());
if symb.is_ignored_symbol() {
symb.name = format!("*^*{}", *ignored_counter).into();
*ignored_counter += 1;
}
let expr = build_expr(src.next().unwrap(), param_pool)?;
InputAtom::Unification {
inner: Unification {
binding: Symbol::new(var.as_str(), var.extract_span()),
binding: symb,
expr,
one_many_unif: true,
span,
@ -690,7 +726,7 @@ struct BadValiditySpecification(#[label] SourceSpan);
fn parse_fixed_rule(
src: Pair<'_>,
param_pool: &BTreeMap<String, DataValue>,
fixedrithms: &BTreeMap<String, Arc<Box<dyn FixedRule>>>,
fixed_rules: &BTreeMap<String, Arc<Box<dyn FixedRule>>>,
cur_vld: ValidityTs,
) -> Result<(Symbol, FixedRuleApply)> {
let mut src = src.into_inner();
@ -701,10 +737,18 @@ fn parse_fixed_rule(
#[diagnostic(code(parser::fixed_aggr_conflict))]
struct AggrInfixedError(#[label] SourceSpan);
#[derive(Debug, Error, Diagnostic)]
#[error("fixed rule cannot have duplicate bindings")]
#[diagnostic(code(parser::duplicate_bindings_for_fixed_rule))]
struct DuplicateBindingError(#[label] SourceSpan);
for (a, v) in aggr.iter().zip(head.iter()) {
ensure!(a.is_none(), AggrInfixedError(v.span))
}
let mut seen_bindings = BTreeSet::new();
let mut binding_gen_id = 0;
let name_pair = src.next().unwrap();
let fixed_name = &name_pair.as_str();
let mut rule_args: Vec<FixedRuleArg> = vec![];
@ -721,9 +765,22 @@ fn parse_fixed_rule(
Rule::fixed_rule_rel => {
let mut els = inner.into_inner();
let name = els.next().unwrap();
let bindings = els
.map(|v| Symbol::new(v.as_str(), v.extract_span()))
.collect_vec();
let mut bindings = Vec::with_capacity(els.size_hint().1.unwrap_or(4));
for v in els {
let s = v.as_str();
if s == "_" {
let symb =
Symbol::new(format!("*_*{}", binding_gen_id), v.extract_span());
binding_gen_id += 1;
bindings.push(symb);
} else {
if !seen_bindings.insert(s) {
bail!(DuplicateBindingError(v.extract_span()))
}
let symb = Symbol::new(s, v.extract_span());
bindings.push(symb);
}
}
rule_args.push(FixedRuleArg::InMem {
name: Symbol::new(name.as_str(), name.extract_span()),
bindings,
@ -738,7 +795,20 @@ fn parse_fixed_rule(
for v in els {
match v.as_rule() {
Rule::var => {
bindings.push(Symbol::new(v.as_str(), v.extract_span()))
let s = v.as_str();
if s == "_" {
let symb = Symbol::new(
format!("*_*{}", binding_gen_id),
v.extract_span(),
);
binding_gen_id += 1;
bindings.push(symb);
} else {
if !seen_bindings.insert(s) {
bail!(DuplicateBindingError(v.extract_span()))
}
bindings.push(Symbol::new(v.as_str(), v.extract_span()))
}
}
Rule::validity_clause => {
let vld_inner = v.into_inner().next().unwrap();
@ -770,8 +840,18 @@ fn parse_fixed_rule(
let kp = vs.next().unwrap();
let k = SmartString::from(kp.as_str());
let v = match vs.next() {
Some(vp) => Symbol::new(vp.as_str(), vp.extract_span()),
None => Symbol::new(k.clone(), kp.extract_span()),
Some(vp) => {
if !seen_bindings.insert(vp.as_str()) {
bail!(DuplicateBindingError(vp.extract_span()))
}
Symbol::new(vp.as_str(), vp.extract_span())
}
None => {
if !seen_bindings.insert(kp.as_str()) {
bail!(DuplicateBindingError(kp.extract_span()))
}
Symbol::new(k.clone(), kp.extract_span())
}
};
bindings.insert(k, v);
}
@ -810,7 +890,7 @@ fn parse_fixed_rule(
let fixed = FixedRuleHandle::new(fixed_name, name_pair.extract_span());
let fixed_impl = fixedrithms
let fixed_impl = fixed_rules
.get(&fixed.name as &str)
.ok_or_else(|| FixedRuleNotFoundError(fixed.name.to_string(), name_pair.extract_span()))?;
fixed_impl.init_options(&mut options, args_list_span)?;

@ -237,7 +237,10 @@ impl InputRuleApplyAtom {
for arg in self.args {
match arg {
Expr::Binding { var, .. } => {
if seen_variables.insert(var.clone()) {
if var.is_ignored_symbol() {
let dup = gen.next(var.span);
args.push(dup);
} else if seen_variables.insert(var.clone()) {
args.push(var);
} else {
let dup = gen.next(var.span);
@ -294,7 +297,9 @@ impl InputRelationApplyAtom {
for arg in self.args {
match arg {
Expr::Binding { var, .. } => {
if seen_variables.insert(var.clone()) {
if var.is_ignored_symbol() {
args.push(gen.next(var.span));
} else if seen_variables.insert(var.clone()) {
args.push(var);
} else {
let span = var.span;

@ -10,6 +10,7 @@ use std::collections::btree_map::Entry;
use std::collections::BTreeMap;
use std::default::Default;
use std::fmt::{Debug, Formatter};
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, RwLock};
#[allow(unused_imports)]
@ -25,13 +26,18 @@ use serde_json::json;
use smartstring::SmartString;
use thiserror::Error;
use crate::data::expr::Expr;
use crate::data::functions::current_validity;
use crate::data::json::JsonValue;
use crate::data::program::{InputProgram, QueryAssertion, RelationOp};
use crate::data::program::{
FixedRuleApply, InputInlineRulesOrFixed, InputProgram, QueryAssertion, RelationOp,
};
use crate::data::relation::ColumnDef;
use crate::data::symb::Symbol;
use crate::data::tuple::{Tuple, TupleT};
use crate::data::value::{DataValue, ValidityTs, LARGEST_UTF_CHAR};
use crate::fixed_rule::DEFAULT_FIXED_RULES;
use crate::fixed_rule::utilities::Constant;
use crate::fixed_rule::{FixedRuleHandle, DEFAULT_FIXED_RULES};
use crate::parse::sys::SysOp;
use crate::parse::{parse_script, CozoScript, SourceSpan};
use crate::query::compile::{CompiledProgram, CompiledRule, CompiledRuleSet};
@ -100,7 +106,7 @@ impl<S> Debug for Db<S> {
#[diagnostic(code(db::init))]
pub(crate) struct BadDbInit(#[help] pub(crate) String);
#[derive(serde_derive::Serialize, serde_derive::Deserialize, Debug)]
#[derive(serde_derive::Serialize, serde_derive::Deserialize, Debug, Clone)]
/// Rows in a relation, together with headers for the fields.
pub struct NamedRows {
/// The headers
@ -510,10 +516,21 @@ impl<'s, S: Storage<'s>> Db<S> {
self.transact()?
};
for p in ps {
let mut propagate_results = BTreeMap::new();
let prog_n = ps.len();
for (i, mut p) in ps.into_iter().enumerate() {
#[allow(unused_variables)]
let sleep_opt = p.out_opts.sleep;
let prop = p.out_opts.yield_const.clone();
propagate_previous_results(&mut p, &propagate_results)?;
let (q_res, q_cleanups) = self.run_query(&mut tx, p, cur_vld)?;
if let Some(to_yield) = prop {
if i != prog_n - 1 {
propagate_results.insert(to_yield, q_res.clone());
}
}
res = q_res;
cleanups.extend(q_cleanups);
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
@ -867,7 +884,7 @@ impl<'s, S: Storage<'s>> Db<S> {
StoreRelationNotFoundError(meta.name.to_string())
);
existing.ensure_compatible(meta)?;
existing.ensure_compatible(meta, *op == RelationOp::Rm)?;
}
};
@ -1194,151 +1211,56 @@ impl Poison {
}
}
#[cfg(test)]
mod tests {
use itertools::Itertools;
use log::debug;
use serde_json::json;
use crate::new_cozo_mem;
#[test]
fn test_limit_offset() {
let db = new_cozo_mem().unwrap();
let res = db
.run_script("?[a] := a in [5,3,1,2,4] :limit 2", Default::default())
.unwrap()
.rows
.into_iter()
.flatten()
.collect_vec();
assert_eq!(json!(res), json!([3, 5]));
let res = db
.run_script(
"?[a] := a in [5,3,1,2,4] :limit 2 :offset 1",
Default::default(),
)
.unwrap()
.rows
.into_iter()
.flatten()
.collect_vec();
assert_eq!(json!(res), json!([1, 3]));
let res = db
.run_script(
"?[a] := a in [5,3,1,2,4] :limit 2 :offset 4",
Default::default(),
)
.unwrap()
.rows
.into_iter()
.flatten()
.collect_vec();
assert_eq!(json!(res), json!([4]));
let res = db
.run_script(
"?[a] := a in [5,3,1,2,4] :limit 2 :offset 5",
Default::default(),
)
.unwrap()
.rows
.into_iter()
.flatten()
.collect_vec();
assert_eq!(json!(res), json!([]));
}
#[test]
fn test_normal_aggr_empty() {
let db = new_cozo_mem().unwrap();
let res = db
.run_script("?[count(a)] := a in []", Default::default())
.unwrap()
.rows;
assert_eq!(res, vec![vec![json!(0)]]);
}
#[test]
fn test_meet_aggr_empty() {
let db = new_cozo_mem().unwrap();
let res = db
.run_script("?[min(a)] := a in []", Default::default())
.unwrap()
.rows;
assert_eq!(res, vec![vec![json!(null)]]);
let res = db
.run_script("?[min(a), count(a)] := a in []", Default::default())
.unwrap()
.rows;
assert_eq!(res, vec![vec![json!(null), json!(0)]]);
}
#[test]
fn test_layers() {
let _ = env_logger::builder().is_test(true).try_init();
let db = new_cozo_mem().unwrap();
let res = db
.run_script(
r#"
y[a] := a in [1,2,3]
x[sum(a)] := y[a]
x[sum(a)] := a in [4,5,6]
?[sum(a)] := x[a]
"#,
Default::default(),
)
.unwrap()
.rows;
assert_eq!(res[0][0], json!(21.))
}
#[test]
fn test_conditions() {
let _ = env_logger::builder().is_test(true).try_init();
let db = new_cozo_mem().unwrap();
db.run_script(
r#"
{
?[code] <- [['a'],['b'],['c']]
:create airport {code}
fn propagate_previous_results(
p: &mut InputProgram,
prev_results: &BTreeMap<Symbol, NamedRows>,
) -> Result<()> {
for (k, v) in prev_results {
if !p.used_rule(k) {
continue;
}
{
?[fr, to, dist] <- [['a', 'b', 1.1], ['a', 'c', 0.5], ['b', 'c', 9.1]]
:create route {fr, to => dist}
let replaced = p.prog.insert(
k.clone(),
InputInlineRulesOrFixed::Fixed {
fixed: FixedRuleApply {
fixed_handle: FixedRuleHandle {
name: Symbol::new("Constant", Default::default()),
},
rule_args: vec![],
options: Rc::new(BTreeMap::from([(
SmartString::from("data"),
Expr::Const {
val: DataValue::List(
v.rows
.iter()
.map(|row| {
DataValue::List(
row.iter().map(DataValue::from).collect_vec(),
)
})
.collect_vec(),
),
span: Default::default(),
},
)])),
head: vec![],
arity: v.headers.len(),
span: Default::default(),
fixed_impl: Arc::new(Box::new(Constant)),
},
},
);
if let Some(replaced_rel) = replaced {
#[derive(Debug, Diagnostic, Error)]
#[error("Name conflict with previous yield: '{0}'")]
#[diagnostic(code(db::name_confilict_with_yield))]
pub(crate) struct ConflictWithPrevYield(String, #[label] SourceSpan);
bail!(ConflictWithPrevYield(
k.to_string(),
replaced_rel.first_span()
))
}
"#,
Default::default(),
)
.unwrap();
debug!("real test begins");
let res = db
.run_script(
r#"
r[code, dist] := *airport{code}, *route{fr: code, dist};
?[dist] := r['a', dist], dist > 0.5, dist <= 1.1;
"#,
Default::default(),
)
.unwrap()
.rows;
assert_eq!(res[0][0], json!(1.1))
}
#[test]
fn test_classical() {
let _ = env_logger::builder().is_test(true).try_init();
let db = new_cozo_mem().unwrap();
let res = db
.run_script(
r#"
parent[] <- [['joseph', 'jakob'],
['jakob', 'issac'],
['issac', 'abraham']]
grandparent[gcld, gp] := parent[gcld, p], parent[p, gp]
?[who] := grandparent[who, 'abraham']
"#,
Default::default(),
)
.unwrap()
.rows;
println!("{:?}", res);
assert_eq!(res[0][0], json!("jakob"))
}
Ok(())
}

@ -10,3 +10,5 @@ pub(crate) mod db;
pub(crate) mod transact;
pub(crate) mod relation;
pub(crate) mod temp_store;
#[cfg(test)]
mod tests;

@ -163,7 +163,7 @@ impl RelationHandle {
tuple.serialize(&mut Serializer::new(&mut ret)).unwrap();
Ok(ret)
}
pub(crate) fn ensure_compatible(&self, inp: &InputRelationHandle) -> Result<()> {
pub(crate) fn ensure_compatible(&self, inp: &InputRelationHandle, is_remove: bool) -> Result<()> {
let InputRelationHandle { metadata, .. } = inp;
// check that every given key is found and compatible
for col in &metadata.keys {
@ -176,8 +176,10 @@ impl RelationHandle {
for col in &self.metadata.keys {
metadata.satisfied_by_required_col(col, true)?;
}
for col in &self.metadata.non_keys {
metadata.satisfied_by_required_col(col, false)?;
if !is_remove {
for col in &self.metadata.non_keys {
metadata.satisfied_by_required_col(col, false)?;
}
}
Ok(())
}
@ -497,49 +499,3 @@ pub(crate) struct InsufficientAccessLevel(
pub(crate) AccessLevel,
);
#[cfg(test)]
mod tests {
use serde_json::json;
use crate::new_cozo_mem;
#[test]
fn test_trigger() {
let db = new_cozo_mem().unwrap();
db.run_script(":create friends {fr: Int, to: Int}", Default::default())
.unwrap();
db.run_script(":create friends.rev {to: Int, fr: Int}", Default::default())
.unwrap();
db.run_script(
r#"
::set_triggers friends
on put {
?[fr, to] := _new[fr, to]
:put friends.rev{ to, fr }
}
on rm {
?[fr, to] := _old[fr, to]
:rm friends.rev{ to, fr }
}
"#,
Default::default(),
)
.unwrap();
db.run_script(
r"?[fr, to] <- [[1,2]] :put friends {fr, to}",
Default::default(),
)
.unwrap();
let ret = db
.export_relations(["friends", "friends.rev"].into_iter())
.unwrap();
let frs = ret.get("friends").unwrap();
assert_eq!(vec![json!(1), json!(2)], frs.rows[0]);
let frs_rev = ret.get("friends.rev").unwrap();
assert_eq!(vec![json!(2), json!(1)], frs_rev.rows[0]);
}
}

@ -95,8 +95,6 @@ pub(crate) struct MeetAggrStore {
grouping_len: usize,
}
// optimization: MeetAggrStore can be used to simulate functional dependency
impl MeetAggrStore {
pub(crate) fn wrap(self) -> TempStore {
TempStore::MeetAggr(self)

@ -0,0 +1,373 @@
/*
* Copyright 2022, The Cozo Project Authors.
*
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/.
*
*/
use itertools::Itertools;
use log::debug;
use serde_json::json;
use crate::new_cozo_mem;
#[test]
fn test_limit_offset() {
let db = new_cozo_mem().unwrap();
let res = db
.run_script("?[a] := a in [5,3,1,2,4] :limit 2", Default::default())
.unwrap()
.rows
.into_iter()
.flatten()
.collect_vec();
assert_eq!(json!(res), json!([3, 5]));
let res = db
.run_script(
"?[a] := a in [5,3,1,2,4] :limit 2 :offset 1",
Default::default(),
)
.unwrap()
.rows
.into_iter()
.flatten()
.collect_vec();
assert_eq!(json!(res), json!([1, 3]));
let res = db
.run_script(
"?[a] := a in [5,3,1,2,4] :limit 2 :offset 4",
Default::default(),
)
.unwrap()
.rows
.into_iter()
.flatten()
.collect_vec();
assert_eq!(json!(res), json!([4]));
let res = db
.run_script(
"?[a] := a in [5,3,1,2,4] :limit 2 :offset 5",
Default::default(),
)
.unwrap()
.rows
.into_iter()
.flatten()
.collect_vec();
assert_eq!(json!(res), json!([]));
}
#[test]
fn test_normal_aggr_empty() {
let db = new_cozo_mem().unwrap();
let res = db
.run_script("?[count(a)] := a in []", Default::default())
.unwrap()
.rows;
assert_eq!(res, vec![vec![json!(0)]]);
}
#[test]
fn test_meet_aggr_empty() {
let db = new_cozo_mem().unwrap();
let res = db
.run_script("?[min(a)] := a in []", Default::default())
.unwrap()
.rows;
assert_eq!(res, vec![vec![json!(null)]]);
let res = db
.run_script("?[min(a), count(a)] := a in []", Default::default())
.unwrap()
.rows;
assert_eq!(res, vec![vec![json!(null), json!(0)]]);
}
#[test]
fn test_layers() {
let _ = env_logger::builder().is_test(true).try_init();
let db = new_cozo_mem().unwrap();
let res = db
.run_script(
r#"
y[a] := a in [1,2,3]
x[sum(a)] := y[a]
x[sum(a)] := a in [4,5,6]
?[sum(a)] := x[a]
"#,
Default::default(),
)
.unwrap()
.rows;
assert_eq!(res[0][0], json!(21.))
}
#[test]
fn test_conditions() {
let _ = env_logger::builder().is_test(true).try_init();
let db = new_cozo_mem().unwrap();
db.run_script(
r#"
{
?[code] <- [['a'],['b'],['c']]
:create airport {code}
}
{
?[fr, to, dist] <- [['a', 'b', 1.1], ['a', 'c', 0.5], ['b', 'c', 9.1]]
:create route {fr, to => dist}
}
"#,
Default::default(),
)
.unwrap();
debug!("real test begins");
let res = db
.run_script(
r#"
r[code, dist] := *airport{code}, *route{fr: code, dist};
?[dist] := r['a', dist], dist > 0.5, dist <= 1.1;
"#,
Default::default(),
)
.unwrap()
.rows;
assert_eq!(res[0][0], json!(1.1))
}
#[test]
fn test_classical() {
let _ = env_logger::builder().is_test(true).try_init();
let db = new_cozo_mem().unwrap();
let res = db
.run_script(
r#"
parent[] <- [['joseph', 'jakob'],
['jakob', 'issac'],
['issac', 'abraham']]
grandparent[gcld, gp] := parent[gcld, p], parent[p, gp]
?[who] := grandparent[who, 'abraham']
"#,
Default::default(),
)
.unwrap()
.rows;
println!("{:?}", res);
assert_eq!(res[0][0], json!("jakob"))
}
#[test]
fn default_columns() {
let db = new_cozo_mem().unwrap();
db.run_script(
r#"
:create status {uid: String, ts default now() => quitted: Bool, mood: String}
"#,
Default::default(),
)
.unwrap();
db.run_script(
r#"
?[uid, quitted, mood] <- [['z', true, 'x']]
:put status {uid => quitted, mood}
"#,
Default::default(),
)
.unwrap();
}
#[test]
fn rm_does_not_need_all_keys() {
let db = new_cozo_mem().unwrap();
db.run_script(":create status {uid => mood}", Default::default())
.unwrap();
assert!(db
.run_script(
"?[uid, mood] <- [[1, 2]] :put status {uid => mood}",
Default::default()
)
.is_ok());
assert!(db
.run_script(
"?[uid, mood] <- [[2]] :put status {uid}",
Default::default()
)
.is_err());
assert!(db
.run_script(
"?[uid, mood] <- [[3, 2]] :rm status {uid => mood}",
Default::default()
)
.is_ok());
assert!(db
.run_script("?[uid] <- [[1]] :rm status {uid}", Default::default())
.is_ok());
}
#[test]
fn strict_checks_for_fixed_rules_args() {
let db = new_cozo_mem().unwrap();
let res = db.run_script(
r#"
r[] <- [[1, 2]]
?[] <~ PageRank(r[_, _])
"#,
Default::default(),
);
assert!(res.is_ok());
let db = new_cozo_mem().unwrap();
let res = db.run_script(
r#"
r[] <- [[1, 2]]
?[] <~ PageRank(r[a, b])
"#,
Default::default(),
);
assert!(res.is_ok());
let db = new_cozo_mem().unwrap();
let res = db.run_script(
r#"
r[] <- [[1, 2]]
?[] <~ PageRank(r[a, a])
"#,
Default::default(),
);
assert!(res.is_err());
}
#[test]
fn do_not_unify_underscore() {
let db = new_cozo_mem().unwrap();
let res = db
.run_script(
r#"
r1[] <- [[1, 'a'], [2, 'b']]
r2[] <- [[2, 'B'], [3, 'C']]
?[l1, l2] := r1[_ , l1], r2[_ , l2]
"#,
Default::default(),
)
.unwrap()
.rows;
assert_eq!(res.len(), 4);
let res = db.run_script(
r#"
?[_] := _ = 1
"#,
Default::default(),
);
assert!(res.is_err());
let res = db
.run_script(
r#"
?[x] := x = 1, _ = 1, _ = 2
"#,
Default::default(),
)
.unwrap()
.rows;
assert_eq!(res.len(), 1);
}
#[test]
fn returning_relations() {
let db = new_cozo_mem().unwrap();
let res = db
.run_script(
r#"
{
?[] <- [[1,2,3]]
:yield nxt
}
{
?[a,b,c] := nxt[a, b, c]
}
"#,
Default::default(),
)
.unwrap()
.rows;
assert_eq!(json!(res), json!([[1, 2, 3]]));
let res = db
.run_script(
r#"
{
?[a] <- [[1]]
:yield first_yield
}
{
?[a] := first_yield[b], a = b + 1
:yield second_yield
}
{
?[a] := first_yield[a]
?[a] := second_yield[a]
}
"#,
Default::default(),
)
.unwrap()
.rows;
assert_eq!(json!(res), json!([[1], [2]]));
let res = db.run_script(
r#"
{
?[] <- [[1,2,3]]
:yield nxt
}
{
nxt[] <- [[2, 3, 5]]
?[a,b,c] := nxt[a, b, c]
}
"#,
Default::default(),
);
assert!(res.is_err());
}
#[test]
fn test_trigger() {
let db = new_cozo_mem().unwrap();
db.run_script(":create friends {fr: Int, to: Int}", Default::default())
.unwrap();
db.run_script(":create friends.rev {to: Int, fr: Int}", Default::default())
.unwrap();
db.run_script(
r#"
::set_triggers friends
on put {
?[fr, to] := _new[fr, to]
:put friends.rev{ to, fr }
}
on rm {
?[fr, to] := _old[fr, to]
:rm friends.rev{ to, fr }
}
"#,
Default::default(),
)
.unwrap();
db.run_script(
r"?[fr, to] <- [[1,2]] :put friends {fr, to}",
Default::default(),
)
.unwrap();
let ret = db
.export_relations(["friends", "friends.rev"].into_iter())
.unwrap();
let frs = ret.get("friends").unwrap();
assert_eq!(vec![json!(1), json!(2)], frs.rows[0]);
let frs_rev = ret.get("friends.rev").unwrap();
assert_eq!(vec![json!(2), json!(1)], frs_rev.rows[0]);
}
Loading…
Cancel
Save