surrealpatch/lib/src/sql/subquery.rs
Tobie Morgan Hitchcock 1017e2fffb Don’t clone variables when processing sub-contexts
Closes SUR-53

When creating a new context for subqueries or statement clauses, we used to have to clone any variables/values, and freeze the context, so that it could be used across threads and async boundaries.

Now with the new executor pattern for parallel queries, we can pass references instead, improving performance by removing unnecessary cloning of values.
2022-05-14 13:38:17 +01:00

238 lines
6.1 KiB
Rust

use crate::ctx::Context;
use crate::dbs::Options;
use crate::dbs::Transaction;
use crate::err::Error;
use crate::sql::error::IResult;
use crate::sql::statements::create::{create, CreateStatement};
use crate::sql::statements::delete::{delete, DeleteStatement};
use crate::sql::statements::ifelse::{ifelse, IfelseStatement};
use crate::sql::statements::insert::{insert, InsertStatement};
use crate::sql::statements::relate::{relate, RelateStatement};
use crate::sql::statements::select::{select, SelectStatement};
use crate::sql::statements::update::{update, UpdateStatement};
use crate::sql::value::{value, Value};
use nom::branch::alt;
use nom::character::complete::char;
use nom::combinator::map;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt;
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum Subquery {
Value(Value),
Ifelse(IfelseStatement),
Select(SelectStatement),
Create(CreateStatement),
Update(UpdateStatement),
Delete(DeleteStatement),
Relate(RelateStatement),
Insert(InsertStatement),
}
impl PartialOrd for Subquery {
#[inline]
fn partial_cmp(&self, _: &Self) -> Option<Ordering> {
None
}
}
impl Subquery {
pub(crate) async fn compute(
&self,
ctx: &Context<'_>,
opt: &Options,
txn: &Transaction,
doc: Option<&Value>,
) -> Result<Value, Error> {
match self {
Subquery::Value(ref v) => v.compute(ctx, opt, txn, doc).await,
Subquery::Ifelse(ref v) => v.compute(ctx, opt, txn, doc).await,
Subquery::Select(ref v) => {
// Duplicate options
let opt = opt.dive()?;
// Duplicate context
let mut ctx = Context::new(ctx);
// Add parent document
if let Some(doc) = doc {
ctx.add_value("parent".into(), doc);
}
// Process subquery
let res = v.compute(&ctx, &opt, txn, doc).await?;
// Process result
match v.limit() {
1 => match v.expr.single() {
Some(v) => res.first().get(&ctx, &opt, txn, &v).await,
None => res.first().ok(),
},
_ => match v.expr.single() {
Some(v) => res.get(&ctx, &opt, txn, &v).await,
None => res.ok(),
},
}
}
Subquery::Create(ref v) => {
// Duplicate options
let opt = opt.dive()?;
// Duplicate context
let mut ctx = Context::new(ctx);
// Add parent document
if let Some(doc) = doc {
ctx.add_value("parent".into(), doc);
}
// Process subquery
match v.compute(&ctx, &opt, txn, doc).await? {
Value::Array(mut v) => match v.len() {
1 => Ok(v.remove(0)),
_ => Ok(v.into()),
},
v => Ok(v),
}
}
Subquery::Update(ref v) => {
// Duplicate options
let opt = opt.dive()?;
// Duplicate context
let mut ctx = Context::new(ctx);
// Add parent document
if let Some(doc) = doc {
ctx.add_value("parent".into(), doc);
}
// Process subquery
match v.compute(&ctx, &opt, txn, doc).await? {
Value::Array(mut v) => match v.len() {
1 => Ok(v.remove(0)),
_ => Ok(v.into()),
},
v => Ok(v),
}
}
Subquery::Delete(ref v) => {
// Duplicate options
let opt = opt.dive()?;
// Duplicate context
let mut ctx = Context::new(ctx);
// Add parent document
if let Some(doc) = doc {
ctx.add_value("parent".into(), doc);
}
// Process subquery
match v.compute(&ctx, &opt, txn, doc).await? {
Value::Array(mut v) => match v.len() {
1 => Ok(v.remove(0)),
_ => Ok(v.into()),
},
v => Ok(v),
}
}
Subquery::Relate(ref v) => {
// Duplicate options
let opt = opt.dive()?;
// Duplicate context
let mut ctx = Context::new(ctx);
// Add parent document
if let Some(doc) = doc {
ctx.add_value("parent".into(), doc);
}
// Process subquery
match v.compute(&ctx, &opt, txn, doc).await? {
Value::Array(mut v) => match v.len() {
1 => Ok(v.remove(0)),
_ => Ok(v.into()),
},
v => Ok(v),
}
}
Subquery::Insert(ref v) => {
// Duplicate options
let opt = opt.dive()?;
// Duplicate context
let mut ctx = Context::new(ctx);
// Add parent document
if let Some(doc) = doc {
ctx.add_value("parent".into(), doc);
}
// Process subquery
match v.compute(&ctx, &opt, txn, doc).await? {
Value::Array(mut v) => match v.len() {
1 => Ok(v.remove(0)),
_ => Ok(v.into()),
},
v => Ok(v),
}
}
}
}
}
impl fmt::Display for Subquery {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Subquery::Value(v) => write!(f, "({})", v),
Subquery::Select(v) => write!(f, "({})", v),
Subquery::Create(v) => write!(f, "({})", v),
Subquery::Update(v) => write!(f, "({})", v),
Subquery::Delete(v) => write!(f, "({})", v),
Subquery::Relate(v) => write!(f, "({})", v),
Subquery::Insert(v) => write!(f, "({})", v),
Subquery::Ifelse(v) => write!(f, "{}", v),
}
}
}
pub fn subquery(i: &str) -> IResult<&str, Subquery> {
alt((subquery_ifelse, subquery_others))(i)
}
fn subquery_ifelse(i: &str) -> IResult<&str, Subquery> {
let (i, v) = map(ifelse, Subquery::Ifelse)(i)?;
Ok((i, v))
}
fn subquery_others(i: &str) -> IResult<&str, Subquery> {
let (i, _) = char('(')(i)?;
let (i, v) = alt((
map(select, Subquery::Select),
map(create, Subquery::Create),
map(update, Subquery::Update),
map(delete, Subquery::Delete),
map(relate, Subquery::Relate),
map(insert, Subquery::Insert),
map(value, Subquery::Value),
))(i)?;
let (i, _) = char(')')(i)?;
Ok((i, v))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn subquery_expression_statement() {
let sql = "(1 + 2 + 3)";
let res = subquery(sql);
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!("(1 + 2 + 3)", format!("{}", out))
}
#[test]
fn subquery_ifelse_statement() {
let sql = "IF true THEN false END";
let res = subquery(sql);
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!("IF true THEN false END", format!("{}", out))
}
#[test]
fn subquery_select_statement() {
let sql = "(SELECT * FROM test)";
let res = subquery(sql);
assert!(res.is_ok());
let out = res.unwrap().1;
assert_eq!("(SELECT * FROM test)", format!("{}", out))
}
}