Add ONLY
keyword for single result outputs (#2624)
This commit is contained in:
parent
e008745a54
commit
e316498e96
19 changed files with 418 additions and 288 deletions
|
@ -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]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
161
lib/tests/return.rs
Normal 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(())
|
||||
}
|
|
@ -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`)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
//
|
||||
|
|
Loading…
Reference in a new issue