diff --git a/cozo-core/src/data/relation.rs b/cozo-core/src/data/relation.rs index ea5e3405..ac542af3 100644 --- a/cozo-core/src/data/relation.rs +++ b/cozo-core/src/data/relation.rs @@ -123,9 +123,8 @@ pub(crate) struct StoredRelationMetadata { } impl StoredRelationMetadata { - pub(crate) fn satisfied_by_required_col(&self, col: &ColumnDef, is_key: bool) -> Result<()> { - let targets = if is_key { &self.keys } else { &self.non_keys }; - for target in targets { + pub(crate) fn satisfied_by_required_col(&self, col: &ColumnDef) -> Result<()> { + for target in self.keys.iter().chain(self.non_keys.iter()) { if target.name == col.name { return Ok(()); } @@ -140,9 +139,8 @@ impl StoredRelationMetadata { } Ok(()) } - pub(crate) fn compatible_with_col(&self, col: &ColumnDef, is_key: bool) -> Result<()> { - let targets = if is_key { &self.keys } else { &self.non_keys }; - for target in targets { + pub(crate) fn compatible_with_col(&self, col: &ColumnDef) -> Result<()> { + for target in self.keys.iter().chain(self.non_keys.iter()) { if target.name == col.name { #[derive(Debug, Error, Diagnostic)] #[error("requested column {0} has typing {1}, but the requested typing is {2}")] diff --git a/cozo-core/src/parse/query.rs b/cozo-core/src/parse/query.rs index 045dd67b..41363ee7 100644 --- a/cozo-core/src/parse/query.rs +++ b/cozo-core/src/parse/query.rs @@ -22,7 +22,11 @@ use thiserror::Error; use crate::data::aggr::{parse_aggr, Aggregation}; use crate::data::expr::Expr; use crate::data::functions::{str2vld, MAX_VALIDITY_TS}; -use crate::data::program::{FixedRuleApply, FixedRuleArg, InputAtom, InputInlineRule, InputInlineRulesOrFixed, InputNamedFieldRelationApplyAtom, InputProgram, InputRelationApplyAtom, InputRuleApplyAtom, QueryAssertion, QueryOutOptions, RelationOp, SearchInput, SortDir, Unification}; +use crate::data::program::{ + FixedRuleApply, FixedRuleArg, InputAtom, InputInlineRule, InputInlineRulesOrFixed, + InputNamedFieldRelationApplyAtom, InputProgram, InputRelationApplyAtom, InputRuleApplyAtom, + QueryAssertion, QueryOutOptions, RelationOp, SearchInput, SortDir, Unification, +}; use crate::data::relation::{ColType, ColumnDef, NullableColType, StoredRelationMetadata}; use crate::data::symb::{Symbol, PROG_ENTRY}; use crate::data::value::{DataValue, ValidityTs}; @@ -319,7 +323,14 @@ pub(crate) fn parse_query( match args.next() { None => stored_relation = Some(Left((name, span, op))), Some(schema_p) => { - let (metadata, key_bindings, dep_bindings) = parse_schema(schema_p)?; + let (mut metadata, mut key_bindings, mut dep_bindings) = + parse_schema(schema_p)?; + if !matches!(op, RelationOp::Create | RelationOp::Replace) { + key_bindings.extend(dep_bindings); + dep_bindings = vec![]; + metadata.keys.extend(metadata.non_keys); + metadata.non_keys = vec![]; + } stored_relation = Some(Right(( InputRelationHandle { name, diff --git a/cozo-core/src/query/ra.rs b/cozo-core/src/query/ra.rs index aa5882d5..0c4a4beb 100644 --- a/cozo-core/src/query/ra.rs +++ b/cozo-core/src/query/ra.rs @@ -7,13 +7,14 @@ */ use std::collections::{BTreeMap, BTreeSet}; -use std::fmt::{Debug, Formatter}; +use std::fmt::{Debug, Formatter, Write}; use std::iter; use either::{Left, Right}; use itertools::Itertools; use log::{debug, error}; use miette::{bail, Diagnostic, Result}; +use smartstring::SmartString; use thiserror::Error; use crate::data::expr::{compute_bounds, eval_bytecode, eval_bytecode_pred, Bytecode, Expr}; @@ -1027,14 +1028,14 @@ impl FtsSearchRA { let q = match tuple[bind_idx].clone() { DataValue::Str(s) => s, DataValue::List(l) => { - let mut coll = String::new(); + let mut coll = SmartString::new(); for d in l { match d { DataValue::Str(s) => { if !coll.is_empty() { - coll += " OR "; + coll.write_str(" OR ").unwrap(); } - coll += &s + coll.write_str(&s).unwrap(); }, d => bail!("Expected string for FTS search, got {:?}", d), } diff --git a/cozo-core/src/query/stored.rs b/cozo-core/src/query/stored.rs index ad786e91..90054df3 100644 --- a/cozo-core/src/query/stored.rs +++ b/cozo-core/src/query/stored.rs @@ -153,7 +153,6 @@ impl<'a> SessionTx<'a> { &mut relation_store, metadata, key_bindings, - dep_bindings, *span, )?, RelationOp::EnsureNot => self.ensure_not_in_relation( @@ -177,7 +176,6 @@ impl<'a> SessionTx<'a> { &mut relation_store, metadata, key_bindings, - dep_bindings, *span, )?, RelationOp::Create | RelationOp::Replace | RelationOp::Put => self.put_into_relation( @@ -243,12 +241,21 @@ impl<'a> SessionTx<'a> { let mut new_tuples: Vec = vec![]; let mut old_tuples: Vec = vec![]; - let val_extractors = make_extractors( - &relation_store.metadata.non_keys, - &metadata.non_keys, - dep_bindings, - headers, - )?; + let val_extractors = if metadata.non_keys.is_empty() { + make_extractors( + &relation_store.metadata.non_keys, + &metadata.keys, + key_bindings, + headers, + )? + } else { + make_extractors( + &relation_store.metadata.non_keys, + &metadata.non_keys, + dep_bindings, + headers, + )? + }; key_extractors.extend(val_extractors); let mut stack = vec![]; let hnsw_filters = Self::make_hnsw_filters(relation_store)?; @@ -499,7 +506,6 @@ impl<'a> SessionTx<'a> { relation_store: &mut RelationHandle, metadata: &StoredRelationMetadata, key_bindings: &[Symbol], - dep_bindings: &[Symbol], span: SourceSpan, ) -> Result<()> { let is_callback_target = callback_targets.contains(&relation_store.name); @@ -531,8 +537,8 @@ impl<'a> SessionTx<'a> { let val_extractors = make_update_extractors( &relation_store.metadata.non_keys, - &metadata.non_keys, - dep_bindings, + &metadata.keys, + key_bindings, headers, ); @@ -817,7 +823,6 @@ impl<'a> SessionTx<'a> { relation_store: &mut RelationHandle, metadata: &StoredRelationMetadata, key_bindings: &[Symbol], - dep_bindings: &[Symbol], span: SourceSpan, ) -> Result<()> { if relation_store.access_level < AccessLevel::ReadOnly { @@ -837,8 +842,8 @@ impl<'a> SessionTx<'a> { let val_extractors = make_extractors( &relation_store.metadata.non_keys, - &metadata.non_keys, - dep_bindings, + &metadata.keys, + key_bindings, headers, )?; key_extractors.extend(val_extractors); diff --git a/cozo-core/src/runtime/relation.rs b/cozo-core/src/runtime/relation.rs index 00380614..d4abf6dd 100644 --- a/cozo-core/src/runtime/relation.rs +++ b/cozo-core/src/runtime/relation.rs @@ -301,19 +301,16 @@ impl RelationHandle { ) -> Result<()> { let InputRelationHandle { metadata, .. } = inp; // check that every given key is found and compatible - for col in &metadata.keys { - self.metadata.compatible_with_col(col, true)? - } - for col in &metadata.non_keys { - self.metadata.compatible_with_col(col, false)? + for col in metadata.keys.iter().chain(self.metadata.non_keys.iter()) { + self.metadata.compatible_with_col(col)? } // check that every key is provided or has default for col in &self.metadata.keys { - metadata.satisfied_by_required_col(col, true)?; + metadata.satisfied_by_required_col(col)?; } if !is_remove_or_update { for col in &self.metadata.non_keys { - metadata.satisfied_by_required_col(col, false)?; + metadata.satisfied_by_required_col(col)?; } } Ok(()) diff --git a/cozo-core/src/runtime/tests.rs b/cozo-core/src/runtime/tests.rs index 95368264..fa032ead 100644 --- a/cozo-core/src/runtime/tests.rs +++ b/cozo-core/src/runtime/tests.rs @@ -536,7 +536,7 @@ fn test_index() { .unwrap(); db.run_script( - r"?[fr, to, data] <- [[1,2,3],[4,5,6]] :put friends {fr, to => data}", + r"?[fr, to, data] <- [[1,2,3],[4,5,6]] :put friends {fr, to, data}", Default::default(), ) .unwrap();