From 5df3d2d190a6b6d056ea07e7596e479be2229abc Mon Sep 17 00:00:00 2001 From: Micha de Vries Date: Tue, 28 May 2024 14:37:16 +0200 Subject: [PATCH] `INSERT RELATION` (#4057) Co-authored-by: Tobie Morgan Hitchcock --- core/src/dbs/iterator.rs | 6 +- core/src/dbs/plan.rs | 11 +- core/src/dbs/processor.rs | 7 +- core/src/doc/alter.rs | 3 + core/src/doc/compute.rs | 4 +- core/src/doc/edges.rs | 2 +- core/src/doc/insert.rs | 2 + core/src/doc/merge.rs | 5 + core/src/doc/process.rs | 4 +- core/src/doc/relation.rs | 24 ++- core/src/doc/reset.rs | 2 +- core/src/err/mod.rs | 36 ++++ core/src/sql/statements/insert.rs | 164 +++++++++++----- core/src/sql/statements/relate.rs | 24 +-- .../sql/value/serde/ser/statement/insert.rs | 15 +- core/src/syn/parser/stmt/insert.rs | 28 +-- core/src/syn/parser/test/stmt.rs | 3 +- core/src/syn/parser/test/streaming.rs | 3 +- lib/src/api/engine/mod.rs | 6 +- lib/tests/insert.rs | 178 ++++++++++++++++++ 20 files changed, 428 insertions(+), 99 deletions(-) diff --git a/core/src/dbs/iterator.rs b/core/src/dbs/iterator.rs index aef87688..2576807d 100644 --- a/core/src/dbs/iterator.rs +++ b/core/src/dbs/iterator.rs @@ -28,7 +28,7 @@ pub(crate) enum Iterable { Edges(Edges), Defer(Thing), Mergeable(Thing, Value), - Relatable(Thing, Thing, Thing), + Relatable(Thing, Thing, Thing, Option), Index(Table, IteratorRef), } @@ -41,13 +41,13 @@ pub(crate) struct Processed { pub(crate) enum Operable { Value(Value), Mergeable(Value, Value), - Relatable(Thing, Value, Thing), + Relatable(Thing, Value, Thing, Option), } pub(crate) enum Workable { Normal, Insert(Value), - Relate(Thing, Thing), + Relate(Thing, Thing, Option), } #[derive(Default)] diff --git a/core/src/dbs/plan.rs b/core/src/dbs/plan.rs index 99a3f70a..32d85a74 100644 --- a/core/src/dbs/plan.rs +++ b/core/src/dbs/plan.rs @@ -117,7 +117,7 @@ impl ExplainItem { name: "Iterate Mergeable".into(), details: vec![("thing", Value::Thing(t.to_owned())), ("value", v.to_owned())], }, - Iterable::Relatable(t1, t2, t3) => Self { + Iterable::Relatable(t1, t2, t3, None) => Self { name: "Iterate Relatable".into(), details: vec![ ("thing-1", Value::Thing(t1.to_owned())), @@ -125,6 +125,15 @@ impl ExplainItem { ("thing-3", Value::Thing(t3.to_owned())), ], }, + Iterable::Relatable(t1, t2, t3, Some(v)) => Self { + name: "Iterate Relatable".into(), + details: vec![ + ("thing-1", Value::Thing(t1.to_owned())), + ("thing-2", Value::Thing(t2.to_owned())), + ("thing-3", Value::Thing(t3.to_owned())), + ("value", v.to_owned()), + ], + }, Iterable::Index(t, ir) => { let mut details = vec![("table", Value::from(t.0.to_owned()))]; if let Some(qp) = ctx.get_query_planner() { diff --git a/core/src/dbs/processor.rs b/core/src/dbs/processor.rs index 4c12d25a..77623e37 100644 --- a/core/src/dbs/processor.rs +++ b/core/src/dbs/processor.rs @@ -152,8 +152,8 @@ impl<'a> Processor<'a> { Iterable::Mergeable(v, o) => { self.process_mergeable(stk, ctx, opt, stm, v, o).await? } - Iterable::Relatable(f, v, w) => { - self.process_relatable(stk, ctx, opt, stm, f, v, w).await? + Iterable::Relatable(f, v, w, o) => { + self.process_relatable(stk, ctx, opt, stm, f, v, w, o).await? } } } @@ -270,6 +270,7 @@ impl<'a> Processor<'a> { f: Thing, v: Thing, w: Thing, + o: Option, ) -> Result<(), Error> { // Check that the table exists ctx.tx_lock().await.check_ns_db_tb(opt.ns(), opt.db(), &v.tb, opt.strict).await?; @@ -282,7 +283,7 @@ impl<'a> Processor<'a> { None => Value::None, }; // Create a new operable value - let val = Operable::Relatable(f, x, w); + let val = Operable::Relatable(f, x, w, o); // Process the document record let pro = Processed { rid: Some(v), diff --git a/core/src/doc/alter.rs b/core/src/doc/alter.rs index ec9b9c10..c51334eb 100644 --- a/core/src/doc/alter.rs +++ b/core/src/doc/alter.rs @@ -75,6 +75,9 @@ impl<'a> Document<'a> { if let Workable::Insert(value) = &self.extras { ctx.add_value("input", value); } + if let Workable::Relate(_, _, Some(value)) = &self.extras { + ctx.add_value("input", value); + } // Process ON DUPLICATE KEY clause for x in x.iter() { let v = x.2.compute(stk, &ctx, opt, Some(&self.current)).await?; diff --git a/core/src/doc/compute.rs b/core/src/doc/compute.rs index f0eb75f4..02011581 100644 --- a/core/src/doc/compute.rs +++ b/core/src/doc/compute.rs @@ -29,7 +29,7 @@ impl<'a> Document<'a> { let ins = match pro.val { Operable::Value(v) => (v, Workable::Normal), Operable::Mergeable(v, o) => (v, Workable::Insert(o)), - Operable::Relatable(f, v, w) => (v, Workable::Relate(f, w)), + Operable::Relatable(f, v, w, o) => (v, Workable::Relate(f, w, o)), }; // Setup a new document let mut doc = Document::new(pro.rid.as_ref(), pro.ir.as_ref(), &ins.0, ins.1); @@ -63,7 +63,7 @@ impl<'a> Document<'a> { val: match doc.extras { Workable::Normal => Operable::Value(val), Workable::Insert(o) => Operable::Mergeable(val, o), - Workable::Relate(f, w) => Operable::Relatable(f, val, w), + Workable::Relate(f, w, o) => Operable::Relatable(f, val, w, o), }, }; // Go to top of loop diff --git a/core/src/doc/edges.rs b/core/src/doc/edges.rs index e6e8e37f..9551c424 100644 --- a/core/src/doc/edges.rs +++ b/core/src/doc/edges.rs @@ -26,7 +26,7 @@ impl<'a> Document<'a> { // Get the record id let rid = self.id.as_ref().unwrap(); // Store the record edges - if let Workable::Relate(l, r) = &self.extras { + if let Workable::Relate(l, r, _) = &self.extras { // Get temporary edge references let (ref o, ref i) = (Dir::Out, Dir::In); // Store the left pointer edge diff --git a/core/src/doc/insert.rs b/core/src/doc/insert.rs index 42bac577..afbcee56 100644 --- a/core/src/doc/insert.rs +++ b/core/src/doc/insert.rs @@ -55,6 +55,8 @@ impl<'a> Document<'a> { self.relation(ctx, opt, stm).await?; // Merge record data self.merge(stk, ctx, opt, stm).await?; + // Store record edges + self.edges(ctx, opt, stm).await?; // Merge fields data self.field(stk, ctx, opt, stm).await?; // Reset fields data diff --git a/core/src/doc/merge.rs b/core/src/doc/merge.rs index 31dc1c7e..87423083 100644 --- a/core/src/doc/merge.rs +++ b/core/src/doc/merge.rs @@ -23,6 +23,11 @@ impl<'a> Document<'a> { let v = v.compute(stk, ctx, opt, Some(&self.current)).await?; self.current.doc.to_mut().merge(v)?; } + // This is an INSERT RELATION statement + if let Workable::Relate(_, _, Some(v)) = &self.extras { + let v = v.compute(stk, ctx, opt, Some(&self.current)).await?; + self.current.doc.to_mut().merge(v)?; + } // Set default field values self.current.doc.to_mut().def(rid); // Carry on diff --git a/core/src/doc/process.rs b/core/src/doc/process.rs index a6ccd63a..5ccf8738 100644 --- a/core/src/doc/process.rs +++ b/core/src/doc/process.rs @@ -23,7 +23,7 @@ impl<'a> Document<'a> { let ins = match pro.val { Operable::Value(v) => (v, Workable::Normal), Operable::Mergeable(v, o) => (v, Workable::Insert(o)), - Operable::Relatable(f, v, w) => (v, Workable::Relate(f, w)), + Operable::Relatable(f, v, w, o) => (v, Workable::Relate(f, w, o)), }; // Setup a new document let mut doc = Document::new(pro.rid.as_ref(), pro.ir.as_ref(), &ins.0, ins.1); @@ -57,7 +57,7 @@ impl<'a> Document<'a> { val: match doc.extras { Workable::Normal => Operable::Value(val), Workable::Insert(o) => Operable::Mergeable(val, o), - Workable::Relate(f, w) => Operable::Relatable(f, val, w), + Workable::Relate(f, w, o) => Operable::Relatable(f, val, w, o), }, }; // Go to top of loop diff --git a/core/src/doc/relation.rs b/core/src/doc/relation.rs index 51871b57..82e34759 100644 --- a/core/src/doc/relation.rs +++ b/core/src/doc/relation.rs @@ -1,6 +1,6 @@ use crate::ctx::Context; use crate::dbs::Options; -use crate::dbs::Statement; +use crate::dbs::{Statement, Workable}; use crate::doc::Document; use crate::err::Error; @@ -15,7 +15,7 @@ impl<'a> Document<'a> { let rid = self.id.as_ref().unwrap(); match stm { - Statement::Create(_) | Statement::Insert(_) => { + Statement::Create(_) => { if !tb.allows_normal() { return Err(Error::TableCheck { thing: rid.to_string(), @@ -24,6 +24,26 @@ impl<'a> Document<'a> { }); } } + Statement::Insert(_) => match self.extras { + Workable::Relate(_, _, _) => { + if !tb.allows_relation() { + return Err(Error::TableCheck { + thing: rid.to_string(), + relation: true, + target_type: tb.kind.clone(), + }); + } + } + _ => { + if !tb.allows_normal() { + return Err(Error::TableCheck { + thing: rid.to_string(), + relation: false, + target_type: tb.kind.clone(), + }); + } + } + }, Statement::Relate(_) => { if !tb.allows_relation() { return Err(Error::TableCheck { diff --git a/core/src/doc/reset.rs b/core/src/doc/reset.rs index 5ca2aea2..33e522c3 100644 --- a/core/src/doc/reset.rs +++ b/core/src/doc/reset.rs @@ -21,7 +21,7 @@ impl<'a> Document<'a> { // Set default field values self.current.doc.to_mut().def(rid); // This is a RELATE statement, so reset fields - if let Workable::Relate(l, r) = &self.extras { + if let Workable::Relate(l, r, _) = &self.extras { self.current.doc.to_mut().put(&*EDGE, Value::Bool(true)); self.current.doc.to_mut().put(&*IN, l.clone().into()); self.current.doc.to_mut().put(&*OUT, r.clone().into()); diff --git a/core/src/err/mod.rs b/core/src/err/mod.rs index ebff3d27..014bc10b 100644 --- a/core/src/err/mod.rs +++ b/core/src/err/mod.rs @@ -458,6 +458,24 @@ pub enum Error { value: String, }, + /// Can not execute RELATE statement using the specified value + #[error("Can not execute RELATE statement where property 'in' is '{value}'")] + RelateStatementIn { + value: String, + }, + + /// Can not execute RELATE statement using the specified value + #[error("Can not execute RELATE statement where property 'id' is '{value}'")] + RelateStatementId { + value: String, + }, + + /// Can not execute RELATE statement using the specified value + #[error("Can not execute RELATE statement where property 'out' is '{value}'")] + RelateStatementOut { + value: String, + }, + /// Can not execute DELETE statement using the specified value #[error("Can not execute DELETE statement using value '{value}'")] DeleteStatement { @@ -470,6 +488,24 @@ pub enum Error { value: String, }, + /// Can not execute INSERT statement using the specified value + #[error("Can not execute INSERT statement where property 'in' is '{value}'")] + InsertStatementIn { + value: String, + }, + + /// Can not execute INSERT statement using the specified value + #[error("Can not execute INSERT statement where property 'id' is '{value}'")] + InsertStatementId { + value: String, + }, + + /// Can not execute INSERT statement using the specified value + #[error("Can not execute INSERT statement where property 'out' is '{value}'")] + InsertStatementOut { + value: String, + }, + /// Can not execute LIVE statement using the specified value #[error("Can not execute LIVE statement using value '{value}'")] LiveStatement { diff --git a/core/src/sql/statements/insert.rs b/core/src/sql/statements/insert.rs index 576b12d7..66eeddb3 100644 --- a/core/src/sql/statements/insert.rs +++ b/core/src/sql/statements/insert.rs @@ -2,25 +2,29 @@ use crate::ctx::Context; use crate::dbs::{Iterable, Iterator, Options, Statement}; use crate::doc::CursorDoc; use crate::err::Error; -use crate::sql::{Data, Output, Timeout, Value}; +use crate::sql::paths::IN; +use crate::sql::paths::OUT; +use crate::sql::{Data, Id, Output, Table, Thing, Timeout, Value}; use derive::Store; use reblessive::tree::Stk; use revision::revisioned; use serde::{Deserialize, Serialize}; use std::fmt; -#[revisioned(revision = 1)] +#[revisioned(revision = 2)] #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[non_exhaustive] pub struct InsertStatement { - pub into: Value, + pub into: Option, pub data: Data, pub ignore: bool, pub update: Option, pub output: Option, pub timeout: Option, pub parallel: bool, + #[revision(start = 2)] + pub relation: bool, } impl InsertStatement { @@ -42,57 +46,62 @@ impl InsertStatement { let mut i = Iterator::new(); // Ensure futures are stored let opt = &opt.new_with_futures(false).with_projections(false); - // Parse the expression - match self.into.compute(stk, ctx, opt, doc).await? { - Value::Table(into) => match &self.data { - // Check if this is a traditional statement - Data::ValuesExpression(v) => { - for v in v { - // Create a new empty base object - let mut o = Value::base(); - // Set each field from the expression - for (k, v) in v.iter() { - let v = v.compute(stk, ctx, opt, None).await?; - o.set(stk, ctx, opt, k, v).await?; - } - // Specify the new table record id - let id = o.rid().generate(&into, true)?; - // Pass the mergeable to the iterator - i.ingest(Iterable::Mergeable(id, o)); - } + // Parse the INTO expression + let into = match &self.into { + None => None, + Some(into) => match into.compute(stk, ctx, opt, doc).await? { + Value::Table(into) => Some(into), + v => { + return Err(Error::InsertStatement { + value: v.to_string(), + }) } - // Check if this is a modern statement - Data::SingleExpression(v) => { - let v = v.compute(stk, ctx, opt, doc).await?; - match v { - Value::Array(v) => { - for v in v { - // Specify the new table record id - let id = v.rid().generate(&into, true)?; - // Pass the mergeable to the iterator - i.ingest(Iterable::Mergeable(id, v)); - } - } - Value::Object(_) => { - // Specify the new table record id - let id = v.rid().generate(&into, true)?; - // Pass the mergeable to the iterator - i.ingest(Iterable::Mergeable(id, v)); - } - v => { - return Err(Error::InsertStatement { - value: v.to_string(), - }) - } - } - } - _ => unreachable!(), }, - v => { - return Err(Error::InsertStatement { - value: v.to_string(), - }) + }; + // Parse the data expression + match &self.data { + // Check if this is a traditional statement + Data::ValuesExpression(v) => { + for v in v { + // Create a new empty base object + let mut o = Value::base(); + // Set each field from the expression + for (k, v) in v.iter() { + let v = v.compute(stk, ctx, opt, None).await?; + o.set(stk, ctx, opt, k, v).await?; + } + // Specify the new table record id + let id = gen_id(&o, &into)?; + // Pass the value to the iterator + i.ingest(iterable(id, o, self.relation)?) + } } + // Check if this is a modern statement + Data::SingleExpression(v) => { + let v = v.compute(stk, ctx, opt, doc).await?; + match v { + Value::Array(v) => { + for v in v { + // Specify the new table record id + let id = gen_id(&v, &into)?; + // Pass the value to the iterator + i.ingest(iterable(id, v, self.relation)?) + } + } + Value::Object(_) => { + // Specify the new table record id + let id = gen_id(&v, &into)?; + // Pass the value to the iterator + i.ingest(iterable(id, v, self.relation)?) + } + v => { + return Err(Error::InsertStatement { + value: v.to_string(), + }) + } + } + } + _ => unreachable!(), } // Assign the statement let stm = Statement::from(self); @@ -104,10 +113,16 @@ impl InsertStatement { impl fmt::Display for InsertStatement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("INSERT")?; + if self.relation { + f.write_str(" RELATION")? + } if self.ignore { f.write_str(" IGNORE")? } - write!(f, " INTO {} {}", self.into, self.data)?; + if let Some(into) = &self.into { + write!(f, " INTO {}", into)?; + } + write!(f, "{}", self.data)?; if let Some(ref v) = self.update { write!(f, " {v}")? } @@ -123,3 +138,48 @@ impl fmt::Display for InsertStatement { Ok(()) } } + +fn iterable(id: Thing, v: Value, relation: bool) -> Result { + match relation { + false => Ok(Iterable::Mergeable(id, v)), + true => { + let _in = match v.pick(&*IN) { + Value::Thing(v) => v, + v => { + return Err(Error::InsertStatementIn { + value: v.to_string(), + }) + } + }; + let out = match v.pick(&*OUT) { + Value::Thing(v) => v, + v => { + return Err(Error::InsertStatementOut { + value: v.to_string(), + }) + } + }; + Ok(Iterable::Relatable(_in, id, out, Some(v))) + } + } +} + +fn gen_id(v: &Value, into: &Option) -> Result { + match into { + Some(into) => v.rid().generate(into, true), + None => match v.rid() { + Value::Thing(v) => match v { + Thing { + id: Id::Generate(_), + .. + } => Err(Error::InsertStatementId { + value: v.to_string(), + }), + v => Ok(v), + }, + v => Err(Error::InsertStatementId { + value: v.to_string(), + }), + }, + } +} diff --git a/core/src/sql/statements/relate.rs b/core/src/sql/statements/relate.rs index 2d1c47c7..0f4c281d 100644 --- a/core/src/sql/statements/relate.rs +++ b/core/src/sql/statements/relate.rs @@ -57,13 +57,13 @@ impl RelateStatement { Value::Object(v) => match v.rid() { Some(v) => out.push(v), _ => { - return Err(Error::RelateStatement { + return Err(Error::RelateStatementOut { value: v.to_string(), }) } }, v => { - return Err(Error::RelateStatement { + return Err(Error::RelateStatementOut { value: v.to_string(), }) } @@ -73,13 +73,13 @@ impl RelateStatement { Value::Object(v) => match v.rid() { Some(v) => out.push(v), None => { - return Err(Error::RelateStatement { + return Err(Error::RelateStatementOut { value: v.to_string(), }) } }, v => { - return Err(Error::RelateStatement { + return Err(Error::RelateStatementOut { value: v.to_string(), }) } @@ -99,13 +99,13 @@ impl RelateStatement { Value::Object(v) => match v.rid() { Some(v) => out.push(v), None => { - return Err(Error::RelateStatement { + return Err(Error::RelateStatementId { value: v.to_string(), }) } }, v => { - return Err(Error::RelateStatement { + return Err(Error::RelateStatementId { value: v.to_string(), }) } @@ -115,13 +115,13 @@ impl RelateStatement { Value::Object(v) => match v.rid() { Some(v) => out.push(v), None => { - return Err(Error::RelateStatement { + return Err(Error::RelateStatementId { value: v.to_string(), }) } }, v => { - return Err(Error::RelateStatement { + return Err(Error::RelateStatementId { value: v.to_string(), }) } @@ -135,7 +135,7 @@ impl RelateStatement { let w = w.clone(); match &self.kind.compute(stk, ctx, opt, doc).await? { // The relation has a specific record id - Value::Thing(id) => i.ingest(Iterable::Relatable(f, id.to_owned(), w)), + Value::Thing(id) => i.ingest(Iterable::Relatable(f, id.to_owned(), w, None)), // The relation does not have a specific record id Value::Table(tb) => match &self.data { // There is a data clause so check for a record id @@ -144,14 +144,14 @@ impl RelateStatement { Some(id) => id.generate(tb, false)?, None => tb.generate(), }; - i.ingest(Iterable::Relatable(f, id, w)) + i.ingest(Iterable::Relatable(f, id, w, None)) } // There is no data clause so create a record id - None => i.ingest(Iterable::Relatable(f, tb.generate(), w)), + None => i.ingest(Iterable::Relatable(f, tb.generate(), w, None)), }, // The relation can not be any other type v => { - return Err(Error::RelateStatement { + return Err(Error::RelateStatementOut { value: v.to_string(), }) } diff --git a/core/src/sql/value/serde/ser/statement/insert.rs b/core/src/sql/value/serde/ser/statement/insert.rs index 6395c1d2..46ec5667 100644 --- a/core/src/sql/value/serde/ser/statement/insert.rs +++ b/core/src/sql/value/serde/ser/statement/insert.rs @@ -47,6 +47,7 @@ pub struct SerializeInsertStatement { output: Option, timeout: Option, parallel: Option, + relation: Option, } impl serde::ser::SerializeStruct for SerializeInsertStatement { @@ -58,9 +59,7 @@ impl serde::ser::SerializeStruct for SerializeInsertStatement { T: ?Sized + Serialize, { match key { - "into" => { - self.into = Some(value.serialize(ser::value::Serializer.wrap())?); - } + "into" => self.into = value.serialize(ser::value::opt::Serializer.wrap())?, "data" => { self.data = Some(value.serialize(ser::data::Serializer.wrap())?); } @@ -79,6 +78,9 @@ impl serde::ser::SerializeStruct for SerializeInsertStatement { "parallel" => { self.parallel = Some(value.serialize(ser::primitive::bool::Serializer.wrap())?); } + "relation" => { + self.relation = Some(value.serialize(ser::primitive::bool::Serializer.wrap())?); + } key => { return Err(Error::custom(format!("unexpected field `InsertStatement::{key}`"))); } @@ -87,15 +89,16 @@ impl serde::ser::SerializeStruct for SerializeInsertStatement { } fn end(self) -> Result { - match (self.into, self.data, self.ignore, self.parallel) { - (Some(into), Some(data), Some(ignore), Some(parallel)) => Ok(InsertStatement { - into, + match (self.data, self.ignore, self.parallel, self.relation) { + (Some(data), Some(ignore), Some(parallel), Some(relation)) => Ok(InsertStatement { + into: self.into, data, ignore, parallel, update: self.update, output: self.output, timeout: self.timeout, + relation, }), _ => Err(Error::custom("`InsertStatement` missing required value(s)")), } diff --git a/core/src/syn/parser/stmt/insert.rs b/core/src/syn/parser/stmt/insert.rs index 722d89f9..82ca10e9 100644 --- a/core/src/syn/parser/stmt/insert.rs +++ b/core/src/syn/parser/stmt/insert.rs @@ -13,18 +13,23 @@ impl Parser<'_> { &mut self, ctx: &mut Stk, ) -> ParseResult { + let relation = self.eat(t!("RELATION")); let ignore = self.eat(t!("IGNORE")); - expected!(self, t!("INTO")); - let next = self.next(); - // TODO: Explain that more complicated expressions are not allowed here. - let into = match next.kind { - t!("$param") => { - let param = self.token_value(next)?; - Value::Param(param) - } - _ => { - let table = self.token_value(next)?; - Value::Table(table) + let into = match self.eat(t!("INTO")) { + false => None, + true => { + let next = self.next(); + // TODO: Explain that more complicated expressions are not allowed here. + Some(match next.kind { + t!("$param") => { + let param = self.token_value(next)?; + Value::Param(param) + } + _ => { + let table = self.token_value(next)?; + Value::Table(table) + } + }) } }; @@ -82,6 +87,7 @@ impl Parser<'_> { output, timeout, parallel, + relation, }) } diff --git a/core/src/syn/parser/test/stmt.rs b/core/src/syn/parser/test/stmt.rs index 52aad45e..537adf16 100644 --- a/core/src/syn/parser/test/stmt.rs +++ b/core/src/syn/parser/test/stmt.rs @@ -1571,7 +1571,7 @@ fn parse_insert() { assert_eq!( res, Statement::Insert(InsertStatement { - into: Value::Param(Param(Ident("foo".to_owned()))), + into: Some(Value::Param(Param(Ident("foo".to_owned())))), data: Data::ValuesExpression(vec![ vec![ ( @@ -1624,6 +1624,7 @@ fn parse_insert() { output: Some(Output::After), timeout: None, parallel: false, + relation: false, }), ) } diff --git a/core/src/syn/parser/test/streaming.rs b/core/src/syn/parser/test/streaming.rs index 3432f5dd..da07956e 100644 --- a/core/src/syn/parser/test/streaming.rs +++ b/core/src/syn/parser/test/streaming.rs @@ -522,7 +522,7 @@ fn statements() -> Vec { error: Value::Duration(Duration(std::time::Duration::from_secs(1))), }), Statement::Insert(InsertStatement { - into: Value::Param(Param(Ident("foo".to_owned()))), + into: Some(Value::Param(Param(Ident("foo".to_owned())))), data: Data::ValuesExpression(vec![ vec![ ( @@ -575,6 +575,7 @@ fn statements() -> Vec { output: Some(Output::After), timeout: None, parallel: false, + relation: false, }), Statement::Kill(KillStatement { id: Value::Uuid(Uuid(uuid::uuid!("e72bee20-f49b-11ec-b939-0242ac120002"))), diff --git a/lib/src/api/engine/mod.rs b/lib/src/api/engine/mod.rs index 040b0af1..cd44ad94 100644 --- a/lib/src/api/engine/mod.rs +++ b/lib/src/api/engine/mod.rs @@ -99,7 +99,11 @@ fn insert_statement(params: &mut [Value]) -> (bool, InsertStatement) { }; let one = !data.is_array(); let mut stmt = InsertStatement::default(); - stmt.into = what; + stmt.into = match what { + Value::None => None, + Value::Null => None, + what => Some(what), + }; stmt.data = Data::SingleExpression(data); stmt.output = Some(Output::After); (one, stmt) diff --git a/lib/tests/insert.rs b/lib/tests/insert.rs index 4025d7c1..b7782d18 100644 --- a/lib/tests/insert.rs +++ b/lib/tests/insert.rs @@ -517,3 +517,181 @@ async fn insert_statement_unique_index() -> Result<(), Error> { // Ok(()) } + +#[tokio::test] +async fn insert_relation() -> Result<(), Error> { + let sql = " + INSERT INTO person [ + { id: person:1 }, + { id: person:2 }, + { id: person:3 }, + ]; + INSERT RELATION INTO likes { + in: person:1, + id: 'object', + out: person:2, + }; + INSERT RELATION INTO likes [ + { + in: person:1, + id: 'array', + out: person:2, + }, + { + in: person:2, + id: 'array_twoo', + out: person:3, + } + ]; + INSERT RELATION INTO likes (in, id, out) + VALUES (person:1, 'values', person:2); + SELECT VALUE ->likes FROM person; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 5); + // + let tmp = res.remove(0).result?; + let val = Value::parse("[{ id: person:1 }, { id: person:2 }, { id: person:3 }]"); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + " + [ + { + id: likes:object, + in: person:1, + out: person:2 + } + ] + ", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + " + [ + { + id: likes:array, + in: person:1, + out: person:2 + }, + { + id: likes:array_twoo, + in: person:2, + out: person:3 + } + ] + ", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + " + [ + { + id: likes:values, + in: person:1, + out: person:2 + } + ] + ", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + " + [ + [ + likes:array, + likes:object, + likes:values + ], + [ + likes:array_twoo + ], + [] + ] + ", + ); + assert_eq!(tmp, val); + // + Ok(()) +} + +#[tokio::test] +async fn insert_invalid_relation() -> Result<(), Error> { + let sql = " + INSERT RELATION INTO likes { + id: 'object', + }; + + INSERT RELATION { + in: person:1, + }; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 2); + // + match res.remove(0).result { + Err(Error::InsertStatementIn { + value, + }) if value == "NONE" => (), + found => panic!("Expected Err(Error::InsertStatementIn), found '{:?}'", found), + } + // + match res.remove(0).result { + Err(Error::InsertStatementId { + value, + }) if value == "NONE" => (), + found => panic!("Expected Err(Error::InsertStatementId), found '{:?}'", found), + } + // + Ok(()) +} + +#[tokio::test] +async fn insert_without_into() -> Result<(), Error> { + let sql = " + INSERT [ + { id: test:1 } + ]; + + INSERT { id: test:2 }; + INSERT (id) VALUES (test:3); + + INSERT {}; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 4); + // + let tmp = res.remove(0).result?; + let val = Value::parse("[{ id: test:1 }]"); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse("[{ id: test:2 }]"); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse("[{ id: test:3 }]"); + assert_eq!(tmp, val); + // + match res.remove(0).result { + Err(Error::InsertStatementId { + value, + }) if value == "NONE" => (), + found => panic!("Expected Err(Error::RelateStatementId), found '{:?}'", found), + } + // + Ok(()) +}