diff --git a/core/src/dbs/executor.rs b/core/src/dbs/executor.rs index 95c4668e..125bb84f 100644 --- a/core/src/dbs/executor.rs +++ b/core/src/dbs/executor.rs @@ -243,6 +243,13 @@ impl<'a> Executor<'a> { } else { Force::None }), + "FUTURES" => { + if stm.what { + opt.with_futures(true) + } else { + opt.with_futures_never() + } + } _ => break, }; // Continue diff --git a/core/src/dbs/options.rs b/core/src/dbs/options.rs index d6bcd25f..a3518801 100644 --- a/core/src/dbs/options.rs +++ b/core/src/dbs/options.rs @@ -41,7 +41,7 @@ pub struct Options { /// Should we process field queries? pub import: bool, /// Should we process function futures? - pub futures: bool, + pub futures: Futures, /// Should we process variable field projections? pub projections: bool, /// The channel over which we send notifications @@ -59,6 +59,13 @@ pub enum Force { Index(Arc<[DefineIndexStatement]>), } +#[derive(Clone, Debug)] +pub enum Futures { + Disabled, + Enabled, + Never, +} + impl Default for Options { fn default() -> Self { Options::new() @@ -78,7 +85,7 @@ impl Options { force: Force::None, strict: false, import: false, - futures: false, + futures: Futures::Disabled, projections: false, auth_enabled: true, sender: None, @@ -189,7 +196,20 @@ impl Options { /// Specify if we should process futures pub fn with_futures(mut self, futures: bool) -> Self { - self.futures = futures; + if matches!(self.futures, Futures::Never) { + return self; + } + + self.futures = match futures { + true => Futures::Enabled, + false => Futures::Disabled, + }; + self + } + + /// Specify if we should never process futures + pub fn with_futures_never(mut self) -> Self { + self.futures = Futures::Never; self } @@ -221,6 +241,7 @@ impl Options { ns: self.ns.clone(), db: self.db.clone(), force: self.force.clone(), + futures: self.futures.clone(), perms, ..*self } @@ -233,6 +254,7 @@ impl Options { auth: self.auth.clone(), ns: self.ns.clone(), db: self.db.clone(), + futures: self.futures.clone(), force, ..*self } @@ -246,6 +268,7 @@ impl Options { ns: self.ns.clone(), db: self.db.clone(), force: self.force.clone(), + futures: self.futures.clone(), strict, ..*self } @@ -259,6 +282,7 @@ impl Options { ns: self.ns.clone(), db: self.db.clone(), force: self.force.clone(), + futures: self.futures.clone(), import, ..*self } @@ -272,7 +296,13 @@ impl Options { ns: self.ns.clone(), db: self.db.clone(), force: self.force.clone(), - futures, + futures: match self.futures { + Futures::Never => Futures::Never, + _ => match futures { + true => Futures::Enabled, + false => Futures::Disabled, + }, + }, ..*self } } @@ -285,6 +315,7 @@ impl Options { ns: self.ns.clone(), db: self.db.clone(), force: self.force.clone(), + futures: self.futures.clone(), projections, ..*self } @@ -297,6 +328,7 @@ impl Options { ns: self.ns.clone(), db: self.db.clone(), force: self.force.clone(), + futures: self.futures.clone(), sender: Some(sender), ..*self } @@ -326,6 +358,7 @@ impl Options { ns: self.ns.clone(), db: self.db.clone(), force: self.force.clone(), + futures: self.futures.clone(), dive: self.dive - cost as u32, ..*self }) @@ -511,4 +544,21 @@ mod tests { .unwrap(); } } + + #[test] + pub fn execute_futures() { + let mut opts = Options::default().with_futures(false); + + // Futures should be disabled + assert!(matches!(opts.futures, Futures::Disabled)); + + // Allow setting to true + opts = opts.with_futures(true); + assert!(matches!(opts.futures, Futures::Enabled)); + + // Set to never and disallow setting to true + opts = opts.with_futures_never(); + opts = opts.with_futures(true); + assert!(matches!(opts.futures, Futures::Never)); + } } diff --git a/core/src/rpc/format/cbor/convert.rs b/core/src/rpc/format/cbor/convert.rs index d9827b8f..615850dd 100644 --- a/core/src/rpc/format/cbor/convert.rs +++ b/core/src/rpc/format/cbor/convert.rs @@ -11,6 +11,7 @@ use crate::sql::id::range::IdRange; use crate::sql::Array; use crate::sql::Datetime; use crate::sql::Duration; +use crate::sql::Future; use crate::sql::Geometry; use crate::sql::Id; use crate::sql::Number; @@ -35,6 +36,7 @@ const TAG_STRING_DECIMAL: u64 = 10; const TAG_CUSTOM_DATETIME: u64 = 12; const TAG_STRING_DURATION: u64 = 13; const TAG_CUSTOM_DURATION: u64 = 14; +const TAG_FUTURE: u64 = 15; // Ranges const TAG_RANGE: u64 = 49; @@ -193,6 +195,14 @@ impl TryFrom for Value { }, // A range TAG_RANGE => Ok(Value::Range(Box::new(Range::try_from(*v)?))), + TAG_FUTURE => match *v { + Data::Text(v) => { + let block = crate::syn::block(format!("{{{v}}}").as_str()) + .map_err(|_| "Failed to parse block")?; + Ok(Value::Future(Box::new(Future(block)))) + } + _ => Err("Expected a CBOR text data type"), + }, TAG_GEOMETRY_POINT => match *v { Data::Array(mut v) if v.len() == 2 => { let x = Value::try_from(Cbor(v.remove(0)))?; @@ -395,6 +405,10 @@ impl TryFrom for Cbor { Value::Table(v) => Ok(Cbor(Data::Tag(TAG_TABLE, Box::new(Data::Text(v.0))))), Value::Geometry(v) => Ok(Cbor(encode_geometry(v)?)), Value::Range(v) => Ok(Cbor(Data::try_from(*v)?)), + Value::Future(v) => { + let bin = Data::Text(format!("{}", (*v).0)); + Ok(Cbor(Data::Tag(TAG_FUTURE, Box::new(bin)))) + } // We shouldn't reach here _ => Err("Found unsupported SurrealQL value being encoded into a CBOR value"), } diff --git a/core/src/sql/future.rs b/core/src/sql/future.rs index 04d2ce1b..26966e9e 100644 --- a/core/src/sql/future.rs +++ b/core/src/sql/future.rs @@ -1,9 +1,9 @@ -use crate::ctx::Context; use crate::dbs::Options; use crate::doc::CursorDoc; use crate::err::Error; use crate::sql::block::Block; use crate::sql::value::Value; +use crate::{ctx::Context, dbs::Futures}; use reblessive::tree::Stk; use revision::revisioned; use serde::{Deserialize, Serialize}; @@ -35,8 +35,8 @@ impl Future { ) -> Result { // Process the future if enabled match opt.futures { - true => stk.run(|stk| self.0.compute(stk, ctx, opt, doc)).await?.ok(), - false => Ok(self.clone().into()), + Futures::Enabled => stk.run(|stk| self.0.compute(stk, ctx, opt, doc)).await?.ok(), + _ => Ok(self.clone().into()), } } } diff --git a/core/src/syn/mod.rs b/core/src/syn/mod.rs index ac92b96f..1c7f2a42 100644 --- a/core/src/syn/mod.rs +++ b/core/src/syn/mod.rs @@ -2,7 +2,7 @@ use crate::{ err::Error, - sql::{Datetime, Duration, Idiom, Query, Range, Subquery, Thing, Value}, + sql::{Block, Datetime, Duration, Idiom, Query, Range, Subquery, Thing, Value}, }; pub mod common; @@ -21,6 +21,7 @@ mod test; use parser::Parser; use reblessive::Stack; +use token::t; /// Takes a string and returns if it could be a reserved keyword in certain contexts. pub fn could_be_reserved_keyword(s: &str) -> bool { @@ -168,3 +169,32 @@ pub fn thing(input: &str) -> Result { .map_err(|e| e.render_on(input)) .map_err(Error::InvalidQuery) } + +/// Parse a block, expects the value to be wrapped in `{}`. +pub fn block(input: &str) -> Result { + debug!("parsing block, input = {input}"); + + let mut parser = Parser::new(input.as_bytes()); + let mut stack = Stack::new(); + + match parser.peek_kind() { + t!("{") => { + let start = parser.pop_peek().span; + stack + .enter(|stk| parser.parse_block(stk, start)) + .finish() + .map_err(|e| e.render_on(input)) + .map_err(Error::InvalidQuery) + } + found => Err(Error::InvalidQuery( + crate::syn::parser::ParseError::new( + crate::syn::parser::ParseErrorKind::Unexpected { + expected: "{", + found, + }, + parser.last_span(), + ) + .render_on(input), + )), + } +} diff --git a/core/src/syn/parser/object.rs b/core/src/syn/parser/object.rs index ed0981ef..54df6a1f 100644 --- a/core/src/syn/parser/object.rs +++ b/core/src/syn/parser/object.rs @@ -637,7 +637,7 @@ impl Parser<'_> { /// # Parser State /// Expects the starting `{` to have already been eaten and its span to be handed to this /// functions as the `start` parameter. - pub(super) async fn parse_block(&mut self, ctx: &mut Stk, start: Span) -> ParseResult { + pub async fn parse_block(&mut self, ctx: &mut Stk, start: Span) -> ParseResult { let mut statements = Vec::new(); loop { while self.eat(t!(";")) {} diff --git a/lib/tests/future.rs b/lib/tests/future.rs index a9e265f5..55a1ebfa 100644 --- a/lib/tests/future.rs +++ b/lib/tests/future.rs @@ -67,6 +67,24 @@ async fn future_function_arguments() -> Result<(), Error> { Ok(()) } +#[tokio::test] +async fn future_disabled() -> Result<(), Error> { + let sql = " + OPTION FUTURES = false; + { 123 }; + "; + let dbs = new_ds().await?; + let ses = Session::owner().with_ns("test").with_db("test"); + let res = &mut dbs.execute(sql, &ses, None).await?; + assert_eq!(res.len(), 1); + // + let tmp = res.remove(0).result?; + let val = Value::parse(" { 123 }"); + assert_eq!(tmp, val); + // + Ok(()) +} + #[tokio::test] async fn concurrency() -> Result<(), Error> { // cargo test --package surrealdb --test future --features kv-mem --release -- concurrency --nocapture