INSERT RELATION
(#4057)
Co-authored-by: Tobie Morgan Hitchcock <tobie@surrealdb.com>
This commit is contained in:
parent
a9e075463d
commit
5df3d2d190
20 changed files with 428 additions and 99 deletions
|
@ -28,7 +28,7 @@ pub(crate) enum Iterable {
|
|||
Edges(Edges),
|
||||
Defer(Thing),
|
||||
Mergeable(Thing, Value),
|
||||
Relatable(Thing, Thing, Thing),
|
||||
Relatable(Thing, Thing, Thing, Option<Value>),
|
||||
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<Value>),
|
||||
}
|
||||
|
||||
pub(crate) enum Workable {
|
||||
Normal,
|
||||
Insert(Value),
|
||||
Relate(Thing, Thing),
|
||||
Relate(Thing, Thing, Option<Value>),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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<Value>,
|
||||
) -> 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),
|
||||
|
|
|
@ -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?;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<Value>,
|
||||
pub data: Data,
|
||||
pub ignore: bool,
|
||||
pub update: Option<Data>,
|
||||
pub output: Option<Output>,
|
||||
pub timeout: Option<Timeout>,
|
||||
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<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(),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ pub struct SerializeInsertStatement {
|
|||
output: Option<Output>,
|
||||
timeout: Option<Timeout>,
|
||||
parallel: Option<bool>,
|
||||
relation: Option<bool>,
|
||||
}
|
||||
|
||||
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<Self::Ok, Error> {
|
||||
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)")),
|
||||
}
|
||||
|
|
|
@ -13,18 +13,23 @@ impl Parser<'_> {
|
|||
&mut self,
|
||||
ctx: &mut Stk,
|
||||
) -> ParseResult<InsertStatement> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -522,7 +522,7 @@ fn statements() -> Vec<Statement> {
|
|||
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<Statement> {
|
|||
output: Some(Output::After),
|
||||
timeout: None,
|
||||
parallel: false,
|
||||
relation: false,
|
||||
}),
|
||||
Statement::Kill(KillStatement {
|
||||
id: Value::Uuid(Uuid(uuid::uuid!("e72bee20-f49b-11ec-b939-0242ac120002"))),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue