diff --git a/lib/src/sql/statements/create.rs b/lib/src/sql/statements/create.rs index 262cbcaf..14563969 100644 --- a/lib/src/sql/statements/create.rs +++ b/lib/src/sql/statements/create.rs @@ -33,6 +33,15 @@ impl CreateStatement { true } + pub(crate) fn single(&self) -> bool { + match self.what.len() { + 1 if self.what[0].is_object() => true, + 1 if self.what[0].is_thing() => true, + 1 if self.what[0].is_table() => true, + _ => false, + } + } + pub(crate) async fn compute( &self, ctx: &Context<'_>, diff --git a/lib/src/sql/statements/delete.rs b/lib/src/sql/statements/delete.rs index f2af53f2..61c696d4 100644 --- a/lib/src/sql/statements/delete.rs +++ b/lib/src/sql/statements/delete.rs @@ -34,6 +34,14 @@ impl DeleteStatement { true } + pub(crate) fn single(&self) -> bool { + match self.what.len() { + 1 if self.what[0].is_object() => true, + 1 if self.what[0].is_thing() => true, + _ => false, + } + } + pub(crate) async fn compute( &self, ctx: &Context<'_>, diff --git a/lib/src/sql/statements/insert.rs b/lib/src/sql/statements/insert.rs index da86cc81..00b6318c 100644 --- a/lib/src/sql/statements/insert.rs +++ b/lib/src/sql/statements/insert.rs @@ -37,6 +37,14 @@ impl InsertStatement { true } + pub(crate) fn single(&self) -> bool { + match &self.data { + Data::SingleExpression(v) if v.is_object() => true, + Data::ValuesExpression(v) if v.len() == 1 => true, + _ => false, + } + } + pub(crate) async fn compute( &self, ctx: &Context<'_>, diff --git a/lib/src/sql/statements/relate.rs b/lib/src/sql/statements/relate.rs index 292465c6..07caedf5 100644 --- a/lib/src/sql/statements/relate.rs +++ b/lib/src/sql/statements/relate.rs @@ -45,6 +45,16 @@ impl RelateStatement { true } + pub(crate) fn single(&self) -> bool { + match (&self.from, &self.with) { + (v, w) if v.is_object() && w.is_object() => true, + (v, w) if v.is_object() && w.is_thing() => true, + (v, w) if v.is_thing() && w.is_object() => true, + (v, w) if v.is_thing() && w.is_thing() => true, + _ => false, + } + } + pub(crate) async fn compute( &self, ctx: &Context<'_>, diff --git a/lib/src/sql/statements/select.rs b/lib/src/sql/statements/select.rs index f3cc17c6..df6430c5 100644 --- a/lib/src/sql/statements/select.rs +++ b/lib/src/sql/statements/select.rs @@ -46,19 +46,6 @@ pub struct SelectStatement { } impl SelectStatement { - pub(crate) async fn limit( - &self, - ctx: &Context<'_>, - opt: &Options, - txn: &Transaction, - doc: Option<&Value>, - ) -> Result { - match &self.limit { - Some(v) => v.process(ctx, opt, txn, doc).await, - None => Ok(0), - } - } - pub(crate) fn writeable(&self) -> bool { if self.expr.iter().any(|v| match v { Field::All => false, @@ -73,6 +60,14 @@ impl SelectStatement { self.cond.as_ref().map_or(false, |v| v.writeable()) } + pub(crate) fn single(&self) -> bool { + match self.what.len() { + 1 if self.what[0].is_object() => true, + 1 if self.what[0].is_thing() => true, + _ => false, + } + } + pub(crate) async fn compute( &self, ctx: &Context<'_>, diff --git a/lib/src/sql/statements/update.rs b/lib/src/sql/statements/update.rs index 936daad4..b298b155 100644 --- a/lib/src/sql/statements/update.rs +++ b/lib/src/sql/statements/update.rs @@ -35,6 +35,14 @@ impl UpdateStatement { true } + pub(crate) fn single(&self) -> bool { + match self.what.len() { + 1 if self.what[0].is_object() => true, + 1 if self.what[0].is_thing() => true, + _ => false, + } + } + pub(crate) async fn compute( &self, ctx: &Context<'_>, diff --git a/lib/src/sql/subquery.rs b/lib/src/sql/subquery.rs index 431a3503..e562aba9 100644 --- a/lib/src/sql/subquery.rs +++ b/lib/src/sql/subquery.rs @@ -4,7 +4,6 @@ use crate::dbs::Transaction; use crate::err::Error; use crate::sql::comment::mightbespace; use crate::sql::error::IResult; -use crate::sql::paths::ID; use crate::sql::statements::create::{create, CreateStatement}; use crate::sql::statements::delete::{delete, DeleteStatement}; use crate::sql::statements::ifelse::{ifelse, IfelseStatement}; @@ -67,27 +66,8 @@ impl Subquery { Self::Value(ref v) => v.compute(ctx, opt, txn, doc).await, Self::Ifelse(ref v) => v.compute(ctx, opt, txn, doc).await, Self::Select(ref v) => { - // 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(&ctx, opt, txn, doc).await? { - 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(), - }, - } - } - Self::Create(ref v) => { + // Is this a single output? + let one = v.single(); // Duplicate context let mut ctx = Context::new(ctx); // Add parent document @@ -96,14 +76,42 @@ impl Subquery { } // Process subquery match v.compute(&ctx, opt, txn, doc).await? { - Value::Array(mut v) => match v.len() { - 1 => Ok(v.remove(0).pick(ID.as_ref())), - _ => Ok(Value::from(v).pick(ID.as_ref())), + // This is a single record result + Value::Array(mut a) if one => match a.len() { + // There was at least one result + v if v > 0 => Ok(a.remove(0)), + // There were no results + _ => Ok(Value::None), }, + // This is standard query result + v => Ok(v), + } + } + Self::Create(ref v) => { + // Is this a single output? + let one = v.single(); + // 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? { + // This is a single record result + Value::Array(mut a) if one => match a.len() { + // There was at least one result + v if v > 0 => Ok(a.remove(0)), + // There were no results + _ => Ok(Value::None), + }, + // This is standard query result v => Ok(v), } } Self::Update(ref v) => { + // Is this a single output? + let one = v.single(); // Duplicate context let mut ctx = Context::new(ctx); // Add parent document @@ -112,14 +120,20 @@ impl Subquery { } // Process subquery match v.compute(&ctx, opt, txn, doc).await? { - Value::Array(mut v) => match v.len() { - 1 => Ok(v.remove(0).pick(ID.as_ref())), - _ => Ok(Value::from(v).pick(ID.as_ref())), + // This is a single record result + Value::Array(mut a) if one => match a.len() { + // There was at least one result + v if v > 0 => Ok(a.remove(0)), + // There were no results + _ => Ok(Value::None), }, + // This is standard query result v => Ok(v), } } Self::Delete(ref v) => { + // Is this a single output? + let one = v.single(); // Duplicate context let mut ctx = Context::new(ctx); // Add parent document @@ -128,14 +142,20 @@ impl Subquery { } // Process subquery match v.compute(&ctx, opt, txn, doc).await? { - Value::Array(mut v) => match v.len() { - 1 => Ok(v.remove(0).pick(ID.as_ref())), - _ => Ok(Value::from(v).pick(ID.as_ref())), + // This is a single record result + Value::Array(mut a) if one => match a.len() { + // There was at least one result + v if v > 0 => Ok(a.remove(0)), + // There were no results + _ => Ok(Value::None), }, + // This is standard query result v => Ok(v), } } Self::Relate(ref v) => { + // Is this a single output? + let one = v.single(); // Duplicate context let mut ctx = Context::new(ctx); // Add parent document @@ -144,14 +164,20 @@ impl Subquery { } // Process subquery match v.compute(&ctx, opt, txn, doc).await? { - Value::Array(mut v) => match v.len() { - 1 => Ok(v.remove(0).pick(ID.as_ref())), - _ => Ok(Value::from(v).pick(ID.as_ref())), + // This is a single record result + Value::Array(mut a) if one => match a.len() { + // There was at least one result + v if v > 0 => Ok(a.remove(0)), + // There were no results + _ => Ok(Value::None), }, + // This is standard query result v => Ok(v), } } Self::Insert(ref v) => { + // Is this a single output? + let one = v.single(); // Duplicate context let mut ctx = Context::new(ctx); // Add parent document @@ -160,10 +186,14 @@ impl Subquery { } // Process subquery match v.compute(&ctx, opt, txn, doc).await? { - Value::Array(mut v) => match v.len() { - 1 => Ok(v.remove(0).pick(ID.as_ref())), - _ => Ok(Value::from(v).pick(ID.as_ref())), + // This is a single record result + Value::Array(mut a) if one => match a.len() { + // There was at least one result + v if v > 0 => Ok(a.remove(0)), + // There were no results + _ => Ok(Value::None), }, + // This is standard query result v => Ok(v), } } diff --git a/lib/tests/complex.rs b/lib/tests/complex.rs index 3c6a3dfe..2135d993 100644 --- a/lib/tests/complex.rs +++ b/lib/tests/complex.rs @@ -102,7 +102,7 @@ fn ok_future_graph_subquery_recursion_depth() -> Result<(), Error> { } // let tmp = res.next().unwrap()?; - let val = Value::parse("[ [42] ]"); + let val = Value::parse("[ { fut: [42] } ]"); assert_eq!(tmp, val); // Ok(()) diff --git a/lib/tests/subquery.rs b/lib/tests/subquery.rs new file mode 100644 index 00000000..f683b55e --- /dev/null +++ b/lib/tests/subquery.rs @@ -0,0 +1,179 @@ +mod parse; +use parse::Parse; +use surrealdb::dbs::Session; +use surrealdb::err::Error; +use surrealdb::kvs::Datastore; +use surrealdb::sql::Value; + +#[tokio::test] +async fn subquery_select() -> Result<(), Error> { + let sql = " + -- Create a record + CREATE person:test SET name = 'Tobie', age = 21; + -- Select all records, returning an array + SELECT age >= 18 as adult FROM person; + -- Select a specific record, still returning an array + SELECT age >= 18 as adult FROM person:test; + -- Select all records in a subquery, returning an array + RETURN (SELECT age >= 18 AS adult FROM person); + -- Select a specific record in a subquery, returning an object + RETURN (SELECT age >= 18 AS adult FROM person:test); + -- Using an outer SELECT, select all records in a subquery, returning an array + SELECT * FROM (SELECT age >= 18 AS adult FROM person) WHERE adult = true; + -- Using an outer SELECT, select a specific record in a subquery, returning an array + SELECT * FROM (SELECT age >= 18 AS adult FROM person:test) WHERE adult = true; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 7); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "[ + { + age: 21, + id: person:test, + name: 'Tobie' + } + ]", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "[ + { + adult: true + } + ]", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "[ + { + adult: true + } + ]", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "[ + { + adult: true + } + ]", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + adult: true + }", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "[ + { + adult: true + } + ]", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "[ + { + adult: true + } + ]", + ); + assert_eq!(tmp, val); + // + Ok(()) +} + +#[tokio::test] +async fn subquery_ifelse() -> Result<(), Error> { + let sql = " + -- Check if the record exists + LET $record = (SELECT *, count() AS count FROM person:test); + -- Return the specified record + RETURN $record; + -- Update the record field if it exists + IF $record.count THEN + ( UPDATE person:test SET sport += 'football' RETURN sport ) + ELSE + ( UPDATE person:test SET sport = ['basketball'] RETURN sport ) + END; + -- Check if the record exists + LET $record = (SELECT *, count() AS count FROM person:test); + -- Return the specified record + RETURN $record; + -- Update the record field if it exists + IF $record.count THEN + ( UPDATE person:test SET sport += 'football' RETURN sport ) + ELSE + ( UPDATE person:test SET sport = ['basketball'] RETURN sport ) + END; + "; + let dbs = Datastore::new("memory").await?; + let ses = Session::for_kv().with_ns("test").with_db("test"); + let res = &mut dbs.execute(&sql, &ses, None, false).await?; + assert_eq!(res.len(), 6); + // + let tmp = res.remove(0).result?; + let val = Value::None; + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::None; + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + sport: [ + 'basketball' + ] + }", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::None; + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + count: 1, + id: person:test, + sport: [ + 'basketball' + ] + }", + ); + assert_eq!(tmp, val); + // + let tmp = res.remove(0).result?; + let val = Value::parse( + "{ + sport: [ + 'basketball', + 'football' + ] + }", + ); + assert_eq!(tmp, val); + // + Ok(()) +}