Add ONLY keyword for single result outputs (#2624)

This commit is contained in:
Tobie Morgan Hitchcock 2023-09-05 16:22:13 +01:00 committed by GitHub
parent e008745a54
commit e316498e96
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 418 additions and 288 deletions

View file

@ -23,7 +23,7 @@ args = ["doc", "--open", "--no-deps", "--package", "surrealdb", "--features", "r
[tasks.test]
category = "LOCAL USAGE"
command = "cargo"
args = ["test", "--workspace"]
args = ["test", "--workspace", "--no-fail-fast"]
# Check
[tasks.cargo-check]

View file

@ -428,6 +428,10 @@ pub enum Error {
value: String,
},
/// Can not execute CREATE statement using the specified value
#[error("Expected a single result output when using the ONLY keyword")]
SingleOnlyOutput,
/// The permissions do not allow this query to be run on this table
#[error("You don't have permission to run this query on the `{table}` table")]
TablePermissions {

View file

@ -21,8 +21,10 @@ use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[revisioned(revision = 1)]
#[revisioned(revision = 2)]
pub struct CreateStatement {
#[revision(start = 2)]
pub only: bool,
pub what: Values,
pub data: Option<Data>,
pub output: Option<Output>,
@ -35,15 +37,6 @@ impl CreateStatement {
pub(crate) fn writeable(&self) -> bool {
true
}
/// Check if this statement is for a single record
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,
}
}
/// Process this type returning a computed simple Value
pub(crate) async fn compute(
&self,
@ -73,13 +66,27 @@ impl CreateStatement {
})?;
}
// Output the results
i.output(ctx, opt, txn, &stm).await
match i.output(ctx, opt, txn, &stm).await? {
// This is a single record result
Value::Array(mut a) if self.only => match a.len() {
// There was exactly one result
v if v == 1 => Ok(a.remove(0)),
// There were no results
_ => Err(Error::SingleOnlyOutput),
},
// This is standard query result
v => Ok(v),
}
}
}
impl fmt::Display for CreateStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "CREATE {}", self.what)?;
write!(f, "CREATE")?;
if self.only {
f.write_str(" ONLY")?
}
write!(f, " {}", self.what)?;
if let Some(ref v) = self.data {
write!(f, " {v}")?
}
@ -98,6 +105,7 @@ impl fmt::Display for CreateStatement {
pub fn create(i: &str) -> IResult<&str, CreateStatement> {
let (i, _) = tag_no_case("CREATE")(i)?;
let (i, only) = opt(preceded(shouldbespace, tag_no_case("ONLY")))(i)?;
let (i, _) = shouldbespace(i)?;
let (i, what) = whats(i)?;
let (i, (data, output, timeout, parallel)) = cut(|i| {
@ -110,6 +118,7 @@ pub fn create(i: &str) -> IResult<&str, CreateStatement> {
Ok((
i,
CreateStatement {
only: only.is_some(),
what,
data,
output,

View file

@ -16,14 +16,15 @@ use nom::bytes::complete::tag_no_case;
use nom::combinator::cut;
use nom::combinator::opt;
use nom::sequence::preceded;
use nom::sequence::terminated;
use revision::revisioned;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[revisioned(revision = 1)]
#[revisioned(revision = 2)]
pub struct DeleteStatement {
#[revision(start = 2)]
pub only: bool,
pub what: Values,
pub cond: Option<Cond>,
pub output: Option<Output>,
@ -36,14 +37,6 @@ impl DeleteStatement {
pub(crate) fn writeable(&self) -> bool {
true
}
/// Check if this statement is for a single record
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,
}
}
/// Process this type returning a computed simple Value
pub(crate) async fn compute(
&self,
@ -73,13 +66,27 @@ impl DeleteStatement {
})?;
}
// Output the results
i.output(ctx, opt, txn, &stm).await
match i.output(ctx, opt, txn, &stm).await? {
// This is a single record result
Value::Array(mut a) if self.only => match a.len() {
// There was exactly one result
v if v == 1 => Ok(a.remove(0)),
// There were no results
_ => Err(Error::SingleOnlyOutput),
},
// This is standard query result
v => Ok(v),
}
}
}
impl fmt::Display for DeleteStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "DELETE {}", self.what)?;
write!(f, "DELETE")?;
if self.only {
f.write_str(" ONLY")?
}
write!(f, " {}", self.what)?;
if let Some(ref v) = self.cond {
write!(f, " {v}")?
}
@ -98,8 +105,9 @@ impl fmt::Display for DeleteStatement {
pub fn delete(i: &str) -> IResult<&str, DeleteStatement> {
let (i, _) = tag_no_case("DELETE")(i)?;
let (i, _) = opt(preceded(shouldbespace, tag_no_case("FROM")))(i)?;
let (i, only) = opt(preceded(shouldbespace, tag_no_case("ONLY")))(i)?;
let (i, _) = shouldbespace(i)?;
let (i, _) = opt(terminated(tag_no_case("FROM"), shouldbespace))(i)?;
let (i, what) = whats(i)?;
let (i, (cond, output, timeout, parallel)) = cut(|i| {
let (i, cond) = opt(preceded(shouldbespace, cond))(i)?;
@ -111,6 +119,7 @@ pub fn delete(i: &str) -> IResult<&str, DeleteStatement> {
Ok((
i,
DeleteStatement {
only: only.is_some(),
what,
cond,
output,

View file

@ -41,14 +41,6 @@ impl InsertStatement {
pub(crate) fn writeable(&self) -> bool {
true
}
/// Check if this statement is for a single record
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,
}
}
/// Process this type returning a computed simple Value
pub(crate) async fn compute(
&self,

View file

@ -30,8 +30,10 @@ use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[revisioned(revision = 1)]
#[revisioned(revision = 2)]
pub struct RelateStatement {
#[revision(start = 2)]
pub only: bool,
pub kind: Value,
pub from: Value,
pub with: Value,
@ -47,16 +49,6 @@ impl RelateStatement {
pub(crate) fn writeable(&self) -> bool {
true
}
/// Check if this statement is for a single record
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,
}
}
/// Process this type returning a computed simple Value
pub(crate) async fn compute(
&self,
@ -183,13 +175,27 @@ impl RelateStatement {
// Assign the statement
let stm = Statement::from(self);
// Output the results
i.output(ctx, opt, txn, &stm).await
match i.output(ctx, opt, txn, &stm).await? {
// This is a single record result
Value::Array(mut a) if self.only => match a.len() {
// There was exactly one result
v if v == 1 => Ok(a.remove(0)),
// There were no results
_ => Err(Error::SingleOnlyOutput),
},
// This is standard query result
v => Ok(v),
}
}
}
impl fmt::Display for RelateStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "RELATE {} -> {} -> {}", self.from, self.kind, self.with)?;
write!(f, "RELATE")?;
if self.only {
f.write_str(" ONLY")?
}
write!(f, " {} -> {} -> {}", self.from, self.kind, self.with)?;
if self.uniq {
f.write_str(" UNIQUE")?
}
@ -211,6 +217,7 @@ impl fmt::Display for RelateStatement {
pub fn relate(i: &str) -> IResult<&str, RelateStatement> {
let (i, _) = tag_no_case("RELATE")(i)?;
let (i, only) = opt(preceded(shouldbespace, tag_no_case("ONLY")))(i)?;
let (i, _) = shouldbespace(i)?;
let (i, path) = relate_oi(i)?;
let (i, uniq) = opt(preceded(shouldbespace, tag_no_case("UNIQUE")))(i)?;
@ -221,6 +228,7 @@ pub fn relate(i: &str) -> IResult<&str, RelateStatement> {
Ok((
i,
RelateStatement {
only: only.is_some(),
kind: path.0,
from: path.1,
with: path.2,

View file

@ -36,10 +36,12 @@ use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[revisioned(revision = 1)]
#[revisioned(revision = 2)]
pub struct SelectStatement {
pub expr: Fields,
pub omit: Option<Idioms>,
#[revision(start = 2)]
pub only: bool,
pub what: Values,
pub with: Option<With>,
pub cond: Option<Cond>,
@ -72,14 +74,6 @@ impl SelectStatement {
}
self.cond.as_ref().map_or(false, |v| v.writeable())
}
/// Check if this statement is for a single record
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,
}
}
/// Process this type returning a computed simple Value
pub(crate) async fn compute(
&self,
@ -131,17 +125,25 @@ impl SelectStatement {
v => i.ingest(Iterable::Value(v)),
};
}
// Create a new context
let mut ctx = Context::new(ctx);
// Assign the statement
let stm = Statement::from(self);
// Add query executors if any
if planner.has_executors() {
let mut ctx = Context::new(ctx);
ctx.set_query_planner(&planner);
// Output the results
i.output(&ctx, opt, txn, &stm).await
} else {
// Output the results
i.output(ctx, opt, txn, &stm).await
}
// Output the results
match i.output(&ctx, opt, txn, &stm).await? {
// This is a single record result
Value::Array(mut a) if self.only => match a.len() {
// There was exactly one result
v if v == 1 => Ok(a.remove(0)),
// There were no results
_ => Err(Error::SingleOnlyOutput),
},
// This is standard query result
v => Ok(v),
}
}
}
@ -152,7 +154,11 @@ impl fmt::Display for SelectStatement {
if let Some(ref v) = self.omit {
write!(f, " OMIT {v}")?
}
write!(f, " FROM {}", self.what)?;
write!(f, " FROM")?;
if self.only {
f.write_str(" ONLY")?
}
write!(f, " {}", self.what)?;
if let Some(ref v) = self.with {
write!(f, " {v}")?
}
@ -200,6 +206,7 @@ pub fn select(i: &str) -> IResult<&str, SelectStatement> {
let (i, omit) = opt(preceded(shouldbespace, omit))(i)?;
let (i, _) = cut(shouldbespace)(i)?;
let (i, _) = cut(tag_no_case("FROM"))(i)?;
let (i, only) = opt(preceded(shouldbespace, tag_no_case("ONLY")))(i)?;
let (i, _) = cut(shouldbespace)(i)?;
let (i, what) = cut(selects)(i)?;
let (i, with) = opt(preceded(shouldbespace, with))(i)?;
@ -222,6 +229,7 @@ pub fn select(i: &str) -> IResult<&str, SelectStatement> {
SelectStatement {
expr,
omit,
only: only.is_some(),
what,
with,
cond,

View file

@ -21,8 +21,10 @@ use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
#[revisioned(revision = 1)]
#[revisioned(revision = 2)]
pub struct UpdateStatement {
#[revision(start = 2)]
pub only: bool,
pub what: Values,
pub data: Option<Data>,
pub cond: Option<Cond>,
@ -36,14 +38,6 @@ impl UpdateStatement {
pub(crate) fn writeable(&self) -> bool {
true
}
/// Check if this statement is for a single record
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,
}
}
/// Process this type returning a computed simple Value
pub(crate) async fn compute(
&self,
@ -73,13 +67,27 @@ impl UpdateStatement {
})?;
}
// Output the results
i.output(ctx, opt, txn, &stm).await
match i.output(ctx, opt, txn, &stm).await? {
// This is a single record result
Value::Array(mut a) if self.only => match a.len() {
// There was exactly one result
v if v == 1 => Ok(a.remove(0)),
// There were no results
_ => Err(Error::SingleOnlyOutput),
},
// This is standard query result
v => Ok(v),
}
}
}
impl fmt::Display for UpdateStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "UPDATE {}", self.what)?;
write!(f, "UPDATE")?;
if self.only {
f.write_str(" ONLY")?
}
write!(f, " {}", self.what)?;
if let Some(ref v) = self.data {
write!(f, " {v}")?
}
@ -101,6 +109,7 @@ impl fmt::Display for UpdateStatement {
pub fn update(i: &str) -> IResult<&str, UpdateStatement> {
let (i, _) = tag_no_case("UPDATE")(i)?;
let (i, only) = opt(preceded(shouldbespace, tag_no_case("ONLY")))(i)?;
let (i, _) = shouldbespace(i)?;
let (i, what) = whats(i)?;
let (i, data) = opt(preceded(shouldbespace, data))(i)?;
@ -111,6 +120,7 @@ pub fn update(i: &str) -> IResult<&str, UpdateStatement> {
Ok((
i,
UpdateStatement {
only: only.is_some(),
what,
data,
cond,

View file

@ -75,145 +75,25 @@ impl Subquery {
txn: &Transaction,
doc: Option<&CursorDoc<'_>>,
) -> Result<Value, Error> {
// Duplicate context
let mut ctx = Context::new(ctx);
// Add parent document
if let Some(doc) = doc {
ctx.add_value("parent", doc.doc.as_ref());
}
// Process the subquery
match self {
Self::Value(ref v) => v.compute(ctx, opt, txn, doc).await,
Self::Ifelse(ref v) => v.compute(ctx, opt, txn, doc).await,
Self::Output(ref v) => v.compute(ctx, opt, txn, doc).await,
Self::Define(ref v) => v.compute(ctx, opt, txn, doc).await,
Self::Remove(ref v) => v.compute(ctx, opt, txn, doc).await,
Self::Select(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", doc.doc.as_ref());
}
// 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::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", doc.doc.as_ref());
}
// 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
if let Some(doc) = doc {
ctx.add_value("parent", doc.doc.as_ref());
}
// 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::Delete(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", doc.doc.as_ref());
}
// 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::Relate(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", doc.doc.as_ref());
}
// 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::Insert(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", doc.doc.as_ref());
}
// 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::Value(ref v) => v.compute(&ctx, opt, txn, doc).await,
Self::Ifelse(ref v) => v.compute(&ctx, opt, txn, doc).await,
Self::Output(ref v) => v.compute(&ctx, opt, txn, doc).await,
Self::Define(ref v) => v.compute(&ctx, opt, txn, doc).await,
Self::Remove(ref v) => v.compute(&ctx, opt, txn, doc).await,
Self::Select(ref v) => v.compute(&ctx, opt, txn, doc).await,
Self::Create(ref v) => v.compute(&ctx, opt, txn, doc).await,
Self::Update(ref v) => v.compute(&ctx, opt, txn, doc).await,
Self::Delete(ref v) => v.compute(&ctx, opt, txn, doc).await,
Self::Relate(ref v) => v.compute(&ctx, opt, txn, doc).await,
Self::Insert(ref v) => v.compute(&ctx, opt, txn, doc).await,
}
}
}

View file

@ -39,6 +39,7 @@ impl ser::Serializer for Serializer {
#[derive(Default)]
pub struct SerializeCreateStatement {
only: Option<bool>,
what: Option<Values>,
data: Option<Data>,
output: Option<Output>,
@ -55,6 +56,9 @@ impl serde::ser::SerializeStruct for SerializeCreateStatement {
T: ?Sized + Serialize,
{
match key {
"only" => {
self.only = Some(value.serialize(ser::primitive::bool::Serializer.wrap())?);
}
"what" => {
self.what = Some(Values(value.serialize(ser::value::vec::Serializer.wrap())?));
}
@ -82,6 +86,7 @@ impl serde::ser::SerializeStruct for SerializeCreateStatement {
fn end(self) -> Result<Self::Ok, Error> {
match (self.what, self.parallel) {
(Some(what), Some(parallel)) => Ok(CreateStatement {
only: self.only.is_some_and(|v| v),
what,
parallel,
data: self.data,

View file

@ -38,6 +38,7 @@ impl ser::Serializer for Serializer {
#[derive(Default)]
pub struct SerializeDeleteStatement {
only: Option<bool>,
what: Option<Values>,
cond: Option<Cond>,
output: Option<Output>,
@ -54,6 +55,9 @@ impl serde::ser::SerializeStruct for SerializeDeleteStatement {
T: ?Sized + Serialize,
{
match key {
"only" => {
self.only = Some(value.serialize(ser::primitive::bool::Serializer.wrap())?);
}
"what" => {
self.what = Some(Values(value.serialize(ser::value::vec::Serializer.wrap())?));
}
@ -79,6 +83,7 @@ impl serde::ser::SerializeStruct for SerializeDeleteStatement {
fn end(self) -> Result<Self::Ok, Error> {
match (self.what, self.parallel) {
(Some(what), Some(parallel)) => Ok(DeleteStatement {
only: self.only.is_some_and(|v| v),
what,
parallel,
cond: self.cond,

View file

@ -38,6 +38,7 @@ impl ser::Serializer for Serializer {
#[derive(Default)]
pub struct SerializeRelateStatement {
only: Option<bool>,
kind: Option<Value>,
from: Option<Value>,
with: Option<Value>,
@ -57,6 +58,9 @@ impl serde::ser::SerializeStruct for SerializeRelateStatement {
T: ?Sized + Serialize,
{
match key {
"only" => {
self.only = Some(value.serialize(ser::primitive::bool::Serializer.wrap())?);
}
"kind" => {
self.kind = Some(value.serialize(ser::value::Serializer.wrap())?);
}
@ -92,6 +96,7 @@ impl serde::ser::SerializeStruct for SerializeRelateStatement {
match (self.kind, self.from, self.with, self.uniq, self.parallel) {
(Some(kind), Some(from), Some(with), Some(uniq), Some(parallel)) => {
Ok(RelateStatement {
only: self.only.is_some_and(|v| v),
kind,
from,
with,

View file

@ -50,6 +50,7 @@ impl ser::Serializer for Serializer {
pub struct SerializeSelectStatement {
expr: Option<Fields>,
omit: Option<Idioms>,
only: Option<bool>,
what: Option<Values>,
with: Option<With>,
cond: Option<Cond>,
@ -80,6 +81,9 @@ impl serde::ser::SerializeStruct for SerializeSelectStatement {
"omit" => {
self.omit = value.serialize(ser::idiom::vec::opt::Serializer.wrap())?.map(Idioms);
}
"only" => {
self.only = Some(value.serialize(ser::primitive::bool::Serializer.wrap())?);
}
"what" => {
self.what = Some(Values(value.serialize(ser::value::vec::Serializer.wrap())?));
}
@ -131,6 +135,7 @@ impl serde::ser::SerializeStruct for SerializeSelectStatement {
(Some(expr), Some(what), Some(parallel)) => Ok(SelectStatement {
expr,
omit: self.omit,
only: self.only.is_some_and(|v| v),
what,
with: self.with,
parallel,

View file

@ -40,6 +40,7 @@ impl ser::Serializer for Serializer {
#[derive(Default)]
pub struct SerializeUpdateStatement {
only: Option<bool>,
what: Option<Values>,
data: Option<Data>,
cond: Option<Cond>,
@ -57,6 +58,9 @@ impl serde::ser::SerializeStruct for SerializeUpdateStatement {
T: ?Sized + Serialize,
{
match key {
"only" => {
self.only = Some(value.serialize(ser::primitive::bool::Serializer.wrap())?);
}
"what" => {
self.what = Some(Values(value.serialize(ser::value::vec::Serializer.wrap())?));
}
@ -87,6 +91,7 @@ impl serde::ser::SerializeStruct for SerializeUpdateStatement {
fn end(self) -> Result<Self::Ok, Error> {
match (self.what, self.parallel) {
(Some(what), Some(parallel)) => Ok(UpdateStatement {
only: self.only.is_some_and(|v| v),
what,
parallel,
data: self.data,

View file

@ -173,7 +173,7 @@ async fn create_with_id() -> Result<(), Error> {
async fn create_with_custom_function() -> Result<(), Error> {
let sql = "
DEFINE FUNCTION fn::record::create($data: any) {
RETURN CREATE person:ulid() CONTENT { data: $data } RETURN AFTER;
RETURN CREATE ONLY person:ulid() CONTENT { data: $data } RETURN AFTER;
};
RETURN fn::record::create({ test: true, name: 'Tobie' });
RETURN fn::record::create({ test: true, name: 'Jaime' });

161
lib/tests/return.rs Normal file
View file

@ -0,0 +1,161 @@
mod parse;
use parse::Parse;
mod helpers;
use helpers::new_ds;
use surrealdb::dbs::Session;
use surrealdb::err::Error;
use surrealdb::sql::Value;
#[tokio::test]
async fn return_subquery_only() -> Result<(), Error> {
let sql = "
CREATE person:tobie SET name = 'Tobie';
CREATE person:jaime SET name = 'Jaime';
LET $single = person:tobie;
--
SELECT name FROM person;
SELECT VALUE name FROM person;
SELECT name FROM ONLY person;
SELECT VALUE name FROM ONLY person;
SELECT name FROM person:tobie;
SELECT VALUE name FROM person:tobie;
SELECT name FROM ONLY person:tobie;
SELECT VALUE name FROM ONLY person:tobie;
SELECT name FROM $single;
SELECT VALUE name FROM $single;
SELECT name FROM ONLY $single;
SELECT VALUE name FROM ONLY $single;
--
RETURN SELECT name FROM person;
RETURN SELECT VALUE name FROM person;
RETURN SELECT name FROM ONLY person;
RETURN SELECT VALUE name FROM ONLY person;
RETURN SELECT name FROM person:tobie;
RETURN SELECT VALUE name FROM person:tobie;
RETURN SELECT name FROM ONLY person:tobie;
RETURN SELECT VALUE name FROM ONLY person:tobie;
RETURN SELECT name FROM $single;
RETURN SELECT VALUE name FROM $single;
RETURN SELECT name FROM ONLY $single;
RETURN SELECT VALUE name FROM ONLY $single;
";
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(), 27);
//
let tmp = res.remove(0).result;
assert!(tmp.is_ok());
//
let tmp = res.remove(0).result;
assert!(tmp.is_ok());
//
let tmp = res.remove(0).result;
assert!(tmp.is_ok());
//
let tmp = res.remove(0).result?;
let val = Value::parse("[{ name: 'Jaime' }, { name: 'Tobie' }]");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("['Jaime', 'Tobie']");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result;
assert!(matches!(
tmp.err(),
Some(e) if e.to_string() == r#"Expected a single result output when using the ONLY keyword"#
));
//
let tmp = res.remove(0).result;
assert!(matches!(
tmp.err(),
Some(e) if e.to_string() == r#"Expected a single result output when using the ONLY keyword"#
));
//
let tmp = res.remove(0).result?;
let val = Value::parse("[{ name: 'Tobie' }]");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("['Tobie']");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("{ name: 'Tobie' }");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::from("Tobie");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("[{ name: 'Tobie' }]");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("['Tobie']");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("{ name: 'Tobie' }");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::from("Tobie");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("[{ name: 'Jaime' }, { name: 'Tobie' }]");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("['Jaime', 'Tobie']");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result;
assert!(matches!(
tmp.err(),
Some(e) if e.to_string() == r#"Expected a single result output when using the ONLY keyword"#
));
//
let tmp = res.remove(0).result;
assert!(matches!(
tmp.err(),
Some(e) if e.to_string() == r#"Expected a single result output when using the ONLY keyword"#
));
//
let tmp = res.remove(0).result?;
let val = Value::parse("[{ name: 'Tobie' }]");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("['Tobie']");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("{ name: 'Tobie' }");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::from("Tobie");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("[{ name: 'Tobie' }]");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("['Tobie']");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse("{ name: 'Tobie' }");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::from("Tobie");
assert_eq!(tmp, val);
//
Ok(())
}

View file

@ -252,7 +252,7 @@ async fn script_query_from_script_select() -> Result<(), Error> {
async fn script_query_from_script() -> Result<(), Error> {
let sql = r#"
RETURN function() {
return await surrealdb.query(`CREATE article:test SET name = "The daily news", issue_number = 3`)
return await surrealdb.query(`CREATE ONLY article:test SET name = "The daily news", issue_number = 3`)
}
"#;
let dbs = new_ds().await?;
@ -289,7 +289,7 @@ async fn script_query_from_script() -> Result<(), Error> {
#[tokio::test]
async fn script_value_function_params() -> Result<(), Error> {
let sql = r#"
LET $test = CREATE article:test SET name = "The daily news", issue_number = 3;
LET $test = CREATE ONLY article:test SET name = "The daily news", issue_number = 3;
RETURN function() {
return await surrealdb.value(`$test.name`)
}

View file

@ -370,9 +370,11 @@ async fn select_writeable_subqueries() -> Result<(), Error> {
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"{
id: tester:test
}",
"[
{
id: tester:test
}
]",
);
assert_eq!(tmp, val);
//
@ -380,7 +382,7 @@ async fn select_writeable_subqueries() -> Result<(), Error> {
assert!(tmp.is_ok());
//
let tmp = res.remove(0).result?;
let val = Value::parse("tester:test");
let val = Value::parse("[tester:test]");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result;

View file

@ -73,9 +73,11 @@ async fn subquery_select() -> Result<(), Error> {
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"{
adult: true
}",
"[
{
adult: true
}
]",
);
assert_eq!(tmp, val);
//
@ -146,16 +148,18 @@ async fn subquery_ifelse_set() -> Result<(), Error> {
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::None;
let val = Value::parse("[]");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"{
sport: [
'basketball',
]
}",
"[
{
sport: [
'basketball',
]
}
]",
);
assert_eq!(tmp, val);
//
@ -165,24 +169,28 @@ async fn subquery_ifelse_set() -> Result<(), Error> {
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"{
count: 1,
id: person:test,
sport: [
'basketball',
]
}",
"[
{
count: 1,
id: person:test,
sport: [
'basketball',
]
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"{
sport: [
'basketball',
'football',
]
}",
"[
{
sport: [
'basketball',
'football',
]
}
]",
);
assert_eq!(tmp, val);
//
@ -192,25 +200,29 @@ async fn subquery_ifelse_set() -> Result<(), Error> {
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"{
count: 1,
id: person:test,
sport: [
'basketball',
'football',
]
}",
"[
{
count: 1,
id: person:test,
sport: [
'basketball',
'football',
]
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"{
sport: [
'basketball',
'football',
]
}",
"[
{
sport: [
'basketball',
'football',
]
}
]",
);
assert_eq!(tmp, val);
//
@ -261,16 +273,18 @@ async fn subquery_ifelse_array() -> Result<(), Error> {
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::None;
let val = Value::parse("[]");
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"{
sport: [
'basketball',
]
}",
"[
{
sport: [
'basketball',
]
}
]",
);
assert_eq!(tmp, val);
//
@ -280,24 +294,28 @@ async fn subquery_ifelse_array() -> Result<(), Error> {
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"{
count: 1,
id: person:test,
sport: [
'basketball',
]
}",
"[
{
count: 1,
id: person:test,
sport: [
'basketball',
]
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"{
sport: [
'basketball',
'football',
]
}",
"[
{
sport: [
'basketball',
'football',
]
}
]",
);
assert_eq!(tmp, val);
//
@ -307,26 +325,30 @@ async fn subquery_ifelse_array() -> Result<(), Error> {
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"{
count: 1,
id: person:test,
sport: [
'basketball',
'football',
]
}",
"[
{
count: 1,
id: person:test,
sport: [
'basketball',
'football',
]
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"{
sport: [
'basketball',
'football',
'football',
]
}",
"[
{
sport: [
'basketball',
'football',
'football',
]
}
]",
);
assert_eq!(tmp, val);
//