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),
|
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)]
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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?;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)")),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"))),
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue