INSERT RELATION (#4057)

Co-authored-by: Tobie Morgan Hitchcock <tobie@surrealdb.com>
This commit is contained in:
Micha de Vries 2024-05-28 14:37:16 +02:00 committed by GitHub
parent a9e075463d
commit 5df3d2d190
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 428 additions and 99 deletions

View file

@ -28,7 +28,7 @@ pub(crate) enum Iterable {
Edges(Edges), Edges(Edges),
Defer(Thing), Defer(Thing),
Mergeable(Thing, Value), Mergeable(Thing, Value),
Relatable(Thing, Thing, Thing), Relatable(Thing, Thing, Thing, Option<Value>),
Index(Table, IteratorRef), Index(Table, IteratorRef),
} }
@ -41,13 +41,13 @@ pub(crate) struct Processed {
pub(crate) enum Operable { pub(crate) enum Operable {
Value(Value), Value(Value),
Mergeable(Value, Value), Mergeable(Value, Value),
Relatable(Thing, Value, Thing), Relatable(Thing, Value, Thing, Option<Value>),
} }
pub(crate) enum Workable { pub(crate) enum Workable {
Normal, Normal,
Insert(Value), Insert(Value),
Relate(Thing, Thing), Relate(Thing, Thing, Option<Value>),
} }
#[derive(Default)] #[derive(Default)]

View file

@ -117,7 +117,7 @@ impl ExplainItem {
name: "Iterate Mergeable".into(), name: "Iterate Mergeable".into(),
details: vec![("thing", Value::Thing(t.to_owned())), ("value", v.to_owned())], 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(), name: "Iterate Relatable".into(),
details: vec![ details: vec![
("thing-1", Value::Thing(t1.to_owned())), ("thing-1", Value::Thing(t1.to_owned())),
@ -125,6 +125,15 @@ impl ExplainItem {
("thing-3", Value::Thing(t3.to_owned())), ("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) => { Iterable::Index(t, ir) => {
let mut details = vec![("table", Value::from(t.0.to_owned()))]; let mut details = vec![("table", Value::from(t.0.to_owned()))];
if let Some(qp) = ctx.get_query_planner() { if let Some(qp) = ctx.get_query_planner() {

View file

@ -152,8 +152,8 @@ impl<'a> Processor<'a> {
Iterable::Mergeable(v, o) => { Iterable::Mergeable(v, o) => {
self.process_mergeable(stk, ctx, opt, stm, v, o).await? self.process_mergeable(stk, ctx, opt, stm, v, o).await?
} }
Iterable::Relatable(f, v, w) => { Iterable::Relatable(f, v, w, o) => {
self.process_relatable(stk, ctx, opt, stm, f, v, w).await? self.process_relatable(stk, ctx, opt, stm, f, v, w, o).await?
} }
} }
} }
@ -270,6 +270,7 @@ impl<'a> Processor<'a> {
f: Thing, f: Thing,
v: Thing, v: Thing,
w: Thing, w: Thing,
o: Option<Value>,
) -> Result<(), Error> { ) -> Result<(), Error> {
// Check that the table exists // Check that the table exists
ctx.tx_lock().await.check_ns_db_tb(opt.ns(), opt.db(), &v.tb, opt.strict).await?; 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, None => Value::None,
}; };
// Create a new operable value // Create a new operable value
let val = Operable::Relatable(f, x, w); let val = Operable::Relatable(f, x, w, o);
// Process the document record // Process the document record
let pro = Processed { let pro = Processed {
rid: Some(v), rid: Some(v),

View file

@ -75,6 +75,9 @@ impl<'a> Document<'a> {
if let Workable::Insert(value) = &self.extras { if let Workable::Insert(value) = &self.extras {
ctx.add_value("input", value); ctx.add_value("input", value);
} }
if let Workable::Relate(_, _, Some(value)) = &self.extras {
ctx.add_value("input", value);
}
// Process ON DUPLICATE KEY clause // Process ON DUPLICATE KEY clause
for x in x.iter() { for x in x.iter() {
let v = x.2.compute(stk, &ctx, opt, Some(&self.current)).await?; let v = x.2.compute(stk, &ctx, opt, Some(&self.current)).await?;

View file

@ -29,7 +29,7 @@ impl<'a> Document<'a> {
let ins = match pro.val { let ins = match pro.val {
Operable::Value(v) => (v, Workable::Normal), Operable::Value(v) => (v, Workable::Normal),
Operable::Mergeable(v, o) => (v, Workable::Insert(o)), 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 // Setup a new document
let mut doc = Document::new(pro.rid.as_ref(), pro.ir.as_ref(), &ins.0, ins.1); 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 { val: match doc.extras {
Workable::Normal => Operable::Value(val), Workable::Normal => Operable::Value(val),
Workable::Insert(o) => Operable::Mergeable(val, o), 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 // Go to top of loop

View file

@ -26,7 +26,7 @@ impl<'a> Document<'a> {
// Get the record id // Get the record id
let rid = self.id.as_ref().unwrap(); let rid = self.id.as_ref().unwrap();
// Store the record edges // Store the record edges
if let Workable::Relate(l, r) = &self.extras { if let Workable::Relate(l, r, _) = &self.extras {
// Get temporary edge references // Get temporary edge references
let (ref o, ref i) = (Dir::Out, Dir::In); let (ref o, ref i) = (Dir::Out, Dir::In);
// Store the left pointer edge // Store the left pointer edge

View file

@ -55,6 +55,8 @@ impl<'a> Document<'a> {
self.relation(ctx, opt, stm).await?; self.relation(ctx, opt, stm).await?;
// Merge record data // Merge record data
self.merge(stk, ctx, opt, stm).await?; self.merge(stk, ctx, opt, stm).await?;
// Store record edges
self.edges(ctx, opt, stm).await?;
// Merge fields data // Merge fields data
self.field(stk, ctx, opt, stm).await?; self.field(stk, ctx, opt, stm).await?;
// Reset fields data // Reset fields data

View file

@ -23,6 +23,11 @@ impl<'a> Document<'a> {
let v = v.compute(stk, ctx, opt, Some(&self.current)).await?; let v = v.compute(stk, ctx, opt, Some(&self.current)).await?;
self.current.doc.to_mut().merge(v)?; 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 // Set default field values
self.current.doc.to_mut().def(rid); self.current.doc.to_mut().def(rid);
// Carry on // Carry on

View file

@ -23,7 +23,7 @@ impl<'a> Document<'a> {
let ins = match pro.val { let ins = match pro.val {
Operable::Value(v) => (v, Workable::Normal), Operable::Value(v) => (v, Workable::Normal),
Operable::Mergeable(v, o) => (v, Workable::Insert(o)), 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 // Setup a new document
let mut doc = Document::new(pro.rid.as_ref(), pro.ir.as_ref(), &ins.0, ins.1); 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 { val: match doc.extras {
Workable::Normal => Operable::Value(val), Workable::Normal => Operable::Value(val),
Workable::Insert(o) => Operable::Mergeable(val, o), 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 // Go to top of loop

View file

@ -1,6 +1,6 @@
use crate::ctx::Context; use crate::ctx::Context;
use crate::dbs::Options; use crate::dbs::Options;
use crate::dbs::Statement; use crate::dbs::{Statement, Workable};
use crate::doc::Document; use crate::doc::Document;
use crate::err::Error; use crate::err::Error;
@ -15,7 +15,7 @@ impl<'a> Document<'a> {
let rid = self.id.as_ref().unwrap(); let rid = self.id.as_ref().unwrap();
match stm { match stm {
Statement::Create(_) | Statement::Insert(_) => { Statement::Create(_) => {
if !tb.allows_normal() { if !tb.allows_normal() {
return Err(Error::TableCheck { return Err(Error::TableCheck {
thing: rid.to_string(), 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(_) => { Statement::Relate(_) => {
if !tb.allows_relation() { if !tb.allows_relation() {
return Err(Error::TableCheck { return Err(Error::TableCheck {

View file

@ -21,7 +21,7 @@ impl<'a> Document<'a> {
// Set default field values // Set default field values
self.current.doc.to_mut().def(rid); self.current.doc.to_mut().def(rid);
// This is a RELATE statement, so reset fields // 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(&*EDGE, Value::Bool(true));
self.current.doc.to_mut().put(&*IN, l.clone().into()); self.current.doc.to_mut().put(&*IN, l.clone().into());
self.current.doc.to_mut().put(&*OUT, r.clone().into()); self.current.doc.to_mut().put(&*OUT, r.clone().into());

View file

@ -458,6 +458,24 @@ pub enum Error {
value: String, 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 /// Can not execute DELETE statement using the specified value
#[error("Can not execute DELETE statement using value '{value}'")] #[error("Can not execute DELETE statement using value '{value}'")]
DeleteStatement { DeleteStatement {
@ -470,6 +488,24 @@ pub enum Error {
value: String, 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 /// Can not execute LIVE statement using the specified value
#[error("Can not execute LIVE statement using value '{value}'")] #[error("Can not execute LIVE statement using value '{value}'")]
LiveStatement { LiveStatement {

View file

@ -2,25 +2,29 @@ use crate::ctx::Context;
use crate::dbs::{Iterable, Iterator, Options, Statement}; use crate::dbs::{Iterable, Iterator, Options, Statement};
use crate::doc::CursorDoc; use crate::doc::CursorDoc;
use crate::err::Error; 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 derive::Store;
use reblessive::tree::Stk; use reblessive::tree::Stk;
use revision::revisioned; use revision::revisioned;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
#[revisioned(revision = 1)] #[revisioned(revision = 2)]
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)] #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive] #[non_exhaustive]
pub struct InsertStatement { pub struct InsertStatement {
pub into: Value, pub into: Option<Value>,
pub data: Data, pub data: Data,
pub ignore: bool, pub ignore: bool,
pub update: Option<Data>, pub update: Option<Data>,
pub output: Option<Output>, pub output: Option<Output>,
pub timeout: Option<Timeout>, pub timeout: Option<Timeout>,
pub parallel: bool, pub parallel: bool,
#[revision(start = 2)]
pub relation: bool,
} }
impl InsertStatement { impl InsertStatement {
@ -42,9 +46,20 @@ impl InsertStatement {
let mut i = Iterator::new(); let mut i = Iterator::new();
// Ensure futures are stored // Ensure futures are stored
let opt = &opt.new_with_futures(false).with_projections(false); let opt = &opt.new_with_futures(false).with_projections(false);
// Parse the expression // Parse the INTO expression
match self.into.compute(stk, ctx, opt, doc).await? { let into = match &self.into {
Value::Table(into) => match &self.data { 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(),
})
}
},
};
// Parse the data expression
match &self.data {
// Check if this is a traditional statement // Check if this is a traditional statement
Data::ValuesExpression(v) => { Data::ValuesExpression(v) => {
for v in v { for v in v {
@ -56,9 +71,9 @@ impl InsertStatement {
o.set(stk, ctx, opt, k, v).await?; o.set(stk, ctx, opt, k, v).await?;
} }
// Specify the new table record id // Specify the new table record id
let id = o.rid().generate(&into, true)?; let id = gen_id(&o, &into)?;
// Pass the mergeable to the iterator // Pass the value to the iterator
i.ingest(Iterable::Mergeable(id, o)); i.ingest(iterable(id, o, self.relation)?)
} }
} }
// Check if this is a modern statement // Check if this is a modern statement
@ -68,16 +83,16 @@ impl InsertStatement {
Value::Array(v) => { Value::Array(v) => {
for v in v { for v in v {
// Specify the new table record id // Specify the new table record id
let id = v.rid().generate(&into, true)?; let id = gen_id(&v, &into)?;
// Pass the mergeable to the iterator // Pass the value to the iterator
i.ingest(Iterable::Mergeable(id, v)); i.ingest(iterable(id, v, self.relation)?)
} }
} }
Value::Object(_) => { Value::Object(_) => {
// Specify the new table record id // Specify the new table record id
let id = v.rid().generate(&into, true)?; let id = gen_id(&v, &into)?;
// Pass the mergeable to the iterator // Pass the value to the iterator
i.ingest(Iterable::Mergeable(id, v)); i.ingest(iterable(id, v, self.relation)?)
} }
v => { v => {
return Err(Error::InsertStatement { return Err(Error::InsertStatement {
@ -87,12 +102,6 @@ impl InsertStatement {
} }
} }
_ => unreachable!(), _ => unreachable!(),
},
v => {
return Err(Error::InsertStatement {
value: v.to_string(),
})
}
} }
// Assign the statement // Assign the statement
let stm = Statement::from(self); let stm = Statement::from(self);
@ -104,10 +113,16 @@ impl InsertStatement {
impl fmt::Display for InsertStatement { impl fmt::Display for InsertStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("INSERT")?; f.write_str("INSERT")?;
if self.relation {
f.write_str(" RELATION")?
}
if self.ignore { if self.ignore {
f.write_str(" 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 { if let Some(ref v) = self.update {
write!(f, " {v}")? write!(f, " {v}")?
} }
@ -123,3 +138,48 @@ impl fmt::Display for InsertStatement {
Ok(()) Ok(())
} }
} }
fn iterable(id: Thing, v: Value, relation: bool) -> Result<Iterable, Error> {
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<Table>) -> Result<Thing, Error> {
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(),
}),
},
}
}

View file

@ -57,13 +57,13 @@ impl RelateStatement {
Value::Object(v) => match v.rid() { Value::Object(v) => match v.rid() {
Some(v) => out.push(v), Some(v) => out.push(v),
_ => { _ => {
return Err(Error::RelateStatement { return Err(Error::RelateStatementOut {
value: v.to_string(), value: v.to_string(),
}) })
} }
}, },
v => { v => {
return Err(Error::RelateStatement { return Err(Error::RelateStatementOut {
value: v.to_string(), value: v.to_string(),
}) })
} }
@ -73,13 +73,13 @@ impl RelateStatement {
Value::Object(v) => match v.rid() { Value::Object(v) => match v.rid() {
Some(v) => out.push(v), Some(v) => out.push(v),
None => { None => {
return Err(Error::RelateStatement { return Err(Error::RelateStatementOut {
value: v.to_string(), value: v.to_string(),
}) })
} }
}, },
v => { v => {
return Err(Error::RelateStatement { return Err(Error::RelateStatementOut {
value: v.to_string(), value: v.to_string(),
}) })
} }
@ -99,13 +99,13 @@ impl RelateStatement {
Value::Object(v) => match v.rid() { Value::Object(v) => match v.rid() {
Some(v) => out.push(v), Some(v) => out.push(v),
None => { None => {
return Err(Error::RelateStatement { return Err(Error::RelateStatementId {
value: v.to_string(), value: v.to_string(),
}) })
} }
}, },
v => { v => {
return Err(Error::RelateStatement { return Err(Error::RelateStatementId {
value: v.to_string(), value: v.to_string(),
}) })
} }
@ -115,13 +115,13 @@ impl RelateStatement {
Value::Object(v) => match v.rid() { Value::Object(v) => match v.rid() {
Some(v) => out.push(v), Some(v) => out.push(v),
None => { None => {
return Err(Error::RelateStatement { return Err(Error::RelateStatementId {
value: v.to_string(), value: v.to_string(),
}) })
} }
}, },
v => { v => {
return Err(Error::RelateStatement { return Err(Error::RelateStatementId {
value: v.to_string(), value: v.to_string(),
}) })
} }
@ -135,7 +135,7 @@ impl RelateStatement {
let w = w.clone(); let w = w.clone();
match &self.kind.compute(stk, ctx, opt, doc).await? { match &self.kind.compute(stk, ctx, opt, doc).await? {
// The relation has a specific record id // 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 // The relation does not have a specific record id
Value::Table(tb) => match &self.data { Value::Table(tb) => match &self.data {
// There is a data clause so check for a record id // There is a data clause so check for a record id
@ -144,14 +144,14 @@ impl RelateStatement {
Some(id) => id.generate(tb, false)?, Some(id) => id.generate(tb, false)?,
None => tb.generate(), 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 // 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 // The relation can not be any other type
v => { v => {
return Err(Error::RelateStatement { return Err(Error::RelateStatementOut {
value: v.to_string(), value: v.to_string(),
}) })
} }

View file

@ -47,6 +47,7 @@ pub struct SerializeInsertStatement {
output: Option<Output>, output: Option<Output>,
timeout: Option<Timeout>, timeout: Option<Timeout>,
parallel: Option<bool>, parallel: Option<bool>,
relation: Option<bool>,
} }
impl serde::ser::SerializeStruct for SerializeInsertStatement { impl serde::ser::SerializeStruct for SerializeInsertStatement {
@ -58,9 +59,7 @@ impl serde::ser::SerializeStruct for SerializeInsertStatement {
T: ?Sized + Serialize, T: ?Sized + Serialize,
{ {
match key { match key {
"into" => { "into" => self.into = value.serialize(ser::value::opt::Serializer.wrap())?,
self.into = Some(value.serialize(ser::value::Serializer.wrap())?);
}
"data" => { "data" => {
self.data = Some(value.serialize(ser::data::Serializer.wrap())?); self.data = Some(value.serialize(ser::data::Serializer.wrap())?);
} }
@ -79,6 +78,9 @@ impl serde::ser::SerializeStruct for SerializeInsertStatement {
"parallel" => { "parallel" => {
self.parallel = Some(value.serialize(ser::primitive::bool::Serializer.wrap())?); self.parallel = Some(value.serialize(ser::primitive::bool::Serializer.wrap())?);
} }
"relation" => {
self.relation = Some(value.serialize(ser::primitive::bool::Serializer.wrap())?);
}
key => { key => {
return Err(Error::custom(format!("unexpected field `InsertStatement::{key}`"))); return Err(Error::custom(format!("unexpected field `InsertStatement::{key}`")));
} }
@ -87,15 +89,16 @@ impl serde::ser::SerializeStruct for SerializeInsertStatement {
} }
fn end(self) -> Result<Self::Ok, Error> { fn end(self) -> Result<Self::Ok, Error> {
match (self.into, self.data, self.ignore, self.parallel) { match (self.data, self.ignore, self.parallel, self.relation) {
(Some(into), Some(data), Some(ignore), Some(parallel)) => Ok(InsertStatement { (Some(data), Some(ignore), Some(parallel), Some(relation)) => Ok(InsertStatement {
into, into: self.into,
data, data,
ignore, ignore,
parallel, parallel,
update: self.update, update: self.update,
output: self.output, output: self.output,
timeout: self.timeout, timeout: self.timeout,
relation,
}), }),
_ => Err(Error::custom("`InsertStatement` missing required value(s)")), _ => Err(Error::custom("`InsertStatement` missing required value(s)")),
} }

View file

@ -13,11 +13,14 @@ impl Parser<'_> {
&mut self, &mut self,
ctx: &mut Stk, ctx: &mut Stk,
) -> ParseResult<InsertStatement> { ) -> ParseResult<InsertStatement> {
let relation = self.eat(t!("RELATION"));
let ignore = self.eat(t!("IGNORE")); let ignore = self.eat(t!("IGNORE"));
expected!(self, t!("INTO")); let into = match self.eat(t!("INTO")) {
false => None,
true => {
let next = self.next(); let next = self.next();
// TODO: Explain that more complicated expressions are not allowed here. // TODO: Explain that more complicated expressions are not allowed here.
let into = match next.kind { Some(match next.kind {
t!("$param") => { t!("$param") => {
let param = self.token_value(next)?; let param = self.token_value(next)?;
Value::Param(param) Value::Param(param)
@ -26,6 +29,8 @@ impl Parser<'_> {
let table = self.token_value(next)?; let table = self.token_value(next)?;
Value::Table(table) Value::Table(table)
} }
})
}
}; };
let data = match self.peek_kind() { let data = match self.peek_kind() {
@ -82,6 +87,7 @@ impl Parser<'_> {
output, output,
timeout, timeout,
parallel, parallel,
relation,
}) })
} }

View file

@ -1571,7 +1571,7 @@ fn parse_insert() {
assert_eq!( assert_eq!(
res, res,
Statement::Insert(InsertStatement { Statement::Insert(InsertStatement {
into: Value::Param(Param(Ident("foo".to_owned()))), into: Some(Value::Param(Param(Ident("foo".to_owned())))),
data: Data::ValuesExpression(vec![ data: Data::ValuesExpression(vec![
vec![ vec![
( (
@ -1624,6 +1624,7 @@ fn parse_insert() {
output: Some(Output::After), output: Some(Output::After),
timeout: None, timeout: None,
parallel: false, parallel: false,
relation: false,
}), }),
) )
} }

View file

@ -522,7 +522,7 @@ fn statements() -> Vec<Statement> {
error: Value::Duration(Duration(std::time::Duration::from_secs(1))), error: Value::Duration(Duration(std::time::Duration::from_secs(1))),
}), }),
Statement::Insert(InsertStatement { Statement::Insert(InsertStatement {
into: Value::Param(Param(Ident("foo".to_owned()))), into: Some(Value::Param(Param(Ident("foo".to_owned())))),
data: Data::ValuesExpression(vec![ data: Data::ValuesExpression(vec![
vec![ vec![
( (
@ -575,6 +575,7 @@ fn statements() -> Vec<Statement> {
output: Some(Output::After), output: Some(Output::After),
timeout: None, timeout: None,
parallel: false, parallel: false,
relation: false,
}), }),
Statement::Kill(KillStatement { Statement::Kill(KillStatement {
id: Value::Uuid(Uuid(uuid::uuid!("e72bee20-f49b-11ec-b939-0242ac120002"))), id: Value::Uuid(Uuid(uuid::uuid!("e72bee20-f49b-11ec-b939-0242ac120002"))),

View file

@ -99,7 +99,11 @@ fn insert_statement(params: &mut [Value]) -> (bool, InsertStatement) {
}; };
let one = !data.is_array(); let one = !data.is_array();
let mut stmt = InsertStatement::default(); 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.data = Data::SingleExpression(data);
stmt.output = Some(Output::After); stmt.output = Some(Output::After);
(one, stmt) (one, stmt)

View file

@ -517,3 +517,181 @@ async fn insert_statement_unique_index() -> Result<(), Error> {
// //
Ok(()) 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(())
}