Performance and behaviour optimisations (#4785)
This commit is contained in:
parent
9f1d376716
commit
439ab99e15
58 changed files with 1540 additions and 1168 deletions
|
@ -29,6 +29,10 @@ pub static MAX_OBJECT_PARSING_DEPTH: LazyLock<u32> =
|
||||||
pub static MAX_QUERY_PARSING_DEPTH: LazyLock<u32> =
|
pub static MAX_QUERY_PARSING_DEPTH: LazyLock<u32> =
|
||||||
lazy_env_parse!("SURREAL_MAX_QUERY_PARSING_DEPTH", u32, 20);
|
lazy_env_parse!("SURREAL_MAX_QUERY_PARSING_DEPTH", u32, 20);
|
||||||
|
|
||||||
|
/// Specifies the number of computed regexes which can be cached in the engine.
|
||||||
|
pub static REGEX_CACHE_SIZE: LazyLock<usize> =
|
||||||
|
lazy_env_parse!("SURREAL_REGEX_CACHE_SIZE", usize, 1_000);
|
||||||
|
|
||||||
/// Specifies the number of items which can be cached within a single transaction.
|
/// Specifies the number of items which can be cached within a single transaction.
|
||||||
pub static TRANSACTION_CACHE_SIZE: LazyLock<usize> =
|
pub static TRANSACTION_CACHE_SIZE: LazyLock<usize> =
|
||||||
lazy_env_parse!("SURREAL_TRANSACTION_CACHE_SIZE", usize, 10_000);
|
lazy_env_parse!("SURREAL_TRANSACTION_CACHE_SIZE", usize, 10_000);
|
||||||
|
|
|
@ -11,7 +11,10 @@ use crate::doc::Document;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::idx::planner::iterators::{IteratorRecord, IteratorRef};
|
use crate::idx::planner::iterators::{IteratorRecord, IteratorRef};
|
||||||
use crate::idx::planner::IterationStage;
|
use crate::idx::planner::IterationStage;
|
||||||
|
use crate::sql::array::Array;
|
||||||
use crate::sql::edges::Edges;
|
use crate::sql::edges::Edges;
|
||||||
|
use crate::sql::mock::Mock;
|
||||||
|
use crate::sql::object::Object;
|
||||||
use crate::sql::table::Table;
|
use crate::sql::table::Table;
|
||||||
use crate::sql::thing::Thing;
|
use crate::sql::thing::Thing;
|
||||||
use crate::sql::value::Value;
|
use crate::sql::value::Value;
|
||||||
|
@ -26,17 +29,65 @@ const TARGET: &str = "surrealdb::core::dbs";
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) enum Iterable {
|
pub(crate) enum Iterable {
|
||||||
|
/// Any [Value] which does not exists in storage. This
|
||||||
|
/// could be the result of a query, an arbritrary
|
||||||
|
/// SurrealQL value, object, or array of values.
|
||||||
Value(Value),
|
Value(Value),
|
||||||
Table(Table, bool), // true = keys only
|
/// An iterable which does not actually fetch the record
|
||||||
Thing(Thing),
|
/// data from storage. This is used in CREATE statements
|
||||||
TableRange(String, IdRange, bool), // true = keys_only
|
/// where we attempt to write data without first checking
|
||||||
Edges(Edges),
|
/// if the record exists, throwing an error on failure.
|
||||||
Defer(Thing),
|
Defer(Thing),
|
||||||
|
/// An iterable whose Record ID needs to be generated
|
||||||
|
/// before processing. This is used in CREATE statements
|
||||||
|
/// when generating a new id, or generating an id based
|
||||||
|
/// on the id field which is specified within the data.
|
||||||
|
Yield(Table),
|
||||||
|
/// An iterable which needs to fetch the data of a
|
||||||
|
/// specific record before processing the document.
|
||||||
|
Thing(Thing),
|
||||||
|
/// An iterable which needs to fetch the related edges
|
||||||
|
/// of a record before processing each document.
|
||||||
|
Edges(Edges),
|
||||||
|
/// An iterable which needs to iterate over the records
|
||||||
|
/// in a table before processing each document. When the
|
||||||
|
/// 2nd argument is true, we iterate over keys only.
|
||||||
|
Table(Table, bool),
|
||||||
|
/// An iterable which fetches a specific range of records
|
||||||
|
/// from storage, used in range and time-series scenarios.
|
||||||
|
/// When the 2nd argument is true, we iterate over keys only.
|
||||||
|
Range(String, IdRange, bool),
|
||||||
|
/// An iterable which fetches a record from storage, and
|
||||||
|
/// which has the specific value to update the record with.
|
||||||
|
/// This is used in INSERT statements, where each value
|
||||||
|
/// passed in to the iterable is unique for each record.
|
||||||
Mergeable(Thing, Value),
|
Mergeable(Thing, Value),
|
||||||
|
/// An iterable which fetches a record from storage, and
|
||||||
|
/// which has the specific value to update the record with.
|
||||||
|
/// This is used in RELATE statements. The optional value
|
||||||
|
/// is used in INSERT RELATION statements, where each value
|
||||||
|
/// passed in to the iterable is unique for each record.
|
||||||
Relatable(Thing, Thing, Thing, Option<Value>),
|
Relatable(Thing, Thing, Thing, Option<Value>),
|
||||||
|
/// An iterable which iterates over an index range for a
|
||||||
|
/// table, which then fetches the correesponding records
|
||||||
|
/// which are matched within the index.
|
||||||
Index(Table, IteratorRef),
|
Index(Table, IteratorRef),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum Operable {
|
||||||
|
Value(Arc<Value>),
|
||||||
|
Mergeable(Arc<Value>, Arc<Value>, bool),
|
||||||
|
Relatable(Thing, Arc<Value>, Thing, Option<Arc<Value>>, bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum Workable {
|
||||||
|
Normal,
|
||||||
|
Insert(Arc<Value>, bool),
|
||||||
|
Relate(Thing, Thing, Option<Arc<Value>>, bool),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Processed {
|
pub(crate) struct Processed {
|
||||||
pub(crate) rid: Option<Arc<Thing>>,
|
pub(crate) rid: Option<Arc<Thing>>,
|
||||||
|
@ -44,18 +95,15 @@ pub(crate) struct Processed {
|
||||||
pub(crate) val: Operable,
|
pub(crate) val: Operable,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl Workable {
|
||||||
pub(crate) enum Operable {
|
/// Check if this is the first iteration of an INSERT statement
|
||||||
Value(Arc<Value>),
|
pub(crate) fn is_insert_initial(&self) -> bool {
|
||||||
Mergeable(Arc<Value>, Arc<Value>),
|
matches!(self, Self::Insert(_, false) | Self::Relate(_, _, _, false))
|
||||||
Relatable(Thing, Arc<Value>, Thing, Option<Arc<Value>>),
|
}
|
||||||
}
|
/// Check if this is an INSERT with a specific id field
|
||||||
|
pub(crate) fn is_insert_with_specific_id(&self) -> bool {
|
||||||
#[derive(Debug)]
|
matches!(self, Self::Insert(v, _) if v.rid().is_some())
|
||||||
pub(crate) enum Workable {
|
}
|
||||||
Normal,
|
|
||||||
Insert(Arc<Value>),
|
|
||||||
Relate(Thing, Thing, Option<Arc<Value>>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -97,182 +145,23 @@ impl Iterator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ingests an iterable for processing
|
/// Ingests an iterable for processing
|
||||||
pub fn ingest(&mut self, val: Iterable) {
|
pub(crate) fn ingest(&mut self, val: Iterable) {
|
||||||
self.entries.push(val)
|
self.entries.push(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepares a value for processing
|
/// Prepares a value for processing
|
||||||
pub async fn prepare(
|
pub(crate) fn prepare(&mut self, stm: &Statement<'_>, val: Value) -> Result<(), Error> {
|
||||||
&mut self,
|
|
||||||
stk: &mut Stk,
|
|
||||||
ctx: &Context,
|
|
||||||
opt: &Options,
|
|
||||||
stm: &Statement<'_>,
|
|
||||||
val: Value,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
// Match the values
|
// Match the values
|
||||||
match val {
|
match val {
|
||||||
Value::Table(v) => match stm.data() {
|
Value::Mock(v) => self.prepare_mock(stm, v)?,
|
||||||
// There is a data clause so fetch a record id
|
Value::Table(v) => self.prepare_table(stm, v)?,
|
||||||
Some(data) => match stm {
|
Value::Edges(v) => self.prepare_edges(stm, *v)?,
|
||||||
Statement::Create(_) => {
|
Value::Object(v) => self.prepare_object(stm, v)?,
|
||||||
let id = match data.rid(stk, ctx, opt).await? {
|
Value::Array(v) => self.prepare_array(stm, v)?,
|
||||||
// Generate a new id from the id field
|
Value::Thing(v) => match v.is_range() {
|
||||||
Some(id) => id.generate(&v, false)?,
|
true => self.prepare_range(stm, v, false)?,
|
||||||
// Generate a new random table id
|
false => self.prepare_thing(stm, v)?,
|
||||||
None => v.generate(),
|
|
||||||
};
|
|
||||||
self.ingest(Iterable::Thing(id))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// Ingest the table for scanning
|
|
||||||
self.ingest(Iterable::Table(v, false))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// There is no data clause so create a record id
|
|
||||||
None => match stm {
|
|
||||||
Statement::Create(_) => {
|
|
||||||
// Generate a new random table id
|
|
||||||
self.ingest(Iterable::Thing(v.generate()))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// Ingest the table for scanning
|
|
||||||
self.ingest(Iterable::Table(v, false))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Value::Thing(v) => {
|
|
||||||
// Check if there is a data clause
|
|
||||||
if let Some(data) = stm.data() {
|
|
||||||
// Check if there is an id field specified
|
|
||||||
if let Some(id) = data.rid(stk, ctx, opt).await? {
|
|
||||||
// Check to see the type of the id
|
|
||||||
match id {
|
|
||||||
// The id is a match, so don't error
|
|
||||||
Value::Thing(id) if id == v => (),
|
|
||||||
// The id does not match
|
|
||||||
id => {
|
|
||||||
return Err(Error::IdMismatch {
|
|
||||||
value: id.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add the record to the iterator
|
|
||||||
match &v.id {
|
|
||||||
Id::Range(r) => {
|
|
||||||
match stm {
|
|
||||||
Statement::Create(_) => {
|
|
||||||
return Err(Error::InvalidStatementTarget {
|
|
||||||
value: v.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
self.ingest(Iterable::TableRange(v.tb, *r.to_owned(), false));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
match stm {
|
|
||||||
Statement::Create(_) => {
|
|
||||||
self.ingest(Iterable::Defer(v));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
self.ingest(Iterable::Thing(v));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Mock(v) => {
|
|
||||||
// Check if there is a data clause
|
|
||||||
if let Some(data) = stm.data() {
|
|
||||||
// Check if there is an id field specified
|
|
||||||
if let Some(id) = data.rid(stk, ctx, opt).await? {
|
|
||||||
return Err(Error::IdMismatch {
|
|
||||||
value: id.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add the records to the iterator
|
|
||||||
for v in v {
|
|
||||||
self.ingest(Iterable::Thing(v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Edges(v) => {
|
|
||||||
// Check if this is a create statement
|
|
||||||
if let Statement::Create(_) = stm {
|
|
||||||
return Err(Error::InvalidStatementTarget {
|
|
||||||
value: v.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Check if there is a data clause
|
|
||||||
if let Some(data) = stm.data() {
|
|
||||||
// Check if there is an id field specified
|
|
||||||
if let Some(id) = data.rid(stk, ctx, opt).await? {
|
|
||||||
return Err(Error::IdMismatch {
|
|
||||||
value: id.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add the record to the iterator
|
|
||||||
self.ingest(Iterable::Edges(*v));
|
|
||||||
}
|
|
||||||
Value::Object(v) => {
|
|
||||||
// Check if there is a data clause
|
|
||||||
if let Some(data) = stm.data() {
|
|
||||||
// Check if there is an id field specified
|
|
||||||
if let Some(id) = data.rid(stk, ctx, opt).await? {
|
|
||||||
return Err(Error::IdMismatch {
|
|
||||||
value: id.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check if the object has an id field
|
|
||||||
match v.rid() {
|
|
||||||
Some(id) => {
|
|
||||||
// Add the record to the iterator
|
|
||||||
self.ingest(Iterable::Thing(id))
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
return Err(Error::InvalidStatementTarget {
|
|
||||||
value: v.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Array(v) => {
|
|
||||||
// Check if there is a data clause
|
|
||||||
if let Some(data) = stm.data() {
|
|
||||||
// Check if there is an id field specified
|
|
||||||
if let Some(id) = data.rid(stk, ctx, opt).await? {
|
|
||||||
return Err(Error::IdMismatch {
|
|
||||||
value: id.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add the records to the iterator
|
|
||||||
for v in v {
|
|
||||||
match v {
|
|
||||||
Value::Thing(v) => self.ingest(Iterable::Thing(v)),
|
|
||||||
Value::Edges(v) => self.ingest(Iterable::Edges(*v)),
|
|
||||||
Value::Object(v) => match v.rid() {
|
|
||||||
Some(v) => self.ingest(Iterable::Thing(v)),
|
|
||||||
None => {
|
|
||||||
return Err(Error::InvalidStatementTarget {
|
|
||||||
value: v.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
return Err(Error::InvalidStatementTarget {
|
|
||||||
value: v.to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v => {
|
v => {
|
||||||
return Err(Error::InvalidStatementTarget {
|
return Err(Error::InvalidStatementTarget {
|
||||||
value: v.to_string(),
|
value: v.to_string(),
|
||||||
|
@ -283,6 +172,120 @@ impl Iterator {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prepares a value for processing
|
||||||
|
pub(crate) fn prepare_table(&mut self, stm: &Statement<'_>, v: Table) -> Result<(), Error> {
|
||||||
|
// Add the record to the iterator
|
||||||
|
match stm.is_create() {
|
||||||
|
true => self.ingest(Iterable::Yield(v)),
|
||||||
|
false => self.ingest(Iterable::Table(v, false)),
|
||||||
|
}
|
||||||
|
// All ingested ok
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares a value for processing
|
||||||
|
pub(crate) fn prepare_thing(&mut self, stm: &Statement<'_>, v: Thing) -> Result<(), Error> {
|
||||||
|
// Add the record to the iterator
|
||||||
|
match stm.is_deferable() {
|
||||||
|
true => self.ingest(Iterable::Defer(v)),
|
||||||
|
false => self.ingest(Iterable::Thing(v)),
|
||||||
|
}
|
||||||
|
// All ingested ok
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares a value for processing
|
||||||
|
pub(crate) fn prepare_mock(&mut self, stm: &Statement<'_>, v: Mock) -> Result<(), Error> {
|
||||||
|
// Add the records to the iterator
|
||||||
|
for v in v {
|
||||||
|
match stm.is_deferable() {
|
||||||
|
true => self.ingest(Iterable::Defer(v)),
|
||||||
|
false => self.ingest(Iterable::Thing(v)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// All ingested ok
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares a value for processing
|
||||||
|
pub(crate) fn prepare_edges(&mut self, stm: &Statement<'_>, v: Edges) -> Result<(), Error> {
|
||||||
|
// Check if this is a create statement
|
||||||
|
if stm.is_create() {
|
||||||
|
return Err(Error::InvalidStatementTarget {
|
||||||
|
value: v.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Add the record to the iterator
|
||||||
|
self.ingest(Iterable::Edges(v));
|
||||||
|
// All ingested ok
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares a value for processing
|
||||||
|
pub(crate) fn prepare_range(
|
||||||
|
&mut self,
|
||||||
|
stm: &Statement<'_>,
|
||||||
|
v: Thing,
|
||||||
|
keys: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Check if this is a create statement
|
||||||
|
if stm.is_create() {
|
||||||
|
return Err(Error::InvalidStatementTarget {
|
||||||
|
value: v.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Add the record to the iterator
|
||||||
|
if let (tb, Id::Range(v)) = (v.tb, v.id) {
|
||||||
|
self.ingest(Iterable::Range(tb, *v, keys));
|
||||||
|
}
|
||||||
|
// All ingested ok
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares a value for processing
|
||||||
|
pub(crate) fn prepare_object(&mut self, stm: &Statement<'_>, v: Object) -> Result<(), Error> {
|
||||||
|
// Add the record to the iterator
|
||||||
|
match v.rid() {
|
||||||
|
// This object has an 'id' field
|
||||||
|
Some(v) => match stm.is_deferable() {
|
||||||
|
true => self.ingest(Iterable::Defer(v)),
|
||||||
|
false => self.ingest(Iterable::Thing(v)),
|
||||||
|
},
|
||||||
|
// This object has no 'id' field
|
||||||
|
None => {
|
||||||
|
return Err(Error::InvalidStatementTarget {
|
||||||
|
value: v.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// All ingested ok
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares a value for processing
|
||||||
|
pub(crate) fn prepare_array(&mut self, stm: &Statement<'_>, v: Array) -> Result<(), Error> {
|
||||||
|
// Add the records to the iterator
|
||||||
|
for v in v {
|
||||||
|
match v {
|
||||||
|
Value::Mock(v) => self.prepare_mock(stm, v)?,
|
||||||
|
Value::Table(v) => self.prepare_table(stm, v)?,
|
||||||
|
Value::Edges(v) => self.prepare_edges(stm, *v)?,
|
||||||
|
Value::Object(v) => self.prepare_object(stm, v)?,
|
||||||
|
Value::Thing(v) => match v.is_range() {
|
||||||
|
true => self.prepare_range(stm, v, false)?,
|
||||||
|
false => self.prepare_thing(stm, v)?,
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(Error::InvalidStatementTarget {
|
||||||
|
value: v.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// All ingested ok
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Process the records and output
|
/// Process the records and output
|
||||||
pub async fn output(
|
pub async fn output(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -93,13 +93,8 @@ impl ExplainItem {
|
||||||
name: "Iterate Value".into(),
|
name: "Iterate Value".into(),
|
||||||
details: vec![("value", v.to_owned())],
|
details: vec![("value", v.to_owned())],
|
||||||
},
|
},
|
||||||
Iterable::Table(t, keys_only) => Self {
|
Iterable::Yield(t) => Self {
|
||||||
name: if *keys_only {
|
name: "Iterate Yield".into(),
|
||||||
"Iterate Table Keys"
|
|
||||||
} else {
|
|
||||||
"Iterate Table"
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
details: vec![("table", Value::from(t.0.to_owned()))],
|
details: vec![("table", Value::from(t.0.to_owned()))],
|
||||||
},
|
},
|
||||||
Iterable::Thing(t) => Self {
|
Iterable::Thing(t) => Self {
|
||||||
|
@ -110,7 +105,20 @@ impl ExplainItem {
|
||||||
name: "Iterate Defer".into(),
|
name: "Iterate Defer".into(),
|
||||||
details: vec![("thing", Value::Thing(t.to_owned()))],
|
details: vec![("thing", Value::Thing(t.to_owned()))],
|
||||||
},
|
},
|
||||||
Iterable::TableRange(tb, r, keys_only) => Self {
|
Iterable::Edges(e) => Self {
|
||||||
|
name: "Iterate Edges".into(),
|
||||||
|
details: vec![("from", Value::Thing(e.from.to_owned()))],
|
||||||
|
},
|
||||||
|
Iterable::Table(t, keys_only) => Self {
|
||||||
|
name: if *keys_only {
|
||||||
|
"Iterate Table Keys"
|
||||||
|
} else {
|
||||||
|
"Iterate Table"
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
details: vec![("table", Value::from(t.0.to_owned()))],
|
||||||
|
},
|
||||||
|
Iterable::Range(tb, r, keys_only) => Self {
|
||||||
name: if *keys_only {
|
name: if *keys_only {
|
||||||
"Iterate Range Keys"
|
"Iterate Range Keys"
|
||||||
} else {
|
} else {
|
||||||
|
@ -119,10 +127,6 @@ impl ExplainItem {
|
||||||
.into(),
|
.into(),
|
||||||
details: vec![("table", tb.to_owned().into()), ("range", r.to_owned().into())],
|
details: vec![("table", tb.to_owned().into()), ("range", r.to_owned().into())],
|
||||||
},
|
},
|
||||||
Iterable::Edges(e) => Self {
|
|
||||||
name: "Iterate Edges".into(),
|
|
||||||
details: vec![("from", Value::Thing(e.from.to_owned()))],
|
|
||||||
},
|
|
||||||
Iterable::Mergeable(t, v) => Self {
|
Iterable::Mergeable(t, v) => Self {
|
||||||
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())],
|
||||||
|
|
|
@ -136,16 +136,17 @@ impl<'a> Processor<'a> {
|
||||||
if ctx.is_ok() {
|
if ctx.is_ok() {
|
||||||
match iterable {
|
match iterable {
|
||||||
Iterable::Value(v) => self.process_value(stk, ctx, opt, stm, v).await?,
|
Iterable::Value(v) => self.process_value(stk, ctx, opt, stm, v).await?,
|
||||||
|
Iterable::Yield(v) => self.process_yield(stk, ctx, opt, stm, v).await?,
|
||||||
Iterable::Thing(v) => self.process_thing(stk, ctx, opt, stm, v).await?,
|
Iterable::Thing(v) => self.process_thing(stk, ctx, opt, stm, v).await?,
|
||||||
Iterable::Defer(v) => self.process_defer(stk, ctx, opt, stm, v).await?,
|
Iterable::Defer(v) => self.process_defer(stk, ctx, opt, stm, v).await?,
|
||||||
Iterable::TableRange(tb, v, keys_only) => {
|
Iterable::Edges(e) => self.process_edges(stk, ctx, opt, stm, e).await?,
|
||||||
|
Iterable::Range(tb, v, keys_only) => {
|
||||||
if keys_only {
|
if keys_only {
|
||||||
self.process_range_keys(stk, ctx, opt, stm, &tb, v).await?
|
self.process_range_keys(stk, ctx, opt, stm, &tb, v).await?
|
||||||
} else {
|
} else {
|
||||||
self.process_range(stk, ctx, opt, stm, &tb, v).await?
|
self.process_range(stk, ctx, opt, stm, &tb, v).await?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Iterable::Edges(e) => self.process_edge(stk, ctx, opt, stm, e).await?,
|
|
||||||
Iterable::Table(v, keys_only) => {
|
Iterable::Table(v, keys_only) => {
|
||||||
let ctx = Self::check_query_planner_context(ctx, &v);
|
let ctx = Self::check_query_planner_context(ctx, &v);
|
||||||
if keys_only {
|
if keys_only {
|
||||||
|
@ -196,6 +197,36 @@ impl<'a> Processor<'a> {
|
||||||
self.process(stk, ctx, opt, stm, pro).await
|
self.process(stk, ctx, opt, stm, pro).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn process_yield(
|
||||||
|
&mut self,
|
||||||
|
stk: &mut Stk,
|
||||||
|
ctx: &Context,
|
||||||
|
opt: &Options,
|
||||||
|
stm: &Statement<'_>,
|
||||||
|
v: Table,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Fetch the record id if specified
|
||||||
|
let v = match stm.data() {
|
||||||
|
// There is a data clause so fetch a record id
|
||||||
|
Some(data) => match data.rid(stk, ctx, opt).await? {
|
||||||
|
// Generate a new id from the id field
|
||||||
|
Some(id) => id.generate(&v, false)?,
|
||||||
|
// Generate a new random table id
|
||||||
|
None => v.generate(),
|
||||||
|
},
|
||||||
|
// There is no data clause so create a record id
|
||||||
|
None => v.generate(),
|
||||||
|
};
|
||||||
|
// Pass the value through
|
||||||
|
let pro = Processed {
|
||||||
|
rid: Some(v.into()),
|
||||||
|
ir: None,
|
||||||
|
val: Operable::Value(Value::None.into()),
|
||||||
|
};
|
||||||
|
// Process the document record
|
||||||
|
self.process(stk, ctx, opt, stm, pro).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn process_defer(
|
async fn process_defer(
|
||||||
&mut self,
|
&mut self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
|
@ -259,21 +290,11 @@ impl<'a> Processor<'a> {
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Check that the table exists
|
// Check that the table exists
|
||||||
ctx.tx().check_ns_db_tb(opt.ns()?, opt.db()?, &v.tb, opt.strict).await?;
|
ctx.tx().check_ns_db_tb(opt.ns()?, opt.db()?, &v.tb, opt.strict).await?;
|
||||||
// Fetch the data from the store
|
|
||||||
let key = thing::new(opt.ns()?, opt.db()?, &v.tb, &v.id);
|
|
||||||
let val = ctx.tx().get(key, None).await?;
|
|
||||||
// Parse the data from the store
|
|
||||||
let x = match val {
|
|
||||||
Some(v) => Value::from(v),
|
|
||||||
None => Value::None,
|
|
||||||
};
|
|
||||||
// Create a new operable value
|
|
||||||
let val = Operable::Mergeable(x.into(), o.into());
|
|
||||||
// Process the document record
|
// Process the document record
|
||||||
let pro = Processed {
|
let pro = Processed {
|
||||||
rid: Some(v.into()),
|
rid: Some(v.into()),
|
||||||
ir: None,
|
ir: None,
|
||||||
val,
|
val: Operable::Mergeable(Value::None.into(), o.into(), false),
|
||||||
};
|
};
|
||||||
self.process(stk, ctx, opt, stm, pro).await?;
|
self.process(stk, ctx, opt, stm, pro).await?;
|
||||||
// Everything ok
|
// Everything ok
|
||||||
|
@ -299,7 +320,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.into(), w, o.map(|v| v.into()));
|
let val = Operable::Relatable(f, x.into(), w, o.map(|v| v.into()), false);
|
||||||
// Process the document record
|
// Process the document record
|
||||||
let pro = Processed {
|
let pro = Processed {
|
||||||
rid: Some(v.into()),
|
rid: Some(v.into()),
|
||||||
|
@ -503,7 +524,7 @@ impl<'a> Processor<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_edge(
|
async fn process_edges(
|
||||||
&mut self,
|
&mut self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
|
|
|
@ -116,18 +116,27 @@ impl<'a> fmt::Display for Statement<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Statement<'a> {
|
impl<'a> Statement<'a> {
|
||||||
/// Check the type of statement
|
/// Check if this is a SELECT statement
|
||||||
#[inline]
|
|
||||||
pub fn is_select(&self) -> bool {
|
pub fn is_select(&self) -> bool {
|
||||||
matches!(self, Statement::Select(_))
|
matches!(self, Statement::Select(_))
|
||||||
}
|
}
|
||||||
/// Check the type of statement
|
/// Check if this is a CREATE statement
|
||||||
#[inline]
|
pub fn is_create(&self) -> bool {
|
||||||
|
matches!(self, Statement::Create(_))
|
||||||
|
}
|
||||||
|
/// Check if this is a DELETE statement
|
||||||
pub fn is_delete(&self) -> bool {
|
pub fn is_delete(&self) -> bool {
|
||||||
matches!(self, Statement::Delete(_))
|
matches!(self, Statement::Delete(_))
|
||||||
}
|
}
|
||||||
|
/// Returns whether retrieval can be deferred
|
||||||
|
pub fn is_deferable(&self) -> bool {
|
||||||
|
matches!(self, Statement::Create(_) | Statement::Upsert(_))
|
||||||
|
}
|
||||||
|
/// Returns whether this requires savepoints
|
||||||
|
pub fn is_retryable(&self) -> bool {
|
||||||
|
matches!(self, Statement::Insert(_) if self.data().is_some())
|
||||||
|
}
|
||||||
/// Returns any query fields if specified
|
/// Returns any query fields if specified
|
||||||
#[inline]
|
|
||||||
pub fn expr(&self) -> Option<&Fields> {
|
pub fn expr(&self) -> Option<&Fields> {
|
||||||
match self {
|
match self {
|
||||||
Statement::Select(v) => Some(&v.expr),
|
Statement::Select(v) => Some(&v.expr),
|
||||||
|
@ -136,15 +145,13 @@ impl<'a> Statement<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns any OMIT clause if specified
|
/// Returns any OMIT clause if specified
|
||||||
#[inline]
|
|
||||||
pub fn omit(&self) -> Option<&Idioms> {
|
pub fn omit(&self) -> Option<&Idioms> {
|
||||||
match self {
|
match self {
|
||||||
Statement::Select(v) => v.omit.as_ref(),
|
Statement::Select(v) => v.omit.as_ref(),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns any SET clause if specified
|
/// Returns any SET, CONTENT, or MERGE clause if specified
|
||||||
#[inline]
|
|
||||||
pub fn data(&self) -> Option<&Data> {
|
pub fn data(&self) -> Option<&Data> {
|
||||||
match self {
|
match self {
|
||||||
Statement::Create(v) => v.data.as_ref(),
|
Statement::Create(v) => v.data.as_ref(),
|
||||||
|
@ -156,7 +163,6 @@ impl<'a> Statement<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns any WHERE clause if specified
|
/// Returns any WHERE clause if specified
|
||||||
#[inline]
|
|
||||||
pub fn conds(&self) -> Option<&Cond> {
|
pub fn conds(&self) -> Option<&Cond> {
|
||||||
match self {
|
match self {
|
||||||
Statement::Live(v) => v.cond.as_ref(),
|
Statement::Live(v) => v.cond.as_ref(),
|
||||||
|
@ -168,7 +174,6 @@ impl<'a> Statement<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns any SPLIT clause if specified
|
/// Returns any SPLIT clause if specified
|
||||||
#[inline]
|
|
||||||
pub fn split(&self) -> Option<&Splits> {
|
pub fn split(&self) -> Option<&Splits> {
|
||||||
match self {
|
match self {
|
||||||
Statement::Select(v) => v.split.as_ref(),
|
Statement::Select(v) => v.split.as_ref(),
|
||||||
|
@ -176,7 +181,6 @@ impl<'a> Statement<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns any GROUP clause if specified
|
/// Returns any GROUP clause if specified
|
||||||
#[inline]
|
|
||||||
pub fn group(&self) -> Option<&Groups> {
|
pub fn group(&self) -> Option<&Groups> {
|
||||||
match self {
|
match self {
|
||||||
Statement::Select(v) => v.group.as_ref(),
|
Statement::Select(v) => v.group.as_ref(),
|
||||||
|
@ -184,7 +188,6 @@ impl<'a> Statement<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns any ORDER clause if specified
|
/// Returns any ORDER clause if specified
|
||||||
#[inline]
|
|
||||||
pub fn order(&self) -> Option<&Orders> {
|
pub fn order(&self) -> Option<&Orders> {
|
||||||
match self {
|
match self {
|
||||||
Statement::Select(v) => v.order.as_ref(),
|
Statement::Select(v) => v.order.as_ref(),
|
||||||
|
@ -192,7 +195,6 @@ impl<'a> Statement<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns any FETCH clause if specified
|
/// Returns any FETCH clause if specified
|
||||||
#[inline]
|
|
||||||
pub fn fetch(&self) -> Option<&Fetchs> {
|
pub fn fetch(&self) -> Option<&Fetchs> {
|
||||||
match self {
|
match self {
|
||||||
Statement::Select(v) => v.fetch.as_ref(),
|
Statement::Select(v) => v.fetch.as_ref(),
|
||||||
|
@ -200,7 +202,6 @@ impl<'a> Statement<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns any START clause if specified
|
/// Returns any START clause if specified
|
||||||
#[inline]
|
|
||||||
pub fn start(&self) -> Option<&Start> {
|
pub fn start(&self) -> Option<&Start> {
|
||||||
match self {
|
match self {
|
||||||
Statement::Select(v) => v.start.as_ref(),
|
Statement::Select(v) => v.start.as_ref(),
|
||||||
|
@ -208,7 +209,6 @@ impl<'a> Statement<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns any LIMIT clause if specified
|
/// Returns any LIMIT clause if specified
|
||||||
#[inline]
|
|
||||||
pub fn limit(&self) -> Option<&Limit> {
|
pub fn limit(&self) -> Option<&Limit> {
|
||||||
match self {
|
match self {
|
||||||
Statement::Select(v) => v.limit.as_ref(),
|
Statement::Select(v) => v.limit.as_ref(),
|
||||||
|
@ -216,7 +216,6 @@ impl<'a> Statement<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns any RETURN clause if specified
|
/// Returns any RETURN clause if specified
|
||||||
#[inline]
|
|
||||||
pub fn output(&self) -> Option<&Output> {
|
pub fn output(&self) -> Option<&Output> {
|
||||||
match self {
|
match self {
|
||||||
Statement::Create(v) => v.output.as_ref(),
|
Statement::Create(v) => v.output.as_ref(),
|
||||||
|
@ -229,7 +228,6 @@ impl<'a> Statement<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns any PARALLEL clause if specified
|
/// Returns any PARALLEL clause if specified
|
||||||
#[inline]
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn parallel(&self) -> bool {
|
pub fn parallel(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
@ -243,9 +241,7 @@ impl<'a> Statement<'a> {
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns any TEMPFILES clause if specified
|
/// Returns any TEMPFILES clause if specified
|
||||||
#[inline]
|
|
||||||
#[cfg(storage)]
|
#[cfg(storage)]
|
||||||
pub fn tempfiles(&self) -> bool {
|
pub fn tempfiles(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
@ -253,9 +249,7 @@ impl<'a> Statement<'a> {
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns any EXPLAIN clause if specified
|
/// Returns any EXPLAIN clause if specified
|
||||||
#[inline]
|
|
||||||
pub fn explain(&self) -> Option<&Explain> {
|
pub fn explain(&self) -> Option<&Explain> {
|
||||||
match self {
|
match self {
|
||||||
Statement::Select(v) => v.explain.as_ref(),
|
Statement::Select(v) => v.explain.as_ref(),
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
use crate::ctx::Context;
|
|
||||||
use crate::dbs::Options;
|
|
||||||
use crate::dbs::Statement;
|
|
||||||
use crate::doc::Document;
|
|
||||||
use crate::err::Error;
|
|
||||||
use crate::sql::permission::Permission;
|
|
||||||
use reblessive::tree::Stk;
|
|
||||||
|
|
||||||
impl Document {
|
|
||||||
pub async fn allow(
|
|
||||||
&self,
|
|
||||||
stk: &mut Stk,
|
|
||||||
ctx: &Context,
|
|
||||||
opt: &Options,
|
|
||||||
stm: &Statement<'_>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
// Check if this record exists
|
|
||||||
if self.id.is_some() {
|
|
||||||
// Should we run permissions checks?
|
|
||||||
if opt.check_perms(stm.into())? {
|
|
||||||
// Check that record authentication matches session
|
|
||||||
if opt.auth.is_record() {
|
|
||||||
let ns = opt.ns()?;
|
|
||||||
if opt.auth.level().ns() != Some(ns) {
|
|
||||||
return Err(Error::NsNotAllowed {
|
|
||||||
ns: ns.into(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let db = opt.db()?;
|
|
||||||
if opt.auth.level().db() != Some(db) {
|
|
||||||
return Err(Error::DbNotAllowed {
|
|
||||||
db: db.into(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Get the table
|
|
||||||
let tb = self.tb(ctx, opt).await?;
|
|
||||||
// Get the permission clause
|
|
||||||
let perms = if stm.is_delete() {
|
|
||||||
&tb.permissions.delete
|
|
||||||
} else if stm.is_select() {
|
|
||||||
&tb.permissions.select
|
|
||||||
} else if self.is_new() {
|
|
||||||
&tb.permissions.create
|
|
||||||
} else {
|
|
||||||
&tb.permissions.update
|
|
||||||
};
|
|
||||||
// Process the table permissions
|
|
||||||
match perms {
|
|
||||||
Permission::None => return Err(Error::Ignore),
|
|
||||||
Permission::Full => return Ok(()),
|
|
||||||
Permission::Specific(e) => {
|
|
||||||
// Disable permissions
|
|
||||||
let opt = &opt.new_with_perms(false);
|
|
||||||
// Process the PERMISSION clause
|
|
||||||
if !e
|
|
||||||
.compute(
|
|
||||||
stk,
|
|
||||||
ctx,
|
|
||||||
opt,
|
|
||||||
Some(match stm.is_delete() {
|
|
||||||
true => &self.initial,
|
|
||||||
false => &self.current,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
.is_truthy()
|
|
||||||
{
|
|
||||||
return Err(Error::Ignore);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Carry on
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,11 +6,132 @@ use crate::doc::Document;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::sql::data::Data;
|
use crate::sql::data::Data;
|
||||||
use crate::sql::operator::Operator;
|
use crate::sql::operator::Operator;
|
||||||
|
use crate::sql::paths::EDGE;
|
||||||
|
use crate::sql::paths::IN;
|
||||||
|
use crate::sql::paths::OUT;
|
||||||
use crate::sql::value::Value;
|
use crate::sql::value::Value;
|
||||||
use reblessive::tree::Stk;
|
use reblessive::tree::Stk;
|
||||||
|
|
||||||
impl Document {
|
impl Document {
|
||||||
pub async fn alter(
|
/// Clears all of the content of this document.
|
||||||
|
/// This is used to empty the current content
|
||||||
|
/// of the document within a `DELETE` statement.
|
||||||
|
/// This function only clears the document in
|
||||||
|
/// memory, and does not store this on disk.
|
||||||
|
pub async fn clear_record_data(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &Context,
|
||||||
|
_opt: &Options,
|
||||||
|
_stm: &Statement<'_>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.current.doc.to_mut().clear()
|
||||||
|
}
|
||||||
|
/// Sets the default field data that should be
|
||||||
|
/// present on this document. For normal records
|
||||||
|
/// the `id` field is always specified, and for
|
||||||
|
/// relation records, the `in`, `out`, and the
|
||||||
|
/// hidden `edge` field are always present. This
|
||||||
|
/// ensures that any user modifications of these
|
||||||
|
/// fields are reset back to the original state.
|
||||||
|
pub async fn default_record_data(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &Context,
|
||||||
|
_opt: &Options,
|
||||||
|
_stm: &Statement<'_>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Get the record id
|
||||||
|
let rid = self.id()?;
|
||||||
|
// 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 {
|
||||||
|
// Mark that this is an edge node
|
||||||
|
self.current.doc.to_mut().put(&*EDGE, Value::Bool(true));
|
||||||
|
// If this document existed before, check the `in` field
|
||||||
|
match (self.initial.doc.pick(&*IN), self.is_new()) {
|
||||||
|
// If the document id matches, then all good
|
||||||
|
(Value::Thing(id), false) if id.eq(l) => {
|
||||||
|
self.current.doc.to_mut().put(&*IN, l.clone().into());
|
||||||
|
}
|
||||||
|
// If the document is new then all good
|
||||||
|
(_, true) => {
|
||||||
|
self.current.doc.to_mut().put(&*IN, l.clone().into());
|
||||||
|
}
|
||||||
|
// Otherwise this is attempting to override the `in` field
|
||||||
|
(v, _) => {
|
||||||
|
return Err(Error::InOverride {
|
||||||
|
value: v.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If this document existed before, check the `out` field
|
||||||
|
match (self.initial.doc.pick(&*OUT), self.is_new()) {
|
||||||
|
// If the document id matches, then all good
|
||||||
|
(Value::Thing(id), false) if id.eq(r) => {
|
||||||
|
self.current.doc.to_mut().put(&*OUT, r.clone().into());
|
||||||
|
}
|
||||||
|
// If the document is new then all good
|
||||||
|
(_, true) => {
|
||||||
|
self.current.doc.to_mut().put(&*OUT, r.clone().into());
|
||||||
|
}
|
||||||
|
// Otherwise this is attempting to override the `in` field
|
||||||
|
(v, _) => {
|
||||||
|
return Err(Error::OutOverride {
|
||||||
|
value: v.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This is an UPDATE of a graph edge, so reset fields
|
||||||
|
if self.initial.doc.pick(&*EDGE).is_true() {
|
||||||
|
self.current.doc.to_mut().put(&*EDGE, Value::Bool(true));
|
||||||
|
self.current.doc.to_mut().put(&*IN, self.initial.doc.pick(&*IN));
|
||||||
|
self.current.doc.to_mut().put(&*OUT, self.initial.doc.pick(&*OUT));
|
||||||
|
}
|
||||||
|
// Carry on
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Updates the current document using the data
|
||||||
|
/// passed in to each document. This is relevant
|
||||||
|
/// for INSERT and RELATE queries where each
|
||||||
|
/// document has its own data block. This
|
||||||
|
/// function also ensures that standard default
|
||||||
|
/// fields are set and reset before and after the
|
||||||
|
/// document data is modified.
|
||||||
|
pub async fn process_merge_data(
|
||||||
|
&mut self,
|
||||||
|
stk: &mut Stk,
|
||||||
|
ctx: &Context,
|
||||||
|
opt: &Options,
|
||||||
|
_stm: &Statement<'_>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Get the record id
|
||||||
|
let rid = self.id()?;
|
||||||
|
// Set default field values
|
||||||
|
self.current.doc.to_mut().def(&rid);
|
||||||
|
// This is an INSERT statement
|
||||||
|
if let Workable::Insert(v, _) = &self.extras {
|
||||||
|
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
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Updates the current document using the data
|
||||||
|
/// clause present on the statement. This can be
|
||||||
|
/// one of CONTENT, REPLACE, MERGE, PATCH, SET,
|
||||||
|
/// UNSET, or ON DUPLICATE KEY UPDATE. This
|
||||||
|
/// function also ensures that standard default
|
||||||
|
/// fields are set and reset before and after the
|
||||||
|
/// document data is modified.
|
||||||
|
pub async fn process_record_data(
|
||||||
&mut self,
|
&mut self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
|
@ -18,9 +139,9 @@ impl Document {
|
||||||
stm: &Statement<'_>,
|
stm: &Statement<'_>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Get the record id
|
// Get the record id
|
||||||
let rid = self.id.as_ref().unwrap();
|
let rid = self.id()?;
|
||||||
// Set default field values
|
// Set default field values
|
||||||
self.current.doc.to_mut().def(rid);
|
self.current.doc.to_mut().def(&rid);
|
||||||
// The statement has a data clause
|
// The statement has a data clause
|
||||||
if let Some(v) = stm.data() {
|
if let Some(v) = stm.data() {
|
||||||
match v {
|
match v {
|
||||||
|
@ -74,14 +195,14 @@ impl Document {
|
||||||
// Duplicate context
|
// Duplicate context
|
||||||
let mut ctx = MutableContext::new(ctx);
|
let mut ctx = MutableContext::new(ctx);
|
||||||
// Add insertable value
|
// Add insertable value
|
||||||
if let Workable::Insert(value) = &self.extras {
|
if let Workable::Insert(value, _) = &self.extras {
|
||||||
ctx.add_value("input", value.clone());
|
ctx.add_value("input", value.clone());
|
||||||
}
|
}
|
||||||
if let Workable::Relate(_, _, Some(value)) = &self.extras {
|
if let Workable::Relate(_, _, Some(value), _) = &self.extras {
|
||||||
ctx.add_value("input", value.clone());
|
ctx.add_value("input", value.clone());
|
||||||
}
|
}
|
||||||
// Freeze the context
|
// Freeze the context
|
||||||
let ctx: Context = ctx.into();
|
let ctx = ctx.freeze();
|
||||||
// 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?;
|
||||||
|
@ -111,7 +232,7 @@ impl Document {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
// 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
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::doc::Document;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
|
|
||||||
impl Document {
|
impl Document {
|
||||||
pub async fn changefeeds(
|
pub async fn process_changefeeds(
|
||||||
&self,
|
&self,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
|
|
|
@ -1,33 +1,302 @@
|
||||||
use crate::ctx::Context;
|
use crate::ctx::Context;
|
||||||
use crate::dbs::Options;
|
use crate::dbs::Options;
|
||||||
use crate::dbs::Statement;
|
use crate::dbs::Statement;
|
||||||
use crate::doc::{CursorDoc, Document};
|
use crate::dbs::Workable;
|
||||||
|
use crate::doc::Document;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::sql::Cond;
|
use crate::sql::paths::ID;
|
||||||
|
use crate::sql::paths::IN;
|
||||||
|
use crate::sql::paths::OUT;
|
||||||
|
use crate::sql::permission::Permission;
|
||||||
|
use crate::sql::value::Value;
|
||||||
use reblessive::tree::Stk;
|
use reblessive::tree::Stk;
|
||||||
|
|
||||||
impl Document {
|
impl Document {
|
||||||
pub async fn check(
|
/// Checks whether this operation is allowed on
|
||||||
|
/// the table for this document. When inserting
|
||||||
|
/// an edge or relation, we check that the table
|
||||||
|
/// type is `ANY` or `RELATION`. When inserting
|
||||||
|
/// a node or normal record, we check that the
|
||||||
|
/// table type is `ANY` or `NORMAL`.
|
||||||
|
pub async fn check_table_type(
|
||||||
|
&mut self,
|
||||||
|
ctx: &Context,
|
||||||
|
opt: &Options,
|
||||||
|
stm: &Statement<'_>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Get the table for this document
|
||||||
|
let tb = self.tb(ctx, opt).await?;
|
||||||
|
// Determine the type of statement
|
||||||
|
match stm {
|
||||||
|
Statement::Create(_) => {
|
||||||
|
if !tb.allows_normal() {
|
||||||
|
return Err(Error::TableCheck {
|
||||||
|
thing: self.id()?.to_string(),
|
||||||
|
relation: false,
|
||||||
|
target_type: tb.kind.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Statement::Upsert(_) => {
|
||||||
|
if !tb.allows_normal() {
|
||||||
|
return Err(Error::TableCheck {
|
||||||
|
thing: self.id()?.to_string(),
|
||||||
|
relation: false,
|
||||||
|
target_type: tb.kind.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Statement::Relate(_) => {
|
||||||
|
if !tb.allows_relation() {
|
||||||
|
return Err(Error::TableCheck {
|
||||||
|
thing: self.id()?.to_string(),
|
||||||
|
relation: true,
|
||||||
|
target_type: tb.kind.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Statement::Insert(_) => match self.extras {
|
||||||
|
Workable::Relate(_, _, _, _) => {
|
||||||
|
if !tb.allows_relation() {
|
||||||
|
return Err(Error::TableCheck {
|
||||||
|
thing: self.id()?.to_string(),
|
||||||
|
relation: true,
|
||||||
|
target_type: tb.kind.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if !tb.allows_normal() {
|
||||||
|
return Err(Error::TableCheck {
|
||||||
|
thing: self.id()?.to_string(),
|
||||||
|
relation: false,
|
||||||
|
target_type: tb.kind.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
// Carry on
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Checks that a specifically selected record
|
||||||
|
/// actually exists in the underlying datastore.
|
||||||
|
/// If the user specifies a record directly
|
||||||
|
/// using a Record ID, and that record does not
|
||||||
|
/// exist, then this function will exit early.
|
||||||
|
pub async fn check_record_exists(
|
||||||
|
&self,
|
||||||
|
_ctx: &Context,
|
||||||
|
_opt: &Options,
|
||||||
|
_stm: &Statement<'_>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Check if this record exists
|
||||||
|
if self.id.is_some() && self.current.doc.is_none() {
|
||||||
|
return Err(Error::Ignore);
|
||||||
|
}
|
||||||
|
// Carry on
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Checks that a specifically selected record
|
||||||
|
/// actually exists in the underlying datastore.
|
||||||
|
/// If the user specifies a record directly
|
||||||
|
/// using a Record ID, and that record does not
|
||||||
|
/// exist, then this function will exit early.
|
||||||
|
pub async fn check_data_fields(
|
||||||
&self,
|
&self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
stm: &Statement<'_>,
|
stm: &Statement<'_>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
Self::check_cond(stk, ctx, opt, stm.conds(), &self.current).await
|
// Get the record id
|
||||||
|
let rid = self.id()?;
|
||||||
|
// This is a CREATE, UPSERT, UPDATE statement
|
||||||
|
if let Workable::Normal = &self.extras {
|
||||||
|
// This is a CONTENT, MERGE or SET clause
|
||||||
|
if let Some(data) = stm.data() {
|
||||||
|
// Check if there is an id field specified
|
||||||
|
if let Some(field) = data.pick(stk, ctx, opt, &*ID).await? {
|
||||||
|
match field {
|
||||||
|
// The id is a match, so don't error
|
||||||
|
Value::Thing(v) if v.eq(&rid) => (),
|
||||||
|
// The id is a match, so don't error
|
||||||
|
v if rid.id.is(&v) => (),
|
||||||
|
// The in field does not match
|
||||||
|
v => match v.convert_to_record() {
|
||||||
|
// This is a value which matches the id
|
||||||
|
Ok(v) if v.eq(&rid) => (),
|
||||||
|
// The value is a record but doesn't match
|
||||||
|
Ok(v) => {
|
||||||
|
return Err(Error::IdMismatch {
|
||||||
|
value: v.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// The in field does not match at all
|
||||||
|
Err(Error::ConvertTo {
|
||||||
|
from,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
return Err(Error::IdMismatch {
|
||||||
|
value: from.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Return any other error
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This is a RELATE statement
|
||||||
|
if let Workable::Relate(l, r, v, _) = &self.extras {
|
||||||
|
// This is a RELATE statement
|
||||||
|
if let Some(data) = stm.data() {
|
||||||
|
// Check that the 'in' field matches
|
||||||
|
if let Some(field) = data.pick(stk, ctx, opt, &*IN).await? {
|
||||||
|
match field {
|
||||||
|
// The in field is a match, so don't error
|
||||||
|
Value::Thing(v) if v.eq(l) => (),
|
||||||
|
// The in is a match, so don't error
|
||||||
|
v if l.id.is(&v) => (),
|
||||||
|
// The in field does not match
|
||||||
|
v => match v.convert_to_record() {
|
||||||
|
// This is a value which matches the id
|
||||||
|
Ok(v) if v.eq(l) => (),
|
||||||
|
// The value is a record but doesn't match
|
||||||
|
Ok(v) => {
|
||||||
|
return Err(Error::InMismatch {
|
||||||
|
value: v.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// The in field does not match at all
|
||||||
|
Err(Error::ConvertTo {
|
||||||
|
from,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
return Err(Error::InMismatch {
|
||||||
|
value: from.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Return any other error
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check that the 'out' field matches
|
||||||
|
if let Some(field) = data.pick(stk, ctx, opt, &*OUT).await? {
|
||||||
|
match field {
|
||||||
|
// The out field is a match, so don't error
|
||||||
|
Value::Thing(v) if v.eq(r) => (),
|
||||||
|
// The out is a match, so don't error
|
||||||
|
v if r.id.is(&v) => (),
|
||||||
|
// The in field does not match
|
||||||
|
v => match v.convert_to_record() {
|
||||||
|
// This is a value which matches the id
|
||||||
|
Ok(v) if v.eq(r) => (),
|
||||||
|
// The value is a record but doesn't match
|
||||||
|
Ok(v) => {
|
||||||
|
return Err(Error::OutMismatch {
|
||||||
|
value: v.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// The in field does not match at all
|
||||||
|
Err(Error::ConvertTo {
|
||||||
|
from,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
return Err(Error::OutMismatch {
|
||||||
|
value: from.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Return any other error
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This is a INSERT RELATION statement
|
||||||
|
if let Some(data) = v {
|
||||||
|
// Check that the 'in' field matches
|
||||||
|
match data.pick(&*IN).compute(stk, ctx, opt, Some(&self.current)).await? {
|
||||||
|
// The in field is a match, so don't error
|
||||||
|
Value::Thing(v) if v.eq(l) => (),
|
||||||
|
// The in is a match, so don't error
|
||||||
|
v if l.id.is(&v) => (),
|
||||||
|
// The in field does not match
|
||||||
|
v => match v.convert_to_record() {
|
||||||
|
// This is a value which matches the id
|
||||||
|
Ok(v) if v.eq(l) => (),
|
||||||
|
// The value is a record but doesn't match
|
||||||
|
Ok(v) => {
|
||||||
|
return Err(Error::InMismatch {
|
||||||
|
value: v.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// The in field does not match at all
|
||||||
|
Err(Error::ConvertTo {
|
||||||
|
from,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
return Err(Error::InMismatch {
|
||||||
|
value: from.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Return any other error
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// Check that the 'out' field matches
|
||||||
|
match data.pick(&*OUT).compute(stk, ctx, opt, Some(&self.current)).await? {
|
||||||
|
// The out field is a match, so don't error
|
||||||
|
Value::Thing(v) if v.eq(r) => (),
|
||||||
|
// The out is a match, so don't error
|
||||||
|
v if l.id.is(&v) => (),
|
||||||
|
// The out field does not match
|
||||||
|
v => match v.convert_to_record() {
|
||||||
|
// This is a value which matches the id
|
||||||
|
Ok(v) if v.eq(l) => (),
|
||||||
|
// The value is a record but doesn't match
|
||||||
|
Ok(v) => {
|
||||||
|
return Err(Error::OutMismatch {
|
||||||
|
value: v.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// The out field does not match at all
|
||||||
|
Err(Error::ConvertTo {
|
||||||
|
from,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
return Err(Error::OutMismatch {
|
||||||
|
value: from.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Return any other error
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Carry on
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
/// Checks that the `WHERE` condition on a query
|
||||||
pub(crate) async fn check_cond(
|
/// matches before proceeding with processing
|
||||||
|
/// the document. This ensures that records from
|
||||||
|
/// a table, or from an index can be filtered out
|
||||||
|
/// before being included within the query output.
|
||||||
|
pub async fn check_where_condition(
|
||||||
|
&self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
cond: Option<&Cond>,
|
stm: &Statement<'_>,
|
||||||
doc: &CursorDoc,
|
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Check where condition
|
// Check where condition
|
||||||
if let Some(cond) = cond {
|
if let Some(cond) = stm.conds() {
|
||||||
// Check if the expression is truthy
|
// Check if the expression is truthy
|
||||||
if !cond.compute(stk, ctx, opt, Some(doc)).await?.is_truthy() {
|
if !cond.compute(stk, ctx, opt, Some(&self.current)).await?.is_truthy() {
|
||||||
// Ignore this document
|
// Ignore this document
|
||||||
return Err(Error::Ignore);
|
return Err(Error::Ignore);
|
||||||
}
|
}
|
||||||
|
@ -35,4 +304,114 @@ impl Document {
|
||||||
// Carry on
|
// Carry on
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
/// Checks the `PERMISSIONS` clause on the table
|
||||||
|
/// for this record, returning immediately if the
|
||||||
|
/// permissions are `NONE`. This function does not
|
||||||
|
/// check any custom advanced table permissions,
|
||||||
|
/// which should be checked at a later stage.
|
||||||
|
pub async fn check_permissions_quick(
|
||||||
|
&self,
|
||||||
|
_stk: &mut Stk,
|
||||||
|
ctx: &Context,
|
||||||
|
opt: &Options,
|
||||||
|
stm: &Statement<'_>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Check if this record exists
|
||||||
|
if self.id.is_some() {
|
||||||
|
// Should we run permissions checks?
|
||||||
|
if opt.check_perms(stm.into())? {
|
||||||
|
// Get the table for this document
|
||||||
|
let table = self.tb(ctx, opt).await?;
|
||||||
|
// Get the permissions for this table
|
||||||
|
let perms = if stm.is_delete() {
|
||||||
|
&table.permissions.delete
|
||||||
|
} else if stm.is_select() {
|
||||||
|
&table.permissions.select
|
||||||
|
} else if self.is_new() {
|
||||||
|
&table.permissions.create
|
||||||
|
} else {
|
||||||
|
&table.permissions.update
|
||||||
|
};
|
||||||
|
// Exit early if permissions are NONE
|
||||||
|
if perms.is_none() {
|
||||||
|
return Err(Error::Ignore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Carry on
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Checks the `PERMISSIONS` clause on the table for
|
||||||
|
/// this record, processing all advanced permissions
|
||||||
|
/// clauses and evaluating the expression. This
|
||||||
|
/// function checks and evaluates `FULL`, `NONE`,
|
||||||
|
/// and specific permissions clauses on the table.
|
||||||
|
pub async fn check_permissions_table(
|
||||||
|
&self,
|
||||||
|
stk: &mut Stk,
|
||||||
|
ctx: &Context,
|
||||||
|
opt: &Options,
|
||||||
|
stm: &Statement<'_>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Check if this record exists
|
||||||
|
if self.id.is_some() {
|
||||||
|
// Should we run permissions checks?
|
||||||
|
if opt.check_perms(stm.into())? {
|
||||||
|
// Check that record authentication matches session
|
||||||
|
if opt.auth.is_record() {
|
||||||
|
let ns = opt.ns()?;
|
||||||
|
if opt.auth.level().ns() != Some(ns) {
|
||||||
|
return Err(Error::NsNotAllowed {
|
||||||
|
ns: ns.into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let db = opt.db()?;
|
||||||
|
if opt.auth.level().db() != Some(db) {
|
||||||
|
return Err(Error::DbNotAllowed {
|
||||||
|
db: db.into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get the table
|
||||||
|
let table = self.tb(ctx, opt).await?;
|
||||||
|
// Get the permission clause
|
||||||
|
let perms = if stm.is_delete() {
|
||||||
|
&table.permissions.delete
|
||||||
|
} else if stm.is_select() {
|
||||||
|
&table.permissions.select
|
||||||
|
} else if self.is_new() {
|
||||||
|
&table.permissions.create
|
||||||
|
} else {
|
||||||
|
&table.permissions.update
|
||||||
|
};
|
||||||
|
// Process the table permissions
|
||||||
|
match perms {
|
||||||
|
Permission::None => return Err(Error::Ignore),
|
||||||
|
Permission::Full => return Ok(()),
|
||||||
|
Permission::Specific(e) => {
|
||||||
|
// Disable permissions
|
||||||
|
let opt = &opt.new_with_perms(false);
|
||||||
|
// Process the PERMISSION clause
|
||||||
|
if !e
|
||||||
|
.compute(
|
||||||
|
stk,
|
||||||
|
ctx,
|
||||||
|
opt,
|
||||||
|
Some(match stm.is_delete() {
|
||||||
|
true => &self.initial,
|
||||||
|
false => &self.current,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.is_truthy()
|
||||||
|
{
|
||||||
|
return Err(Error::Ignore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Carry on
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
use crate::ctx::Context;
|
|
||||||
use crate::dbs::Options;
|
|
||||||
use crate::dbs::Statement;
|
|
||||||
use crate::doc::Document;
|
|
||||||
use crate::err::Error;
|
|
||||||
use crate::sql::idiom::Idiom;
|
|
||||||
use reblessive::tree::Stk;
|
|
||||||
|
|
||||||
impl Document {
|
|
||||||
pub async fn clean(
|
|
||||||
&mut self,
|
|
||||||
stk: &mut Stk,
|
|
||||||
ctx: &Context,
|
|
||||||
opt: &Options,
|
|
||||||
_stm: &Statement<'_>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
// Get the table
|
|
||||||
let tb = self.tb(ctx, opt).await?;
|
|
||||||
// This table is schemafull
|
|
||||||
if tb.full {
|
|
||||||
// Create a vector to store the keys
|
|
||||||
let mut keys: Vec<Idiom> = vec![];
|
|
||||||
// Loop through all field statements
|
|
||||||
for fd in self.fd(ctx, opt).await?.iter() {
|
|
||||||
// Is this a schemaless field?
|
|
||||||
match fd.flex || fd.kind.as_ref().is_some_and(|k| k.is_literal_nested()) {
|
|
||||||
false => {
|
|
||||||
// Loop over this field in the document
|
|
||||||
for k in self.current.doc.as_ref().each(&fd.name).into_iter() {
|
|
||||||
keys.push(k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true => {
|
|
||||||
// Loop over every field under this field in the document
|
|
||||||
for k in
|
|
||||||
self.current.doc.as_ref().every(Some(&fd.name), true, true).into_iter()
|
|
||||||
{
|
|
||||||
keys.push(k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Loop over every field in the document
|
|
||||||
for fd in self.current.doc.as_ref().every(None, true, true).iter() {
|
|
||||||
if !keys.contains(fd) {
|
|
||||||
match fd {
|
|
||||||
fd if fd.is_id() => continue,
|
|
||||||
fd if fd.is_in() => continue,
|
|
||||||
fd if fd.is_out() => continue,
|
|
||||||
fd if fd.is_meta() => continue,
|
|
||||||
fd => self.current.doc.to_mut().del(stk, ctx, opt, fd).await?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Carry on
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -29,13 +29,13 @@ impl Document {
|
||||||
// Setup a new workable
|
// Setup a new workable
|
||||||
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, u) => (v, Workable::Insert(o, u)),
|
||||||
Operable::Relatable(f, v, w, o) => (v, Workable::Relate(f, w, o)),
|
Operable::Relatable(f, v, w, o, u) => (v, Workable::Relate(f, w, o, u)),
|
||||||
};
|
};
|
||||||
// Setup a new document
|
// Setup a new document
|
||||||
let mut doc = Document::new(pro.rid, pro.ir, ins.0, ins.1);
|
let mut doc = Document::new(pro.rid, pro.ir, ins.0, ins.1);
|
||||||
// Optionally create a save point so we can roll back any upcoming changes
|
// Optionally create a save point so we can roll back any upcoming changes
|
||||||
let is_save_point = if !stm.is_select() {
|
let is_save_point = if stm.is_retryable() {
|
||||||
ctx.tx().lock().await.new_save_point().await;
|
ctx.tx().lock().await.new_save_point().await;
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
@ -75,16 +75,16 @@ impl Document {
|
||||||
ir: None,
|
ir: None,
|
||||||
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, true),
|
||||||
Workable::Relate(f, w, o) => Operable::Relatable(f, val, w, o),
|
Workable::Relate(f, w, o, _) => Operable::Relatable(f, val, w, o, true),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// Go to top of loop
|
// Go to top of loop
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// This record didn't match conditions, so skip
|
||||||
Err(Error::Ignore) => Err(Error::Ignore),
|
Err(Error::Ignore) => Err(Error::Ignore),
|
||||||
// If any other error was received, then let's
|
// Pass other errors through and return the error
|
||||||
// pass that error through and return an error
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// We roll back any change following the save point
|
// We roll back any change following the save point
|
||||||
if is_save_point {
|
if is_save_point {
|
||||||
|
|
|
@ -14,31 +14,20 @@ impl Document {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
stm: &Statement<'_>,
|
stm: &Statement<'_>,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
// Check if table has current relation status
|
self.check_permissions_quick(stk, ctx, opt, stm).await?;
|
||||||
self.relation(ctx, opt, stm).await?;
|
self.check_table_type(ctx, opt, stm).await?;
|
||||||
// Alter record data
|
self.check_data_fields(stk, ctx, opt, stm).await?;
|
||||||
self.alter(stk, ctx, opt, stm).await?;
|
self.process_record_data(stk, ctx, opt, stm).await?;
|
||||||
// Merge fields data
|
self.process_table_fields(stk, ctx, opt, stm).await?;
|
||||||
self.field(stk, ctx, opt, stm).await?;
|
self.cleanup_table_fields(stk, ctx, opt, stm).await?;
|
||||||
// Reset fields data
|
self.default_record_data(ctx, opt, stm).await?;
|
||||||
self.reset(ctx, opt, stm).await?;
|
self.check_permissions_table(stk, ctx, opt, stm).await?;
|
||||||
// Clean fields data
|
self.store_record_data(ctx, opt, stm).await?;
|
||||||
self.clean(stk, ctx, opt, stm).await?;
|
self.store_index_data(stk, ctx, opt, stm).await?;
|
||||||
// Check if allowed
|
self.process_table_views(stk, ctx, opt, stm).await?;
|
||||||
self.allow(stk, ctx, opt, stm).await?;
|
self.process_table_lives(stk, ctx, opt, stm).await?;
|
||||||
// Store record data
|
self.process_table_events(stk, ctx, opt, stm).await?;
|
||||||
self.store(ctx, opt, stm).await?;
|
self.process_changefeeds(ctx, opt, stm).await?;
|
||||||
// Store index data
|
|
||||||
self.index(stk, ctx, opt, stm).await?;
|
|
||||||
// Run table queries
|
|
||||||
self.table(stk, ctx, opt, stm).await?;
|
|
||||||
// Run lives queries
|
|
||||||
self.lives(stk, ctx, opt, stm).await?;
|
|
||||||
// Run change feeds queries
|
|
||||||
self.changefeeds(ctx, opt, stm).await?;
|
|
||||||
// Run event queries
|
|
||||||
self.event(stk, ctx, opt, stm).await?;
|
|
||||||
// Yield document
|
|
||||||
self.pluck(stk, ctx, opt, stm).await
|
self.pluck(stk, ctx, opt, stm).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,25 +14,17 @@ impl Document {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
stm: &Statement<'_>,
|
stm: &Statement<'_>,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
// Check where clause
|
self.check_record_exists(ctx, opt, stm).await?;
|
||||||
self.check(stk, ctx, opt, stm).await?;
|
self.check_permissions_quick(stk, ctx, opt, stm).await?;
|
||||||
// Check if allowed
|
self.check_where_condition(stk, ctx, opt, stm).await?;
|
||||||
self.allow(stk, ctx, opt, stm).await?;
|
self.check_permissions_table(stk, ctx, opt, stm).await?;
|
||||||
// Erase document
|
self.clear_record_data(ctx, opt, stm).await?;
|
||||||
self.erase(ctx, opt, stm).await?;
|
self.store_index_data(stk, ctx, opt, stm).await?;
|
||||||
// Purge index data
|
|
||||||
self.index(stk, ctx, opt, stm).await?;
|
|
||||||
// Purge record data
|
|
||||||
self.purge(stk, ctx, opt, stm).await?;
|
self.purge(stk, ctx, opt, stm).await?;
|
||||||
// Run table queries
|
self.process_table_views(stk, ctx, opt, stm).await?;
|
||||||
self.table(stk, ctx, opt, stm).await?;
|
self.process_table_lives(stk, ctx, opt, stm).await?;
|
||||||
// Run lives queries
|
self.process_table_events(stk, ctx, opt, stm).await?;
|
||||||
self.lives(stk, ctx, opt, stm).await?;
|
self.process_changefeeds(ctx, opt, stm).await?;
|
||||||
// Run change feeds queries
|
|
||||||
self.changefeeds(ctx, opt, stm).await?;
|
|
||||||
// Run event queries
|
|
||||||
self.event(stk, ctx, opt, stm).await?;
|
|
||||||
// Yield document
|
|
||||||
self.pluck(stk, ctx, opt, stm).await
|
self.pluck(stk, ctx, opt, stm).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,6 @@ impl CursorValue {
|
||||||
|
|
||||||
impl Deref for CursorValue {
|
impl Deref for CursorValue {
|
||||||
type Target = Value;
|
type Target = Value;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
self.as_ref()
|
self.as_ref()
|
||||||
}
|
}
|
||||||
|
@ -191,7 +190,15 @@ impl Document {
|
||||||
|
|
||||||
/// Check if document is being created
|
/// Check if document is being created
|
||||||
pub fn is_new(&self) -> bool {
|
pub fn is_new(&self) -> bool {
|
||||||
self.initial.doc.as_ref().is_none() && self.current.doc.as_ref().is_some()
|
self.initial.doc.as_ref().is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the record id for this document
|
||||||
|
pub fn id(&self) -> Result<Arc<Thing>, Error> {
|
||||||
|
match self.id.as_ref() {
|
||||||
|
Some(id) => Ok(id.clone()),
|
||||||
|
_ => Err(fail!("Expected a document id to be present")),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the table for this document
|
/// Get the table for this document
|
||||||
|
@ -203,9 +210,9 @@ impl Document {
|
||||||
// Get transaction
|
// Get transaction
|
||||||
let txn = ctx.tx();
|
let txn = ctx.tx();
|
||||||
// Get the record id
|
// Get the record id
|
||||||
let rid = self.id.as_ref().unwrap();
|
let id = self.id()?;
|
||||||
// Get the table definition
|
// Get the table definition
|
||||||
let tb = txn.get_tb(opt.ns()?, opt.db()?, &rid.tb).await;
|
let tb = txn.get_tb(opt.ns()?, opt.db()?, &id.tb).await;
|
||||||
// Return the table or attempt to define it
|
// Return the table or attempt to define it
|
||||||
match tb {
|
match tb {
|
||||||
// The table doesn't exist
|
// The table doesn't exist
|
||||||
|
@ -215,7 +222,7 @@ impl Document {
|
||||||
// Allowed to run?
|
// Allowed to run?
|
||||||
opt.is_allowed(Action::Edit, ResourceKind::Table, &Base::Db)?;
|
opt.is_allowed(Action::Edit, ResourceKind::Table, &Base::Db)?;
|
||||||
// We can create the table automatically
|
// We can create the table automatically
|
||||||
txn.ensure_ns_db_tb(opt.ns()?, opt.db()?, &rid.tb, opt.strict).await
|
txn.ensure_ns_db_tb(opt.ns()?, opt.db()?, &id.tb, opt.strict).await
|
||||||
}
|
}
|
||||||
// There was an error
|
// There was an error
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
|
@ -230,7 +237,7 @@ impl Document {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
) -> Result<Arc<[DefineTableStatement]>, Error> {
|
) -> Result<Arc<[DefineTableStatement]>, Error> {
|
||||||
// Get the record id
|
// Get the record id
|
||||||
let id = self.id.as_ref().unwrap();
|
let id = self.id()?;
|
||||||
// Get the table definitions
|
// Get the table definitions
|
||||||
ctx.tx().all_tb_views(opt.ns()?, opt.db()?, &id.tb).await
|
ctx.tx().all_tb_views(opt.ns()?, opt.db()?, &id.tb).await
|
||||||
}
|
}
|
||||||
|
@ -241,7 +248,7 @@ impl Document {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
) -> Result<Arc<[DefineEventStatement]>, Error> {
|
) -> Result<Arc<[DefineEventStatement]>, Error> {
|
||||||
// Get the record id
|
// Get the record id
|
||||||
let id = self.id.as_ref().unwrap();
|
let id = self.id()?;
|
||||||
// Get the event definitions
|
// Get the event definitions
|
||||||
ctx.tx().all_tb_events(opt.ns()?, opt.db()?, &id.tb).await
|
ctx.tx().all_tb_events(opt.ns()?, opt.db()?, &id.tb).await
|
||||||
}
|
}
|
||||||
|
@ -252,7 +259,7 @@ impl Document {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
) -> Result<Arc<[DefineFieldStatement]>, Error> {
|
) -> Result<Arc<[DefineFieldStatement]>, Error> {
|
||||||
// Get the record id
|
// Get the record id
|
||||||
let id = self.id.as_ref().unwrap();
|
let id = self.id()?;
|
||||||
// Get the field definitions
|
// Get the field definitions
|
||||||
ctx.tx().all_tb_fields(opt.ns()?, opt.db()?, &id.tb, None).await
|
ctx.tx().all_tb_fields(opt.ns()?, opt.db()?, &id.tb, None).await
|
||||||
}
|
}
|
||||||
|
@ -263,14 +270,14 @@ impl Document {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
) -> Result<Arc<[DefineIndexStatement]>, Error> {
|
) -> Result<Arc<[DefineIndexStatement]>, Error> {
|
||||||
// Get the record id
|
// Get the record id
|
||||||
let id = self.id.as_ref().unwrap();
|
let id = self.id()?;
|
||||||
// Get the index definitions
|
// Get the index definitions
|
||||||
ctx.tx().all_tb_indexes(opt.ns()?, opt.db()?, &id.tb).await
|
ctx.tx().all_tb_indexes(opt.ns()?, opt.db()?, &id.tb).await
|
||||||
}
|
}
|
||||||
// Get the lives for this document
|
// Get the lives for this document
|
||||||
pub async fn lv(&self, ctx: &Context, opt: &Options) -> Result<Arc<[LiveStatement]>, Error> {
|
pub async fn lv(&self, ctx: &Context, opt: &Options) -> Result<Arc<[LiveStatement]>, Error> {
|
||||||
// Get the record id
|
// Get the record id
|
||||||
let id = self.id.as_ref().unwrap();
|
let id = self.id()?;
|
||||||
// Get the table definition
|
// Get the table definition
|
||||||
ctx.tx().all_tb_lives(opt.ns()?, opt.db()?, &id.tb).await
|
ctx.tx().all_tb_lives(opt.ns()?, opt.db()?, &id.tb).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use crate::sql::Relation;
|
||||||
use crate::sql::TableType;
|
use crate::sql::TableType;
|
||||||
|
|
||||||
impl Document {
|
impl Document {
|
||||||
pub async fn edges(
|
pub async fn store_edges_data(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
|
@ -30,9 +30,9 @@ impl Document {
|
||||||
// Lock the transaction
|
// Lock the transaction
|
||||||
let mut txn = txn.lock().await;
|
let mut txn = txn.lock().await;
|
||||||
// Get the record id
|
// Get the record id
|
||||||
let rid = self.id.as_ref().unwrap();
|
let rid = self.id()?;
|
||||||
// Store the record edges
|
// Store the record edges
|
||||||
if let Workable::Relate(l, r, _) = &self.extras {
|
if let Workable::Relate(l, r, _, _) = &self.extras {
|
||||||
// For enforced relations, ensure that the edges exist
|
// For enforced relations, ensure that the edges exist
|
||||||
if matches!(
|
if matches!(
|
||||||
tb.kind,
|
tb.kind,
|
||||||
|
@ -41,13 +41,14 @@ impl Document {
|
||||||
..
|
..
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
|
// Check that the `in` record exists
|
||||||
let key = crate::key::thing::new(opt.ns()?, opt.db()?, &l.tb, &l.id);
|
let key = crate::key::thing::new(opt.ns()?, opt.db()?, &l.tb, &l.id);
|
||||||
if !txn.exists(key).await? {
|
if !txn.exists(key).await? {
|
||||||
return Err(Error::IdNotFound {
|
return Err(Error::IdNotFound {
|
||||||
value: l.to_string(),
|
value: l.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// Check that the `out` record exists
|
||||||
let key = crate::key::thing::new(opt.ns()?, opt.db()?, &r.tb, &r.id);
|
let key = crate::key::thing::new(opt.ns()?, opt.db()?, &r.tb, &r.id);
|
||||||
if !txn.exists(key).await? {
|
if !txn.exists(key).await? {
|
||||||
return Err(Error::IdNotFound {
|
return Err(Error::IdNotFound {
|
||||||
|
@ -58,7 +59,7 @@ impl Document {
|
||||||
// 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
|
||||||
let key = crate::key::graph::new(opt.ns()?, opt.db()?, &l.tb, &l.id, o, rid);
|
let key = crate::key::graph::new(opt.ns()?, opt.db()?, &l.tb, &l.id, o, &rid);
|
||||||
txn.set(key, vec![], None).await?;
|
txn.set(key, vec![], None).await?;
|
||||||
// Store the left inner edge
|
// Store the left inner edge
|
||||||
let key = crate::key::graph::new(opt.ns()?, opt.db()?, &rid.tb, &rid.id, i, l);
|
let key = crate::key::graph::new(opt.ns()?, opt.db()?, &rid.tb, &rid.id, i, l);
|
||||||
|
@ -67,7 +68,7 @@ impl Document {
|
||||||
let key = crate::key::graph::new(opt.ns()?, opt.db()?, &rid.tb, &rid.id, o, r);
|
let key = crate::key::graph::new(opt.ns()?, opt.db()?, &rid.tb, &rid.id, o, r);
|
||||||
txn.set(key, vec![], None).await?;
|
txn.set(key, vec![], None).await?;
|
||||||
// Store the right pointer edge
|
// Store the right pointer edge
|
||||||
let key = crate::key::graph::new(opt.ns()?, opt.db()?, &r.tb, &r.id, i, rid);
|
let key = crate::key::graph::new(opt.ns()?, opt.db()?, &r.tb, &r.id, i, &rid);
|
||||||
txn.set(key, vec![], None).await?;
|
txn.set(key, vec![], None).await?;
|
||||||
// Store the edges on the record
|
// Store the edges on the record
|
||||||
self.current.doc.to_mut().put(&*EDGE, Value::Bool(true));
|
self.current.doc.to_mut().put(&*EDGE, Value::Bool(true));
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
use crate::ctx::Context;
|
|
||||||
use crate::dbs::Options;
|
|
||||||
use crate::dbs::Statement;
|
|
||||||
use crate::doc::Document;
|
|
||||||
use crate::err::Error;
|
|
||||||
|
|
||||||
impl Document {
|
|
||||||
pub async fn empty(
|
|
||||||
&self,
|
|
||||||
_ctx: &Context,
|
|
||||||
_opt: &Options,
|
|
||||||
_stm: &Statement<'_>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
// Check if this record exists
|
|
||||||
if self.id.is_some() {
|
|
||||||
// There is no current record
|
|
||||||
if self.current.doc.as_ref().is_none() {
|
|
||||||
// Ignore this requested record
|
|
||||||
return Err(Error::Ignore);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Carry on
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
use crate::ctx::Context;
|
|
||||||
use crate::dbs::Options;
|
|
||||||
use crate::dbs::Statement;
|
|
||||||
use crate::doc::Document;
|
|
||||||
use crate::err::Error;
|
|
||||||
|
|
||||||
impl Document {
|
|
||||||
pub async fn erase(
|
|
||||||
&mut self,
|
|
||||||
_ctx: &Context,
|
|
||||||
_opt: &Options,
|
|
||||||
_stm: &Statement<'_>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.current.doc.to_mut().clear()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,7 +7,12 @@ use crate::sql::value::Value;
|
||||||
use reblessive::tree::Stk;
|
use reblessive::tree::Stk;
|
||||||
|
|
||||||
impl Document {
|
impl Document {
|
||||||
pub async fn event(
|
/// Processes any DEFINE EVENT clauses which
|
||||||
|
/// have been defined for the table which this
|
||||||
|
/// record belongs to. This functions loops
|
||||||
|
/// through the events and processes them all
|
||||||
|
/// within the currently running transaction.
|
||||||
|
pub async fn process_table_events(
|
||||||
&mut self,
|
&mut self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
|
|
|
@ -4,14 +4,80 @@ use crate::dbs::Statement;
|
||||||
use crate::doc::Document;
|
use crate::doc::Document;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::iam::Action;
|
use crate::iam::Action;
|
||||||
|
use crate::sql::idiom::Idiom;
|
||||||
|
use crate::sql::kind::Kind;
|
||||||
use crate::sql::permission::Permission;
|
use crate::sql::permission::Permission;
|
||||||
use crate::sql::value::Value;
|
use crate::sql::value::Value;
|
||||||
use crate::sql::{Idiom, Kind};
|
|
||||||
use reblessive::tree::Stk;
|
use reblessive::tree::Stk;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
impl Document {
|
impl Document {
|
||||||
pub async fn field(
|
/// Ensures that any remaining fields on a
|
||||||
|
/// SCHEMAFULL table are cleaned up and removed.
|
||||||
|
/// If a field is defined as FLEX, then any
|
||||||
|
/// nested fields or array values are untouched.
|
||||||
|
pub async fn cleanup_table_fields(
|
||||||
|
&mut self,
|
||||||
|
stk: &mut Stk,
|
||||||
|
ctx: &Context,
|
||||||
|
opt: &Options,
|
||||||
|
_stm: &Statement<'_>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Get the table
|
||||||
|
let tb = self.tb(ctx, opt).await?;
|
||||||
|
// This table is schemafull
|
||||||
|
if tb.full {
|
||||||
|
// Create a vector to store the keys
|
||||||
|
let mut keys: Vec<Idiom> = vec![];
|
||||||
|
// Loop through all field statements
|
||||||
|
for fd in self.fd(ctx, opt).await?.iter() {
|
||||||
|
// Is this a schemaless field?
|
||||||
|
match fd.flex || fd.kind.as_ref().is_some_and(|k| k.is_literal_nested()) {
|
||||||
|
false => {
|
||||||
|
// Loop over this field in the document
|
||||||
|
for k in self.current.doc.each(&fd.name).into_iter() {
|
||||||
|
keys.push(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true => {
|
||||||
|
// Loop over every field under this field in the document
|
||||||
|
for k in self.current.doc.every(Some(&fd.name), true, true).into_iter() {
|
||||||
|
keys.push(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Loop over every field in the document
|
||||||
|
for fd in self.current.doc.every(None, true, true).iter() {
|
||||||
|
if !keys.contains(fd) {
|
||||||
|
match fd {
|
||||||
|
fd if fd.is_id() => continue,
|
||||||
|
fd if fd.is_in() => continue,
|
||||||
|
fd if fd.is_out() => continue,
|
||||||
|
fd if fd.is_meta() => continue,
|
||||||
|
fd => match opt.strict {
|
||||||
|
// If strict, then throw an error on an undefined field
|
||||||
|
true => {
|
||||||
|
return Err(Error::FieldUndefined {
|
||||||
|
table: tb.name.to_raw(),
|
||||||
|
field: fd.to_owned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Otherwise, delete the field silently and don't error
|
||||||
|
false => self.current.doc.to_mut().del(stk, ctx, opt, fd).await?,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Carry on
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Processes `DEFINE FIELD` statements which
|
||||||
|
/// have been defined on the table for this
|
||||||
|
/// record. These fields are executed for
|
||||||
|
/// every matching field in the input document.
|
||||||
|
pub async fn process_table_fields(
|
||||||
&mut self,
|
&mut self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
|
@ -23,26 +89,30 @@ impl Document {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
// Get the record id
|
// Get the record id
|
||||||
let rid = self.id.as_ref().unwrap();
|
let rid = self.id()?;
|
||||||
// Get the user applied input
|
// Get the user applied input
|
||||||
let inp = self.initial.doc.as_ref().changed(self.current.doc.as_ref());
|
let inp = self.initial.doc.as_ref().changed(self.current.doc.as_ref());
|
||||||
// If set, the loop will skip certain clauses as long
|
// When set, any matching embedded object fields
|
||||||
// as the field name starts with the set idiom
|
// which are prefixed with the specified idiom
|
||||||
let mut skip: Option<Idiom> = None;
|
// will be skipped, as the parent object is optional
|
||||||
|
let mut skip: Option<&Idiom> = None;
|
||||||
// Loop through all field statements
|
// Loop through all field statements
|
||||||
for fd in self.fd(ctx, opt).await?.iter() {
|
for fd in self.fd(ctx, opt).await?.iter() {
|
||||||
|
// Check if we should skip this field
|
||||||
let skipped = match skip {
|
let skipped = match skip {
|
||||||
Some(ref inner) => {
|
// We are skipping a parent field
|
||||||
|
Some(inner) => {
|
||||||
|
// Check if this field is a child field
|
||||||
let skipped = fd.name.starts_with(inner);
|
let skipped = fd.name.starts_with(inner);
|
||||||
|
// Let's stop skipping fields if not
|
||||||
if !skipped {
|
if !skipped {
|
||||||
skip = None;
|
skip = None;
|
||||||
}
|
}
|
||||||
|
// Specify whether we should skip
|
||||||
skipped
|
skipped
|
||||||
}
|
}
|
||||||
None => false,
|
None => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Loop over each field in document
|
// Loop over each field in document
|
||||||
for (k, mut val) in self.current.doc.as_ref().walk(&fd.name).into_iter() {
|
for (k, mut val) in self.current.doc.as_ref().walk(&fd.name).into_iter() {
|
||||||
// Get the initial value
|
// Get the initial value
|
||||||
|
@ -50,12 +120,17 @@ impl Document {
|
||||||
// Get the input value
|
// Get the input value
|
||||||
let inp = Arc::new(inp.pick(&k));
|
let inp = Arc::new(inp.pick(&k));
|
||||||
// Check for READONLY clause
|
// Check for READONLY clause
|
||||||
if fd.readonly && !self.is_new() && val.ne(&old) {
|
if fd.readonly || fd.name.is_id() {
|
||||||
return Err(Error::FieldReadonly {
|
if !self.is_new() && val.ne(&old) {
|
||||||
field: fd.name.clone(),
|
return Err(Error::FieldReadonly {
|
||||||
thing: rid.to_string(),
|
field: fd.name.clone(),
|
||||||
});
|
thing: rid.to_string(),
|
||||||
|
});
|
||||||
|
} else if !self.is_new() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Skip this field?
|
||||||
if !skipped {
|
if !skipped {
|
||||||
// Get the default value
|
// Get the default value
|
||||||
let def = match &fd.default {
|
let def = match &fd.default {
|
||||||
|
@ -67,14 +142,17 @@ impl Document {
|
||||||
};
|
};
|
||||||
// Check for a DEFAULT clause
|
// Check for a DEFAULT clause
|
||||||
if let Some(expr) = def {
|
if let Some(expr) = def {
|
||||||
|
// Only run value clause for new and empty fields
|
||||||
if self.is_new() && val.is_none() {
|
if self.is_new() && val.is_none() {
|
||||||
|
// Arc the current value
|
||||||
|
let now = Arc::new(val);
|
||||||
// Configure the context
|
// Configure the context
|
||||||
let mut ctx = MutableContext::new(ctx);
|
let mut ctx = MutableContext::new(ctx);
|
||||||
let v = Arc::new(val);
|
|
||||||
ctx.add_value("input", inp.clone());
|
|
||||||
ctx.add_value("value", v.clone());
|
|
||||||
ctx.add_value("after", v);
|
|
||||||
ctx.add_value("before", old.clone());
|
ctx.add_value("before", old.clone());
|
||||||
|
ctx.add_value("input", inp.clone());
|
||||||
|
ctx.add_value("after", now.clone());
|
||||||
|
ctx.add_value("value", now);
|
||||||
|
// Freeze the new context
|
||||||
let ctx = ctx.freeze();
|
let ctx = ctx.freeze();
|
||||||
// Process the VALUE clause
|
// Process the VALUE clause
|
||||||
val = expr.compute(stk, &ctx, opt, Some(&self.current)).await?;
|
val = expr.compute(stk, &ctx, opt, Some(&self.current)).await?;
|
||||||
|
@ -82,7 +160,17 @@ impl Document {
|
||||||
}
|
}
|
||||||
// Check for a TYPE clause
|
// Check for a TYPE clause
|
||||||
if let Some(kind) = &fd.kind {
|
if let Some(kind) = &fd.kind {
|
||||||
val = val.coerce_to(kind).map_err(|e| match e {
|
// If this is the `id` field, it must be a record
|
||||||
|
let cast = match &fd.name {
|
||||||
|
name if name.is_id() => match kind.to_owned() {
|
||||||
|
Kind::Option(v) if v.is_record() => &*v.to_owned(),
|
||||||
|
Kind::Record(r) => &Kind::Record(r),
|
||||||
|
_ => &Kind::Record(vec![]),
|
||||||
|
},
|
||||||
|
_ => kind,
|
||||||
|
};
|
||||||
|
// Check the type of the field value
|
||||||
|
val = val.coerce_to(cast).map_err(|e| match e {
|
||||||
// There was a conversion error
|
// There was a conversion error
|
||||||
Error::CoerceTo {
|
Error::CoerceTo {
|
||||||
from,
|
from,
|
||||||
|
@ -91,31 +179,60 @@ impl Document {
|
||||||
thing: rid.to_string(),
|
thing: rid.to_string(),
|
||||||
field: fd.name.clone(),
|
field: fd.name.clone(),
|
||||||
value: from.to_string(),
|
value: from.to_string(),
|
||||||
check: kind.to_string(),
|
check: cast.to_string(),
|
||||||
},
|
},
|
||||||
// There was a different error
|
// There was a different error
|
||||||
e => e,
|
e => e,
|
||||||
})?;
|
})?;
|
||||||
|
// If this is the `id` field, check the inner type
|
||||||
|
if fd.name.is_id() {
|
||||||
|
if let Value::Thing(id) = val.clone() {
|
||||||
|
let inner = Value::from(id.id);
|
||||||
|
inner.coerce_to(kind).map_err(|e| match e {
|
||||||
|
// There was a conversion error
|
||||||
|
Error::CoerceTo {
|
||||||
|
from,
|
||||||
|
..
|
||||||
|
} => Error::FieldCheck {
|
||||||
|
thing: rid.to_string(),
|
||||||
|
field: fd.name.clone(),
|
||||||
|
value: from.to_string(),
|
||||||
|
check: kind.to_string(),
|
||||||
|
},
|
||||||
|
// There was a different error
|
||||||
|
e => e,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Check for a VALUE clause
|
// Check for a VALUE clause
|
||||||
if let Some(expr) = &fd.value {
|
if let Some(expr) = &fd.value {
|
||||||
// Only run value clause for mutable and new fields
|
// Arc the current value
|
||||||
if !fd.readonly || self.is_new() {
|
let now = Arc::new(val);
|
||||||
// Configure the context
|
// Configure the context
|
||||||
let v = Arc::new(val);
|
let mut ctx = MutableContext::new(ctx);
|
||||||
let mut ctx = MutableContext::new(ctx);
|
ctx.add_value("before", old.clone());
|
||||||
ctx.add_value("input", inp.clone());
|
ctx.add_value("input", inp.clone());
|
||||||
ctx.add_value("value", v.clone());
|
ctx.add_value("after", now.clone());
|
||||||
ctx.add_value("after", v);
|
ctx.add_value("value", now);
|
||||||
ctx.add_value("before", old.clone());
|
// Freeze the new context
|
||||||
let ctx = ctx.freeze();
|
let ctx = ctx.freeze();
|
||||||
// Process the VALUE clause
|
// Process the VALUE clause
|
||||||
val = expr.compute(stk, &ctx, opt, Some(&self.current)).await?;
|
val = expr.compute(stk, &ctx, opt, Some(&self.current)).await?;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Check for a TYPE clause
|
// Check for a TYPE clause
|
||||||
if let Some(kind) = &fd.kind {
|
if let Some(kind) = &fd.kind {
|
||||||
val = val.coerce_to(kind).map_err(|e| match e {
|
// If this is the `id` field, it must be a record
|
||||||
|
let cast = match &fd.name {
|
||||||
|
name if name.is_id() => match kind.to_owned() {
|
||||||
|
Kind::Option(v) if v.is_record() => &*v.to_owned(),
|
||||||
|
Kind::Record(r) => &Kind::Record(r),
|
||||||
|
_ => &Kind::Record(vec![]),
|
||||||
|
},
|
||||||
|
_ => kind,
|
||||||
|
};
|
||||||
|
// Check the type of the field value
|
||||||
|
val = val.coerce_to(cast).map_err(|e| match e {
|
||||||
// There was a conversion error
|
// There was a conversion error
|
||||||
Error::CoerceTo {
|
Error::CoerceTo {
|
||||||
from,
|
from,
|
||||||
|
@ -124,11 +241,31 @@ impl Document {
|
||||||
thing: rid.to_string(),
|
thing: rid.to_string(),
|
||||||
field: fd.name.clone(),
|
field: fd.name.clone(),
|
||||||
value: from.to_string(),
|
value: from.to_string(),
|
||||||
check: kind.to_string(),
|
check: cast.to_string(),
|
||||||
},
|
},
|
||||||
// There was a different error
|
// There was a different error
|
||||||
e => e,
|
e => e,
|
||||||
})?;
|
})?;
|
||||||
|
// If this is the `id` field, check the inner type
|
||||||
|
if fd.name.is_id() {
|
||||||
|
if let Value::Thing(id) = val.clone() {
|
||||||
|
let inner = Value::from(id.id);
|
||||||
|
inner.coerce_to(kind).map_err(|e| match e {
|
||||||
|
// There was a conversion error
|
||||||
|
Error::CoerceTo {
|
||||||
|
from,
|
||||||
|
..
|
||||||
|
} => Error::FieldCheck {
|
||||||
|
thing: rid.to_string(),
|
||||||
|
field: fd.name.clone(),
|
||||||
|
value: from.to_string(),
|
||||||
|
check: kind.to_string(),
|
||||||
|
},
|
||||||
|
// There was a different error
|
||||||
|
e => e,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Check for a ASSERT clause
|
// Check for a ASSERT clause
|
||||||
if let Some(expr) = &fd.assert {
|
if let Some(expr) = &fd.assert {
|
||||||
|
@ -139,13 +276,15 @@ impl Document {
|
||||||
(Value::None, Some(Kind::Option(_))) => (),
|
(Value::None, Some(Kind::Option(_))) => (),
|
||||||
// Otherwise let's process the ASSERT clause
|
// Otherwise let's process the ASSERT clause
|
||||||
_ => {
|
_ => {
|
||||||
|
// Arc the current value
|
||||||
|
let now = Arc::new(val.clone());
|
||||||
// Configure the context
|
// Configure the context
|
||||||
let mut ctx = MutableContext::new(ctx);
|
let mut ctx = MutableContext::new(ctx);
|
||||||
let v = Arc::new(val.clone());
|
|
||||||
ctx.add_value("input", inp.clone());
|
|
||||||
ctx.add_value("value", v.clone());
|
|
||||||
ctx.add_value("after", v);
|
|
||||||
ctx.add_value("before", old.clone());
|
ctx.add_value("before", old.clone());
|
||||||
|
ctx.add_value("input", inp.clone());
|
||||||
|
ctx.add_value("after", now.clone());
|
||||||
|
ctx.add_value("value", now.clone());
|
||||||
|
// Freeze the new context
|
||||||
let ctx = ctx.freeze();
|
let ctx = ctx.freeze();
|
||||||
// Process the ASSERT clause
|
// Process the ASSERT clause
|
||||||
if !expr
|
if !expr
|
||||||
|
@ -156,8 +295,8 @@ impl Document {
|
||||||
return Err(Error::FieldValue {
|
return Err(Error::FieldValue {
|
||||||
thing: rid.to_string(),
|
thing: rid.to_string(),
|
||||||
field: fd.name.clone(),
|
field: fd.name.clone(),
|
||||||
value: val.to_string(),
|
|
||||||
check: expr.to_string(),
|
check: expr.to_string(),
|
||||||
|
value: now.to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -187,15 +326,17 @@ impl Document {
|
||||||
// we check the expression and
|
// we check the expression and
|
||||||
// revert the field if denied.
|
// revert the field if denied.
|
||||||
Permission::Specific(e) => {
|
Permission::Specific(e) => {
|
||||||
|
// Arc the current value
|
||||||
|
let now = Arc::new(val.clone());
|
||||||
// Disable permissions
|
// Disable permissions
|
||||||
let opt = &opt.new_with_perms(false);
|
let opt = &opt.new_with_perms(false);
|
||||||
// Configure the context
|
// Configure the context
|
||||||
let mut ctx = MutableContext::new(ctx);
|
let mut ctx = MutableContext::new(ctx);
|
||||||
let v = Arc::new(val.clone());
|
|
||||||
ctx.add_value("input", inp);
|
|
||||||
ctx.add_value("value", v.clone());
|
|
||||||
ctx.add_value("after", v);
|
|
||||||
ctx.add_value("before", old.clone());
|
ctx.add_value("before", old.clone());
|
||||||
|
ctx.add_value("input", inp.clone());
|
||||||
|
ctx.add_value("after", now.clone());
|
||||||
|
ctx.add_value("value", now.clone());
|
||||||
|
// Freeze the new context
|
||||||
let ctx = ctx.freeze();
|
let ctx = ctx.freeze();
|
||||||
// Process the PERMISSION clause
|
// Process the PERMISSION clause
|
||||||
if !e.compute(stk, &ctx, opt, Some(&self.current)).await?.is_truthy() {
|
if !e.compute(stk, &ctx, opt, Some(&self.current)).await?.is_truthy() {
|
||||||
|
@ -204,13 +345,13 @@ impl Document {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Skip this field?
|
||||||
if !skipped {
|
if !skipped {
|
||||||
if matches!(val, Value::None) && matches!(fd.kind, Some(Kind::Option(_))) {
|
// If the field is empty, mark child fields as skippable
|
||||||
skip = Some(fd.name.to_owned());
|
if val.is_none() && fd.kind.as_ref().is_some_and(Kind::can_be_none) {
|
||||||
|
skip = Some(&fd.name);
|
||||||
}
|
}
|
||||||
|
// Set the new value of the field, or delete it if empty
|
||||||
// Set the value of the field
|
|
||||||
match val {
|
match val {
|
||||||
Value::None => self.current.doc.to_mut().del(stk, ctx, opt, &k).await?,
|
Value::None => self.current.doc.to_mut().del(stk, ctx, opt, &k).await?,
|
||||||
v => self.current.doc.to_mut().set(stk, ctx, opt, &k, v).await?,
|
v => self.current.doc.to_mut().set(stk, ctx, opt, &k, v).await?,
|
||||||
|
|
|
@ -17,7 +17,7 @@ use crate::sql::{Part, Thing, Value};
|
||||||
use reblessive::tree::Stk;
|
use reblessive::tree::Stk;
|
||||||
|
|
||||||
impl Document {
|
impl Document {
|
||||||
pub async fn index(
|
pub async fn store_index_data(
|
||||||
&self,
|
&self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
|
@ -44,7 +44,7 @@ impl Document {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
// Get the record id
|
// Get the record id
|
||||||
let rid = self.id.as_ref().unwrap();
|
let rid = self.id()?;
|
||||||
// Loop through all index statements
|
// Loop through all index statements
|
||||||
for ix in ixs.iter() {
|
for ix in ixs.iter() {
|
||||||
// Calculate old values
|
// Calculate old values
|
||||||
|
@ -55,7 +55,7 @@ impl Document {
|
||||||
|
|
||||||
// Update the index entries
|
// Update the index entries
|
||||||
if targeted_force || o != n {
|
if targeted_force || o != n {
|
||||||
Self::one_index(stk, ctx, opt, ix, o, n, rid).await?;
|
Self::one_index(stk, ctx, opt, ix, o, n, &rid).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Carry on
|
// Carry on
|
||||||
|
|
|
@ -14,37 +14,72 @@ impl Document {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
stm: &Statement<'_>,
|
stm: &Statement<'_>,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
// Check whether current record exists
|
// On the first iteration, this will always be
|
||||||
match self.current.doc.as_ref().is_some() {
|
// false, as we do not first attempt to fetch the
|
||||||
// We attempted to INSERT a document with an ID,
|
// record from the storage engine. After attempting
|
||||||
// and this ID already exists in the database,
|
// to create the record, if the record already exists
|
||||||
// so we need to update the record instead.
|
// then we will fetch the record from the storage
|
||||||
true => self.insert_update(stk, ctx, opt, stm).await,
|
// engine, and will update the record subsequently
|
||||||
// We attempted to INSERT a document with an ID,
|
match self.extras.is_insert_initial() {
|
||||||
// which does not exist in the database, or we
|
// We haven't yet checked if the record exists
|
||||||
// are creating a new record with a new ID.
|
// so let's assume that the record does not exist
|
||||||
false => {
|
// and attempt to create the record in the database
|
||||||
// First of all let's try to create the record
|
true => match self.insert_create(stk, ctx, opt, stm).await {
|
||||||
match self.insert_create(stk, ctx, opt, stm).await {
|
// We received an index exists error, so we
|
||||||
// We received an index exists error, so we
|
// ignore the error, and attempt to update the
|
||||||
// ignore the error, and attempt to update the
|
// record using the ON DUPLICATE KEY UPDATE
|
||||||
// record using the ON DUPLICATE KEY clause
|
// clause with the ID received in the error
|
||||||
// with the Record ID received in the error
|
Err(Error::IndexExists {
|
||||||
Err(Error::IndexExists {
|
thing,
|
||||||
|
index,
|
||||||
|
value,
|
||||||
|
}) => match stm.is_retryable() {
|
||||||
|
// There is an ON DUPLICATE KEY UPDATE clause
|
||||||
|
true => match self.extras.is_insert_with_specific_id() {
|
||||||
|
// No specific Record ID has been specified, so retry
|
||||||
|
false => Err(Error::RetryWithId(thing)),
|
||||||
|
// A specific Record ID was specified, so error
|
||||||
|
true => Err(Error::IndexExists {
|
||||||
|
thing,
|
||||||
|
index,
|
||||||
|
value,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
// There is no ON DUPLICATE KEY UPDATE clause
|
||||||
|
false => Err(Error::IndexExists {
|
||||||
thing,
|
thing,
|
||||||
..
|
index,
|
||||||
}) => Err(Error::RetryWithId(thing)),
|
value,
|
||||||
// If any other error was received, then let's
|
}),
|
||||||
// pass that error through and return an error
|
},
|
||||||
Err(e) => Err(e),
|
// We attempted to INSERT a document with an ID,
|
||||||
// Otherwise the record creation succeeded
|
// and this ID already exists in the database,
|
||||||
Ok(v) => Ok(v),
|
// so we need to update the record instead using
|
||||||
}
|
// the ON DUPLICATE KEY UPDATE statement clause
|
||||||
}
|
Err(Error::RecordExists {
|
||||||
|
thing,
|
||||||
|
}) => match stm.is_retryable() {
|
||||||
|
// There is an ON DUPLICATE KEY UPDATE clause
|
||||||
|
true => Err(Error::RetryWithId(thing)),
|
||||||
|
// There is no ON DUPLICATE KEY UPDATE clause
|
||||||
|
false => Err(Error::RecordExists {
|
||||||
|
thing,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
// If any other error was received, then let's
|
||||||
|
// pass that error through and return an error
|
||||||
|
Err(e) => Err(e),
|
||||||
|
// Otherwise the record creation succeeded
|
||||||
|
Ok(v) => Ok(v),
|
||||||
|
},
|
||||||
|
// If we first attempted to create the record,
|
||||||
|
// but the record existed already, then we will
|
||||||
|
// fetch the record from the storage engine,
|
||||||
|
// and will update the record subsequently
|
||||||
|
false => self.insert_update(stk, ctx, opt, stm).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Attempt to run an INSERT clause
|
/// Attempt to run an INSERT clause
|
||||||
#[inline(always)]
|
|
||||||
async fn insert_create(
|
async fn insert_create(
|
||||||
&mut self,
|
&mut self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
|
@ -52,37 +87,24 @@ impl Document {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
stm: &Statement<'_>,
|
stm: &Statement<'_>,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
// Check if table has correct relation status
|
self.check_permissions_quick(stk, ctx, opt, stm).await?;
|
||||||
self.relation(ctx, opt, stm).await?;
|
self.check_table_type(ctx, opt, stm).await?;
|
||||||
// Merge record data
|
self.check_data_fields(stk, ctx, opt, stm).await?;
|
||||||
self.merge(stk, ctx, opt, stm).await?;
|
self.process_merge_data(stk, ctx, opt, stm).await?;
|
||||||
// Store record edges
|
self.store_edges_data(ctx, opt, stm).await?;
|
||||||
self.edges(ctx, opt, stm).await?;
|
self.process_table_fields(stk, ctx, opt, stm).await?;
|
||||||
// Merge fields data
|
self.cleanup_table_fields(stk, ctx, opt, stm).await?;
|
||||||
self.field(stk, ctx, opt, stm).await?;
|
self.default_record_data(ctx, opt, stm).await?;
|
||||||
// Reset fields data
|
self.check_permissions_table(stk, ctx, opt, stm).await?;
|
||||||
self.reset(ctx, opt, stm).await?;
|
self.store_record_data(ctx, opt, stm).await?;
|
||||||
// Clean fields data
|
self.store_index_data(stk, ctx, opt, stm).await?;
|
||||||
self.clean(stk, ctx, opt, stm).await?;
|
self.process_table_views(stk, ctx, opt, stm).await?;
|
||||||
// Check if allowed
|
self.process_table_lives(stk, ctx, opt, stm).await?;
|
||||||
self.allow(stk, ctx, opt, stm).await?;
|
self.process_table_events(stk, ctx, opt, stm).await?;
|
||||||
// Store index data
|
self.process_changefeeds(ctx, opt, stm).await?;
|
||||||
self.index(stk, ctx, opt, stm).await?;
|
|
||||||
// Store record data
|
|
||||||
self.store(ctx, opt, stm).await?;
|
|
||||||
// Run table queries
|
|
||||||
self.table(stk, ctx, opt, stm).await?;
|
|
||||||
// Run lives queries
|
|
||||||
self.lives(stk, ctx, opt, stm).await?;
|
|
||||||
// Run change feeds queries
|
|
||||||
self.changefeeds(ctx, opt, stm).await?;
|
|
||||||
// Run event queries
|
|
||||||
self.event(stk, ctx, opt, stm).await?;
|
|
||||||
// Yield document
|
|
||||||
self.pluck(stk, ctx, opt, stm).await
|
self.pluck(stk, ctx, opt, stm).await
|
||||||
}
|
}
|
||||||
// Attempt to run an UPDATE clause
|
/// Attempt to run an UPDATE clause
|
||||||
#[inline(always)]
|
|
||||||
async fn insert_update(
|
async fn insert_update(
|
||||||
&mut self,
|
&mut self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
|
@ -90,31 +112,21 @@ impl Document {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
stm: &Statement<'_>,
|
stm: &Statement<'_>,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
// Check if allowed
|
self.check_permissions_quick(stk, ctx, opt, stm).await?;
|
||||||
self.allow(stk, ctx, opt, stm).await?;
|
self.check_table_type(ctx, opt, stm).await?;
|
||||||
// Alter record data
|
self.check_data_fields(stk, ctx, opt, stm).await?;
|
||||||
self.alter(stk, ctx, opt, stm).await?;
|
self.check_permissions_table(stk, ctx, opt, stm).await?;
|
||||||
// Merge fields data
|
self.process_record_data(stk, ctx, opt, stm).await?;
|
||||||
self.field(stk, ctx, opt, stm).await?;
|
self.process_table_fields(stk, ctx, opt, stm).await?;
|
||||||
// Reset fields data
|
self.cleanup_table_fields(stk, ctx, opt, stm).await?;
|
||||||
self.reset(ctx, opt, stm).await?;
|
self.default_record_data(ctx, opt, stm).await?;
|
||||||
// Clean fields data
|
self.check_permissions_table(stk, ctx, opt, stm).await?;
|
||||||
self.clean(stk, ctx, opt, stm).await?;
|
self.store_record_data(ctx, opt, stm).await?;
|
||||||
// Check if allowed
|
self.store_index_data(stk, ctx, opt, stm).await?;
|
||||||
self.allow(stk, ctx, opt, stm).await?;
|
self.process_table_views(stk, ctx, opt, stm).await?;
|
||||||
// Store index data
|
self.process_table_lives(stk, ctx, opt, stm).await?;
|
||||||
self.index(stk, ctx, opt, stm).await?;
|
self.process_table_events(stk, ctx, opt, stm).await?;
|
||||||
// Store record data
|
self.process_changefeeds(ctx, opt, stm).await?;
|
||||||
self.store(ctx, opt, stm).await?;
|
|
||||||
// Run table queries
|
|
||||||
self.table(stk, ctx, opt, stm).await?;
|
|
||||||
// Run lives queries
|
|
||||||
self.lives(stk, ctx, opt, stm).await?;
|
|
||||||
// Run change feeds queries
|
|
||||||
self.changefeeds(ctx, opt, stm).await?;
|
|
||||||
// Run event queries
|
|
||||||
self.event(stk, ctx, opt, stm).await?;
|
|
||||||
// Yield document
|
|
||||||
self.pluck(stk, ctx, opt, stm).await
|
self.pluck(stk, ctx, opt, stm).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,12 @@ use reblessive::tree::Stk;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
impl Document {
|
impl Document {
|
||||||
pub async fn lives(
|
/// Processes any LIVE SELECT statements which
|
||||||
|
/// have been defined for the table which this
|
||||||
|
/// record belongs to. This functions loops
|
||||||
|
/// through the live queries and processes them
|
||||||
|
/// all within the currently running transaction.
|
||||||
|
pub async fn process_table_lives(
|
||||||
&mut self,
|
&mut self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
use crate::ctx::Context;
|
|
||||||
use crate::dbs::Options;
|
|
||||||
use crate::dbs::Statement;
|
|
||||||
use crate::dbs::Workable;
|
|
||||||
use crate::doc::Document;
|
|
||||||
use crate::err::Error;
|
|
||||||
use reblessive::tree::Stk;
|
|
||||||
|
|
||||||
impl Document {
|
|
||||||
pub async fn merge(
|
|
||||||
&mut self,
|
|
||||||
stk: &mut Stk,
|
|
||||||
ctx: &Context,
|
|
||||||
opt: &Options,
|
|
||||||
_stm: &Statement<'_>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
// Get the record id
|
|
||||||
let rid = self.id.as_ref().unwrap();
|
|
||||||
// Set default field values
|
|
||||||
self.current.doc.to_mut().def(rid);
|
|
||||||
// This is an INSERT statement
|
|
||||||
if let Workable::Insert(v) = &self.extras {
|
|
||||||
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
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,22 +20,15 @@ mod select; // Processes a SELECT statement for this document
|
||||||
mod update; // Processes a UPDATE statement for this document
|
mod update; // Processes a UPDATE statement for this document
|
||||||
mod upsert; // Processes a UPSERT statement for this document
|
mod upsert; // Processes a UPSERT statement for this document
|
||||||
|
|
||||||
mod allow; // Checks whether the query can access this document
|
|
||||||
mod alter; // Modifies and updates the fields in this document
|
mod alter; // Modifies and updates the fields in this document
|
||||||
mod changefeeds; // Processes any change feeds relevant for this document
|
mod changefeeds; // Processes any change feeds relevant for this document
|
||||||
mod check; // Checks whether the WHERE clauses matches this document
|
mod check; // Checks whether the WHERE clauses matches this document
|
||||||
mod clean; // Ensures records adhere to the table schema
|
|
||||||
mod edges; // Attempts to store the edge data for this document
|
mod edges; // Attempts to store the edge data for this document
|
||||||
mod empty; // Checks whether the specified document actually exists
|
|
||||||
mod erase; // Removes all content and field data for this document
|
|
||||||
mod event; // Processes any table events relevant for this document
|
mod event; // Processes any table events relevant for this document
|
||||||
mod field; // Processes any schema-defined fields for this document
|
mod field; // Processes any schema-defined fields for this document
|
||||||
mod index; // Attempts to store the index data for this document
|
mod index; // Attempts to store the index data for this document
|
||||||
mod lives; // Processes any live queries relevant for this document
|
mod lives; // Processes any live queries relevant for this document
|
||||||
mod merge; // Merges any field changes for an INSERT statement
|
|
||||||
mod pluck; // Pulls the projected expressions from the document
|
mod pluck; // Pulls the projected expressions from the document
|
||||||
mod purge; // Deletes this document, and any edges or indexes
|
mod purge; // Deletes this document, and any edges or indexes
|
||||||
mod relation; // Checks whether the record is the right kind for the table
|
|
||||||
mod reset; // Resets internal fields which were set for this document
|
|
||||||
mod store; // Writes the document content to the storage engine
|
mod store; // Writes the document content to the storage engine
|
||||||
mod table; // Processes any foreign tables relevant for this document
|
mod table; // Processes any foreign tables relevant for this document
|
||||||
|
|
|
@ -23,13 +23,13 @@ impl Document {
|
||||||
// Setup a new workable
|
// Setup a new workable
|
||||||
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, u) => (v, Workable::Insert(o, u)),
|
||||||
Operable::Relatable(f, v, w, o) => (v, Workable::Relate(f, w, o)),
|
Operable::Relatable(f, v, w, o, u) => (v, Workable::Relate(f, w, o, u)),
|
||||||
};
|
};
|
||||||
// Setup a new document
|
// Setup a new document
|
||||||
let mut doc = Document::new(pro.rid, pro.ir, ins.0, ins.1);
|
let mut doc = Document::new(pro.rid, pro.ir, ins.0, ins.1);
|
||||||
// Optionally create a save point so we can roll back any upcoming changes
|
// Optionally create a save point so we can roll back any upcoming changes
|
||||||
let is_save_point = if !stm.is_select() {
|
let is_save_point = if stm.is_retryable() {
|
||||||
ctx.tx().lock().await.new_save_point().await;
|
ctx.tx().lock().await.new_save_point().await;
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
@ -69,16 +69,16 @@ impl Document {
|
||||||
ir: None,
|
ir: None,
|
||||||
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, true),
|
||||||
Workable::Relate(f, w, o) => Operable::Relatable(f, val, w, o),
|
Workable::Relate(f, w, o, _) => Operable::Relatable(f, val, w, o, true),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// Go to top of loop
|
// Go to top of loop
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// This record didn't match conditions, so skip
|
||||||
Err(Error::Ignore) => Err(Error::Ignore),
|
Err(Error::Ignore) => Err(Error::Ignore),
|
||||||
// If any other error was received, then let's
|
// Pass other errors through and return the error
|
||||||
// pass that error through and return an error
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// We roll back any change following the save point
|
// We roll back any change following the save point
|
||||||
if is_save_point {
|
if is_save_point {
|
||||||
|
|
|
@ -15,7 +15,7 @@ impl Document {
|
||||||
stm: &Statement<'_>,
|
stm: &Statement<'_>,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
// Check if table has correct relation status
|
// Check if table has correct relation status
|
||||||
self.relation(ctx, opt, stm).await?;
|
self.check_table_type(ctx, opt, stm).await?;
|
||||||
// Check whether current record exists
|
// Check whether current record exists
|
||||||
match self.current.doc.as_ref().is_some() {
|
match self.current.doc.as_ref().is_some() {
|
||||||
// We attempted to RELATE a document with an ID,
|
// We attempted to RELATE a document with an ID,
|
||||||
|
@ -28,8 +28,7 @@ impl Document {
|
||||||
false => self.relate_create(stk, ctx, opt, stm).await,
|
false => self.relate_create(stk, ctx, opt, stm).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Attempt to run an INSERT clause
|
/// Attempt to run a RELATE clause
|
||||||
#[inline(always)]
|
|
||||||
async fn relate_create(
|
async fn relate_create(
|
||||||
&mut self,
|
&mut self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
|
@ -37,35 +36,24 @@ impl Document {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
stm: &Statement<'_>,
|
stm: &Statement<'_>,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
// Alter record data
|
self.check_permissions_quick(stk, ctx, opt, stm).await?;
|
||||||
self.alter(stk, ctx, opt, stm).await?;
|
self.check_table_type(ctx, opt, stm).await?;
|
||||||
// Store record edges
|
self.check_data_fields(stk, ctx, opt, stm).await?;
|
||||||
self.edges(ctx, opt, stm).await?;
|
self.process_record_data(stk, ctx, opt, stm).await?;
|
||||||
// Merge fields data
|
self.store_edges_data(ctx, opt, stm).await?;
|
||||||
self.field(stk, ctx, opt, stm).await?;
|
self.process_table_fields(stk, ctx, opt, stm).await?;
|
||||||
// Reset fields data
|
self.cleanup_table_fields(stk, ctx, opt, stm).await?;
|
||||||
self.reset(ctx, opt, stm).await?;
|
self.default_record_data(ctx, opt, stm).await?;
|
||||||
// Clean fields data
|
self.check_permissions_table(stk, ctx, opt, stm).await?;
|
||||||
self.clean(stk, ctx, opt, stm).await?;
|
self.store_record_data(ctx, opt, stm).await?;
|
||||||
// Check if allowed
|
self.store_index_data(stk, ctx, opt, stm).await?;
|
||||||
self.allow(stk, ctx, opt, stm).await?;
|
self.process_table_views(stk, ctx, opt, stm).await?;
|
||||||
// Store record data
|
self.process_table_lives(stk, ctx, opt, stm).await?;
|
||||||
self.store(ctx, opt, stm).await?;
|
self.process_changefeeds(ctx, opt, stm).await?;
|
||||||
// Store index data
|
self.process_table_events(stk, ctx, opt, stm).await?;
|
||||||
self.index(stk, ctx, opt, stm).await?;
|
|
||||||
// Run table queries
|
|
||||||
self.table(stk, ctx, opt, stm).await?;
|
|
||||||
// Run lives queries
|
|
||||||
self.lives(stk, ctx, opt, stm).await?;
|
|
||||||
// Run change feeds queries
|
|
||||||
self.changefeeds(ctx, opt, stm).await?;
|
|
||||||
// Run event queries
|
|
||||||
self.event(stk, ctx, opt, stm).await?;
|
|
||||||
// Yield document
|
|
||||||
self.pluck(stk, ctx, opt, stm).await
|
self.pluck(stk, ctx, opt, stm).await
|
||||||
}
|
}
|
||||||
// Attempt to run an UPDATE clause
|
/// Attempt to run an UPDATE clause
|
||||||
#[inline(always)]
|
|
||||||
async fn relate_update(
|
async fn relate_update(
|
||||||
&mut self,
|
&mut self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
|
@ -73,33 +61,22 @@ impl Document {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
stm: &Statement<'_>,
|
stm: &Statement<'_>,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
// Check if allowed
|
self.check_permissions_quick(stk, ctx, opt, stm).await?;
|
||||||
self.allow(stk, ctx, opt, stm).await?;
|
self.check_table_type(ctx, opt, stm).await?;
|
||||||
// Store record edges
|
self.check_data_fields(stk, ctx, opt, stm).await?;
|
||||||
self.edges(ctx, opt, stm).await?;
|
self.check_permissions_table(stk, ctx, opt, stm).await?;
|
||||||
// Alter record data
|
self.store_edges_data(ctx, opt, stm).await?;
|
||||||
self.alter(stk, ctx, opt, stm).await?;
|
self.process_record_data(stk, ctx, opt, stm).await?;
|
||||||
// Merge fields data
|
self.process_table_fields(stk, ctx, opt, stm).await?;
|
||||||
self.field(stk, ctx, opt, stm).await?;
|
self.cleanup_table_fields(stk, ctx, opt, stm).await?;
|
||||||
// Reset fields data
|
self.default_record_data(ctx, opt, stm).await?;
|
||||||
self.reset(ctx, opt, stm).await?;
|
self.check_permissions_table(stk, ctx, opt, stm).await?;
|
||||||
// Clean fields data
|
self.store_record_data(ctx, opt, stm).await?;
|
||||||
self.clean(stk, ctx, opt, stm).await?;
|
self.store_index_data(stk, ctx, opt, stm).await?;
|
||||||
// Check if allowed
|
self.process_table_views(stk, ctx, opt, stm).await?;
|
||||||
self.allow(stk, ctx, opt, stm).await?;
|
self.process_table_lives(stk, ctx, opt, stm).await?;
|
||||||
// Store record data
|
self.process_table_events(stk, ctx, opt, stm).await?;
|
||||||
self.store(ctx, opt, stm).await?;
|
self.process_changefeeds(ctx, opt, stm).await?;
|
||||||
// Store index data
|
|
||||||
self.index(stk, ctx, opt, stm).await?;
|
|
||||||
// Run table queries
|
|
||||||
self.table(stk, ctx, opt, stm).await?;
|
|
||||||
// Run lives queries
|
|
||||||
self.lives(stk, ctx, opt, stm).await?;
|
|
||||||
// Run change feeds queries
|
|
||||||
self.changefeeds(ctx, opt, stm).await?;
|
|
||||||
// Run event queries
|
|
||||||
self.event(stk, ctx, opt, stm).await?;
|
|
||||||
// Yield document
|
|
||||||
self.pluck(stk, ctx, opt, stm).await
|
self.pluck(stk, ctx, opt, stm).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
use crate::ctx::Context;
|
|
||||||
use crate::dbs::Options;
|
|
||||||
use crate::dbs::{Statement, Workable};
|
|
||||||
use crate::doc::Document;
|
|
||||||
use crate::err::Error;
|
|
||||||
|
|
||||||
impl Document {
|
|
||||||
pub async fn relation(
|
|
||||||
&mut self,
|
|
||||||
ctx: &Context,
|
|
||||||
opt: &Options,
|
|
||||||
stm: &Statement<'_>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let tb = self.tb(ctx, opt).await?;
|
|
||||||
|
|
||||||
let rid = self.id.as_ref().unwrap();
|
|
||||||
match stm {
|
|
||||||
Statement::Create(_) => {
|
|
||||||
if !tb.allows_normal() {
|
|
||||||
return Err(Error::TableCheck {
|
|
||||||
thing: rid.to_string(),
|
|
||||||
relation: false,
|
|
||||||
target_type: tb.kind.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Statement::Upsert(_) => {
|
|
||||||
if !tb.allows_normal() {
|
|
||||||
return Err(Error::TableCheck {
|
|
||||||
thing: rid.to_string(),
|
|
||||||
relation: false,
|
|
||||||
target_type: tb.kind.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Statement::Relate(_) => {
|
|
||||||
if !tb.allows_relation() {
|
|
||||||
return Err(Error::TableCheck {
|
|
||||||
thing: rid.to_string(),
|
|
||||||
relation: true,
|
|
||||||
target_type: tb.kind.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
if !tb.allows_normal() {
|
|
||||||
return Err(Error::TableCheck {
|
|
||||||
thing: rid.to_string(),
|
|
||||||
relation: false,
|
|
||||||
target_type: tb.kind.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
// Carry on
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
use crate::ctx::Context;
|
|
||||||
use crate::dbs::Options;
|
|
||||||
use crate::dbs::Statement;
|
|
||||||
use crate::dbs::Workable;
|
|
||||||
use crate::doc::Document;
|
|
||||||
use crate::err::Error;
|
|
||||||
use crate::sql::paths::EDGE;
|
|
||||||
use crate::sql::paths::IN;
|
|
||||||
use crate::sql::paths::OUT;
|
|
||||||
use crate::sql::value::Value;
|
|
||||||
|
|
||||||
impl Document {
|
|
||||||
pub async fn reset(
|
|
||||||
&mut self,
|
|
||||||
_ctx: &Context,
|
|
||||||
_opt: &Options,
|
|
||||||
_stm: &Statement<'_>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
// Get the record id
|
|
||||||
let rid = self.id.as_ref().unwrap();
|
|
||||||
// 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 {
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
// This is an UPDATE of a graph edge, so reset fields
|
|
||||||
if self.initial.doc.as_ref().pick(&*EDGE).is_true() {
|
|
||||||
self.current.doc.to_mut().put(&*EDGE, Value::Bool(true));
|
|
||||||
self.current.doc.to_mut().put(&*IN, self.initial.doc.as_ref().pick(&*IN));
|
|
||||||
self.current.doc.to_mut().put(&*OUT, self.initial.doc.as_ref().pick(&*OUT));
|
|
||||||
}
|
|
||||||
// Carry on
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,13 +14,10 @@ impl Document {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
stm: &Statement<'_>,
|
stm: &Statement<'_>,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
// Check if record exists
|
self.check_record_exists(ctx, opt, stm).await?;
|
||||||
self.empty(ctx, opt, stm).await?;
|
self.check_permissions_quick(stk, ctx, opt, stm).await?;
|
||||||
// Check where clause
|
self.check_where_condition(stk, ctx, opt, stm).await?;
|
||||||
self.check(stk, ctx, opt, stm).await?;
|
self.check_permissions_table(stk, ctx, opt, stm).await?;
|
||||||
// Check if allowed
|
|
||||||
self.allow(stk, ctx, opt, stm).await?;
|
|
||||||
// Yield document
|
|
||||||
self.pluck(stk, ctx, opt, stm).await
|
self.pluck(stk, ctx, opt, stm).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::doc::Document;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
|
|
||||||
impl Document {
|
impl Document {
|
||||||
pub async fn store(
|
pub async fn store_record_data(
|
||||||
&self,
|
&self,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
|
@ -22,25 +22,69 @@ impl Document {
|
||||||
// Get the transaction
|
// Get the transaction
|
||||||
let txn = ctx.tx();
|
let txn = ctx.tx();
|
||||||
// Get the record id
|
// Get the record id
|
||||||
let rid = self.id.as_ref().unwrap();
|
let rid = self.id()?;
|
||||||
// Store the record data
|
// Store the record data
|
||||||
let key = crate::key::thing::new(opt.ns()?, opt.db()?, &rid.tb, &rid.id);
|
let key = crate::key::thing::new(opt.ns()?, opt.db()?, &rid.tb, &rid.id);
|
||||||
// Match the statement type
|
// Match the statement type
|
||||||
match stm {
|
match stm {
|
||||||
// This is a CREATE statement so try to insert the key
|
// This is a INSERT statement so try to insert the key.
|
||||||
Statement::Create(_) => match txn.put(key, self, opt.version).await {
|
// For INSERT statements we don't first check for the
|
||||||
// The key already exists, so return an error
|
// entry from the storage engine, so when we attempt
|
||||||
Err(Error::TxKeyAlreadyExists) => Err(Error::RecordExists {
|
// to store the record value, we presume that the key
|
||||||
thing: rid.to_string(),
|
// does not exist. If the record value exists then we
|
||||||
}),
|
// attempt to run the ON DUPLICATE KEY UPDATE clause but
|
||||||
// Return any other received error
|
// at this point the current document is not empty so we
|
||||||
Err(e) => Err(e),
|
// set and update the key, without checking if the key
|
||||||
// Record creation worked fine
|
// already exists in the storage engine.
|
||||||
Ok(v) => Ok(v),
|
Statement::Insert(_) if self.extras.is_insert_initial() => {
|
||||||
},
|
match txn.put(key, self, opt.version).await {
|
||||||
// INSERT can be versioned
|
// The key already exists, so return an error
|
||||||
Statement::Insert(_) => txn.set(key, self, opt.version).await,
|
Err(Error::TxKeyAlreadyExists) => Err(Error::RecordExists {
|
||||||
// This is not a CREATE statement, so update the key
|
thing: rid.as_ref().to_owned(),
|
||||||
|
}),
|
||||||
|
// Return any other received error
|
||||||
|
Err(e) => Err(e),
|
||||||
|
// Record creation worked fine
|
||||||
|
Ok(v) => Ok(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This is a UPSERT statement so try to insert the key.
|
||||||
|
// For UPSERT statements we don't first check for the
|
||||||
|
// entry from the storage engine, so when we attempt
|
||||||
|
// to store the record value, we must ensure that the
|
||||||
|
// key does not exist. If the record value exists then we
|
||||||
|
// retry and attempt to update the record which exists.
|
||||||
|
Statement::Upsert(_) if self.is_new() => {
|
||||||
|
match txn.put(key, self, opt.version).await {
|
||||||
|
// The key already exists, so return an error
|
||||||
|
Err(Error::TxKeyAlreadyExists) => Err(Error::RecordExists {
|
||||||
|
thing: rid.as_ref().to_owned(),
|
||||||
|
}),
|
||||||
|
// Return any other received error
|
||||||
|
Err(e) => Err(e),
|
||||||
|
// Record creation worked fine
|
||||||
|
Ok(v) => Ok(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This is a CREATE statement so try to insert the key.
|
||||||
|
// For CREATE statements we don't first check for the
|
||||||
|
// entry from the storage engine, so when we attempt
|
||||||
|
// to store the record value, we must ensure that the
|
||||||
|
// key does not exist. If it already exists, then we
|
||||||
|
// return an error, and the statement fails.
|
||||||
|
Statement::Create(_) => {
|
||||||
|
match txn.put(key, self, opt.version).await {
|
||||||
|
// The key already exists, so return an error
|
||||||
|
Err(Error::TxKeyAlreadyExists) => Err(Error::RecordExists {
|
||||||
|
thing: rid.as_ref().to_owned(),
|
||||||
|
}),
|
||||||
|
// Return any other received error
|
||||||
|
Err(e) => Err(e),
|
||||||
|
// Record creation worked fine
|
||||||
|
Ok(v) => Ok(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Let's update the stored value for the specified key
|
||||||
_ => txn.set(key, self, None).await,
|
_ => txn.set(key, self, None).await,
|
||||||
}?;
|
}?;
|
||||||
// Carry on
|
// Carry on
|
||||||
|
|
|
@ -47,7 +47,12 @@ struct FieldDataContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Document {
|
impl Document {
|
||||||
pub async fn table(
|
/// Processes any DEFINE TABLE AS clauses which
|
||||||
|
/// have been defined for the table which this
|
||||||
|
/// record belongs to. This functions loops
|
||||||
|
/// through the tables and processes them all
|
||||||
|
/// within the currently running transaction.
|
||||||
|
pub async fn process_table_views(
|
||||||
&self,
|
&self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
|
@ -78,7 +83,7 @@ impl Document {
|
||||||
// Don't run permissions
|
// Don't run permissions
|
||||||
let opt = &opt.new_with_perms(false);
|
let opt = &opt.new_with_perms(false);
|
||||||
// Get the record id
|
// Get the record id
|
||||||
let rid = self.id.as_ref().unwrap();
|
let rid = self.id()?;
|
||||||
// Get the query action
|
// Get the query action
|
||||||
let act = if stm.is_delete() {
|
let act = if stm.is_delete() {
|
||||||
Action::Delete
|
Action::Delete
|
||||||
|
|
|
@ -14,35 +14,22 @@ impl Document {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
stm: &Statement<'_>,
|
stm: &Statement<'_>,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
// Check if record exists
|
self.check_record_exists(ctx, opt, stm).await?;
|
||||||
self.empty(ctx, opt, stm).await?;
|
self.check_permissions_quick(stk, ctx, opt, stm).await?;
|
||||||
// Check where clause
|
self.check_data_fields(stk, ctx, opt, stm).await?;
|
||||||
self.check(stk, ctx, opt, stm).await?;
|
self.check_where_condition(stk, ctx, opt, stm).await?;
|
||||||
// Check if allowed
|
self.check_permissions_table(stk, ctx, opt, stm).await?;
|
||||||
self.allow(stk, ctx, opt, stm).await?;
|
self.process_record_data(stk, ctx, opt, stm).await?;
|
||||||
// Alter record data
|
self.process_table_fields(stk, ctx, opt, stm).await?;
|
||||||
self.alter(stk, ctx, opt, stm).await?;
|
self.cleanup_table_fields(stk, ctx, opt, stm).await?;
|
||||||
// Merge fields data
|
self.default_record_data(ctx, opt, stm).await?;
|
||||||
self.field(stk, ctx, opt, stm).await?;
|
self.check_permissions_table(stk, ctx, opt, stm).await?;
|
||||||
// Reset fields data
|
self.store_record_data(ctx, opt, stm).await?;
|
||||||
self.reset(ctx, opt, stm).await?;
|
self.store_index_data(stk, ctx, opt, stm).await?;
|
||||||
// Clean fields data
|
self.process_table_views(stk, ctx, opt, stm).await?;
|
||||||
self.clean(stk, ctx, opt, stm).await?;
|
self.process_table_lives(stk, ctx, opt, stm).await?;
|
||||||
// Check if allowed
|
self.process_table_events(stk, ctx, opt, stm).await?;
|
||||||
self.allow(stk, ctx, opt, stm).await?;
|
self.process_changefeeds(ctx, opt, stm).await?;
|
||||||
// Store record data
|
|
||||||
self.store(ctx, opt, stm).await?;
|
|
||||||
// Store index data
|
|
||||||
self.index(stk, ctx, opt, stm).await?;
|
|
||||||
// Run table queries
|
|
||||||
self.table(stk, ctx, opt, stm).await?;
|
|
||||||
// Run lives queries
|
|
||||||
self.lives(stk, ctx, opt, stm).await?;
|
|
||||||
// Run change feeds queries
|
|
||||||
self.changefeeds(ctx, opt, stm).await?;
|
|
||||||
// Run event queries
|
|
||||||
self.event(stk, ctx, opt, stm).await?;
|
|
||||||
// Yield document
|
|
||||||
self.pluck(stk, ctx, opt, stm).await
|
self.pluck(stk, ctx, opt, stm).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,33 +14,44 @@ impl Document {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
stm: &Statement<'_>,
|
stm: &Statement<'_>,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
// Check where clause
|
match self.upsert_process(stk, ctx, opt, stm).await {
|
||||||
self.check(stk, ctx, opt, stm).await?;
|
// We attempted to INSERT a document with an ID,
|
||||||
// Check if allowed
|
// and this ID already exists in the database,
|
||||||
self.allow(stk, ctx, opt, stm).await?;
|
// so we need to UPDATE the record instead.
|
||||||
// Alter record data
|
Err(Error::RecordExists {
|
||||||
self.alter(stk, ctx, opt, stm).await?;
|
thing,
|
||||||
// Merge fields data
|
}) => Err(Error::RetryWithId(thing)),
|
||||||
self.field(stk, ctx, opt, stm).await?;
|
// If any other error was received, then let's
|
||||||
// Reset fields data
|
// pass that error through and return an error
|
||||||
self.reset(ctx, opt, stm).await?;
|
Err(e) => Err(e),
|
||||||
// Clean fields data
|
// Otherwise the record creation succeeded
|
||||||
self.clean(stk, ctx, opt, stm).await?;
|
Ok(v) => Ok(v),
|
||||||
// Check if allowed
|
}
|
||||||
self.allow(stk, ctx, opt, stm).await?;
|
}
|
||||||
// Store record data
|
/// Attempt to run an UPSERT clause
|
||||||
self.store(ctx, opt, stm).await?;
|
async fn upsert_process(
|
||||||
// Store index data
|
&mut self,
|
||||||
self.index(stk, ctx, opt, stm).await?;
|
stk: &mut Stk,
|
||||||
// Run table queries
|
ctx: &Context,
|
||||||
self.table(stk, ctx, opt, stm).await?;
|
opt: &Options,
|
||||||
// Run lives queries
|
stm: &Statement<'_>,
|
||||||
self.lives(stk, ctx, opt, stm).await?;
|
) -> Result<Value, Error> {
|
||||||
// Run change feeds queries
|
self.check_permissions_quick(stk, ctx, opt, stm).await?;
|
||||||
self.changefeeds(ctx, opt, stm).await?;
|
self.check_table_type(ctx, opt, stm).await?;
|
||||||
// Run event queries
|
self.check_data_fields(stk, ctx, opt, stm).await?;
|
||||||
self.event(stk, ctx, opt, stm).await?;
|
self.check_where_condition(stk, ctx, opt, stm).await?;
|
||||||
// Yield document
|
self.check_permissions_table(stk, ctx, opt, stm).await?;
|
||||||
|
self.process_record_data(stk, ctx, opt, stm).await?;
|
||||||
|
self.process_table_fields(stk, ctx, opt, stm).await?;
|
||||||
|
self.cleanup_table_fields(stk, ctx, opt, stm).await?;
|
||||||
|
self.default_record_data(ctx, opt, stm).await?;
|
||||||
|
self.check_permissions_table(stk, ctx, opt, stm).await?;
|
||||||
|
self.store_record_data(ctx, opt, stm).await?;
|
||||||
|
self.store_index_data(stk, ctx, opt, stm).await?;
|
||||||
|
self.process_table_views(stk, ctx, opt, stm).await?;
|
||||||
|
self.process_table_lives(stk, ctx, opt, stm).await?;
|
||||||
|
self.process_table_events(stk, ctx, opt, stm).await?;
|
||||||
|
self.process_changefeeds(ctx, opt, stm).await?;
|
||||||
self.pluck(stk, ctx, opt, stm).await
|
self.pluck(stk, ctx, opt, stm).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -353,12 +353,6 @@ pub enum Error {
|
||||||
value: String,
|
value: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
// The cluster node already exists
|
|
||||||
#[error("The node '{value}' already exists")]
|
|
||||||
ClAlreadyExists {
|
|
||||||
value: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
// The cluster node does not exist
|
// The cluster node does not exist
|
||||||
#[error("The node '{value}' does not exist")]
|
#[error("The node '{value}' does not exist")]
|
||||||
NdNotFound {
|
NdNotFound {
|
||||||
|
@ -566,7 +560,7 @@ pub enum Error {
|
||||||
/// A database entry for the specified record already exists
|
/// A database entry for the specified record already exists
|
||||||
#[error("Database record `{thing}` already exists")]
|
#[error("Database record `{thing}` already exists")]
|
||||||
RecordExists {
|
RecordExists {
|
||||||
thing: String,
|
thing: Thing,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// A database index entry for the specified record already exists
|
/// A database index entry for the specified record already exists
|
||||||
|
@ -620,10 +614,11 @@ pub enum Error {
|
||||||
field: Idiom,
|
field: Idiom,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Found a record id for the record but we are creating a specific record
|
/// The specified field on a SCHEMAFUL table was not defined
|
||||||
#[error("Found {value} for the id field, but a specific record has been specified")]
|
#[error("Found field '{field}', but no such field exists for table '{table}'")]
|
||||||
IdMismatch {
|
FieldUndefined {
|
||||||
value: String,
|
table: String,
|
||||||
|
field: Idiom,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Found a record id for the record but this is not a valid id
|
/// Found a record id for the record but this is not a valid id
|
||||||
|
@ -632,6 +627,36 @@ pub enum Error {
|
||||||
value: String,
|
value: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Found a record id for the record but we are creating a specific record
|
||||||
|
#[error("Found {value} for the `id` field, but a specific record has been specified")]
|
||||||
|
IdMismatch {
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Found a record id for the record but we are creating a specific record
|
||||||
|
#[error("Found {value} for the `in` field, but the value does not match the `in` record id")]
|
||||||
|
InMismatch {
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Found a record id for the record but we are creating a specific record
|
||||||
|
#[error("Found {value} for the `in` field, which does not match the existing field value")]
|
||||||
|
InOverride {
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Found a record id for the record but we are creating a specific record
|
||||||
|
#[error("Found {value} for the `out` field, but the value does not match the `out` record id")]
|
||||||
|
OutMismatch {
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Found a record id for the record but we are creating a specific record
|
||||||
|
#[error("Found {value} for the `out` field, which does not match the existing field value")]
|
||||||
|
OutOverride {
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
|
||||||
/// Unable to coerce to a value to another value
|
/// Unable to coerce to a value to another value
|
||||||
#[error("Expected a {into} but found {from}")]
|
#[error("Expected a {into} but found {from}")]
|
||||||
CoerceTo {
|
CoerceTo {
|
||||||
|
@ -847,6 +872,12 @@ pub enum Error {
|
||||||
#[error("The db is running without an available storage engine")]
|
#[error("The db is running without an available storage engine")]
|
||||||
MissingStorageEngine,
|
MissingStorageEngine,
|
||||||
|
|
||||||
|
// The cluster node already exists
|
||||||
|
#[error("The node '{value}' already exists")]
|
||||||
|
ClAlreadyExists {
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
|
||||||
/// The requested analyzer already exists
|
/// The requested analyzer already exists
|
||||||
#[error("The analyzer '{value}' already exists")]
|
#[error("The analyzer '{value}' already exists")]
|
||||||
AzAlreadyExists {
|
AzAlreadyExists {
|
||||||
|
|
|
@ -27,7 +27,7 @@ impl SavedValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "kv-surrealkv", feature = "kv-fdb", feature = "kv-tikv"))]
|
#[cfg(any(feature = "kv-fdb", feature = "kv-tikv"))]
|
||||||
pub(super) fn get_val(&self) -> Option<&Val> {
|
pub(super) fn get_val(&self) -> Option<&Val> {
|
||||||
self.saved_val.as_ref()
|
self.saved_val.as_ref()
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@ impl<'a> Stream for Scanner<'a, (Key, Val)> {
|
||||||
}
|
}
|
||||||
// Get the last element of the results
|
// Get the last element of the results
|
||||||
let last = v.last().ok_or_else(|| {
|
let last = v.last().ok_or_else(|| {
|
||||||
Error::Unreachable("Last key/val can't be none".to_string())
|
fail!("Expected the last key-value pair to not be none")
|
||||||
})?;
|
})?;
|
||||||
// Start the next scan from the last result
|
// Start the next scan from the last result
|
||||||
self.range.start.clone_from(&last.0);
|
self.range.start.clone_from(&last.0);
|
||||||
|
@ -161,7 +161,7 @@ impl<'a> Stream for Scanner<'a, Key> {
|
||||||
}
|
}
|
||||||
// Get the last element of the results
|
// Get the last element of the results
|
||||||
let last = v.last().ok_or_else(|| {
|
let last = v.last().ok_or_else(|| {
|
||||||
Error::Unreachable("Last key can't be none".to_string())
|
fail!("Expected the last key-value pair to not be none")
|
||||||
})?;
|
})?;
|
||||||
// Start the next scan from the last result
|
// Start the next scan from the last result
|
||||||
self.range.start.clone_from(last);
|
self.range.start.clone_from(last);
|
||||||
|
|
|
@ -33,6 +33,10 @@ impl Cast {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cast {
|
impl Cast {
|
||||||
|
/// Check if we require a writeable transaction
|
||||||
|
pub(crate) fn writeable(&self) -> bool {
|
||||||
|
self.1.writeable()
|
||||||
|
}
|
||||||
/// Was marked recursively
|
/// Was marked recursively
|
||||||
pub(crate) async fn compute(
|
pub(crate) async fn compute(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -4,6 +4,8 @@ use crate::err::Error;
|
||||||
use crate::sql::fmt::Fmt;
|
use crate::sql::fmt::Fmt;
|
||||||
use crate::sql::idiom::Idiom;
|
use crate::sql::idiom::Idiom;
|
||||||
use crate::sql::operator::Operator;
|
use crate::sql::operator::Operator;
|
||||||
|
use crate::sql::part::Part;
|
||||||
|
use crate::sql::paths::ID;
|
||||||
use crate::sql::value::Value;
|
use crate::sql::value::Value;
|
||||||
use reblessive::tree::Stk;
|
use reblessive::tree::Stk;
|
||||||
use revision::revisioned;
|
use revision::revisioned;
|
||||||
|
@ -40,32 +42,42 @@ impl Data {
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
|
) -> Result<Option<Value>, Error> {
|
||||||
|
self.pick(stk, ctx, opt, &*ID).await
|
||||||
|
}
|
||||||
|
/// Fetch a field path value if one is specified
|
||||||
|
pub(crate) async fn pick(
|
||||||
|
&self,
|
||||||
|
stk: &mut Stk,
|
||||||
|
ctx: &Context,
|
||||||
|
opt: &Options,
|
||||||
|
path: &[Part],
|
||||||
) -> Result<Option<Value>, Error> {
|
) -> Result<Option<Value>, Error> {
|
||||||
match self {
|
match self {
|
||||||
Self::MergeExpression(v) => match v {
|
Self::MergeExpression(v) => match v {
|
||||||
Value::Param(v) => Ok(v.compute(stk, ctx, opt, None).await?.rid().some()),
|
Value::Param(v) => Ok(v.compute(stk, ctx, opt, None).await?.pick(path).some()),
|
||||||
Value::Object(_) => Ok(v.rid().compute(stk, ctx, opt, None).await?.some()),
|
Value::Object(_) => Ok(v.pick(path).compute(stk, ctx, opt, None).await?.some()),
|
||||||
_ => Ok(None),
|
_ => Ok(None),
|
||||||
},
|
},
|
||||||
Self::ReplaceExpression(v) => match v {
|
Self::ReplaceExpression(v) => match v {
|
||||||
Value::Param(v) => Ok(v.compute(stk, ctx, opt, None).await?.rid().some()),
|
Value::Param(v) => Ok(v.compute(stk, ctx, opt, None).await?.pick(path).some()),
|
||||||
Value::Object(_) => Ok(v.rid().compute(stk, ctx, opt, None).await?.some()),
|
Value::Object(_) => Ok(v.pick(path).compute(stk, ctx, opt, None).await?.some()),
|
||||||
_ => Ok(None),
|
_ => Ok(None),
|
||||||
},
|
},
|
||||||
Self::ContentExpression(v) => match v {
|
Self::ContentExpression(v) => match v {
|
||||||
Value::Param(v) => Ok(v.compute(stk, ctx, opt, None).await?.rid().some()),
|
Value::Param(v) => Ok(v.compute(stk, ctx, opt, None).await?.pick(path).some()),
|
||||||
Value::Object(_) => Ok(v.rid().compute(stk, ctx, opt, None).await?.some()),
|
Value::Object(_) => Ok(v.pick(path).compute(stk, ctx, opt, None).await?.some()),
|
||||||
_ => Ok(None),
|
_ => Ok(None),
|
||||||
},
|
},
|
||||||
Self::SetExpression(v) => match v.iter().find(|f| f.0.is_id()) {
|
Self::SetExpression(v) => match v.iter().find(|f| f.0.is_field(path)) {
|
||||||
Some((_, _, v)) => {
|
Some((_, _, v)) => {
|
||||||
// This SET expression has an 'id' field
|
// This SET expression has this field
|
||||||
Ok(v.compute(stk, ctx, opt, None).await?.some())
|
Ok(v.compute(stk, ctx, opt, None).await?.some())
|
||||||
}
|
}
|
||||||
// This SET expression had no 'id' field
|
// This SET expression does not have this field
|
||||||
_ => Ok(None),
|
_ => Ok(None),
|
||||||
},
|
},
|
||||||
// Generate a random id for all other data clauses
|
// Return nothing
|
||||||
_ => Ok(None),
|
_ => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,6 +192,18 @@ impl Id {
|
||||||
pub fn uuid() -> Self {
|
pub fn uuid() -> Self {
|
||||||
Self::Uuid(Uuid::new_v7())
|
Self::Uuid(Uuid::new_v7())
|
||||||
}
|
}
|
||||||
|
/// Check if this Id matches a value
|
||||||
|
pub fn is(&self, val: &Value) -> bool {
|
||||||
|
match (self, val) {
|
||||||
|
(Self::Number(i), Value::Number(Number::Int(j))) if *i == *j => true,
|
||||||
|
(Self::String(i), Value::Strand(j)) if *i == j.0 => true,
|
||||||
|
(Self::Uuid(i), Value::Uuid(j)) if i == j => true,
|
||||||
|
(Self::Array(i), Value::Array(j)) if i == j => true,
|
||||||
|
(Self::Object(i), Value::Object(j)) if i == j => true,
|
||||||
|
(i, Value::Thing(t)) if i == &t.id => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Convert the Id to a raw String
|
/// Convert the Id to a raw String
|
||||||
pub fn to_raw(&self) -> String {
|
pub fn to_raw(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -137,6 +137,10 @@ impl Idiom {
|
||||||
pub(crate) fn is_meta(&self) -> bool {
|
pub(crate) fn is_meta(&self) -> bool {
|
||||||
self.0.len() == 1 && self.0[0].eq(&META[0])
|
self.0.len() == 1 && self.0[0].eq(&META[0])
|
||||||
}
|
}
|
||||||
|
/// Check if this Idiom is an specific field
|
||||||
|
pub(crate) fn is_field(&self, other: &[Part]) -> bool {
|
||||||
|
self.as_ref().eq(other)
|
||||||
|
}
|
||||||
/// Check if this is an expression with multiple yields
|
/// Check if this is an expression with multiple yields
|
||||||
pub(crate) fn is_multi_yield(&self) -> bool {
|
pub(crate) fn is_multi_yield(&self) -> bool {
|
||||||
self.iter().any(Self::split_multi_yield)
|
self.iter().any(Self::split_multi_yield)
|
||||||
|
|
|
@ -47,12 +47,12 @@ impl Default for Kind {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Kind {
|
impl Kind {
|
||||||
// Returns true if this type is an `any`
|
/// Returns true if this type is an `any`
|
||||||
pub(crate) fn is_any(&self) -> bool {
|
pub(crate) fn is_any(&self) -> bool {
|
||||||
matches!(self, Kind::Any)
|
matches!(self, Kind::Any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if this type is a record
|
/// Returns true if this type is a record
|
||||||
pub(crate) fn is_record(&self) -> bool {
|
pub(crate) fn is_record(&self) -> bool {
|
||||||
matches!(self, Kind::Record(_))
|
matches!(self, Kind::Record(_))
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,7 @@ impl Kind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the kind of the contained value.
|
// Return the kind of the contained value.
|
||||||
//
|
//
|
||||||
// For example: for `array<number>` or `set<number>` this returns `number`.
|
// For example: for `array<number>` or `set<number>` this returns `number`.
|
||||||
// For `array<number> | set<float>` this returns `number | float`.
|
// For `array<number> | set<float>` this returns `number | float`.
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::cnf::REGEX_CACHE_SIZE;
|
||||||
use quick_cache::sync::{Cache, GuardResult};
|
use quick_cache::sync::{Cache, GuardResult};
|
||||||
use revision::revisioned;
|
use revision::revisioned;
|
||||||
use serde::{
|
use serde::{
|
||||||
|
@ -8,9 +9,9 @@ use std::cmp::Ordering;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::str;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
use std::{env, str};
|
|
||||||
|
|
||||||
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Regex";
|
pub(crate) const TOKEN: &str = "$surrealdb::private::sql::Regex";
|
||||||
|
|
||||||
|
@ -27,12 +28,8 @@ impl Regex {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn regex_new(str: &str) -> Result<regex::Regex, regex::Error> {
|
fn regex_new(str: &str) -> Result<regex::Regex, regex::Error> {
|
||||||
static REGEX_CACHE: LazyLock<Cache<String, regex::Regex>> = LazyLock::new(|| {
|
static REGEX_CACHE: LazyLock<Cache<String, regex::Regex>> =
|
||||||
let cache_size: usize = env::var("SURREAL_REGEX_CACHE_SIZE")
|
LazyLock::new(|| Cache::new(REGEX_CACHE_SIZE.max(10)));
|
||||||
.map_or(1000, |v| v.parse().unwrap_or(1000))
|
|
||||||
.max(10); // The minimum cache size is 10
|
|
||||||
Cache::new(cache_size)
|
|
||||||
});
|
|
||||||
match REGEX_CACHE.get_value_or_guard(str, None) {
|
match REGEX_CACHE.get_value_or_guard(str, None) {
|
||||||
GuardResult::Value(v) => Ok(v),
|
GuardResult::Value(v) => Ok(v),
|
||||||
GuardResult::Guard(g) => {
|
GuardResult::Guard(g) => {
|
||||||
|
|
|
@ -23,7 +23,6 @@ use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{self, Display, Formatter, Write},
|
fmt::{self, Display, Formatter, Write},
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
time::Duration,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[revisioned(revision = 1)]
|
#[revisioned(revision = 1)]
|
||||||
|
@ -101,19 +100,6 @@ pub enum Statement {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Statement {
|
impl Statement {
|
||||||
/// Get the statement timeout duration, if any
|
|
||||||
pub fn timeout(&self) -> Option<Duration> {
|
|
||||||
match self {
|
|
||||||
Self::Create(v) => v.timeout.as_ref().map(|v| *v.0),
|
|
||||||
Self::Delete(v) => v.timeout.as_ref().map(|v| *v.0),
|
|
||||||
Self::Insert(v) => v.timeout.as_ref().map(|v| *v.0),
|
|
||||||
Self::Relate(v) => v.timeout.as_ref().map(|v| *v.0),
|
|
||||||
Self::Select(v) => v.timeout.as_ref().map(|v| *v.0),
|
|
||||||
Self::Upsert(v) => v.timeout.as_ref().map(|v| *v.0),
|
|
||||||
Self::Update(v) => v.timeout.as_ref().map(|v| *v.0),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Check if we require a writeable transaction
|
/// Check if we require a writeable transaction
|
||||||
pub(crate) fn writeable(&self) -> bool {
|
pub(crate) fn writeable(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -67,7 +67,7 @@ impl CreateStatement {
|
||||||
// Loop over the create targets
|
// Loop over the create targets
|
||||||
for w in self.what.0.iter() {
|
for w in self.what.0.iter() {
|
||||||
let v = w.compute(stk, &ctx, opt, doc).await?;
|
let v = w.compute(stk, &ctx, opt, doc).await?;
|
||||||
i.prepare(stk, &ctx, opt, &stm, v).await.map_err(|e| match e {
|
i.prepare(&stm, v).map_err(|e| match e {
|
||||||
Error::InvalidStatementTarget {
|
Error::InvalidStatementTarget {
|
||||||
value: v,
|
value: v,
|
||||||
} => Error::CreateStatement {
|
} => Error::CreateStatement {
|
||||||
|
|
|
@ -56,7 +56,7 @@ impl DeleteStatement {
|
||||||
// Loop over the delete targets
|
// Loop over the delete targets
|
||||||
for w in self.what.0.iter() {
|
for w in self.what.0.iter() {
|
||||||
let v = w.compute(stk, &ctx, opt, doc).await?;
|
let v = w.compute(stk, &ctx, opt, doc).await?;
|
||||||
i.prepare(stk, &ctx, opt, &stm, v).await.map_err(|e| match e {
|
i.prepare(&stm, v).map_err(|e| match e {
|
||||||
Error::InvalidStatementTarget {
|
Error::InvalidStatementTarget {
|
||||||
value: v,
|
value: v,
|
||||||
} => Error::DeleteStatement {
|
} => Error::DeleteStatement {
|
||||||
|
|
|
@ -165,7 +165,7 @@ fn iterable(id: Thing, v: Value, relation: bool) -> Result<Iterable, Error> {
|
||||||
match relation {
|
match relation {
|
||||||
false => Ok(Iterable::Mergeable(id, v)),
|
false => Ok(Iterable::Mergeable(id, v)),
|
||||||
true => {
|
true => {
|
||||||
let _in = match v.pick(&*IN) {
|
let f = match v.pick(&*IN) {
|
||||||
Value::Thing(v) => v,
|
Value::Thing(v) => v,
|
||||||
v => {
|
v => {
|
||||||
return Err(Error::InsertStatementIn {
|
return Err(Error::InsertStatementIn {
|
||||||
|
@ -173,7 +173,7 @@ fn iterable(id: Thing, v: Value, relation: bool) -> Result<Iterable, Error> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let out = match v.pick(&*OUT) {
|
let w = match v.pick(&*OUT) {
|
||||||
Value::Thing(v) => v,
|
Value::Thing(v) => v,
|
||||||
v => {
|
v => {
|
||||||
return Err(Error::InsertStatementOut {
|
return Err(Error::InsertStatementOut {
|
||||||
|
@ -181,7 +181,7 @@ fn iterable(id: Thing, v: Value, relation: bool) -> Result<Iterable, Error> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(Iterable::Relatable(_in, id, out, Some(v)))
|
Ok(Iterable::Relatable(f, id, w, Some(v)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@ use crate::ctx::{Context, MutableContext};
|
||||||
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::idx::planner::QueryPlanner;
|
use crate::idx::planner::{QueryPlanner, QueryPlannerParams};
|
||||||
use crate::sql::{
|
use crate::sql::{
|
||||||
Cond, Explain, Fetchs, Field, Fields, Groups, Id, Idioms, Limit, Orders, Splits, Start,
|
Cond, Explain, Fetchs, Field, Fields, Groups, Idioms, Limit, Orders, Splits, Start, Timeout,
|
||||||
Timeout, Value, Values, Version, With,
|
Value, Values, Version, With,
|
||||||
};
|
};
|
||||||
use derive::Store;
|
use derive::Store;
|
||||||
use reblessive::tree::Stk;
|
use reblessive::tree::Stk;
|
||||||
|
@ -100,56 +100,49 @@ impl SelectStatement {
|
||||||
};
|
};
|
||||||
// Get a query planner
|
// Get a query planner
|
||||||
let mut planner = QueryPlanner::new();
|
let mut planner = QueryPlanner::new();
|
||||||
let params = self.into();
|
let params: QueryPlannerParams<'_> = self.into();
|
||||||
|
let keys = params.is_keys_only();
|
||||||
// Loop over the select targets
|
// Loop over the select targets
|
||||||
for w in self.what.0.iter() {
|
for w in self.what.0.iter() {
|
||||||
let v = w.compute(stk, &ctx, &opt, doc).await?;
|
let v = w.compute(stk, &ctx, &opt, doc).await?;
|
||||||
match v {
|
match v {
|
||||||
|
Value::Thing(v) => match v.is_range() {
|
||||||
|
true => i.prepare_range(&stm, v, keys)?,
|
||||||
|
false => i.prepare_thing(&stm, v)?,
|
||||||
|
},
|
||||||
|
Value::Edges(v) => {
|
||||||
|
if self.only && !limit_is_one_or_zero {
|
||||||
|
return Err(Error::SingleOnlyOutput);
|
||||||
|
}
|
||||||
|
i.prepare_edges(&stm, *v)?;
|
||||||
|
}
|
||||||
|
Value::Mock(v) => {
|
||||||
|
if self.only && !limit_is_one_or_zero {
|
||||||
|
return Err(Error::SingleOnlyOutput);
|
||||||
|
}
|
||||||
|
i.prepare_mock(&stm, v)?;
|
||||||
|
}
|
||||||
Value::Table(t) => {
|
Value::Table(t) => {
|
||||||
if self.only && !limit_is_one_or_zero {
|
if self.only && !limit_is_one_or_zero {
|
||||||
return Err(Error::SingleOnlyOutput);
|
return Err(Error::SingleOnlyOutput);
|
||||||
}
|
}
|
||||||
planner.add_iterables(stk, &ctx, &opt, t, ¶ms, &mut i).await?;
|
planner.add_iterables(stk, &ctx, &opt, t, ¶ms, &mut i).await?;
|
||||||
}
|
}
|
||||||
Value::Thing(v) => match &v.id {
|
|
||||||
Id::Range(r) => {
|
|
||||||
i.ingest(Iterable::TableRange(v.tb, *r.to_owned(), params.is_keys_only()))
|
|
||||||
}
|
|
||||||
_ => i.ingest(Iterable::Thing(v)),
|
|
||||||
},
|
|
||||||
Value::Edges(v) => {
|
|
||||||
if self.only && !limit_is_one_or_zero {
|
|
||||||
return Err(Error::SingleOnlyOutput);
|
|
||||||
}
|
|
||||||
|
|
||||||
i.ingest(Iterable::Edges(*v))
|
|
||||||
}
|
|
||||||
Value::Mock(v) => {
|
|
||||||
if self.only && !limit_is_one_or_zero {
|
|
||||||
return Err(Error::SingleOnlyOutput);
|
|
||||||
}
|
|
||||||
|
|
||||||
for v in v {
|
|
||||||
i.ingest(Iterable::Thing(v));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Array(v) => {
|
Value::Array(v) => {
|
||||||
if self.only && !limit_is_one_or_zero {
|
if self.only && !limit_is_one_or_zero {
|
||||||
return Err(Error::SingleOnlyOutput);
|
return Err(Error::SingleOnlyOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
for v in v {
|
for v in v {
|
||||||
match v {
|
match v {
|
||||||
Value::Table(t) => {
|
Value::Table(t) => {
|
||||||
planner.add_iterables(stk, &ctx, &opt, t, ¶ms, &mut i).await?;
|
planner.add_iterables(stk, &ctx, &opt, t, ¶ms, &mut i).await?;
|
||||||
}
|
}
|
||||||
Value::Thing(v) => i.ingest(Iterable::Thing(v)),
|
Value::Mock(v) => i.prepare_mock(&stm, v)?,
|
||||||
Value::Edges(v) => i.ingest(Iterable::Edges(*v)),
|
Value::Edges(v) => i.prepare_edges(&stm, *v)?,
|
||||||
Value::Mock(v) => {
|
Value::Thing(v) => match v.is_range() {
|
||||||
for v in v {
|
true => i.prepare_range(&stm, v, keys)?,
|
||||||
i.ingest(Iterable::Thing(v));
|
false => i.prepare_thing(&stm, v)?,
|
||||||
}
|
},
|
||||||
}
|
|
||||||
_ => i.ingest(Iterable::Value(v)),
|
_ => i.ingest(Iterable::Value(v)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,6 @@ impl SetStatement {
|
||||||
}
|
}
|
||||||
// The user tried to set a protected variable
|
// The user tried to set a protected variable
|
||||||
true => Err(Error::InvalidParam {
|
true => Err(Error::InvalidParam {
|
||||||
// Move the parameter name, as we no longer need it
|
|
||||||
name: self.name.to_owned(),
|
name: self.name.to_owned(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ impl UpdateStatement {
|
||||||
// Loop over the update targets
|
// Loop over the update targets
|
||||||
for w in self.what.0.iter() {
|
for w in self.what.0.iter() {
|
||||||
let v = w.compute(stk, &ctx, opt, doc).await?;
|
let v = w.compute(stk, &ctx, opt, doc).await?;
|
||||||
i.prepare(stk, &ctx, opt, &stm, v).await.map_err(|e| match e {
|
i.prepare(&stm, v).map_err(|e| match e {
|
||||||
Error::InvalidStatementTarget {
|
Error::InvalidStatementTarget {
|
||||||
value: v,
|
value: v,
|
||||||
} => Error::UpdateStatement {
|
} => Error::UpdateStatement {
|
||||||
|
|
|
@ -56,7 +56,7 @@ impl UpsertStatement {
|
||||||
// Loop over the upsert targets
|
// Loop over the upsert targets
|
||||||
for w in self.what.0.iter() {
|
for w in self.what.0.iter() {
|
||||||
let v = w.compute(stk, &ctx, opt, doc).await?;
|
let v = w.compute(stk, &ctx, opt, doc).await?;
|
||||||
i.prepare(stk, &ctx, opt, &stm, v).await.map_err(|e| match e {
|
i.prepare(&stm, v).map_err(|e| match e {
|
||||||
Error::InvalidStatementTarget {
|
Error::InvalidStatementTarget {
|
||||||
value: v,
|
value: v,
|
||||||
} => Error::UpsertStatement {
|
} => Error::UpsertStatement {
|
||||||
|
|
|
@ -76,6 +76,21 @@ impl Subquery {
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
doc: Option<&CursorDoc>,
|
doc: Option<&CursorDoc>,
|
||||||
|
) -> Result<Value, Error> {
|
||||||
|
match self.compute_unbordered(stk, ctx, opt, doc).await {
|
||||||
|
Err(Error::Return {
|
||||||
|
value,
|
||||||
|
}) => Ok(value),
|
||||||
|
res => res,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Process this type returning a computed simple Value, without catching errors
|
||||||
|
pub(crate) async fn compute_unbordered(
|
||||||
|
&self,
|
||||||
|
stk: &mut Stk,
|
||||||
|
ctx: &Context,
|
||||||
|
opt: &Options,
|
||||||
|
doc: Option<&CursorDoc>,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Value, Error> {
|
||||||
// Duplicate context
|
// Duplicate context
|
||||||
let mut ctx = MutableContext::new(ctx);
|
let mut ctx = MutableContext::new(ctx);
|
||||||
|
|
|
@ -216,7 +216,11 @@ impl Thing {
|
||||||
pub fn to_raw(&self) -> String {
|
pub fn to_raw(&self) -> String {
|
||||||
self.to_string()
|
self.to_string()
|
||||||
}
|
}
|
||||||
|
/// Check if this Thing is a range
|
||||||
|
pub fn is_range(&self) -> bool {
|
||||||
|
matches!(self.id, Id::Range(_))
|
||||||
|
}
|
||||||
|
/// Check if this Thing is of a certain table type
|
||||||
pub fn is_record_type(&self, types: &[Table]) -> bool {
|
pub fn is_record_type(&self, types: &[Table]) -> bool {
|
||||||
types.is_empty() || types.iter().any(|tb| tb.0 == self.tb)
|
types.is_empty() || types.iter().any(|tb| tb.0 == self.tb)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,6 @@ use crate::sql::value::Value;
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
pub(crate) fn def(&mut self, val: &Thing) {
|
pub(crate) fn def(&mut self, val: &Thing) {
|
||||||
self.put(ID.as_ref(), val.clone().into())
|
self.put(&*ID, val.clone().into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2909,6 +2909,7 @@ impl Value {
|
||||||
/// Check if we require a writeable transaction
|
/// Check if we require a writeable transaction
|
||||||
pub(crate) fn writeable(&self) -> bool {
|
pub(crate) fn writeable(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
Value::Cast(v) => v.writeable(),
|
||||||
Value::Block(v) => v.writeable(),
|
Value::Block(v) => v.writeable(),
|
||||||
Value::Idiom(v) => v.writeable(),
|
Value::Idiom(v) => v.writeable(),
|
||||||
Value::Array(v) => v.iter().any(Value::writeable),
|
Value::Array(v) => v.iter().any(Value::writeable),
|
||||||
|
@ -2923,8 +2924,21 @@ impl Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Process this type returning a computed simple Value
|
/// Process this type returning a computed simple Value
|
||||||
///
|
pub(crate) async fn compute(
|
||||||
/// Is used recursively.
|
&self,
|
||||||
|
stk: &mut Stk,
|
||||||
|
ctx: &Context,
|
||||||
|
opt: &Options,
|
||||||
|
doc: Option<&CursorDoc>,
|
||||||
|
) -> Result<Value, Error> {
|
||||||
|
match self.compute_unbordered(stk, ctx, opt, doc).await {
|
||||||
|
Err(Error::Return {
|
||||||
|
value,
|
||||||
|
}) => Ok(value),
|
||||||
|
res => res,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Process this type returning a computed simple Value, without catching errors
|
||||||
pub(crate) async fn compute_unbordered(
|
pub(crate) async fn compute_unbordered(
|
||||||
&self,
|
&self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
|
@ -2953,21 +2967,6 @@ impl Value {
|
||||||
_ => Ok(self.to_owned()),
|
_ => Ok(self.to_owned()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn compute(
|
|
||||||
&self,
|
|
||||||
stk: &mut Stk,
|
|
||||||
ctx: &Context,
|
|
||||||
opt: &Options,
|
|
||||||
doc: Option<&CursorDoc>,
|
|
||||||
) -> Result<Value, Error> {
|
|
||||||
match self.compute_unbordered(stk, ctx, opt, doc).await {
|
|
||||||
Err(Error::Return {
|
|
||||||
value,
|
|
||||||
}) => Ok(value),
|
|
||||||
res => res,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------
|
// ------------------------------
|
||||||
|
|
|
@ -527,15 +527,15 @@ async fn insert_thing() {
|
||||||
let table = "user";
|
let table = "user";
|
||||||
let _: Option<ApiRecordId> = db.insert((table, "user1")).await.unwrap();
|
let _: Option<ApiRecordId> = db.insert((table, "user1")).await.unwrap();
|
||||||
let _: Option<ApiRecordId> =
|
let _: Option<ApiRecordId> =
|
||||||
db.insert((table, "user1")).content(json!({ "foo": "bar" })).await.unwrap();
|
db.insert((table, "user2")).content(json!({ "foo": "bar" })).await.unwrap();
|
||||||
let _: Value = db.insert(Resource::from((table, "user2"))).await.unwrap();
|
let _: Value = db.insert(Resource::from((table, "user3"))).await.unwrap();
|
||||||
let _: Value =
|
let _: Value =
|
||||||
db.insert(Resource::from((table, "user2"))).content(json!({ "foo": "bar" })).await.unwrap();
|
db.insert(Resource::from((table, "user4"))).content(json!({ "foo": "bar" })).await.unwrap();
|
||||||
let user: Option<ApiRecordId> = db.insert((table, "user3")).await.unwrap();
|
let user: Option<ApiRecordId> = db.insert((table, "user5")).await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
user,
|
user,
|
||||||
Some(ApiRecordId {
|
Some(ApiRecordId {
|
||||||
id: "user:user3".parse().unwrap(),
|
id: "user:user5".parse().unwrap(),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -586,104 +586,16 @@ async fn insert_relation_table() {
|
||||||
let val = "{in: person:a, out: thing:a}".parse::<Value>().unwrap();
|
let val = "{in: person:a, out: thing:a}".parse::<Value>().unwrap();
|
||||||
let _: Vec<ApiRecordId> = db.insert("likes").relation(val).await.unwrap();
|
let _: Vec<ApiRecordId> = db.insert("likes").relation(val).await.unwrap();
|
||||||
|
|
||||||
let vals =
|
let vals = r#"[
|
||||||
"[{in: person:b, out: thing:a}, {id: likes:2, in: person:a, out: thing:a}, {id: hates:3, in: person:a, out: thing:a}]"
|
{ in: person:b, out: thing:a },
|
||||||
.parse::<Value>()
|
{ id: likes:2, in: person:c, out: thing:a },
|
||||||
|
{ id: hates:3, in: person:d, out: thing:a },
|
||||||
|
]"#
|
||||||
|
.parse::<Value>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let _: Vec<ApiRecordId> = db.insert("likes").relation(vals).await.unwrap();
|
let _: Vec<ApiRecordId> = db.insert("likes").relation(vals).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn insert_with_savepoint() -> Result<(), surrealdb_core::err::Error> {
|
|
||||||
let (permit, db) = new_db().await;
|
|
||||||
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
|
|
||||||
drop(permit);
|
|
||||||
let sqls = vec![
|
|
||||||
("DEFINE INDEX a ON pokemon FIELDS a UNIQUE", "None"),
|
|
||||||
("DEFINE INDEX b ON pokemon FIELDS b UNIQUE", "None"),
|
|
||||||
(
|
|
||||||
"INSERT INTO pokemon (id, b) VALUES (1, 'b')",
|
|
||||||
"[
|
|
||||||
{
|
|
||||||
b: 'b',
|
|
||||||
id: pokemon:1
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"INSERT INTO pokemon (id, a, b) VALUES (2, 'a', 'b')",
|
|
||||||
"[
|
|
||||||
{
|
|
||||||
b: 'b',
|
|
||||||
id: pokemon:1
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"INSERT INTO pokemon (id, a, b) VALUES (2, 'a', 'b')",
|
|
||||||
"[
|
|
||||||
{
|
|
||||||
b: 'b',
|
|
||||||
id: pokemon:1
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"INSERT INTO pokemon (id, a, b) VALUES (2, 'a', 'b') PARALLEL",
|
|
||||||
"[
|
|
||||||
{
|
|
||||||
b: 'b',
|
|
||||||
id: pokemon:1
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"INSERT INTO pokemon (id, a, b) VALUES (2, 'a', 'b') PARALLEL",
|
|
||||||
"[
|
|
||||||
{
|
|
||||||
b: 'b',
|
|
||||||
id: pokemon:1
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"INSERT INTO pokemon (id, a, b) VALUES (2, 'a', 'b') ON DUPLICATE KEY UPDATE something = 'else'",
|
|
||||||
"[
|
|
||||||
{
|
|
||||||
b: 'b',
|
|
||||||
id: pokemon:1,
|
|
||||||
something: 'else'
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"SELECT * FROM pokemon;",
|
|
||||||
"[
|
|
||||||
{
|
|
||||||
b: 'b',
|
|
||||||
id: pokemon:1,
|
|
||||||
something: 'else'
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
let check_fetch = |mut response: Response, expected: &str| {
|
|
||||||
let val: Value = response.take(0).unwrap();
|
|
||||||
let exp: Value = expected.parse().unwrap();
|
|
||||||
assert_eq!(format!("{val:#}"), format!("{exp:#}"));
|
|
||||||
};
|
|
||||||
|
|
||||||
for (sql, expected) in sqls {
|
|
||||||
let res = db.query(sql).await.unwrap().check().unwrap();
|
|
||||||
check_fetch(res, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[test_log::test(tokio::test)]
|
#[test_log::test(tokio::test)]
|
||||||
async fn select_table() {
|
async fn select_table() {
|
||||||
let (permit, db) = new_db().await;
|
let (permit, db) = new_db().await;
|
||||||
|
|
|
@ -113,7 +113,8 @@ fn ok_future_graph_subquery_recursion_depth() -> Result<(), Error> {
|
||||||
fn ok_graph_traversal_depth() -> Result<(), Error> {
|
fn ok_graph_traversal_depth() -> Result<(), Error> {
|
||||||
// Build the SQL traversal query
|
// Build the SQL traversal query
|
||||||
fn graph_traversal(n: usize) -> String {
|
fn graph_traversal(n: usize) -> String {
|
||||||
let mut ret = String::from("CREATE node:0;\n");
|
let mut ret = String::from("DELETE node;\n");
|
||||||
|
ret.push_str("CREATE node:0;\n");
|
||||||
for i in 1..=n {
|
for i in 1..=n {
|
||||||
let prev = i - 1;
|
let prev = i - 1;
|
||||||
ret.push_str(&format!("CREATE node:{i};\n"));
|
ret.push_str(&format!("CREATE node:{i};\n"));
|
||||||
|
|
|
@ -169,13 +169,13 @@ async fn create_with_id() -> Result<(), Error> {
|
||||||
let tmp = res.remove(0).result;
|
let tmp = res.remove(0).result;
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
tmp.err(),
|
tmp.err(),
|
||||||
Some(e) if e.to_string() == r#"Found 'tobie' for the id field, but a specific record has been specified"#
|
Some(e) if e.to_string() == r#"Found 'tobie' for the `id` field, but a specific record has been specified"#
|
||||||
));
|
));
|
||||||
//
|
//
|
||||||
let tmp = res.remove(0).result;
|
let tmp = res.remove(0).result;
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
tmp.err(),
|
tmp.err(),
|
||||||
Some(e) if e.to_string() == r#"Found 'tobie' for the id field, but a specific record has been specified"#
|
Some(e) if e.to_string() == r#"Found 'tobie' for the `id` field, but a specific record has been specified"#
|
||||||
));
|
));
|
||||||
//
|
//
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -149,40 +149,66 @@ async fn insert_statement_on_duplicate_key() -> Result<(), Error> {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn insert_with_savepoint() -> Result<(), Error> {
|
async fn insert_with_savepoint() -> Result<(), Error> {
|
||||||
let sql = "
|
let sql = "
|
||||||
DEFINE INDEX a ON pokemon FIELDS a UNIQUE;
|
DEFINE INDEX one ON pokemon FIELDS one UNIQUE;
|
||||||
DEFINE INDEX b ON pokemon FIELDS b UNIQUE;
|
DEFINE INDEX two ON pokemon FIELDS two UNIQUE;
|
||||||
INSERT INTO pokemon (id, b) VALUES (1, 'b');
|
-- This will INSERT a record with a specific id
|
||||||
INSERT INTO pokemon (id, a, b) VALUES (2, 'a', 'b');
|
INSERT INTO pokemon (id, two) VALUES (1, 'two');
|
||||||
INSERT INTO pokemon (id, a, b) VALUES (2, 'a', 'b');
|
-- This will INSERT a record with a random id
|
||||||
INSERT INTO pokemon (id, a, b) VALUES (2, 'a', 'b') PARALLEL;
|
INSERT INTO pokemon (id, one) VALUES ('test', 'one');
|
||||||
INSERT INTO pokemon (id, a, b) VALUES (2, 'a', 'b') PARALLEL;
|
-- This will fail, because a UNIQUE index value already exists
|
||||||
INSERT INTO pokemon (id, a, b) VALUES (2, 'a', 'b') ON DUPLICATE KEY UPDATE something = 'else';
|
INSERT INTO pokemon (two) VALUES ('two');
|
||||||
|
-- This will fail, because a UNIQUE index value already exists
|
||||||
|
INSERT INTO pokemon (id, one, two) VALUES (2, 'one', 'two');
|
||||||
|
-- This will fail, because we are specifying a specific id even though we also have an ON DUPLICATE KEY UPDATE clause
|
||||||
|
INSERT INTO pokemon (id, one, two) VALUES (2, 'one', 'two') ON DUPLICATE KEY UPDATE two = 'changed';
|
||||||
|
-- This will succeed, because we are not specifying a specific id and we also have an ON DUPLICATE KEY UPDATE clause
|
||||||
|
INSERT INTO pokemon (one, two) VALUES ('one', 'two') ON DUPLICATE KEY UPDATE two = 'changed';
|
||||||
SELECT * FROM pokemon;
|
SELECT * FROM pokemon;
|
||||||
";
|
";
|
||||||
let mut t = Test::new(sql).await?;
|
let mut t = Test::new(sql).await?;
|
||||||
t.expect_size(9)?;
|
t.expect_size(9)?;
|
||||||
t.skip_ok(2)?;
|
t.skip_ok(2)?;
|
||||||
for _ in 0..5 {
|
t.expect_val(
|
||||||
t.expect_val(
|
"[
|
||||||
"[
|
{
|
||||||
{
|
id: pokemon:1,
|
||||||
b: 'b',
|
two: 'two'
|
||||||
id: pokemon:1,
|
}
|
||||||
}
|
]",
|
||||||
]",
|
)?;
|
||||||
)?;
|
t.expect_val(
|
||||||
}
|
"[
|
||||||
for _ in 0..2 {
|
{
|
||||||
t.expect_val(
|
id: pokemon:test,
|
||||||
"[
|
one: 'one'
|
||||||
{
|
}
|
||||||
b: 'b',
|
]",
|
||||||
id: pokemon:1,
|
)?;
|
||||||
something: 'else'
|
t.expect_error("Database index `two` already contains 'two', with record `pokemon:1`")?;
|
||||||
}
|
t.expect_error("Database index `one` already contains 'one', with record `pokemon:test`")?;
|
||||||
]",
|
t.expect_error("Database index `one` already contains 'one', with record `pokemon:test`")?;
|
||||||
)?;
|
t.expect_val(
|
||||||
}
|
"[
|
||||||
|
{
|
||||||
|
id: pokemon:test,
|
||||||
|
one: 'one',
|
||||||
|
two: 'changed'
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
)?;
|
||||||
|
t.expect_val(
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
id: pokemon:1,
|
||||||
|
two: 'two'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: pokemon:test,
|
||||||
|
one: 'one',
|
||||||
|
two: 'changed'
|
||||||
|
}
|
||||||
|
]",
|
||||||
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -550,7 +576,14 @@ async fn insert_statement_unique_index() -> Result<(), Error> {
|
||||||
assert!(tmp.is_ok());
|
assert!(tmp.is_ok());
|
||||||
//
|
//
|
||||||
let tmp = res.remove(0).result;
|
let tmp = res.remove(0).result;
|
||||||
assert!(tmp.is_ok());
|
match tmp {
|
||||||
|
Err(Error::IndexExists {
|
||||||
|
index,
|
||||||
|
value,
|
||||||
|
..
|
||||||
|
}) if index.eq("name") && value.eq("'SurrealDB'") => (),
|
||||||
|
found => panic!("Expected Err(Error::IndexExists), found '{:?}'", found),
|
||||||
|
}
|
||||||
//
|
//
|
||||||
let tmp = res.remove(0).result?;
|
let tmp = res.remove(0).result?;
|
||||||
let val = Value::parse("[ { count: 1 } ]");
|
let val = Value::parse("[ { count: 1 } ]");
|
||||||
|
|
Loading…
Reference in a new issue