Ensure FETCH clauses fetch the respective data correctly

Closes 
This commit is contained in:
Tobie Morgan Hitchcock 2022-09-30 21:42:16 +01:00
parent 79b5ef6411
commit 7cd8bab75c
6 changed files with 430 additions and 38 deletions

View file

@ -9,7 +9,6 @@ use crate::err::Error;
use crate::sql::array::Array;
use crate::sql::edges::Edges;
use crate::sql::field::Field;
use crate::sql::part::Part;
use crate::sql::range::Range;
use crate::sql::table::Table;
use crate::sql::thing::Thing;
@ -305,30 +304,11 @@ impl Iterator {
stm: &Statement<'_>,
) -> Result<(), Error> {
if let Some(fetchs) = stm.fetch() {
for fetch in &fetchs.0 {
// Loop over each value
for fetch in fetchs.iter() {
// Loop over each result value
for obj in &mut self.results {
// Get the value at the path
let val = obj.get(ctx, opt, txn, fetch).await?;
// Set the value at the path
match val {
Value::Array(v) => {
// Fetch all remote records
let val = Value::Array(v).get(ctx, opt, txn, &[Part::Any]).await?;
// Set the value at the path
obj.set(ctx, opt, txn, fetch, val).await?;
}
Value::Thing(v) => {
// Fetch all remote records
let val = Value::Thing(v).get(ctx, opt, txn, &[Part::All]).await?;
// Set the value at the path
obj.set(ctx, opt, txn, fetch, val).await?;
}
_ => {
// Set the value at the path
obj.set(ctx, opt, txn, fetch, val).await?;
}
}
// Fetch the value at the path
obj.fetch(ctx, opt, txn, fetch).await?;
}
}
}

View file

@ -17,7 +17,6 @@ use std::str;
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Serialize, Deserialize)]
pub enum Part {
Any,
All,
Last,
First,
@ -104,7 +103,6 @@ impl Part {
impl fmt::Display for Part {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Part::Any => write!(f, ".."),
Part::All => write!(f, "[*]"),
Part::Last => write!(f, "[$]"),
Part::First => write!(f, "[0]"),

144
lib/src/sql/value/fetch.rs Normal file
View file

@ -0,0 +1,144 @@
use crate::ctx::Context;
use crate::dbs::Options;
use crate::dbs::Transaction;
use crate::err::Error;
use crate::sql::edges::Edges;
use crate::sql::field::{Field, Fields};
use crate::sql::part::Next;
use crate::sql::part::Part;
use crate::sql::statements::select::SelectStatement;
use crate::sql::value::{Value, Values};
use async_recursion::async_recursion;
use futures::future::try_join_all;
impl Value {
#[cfg_attr(feature = "parallel", async_recursion)]
#[cfg_attr(not(feature = "parallel"), async_recursion(?Send))]
pub async fn fetch(
&mut self,
ctx: &Context<'_>,
opt: &Options,
txn: &Transaction,
path: &[Part],
) -> Result<(), Error> {
match path.first() {
// Get the current path part
Some(p) => match self {
// Current path part is an object
Value::Object(v) => match p {
Part::Graph(_) => match v.rid() {
Some(v) => Value::Thing(v).fetch(ctx, opt, txn, path.next()).await,
None => Ok(()),
},
Part::Field(f) => match v.get_mut(f as &str) {
Some(v) => v.fetch(ctx, opt, txn, path.next()).await,
None => Ok(()),
},
Part::All => self.fetch(ctx, opt, txn, path.next()).await,
_ => Ok(()),
},
// Current path part is an array
Value::Array(v) => match p {
Part::All => {
let path = path.next();
let futs = v.iter_mut().map(|v| v.fetch(ctx, opt, txn, path));
try_join_all(futs).await?;
Ok(())
}
Part::First => match v.first_mut() {
Some(v) => v.fetch(ctx, opt, txn, path.next()).await,
None => Ok(()),
},
Part::Last => match v.last_mut() {
Some(v) => v.fetch(ctx, opt, txn, path.next()).await,
None => Ok(()),
},
Part::Index(i) => match v.get_mut(i.to_usize()) {
Some(v) => v.fetch(ctx, opt, txn, path.next()).await,
None => Ok(()),
},
Part::Where(w) => {
let path = path.next();
for v in v.iter_mut() {
if w.compute(ctx, opt, txn, Some(v)).await?.is_truthy() {
v.fetch(ctx, opt, txn, path).await?;
}
}
Ok(())
}
_ => {
let futs = v.iter_mut().map(|v| v.fetch(ctx, opt, txn, path));
try_join_all(futs).await?;
Ok(())
}
},
// Current path part is a thing
Value::Thing(v) => {
// Clone the thing
let val = v.clone();
// Fetch the remote embedded record
match p {
// This is a graph traversal expression
Part::Graph(g) => {
let stm = SelectStatement {
expr: Fields(vec![Field::All]),
what: Values(vec![Value::from(Edges {
from: val,
dir: g.dir.clone(),
what: g.what.clone(),
})]),
cond: g.cond.clone(),
..SelectStatement::default()
};
*self = stm
.compute(ctx, opt, txn, None)
.await?
.all()
.get(ctx, opt, txn, path.next())
.await?
.flatten()
.ok()?;
Ok(())
}
// This is a remote field expression
_ => {
let stm = SelectStatement {
expr: Fields(vec![Field::All]),
what: Values(vec![Value::from(val)]),
..SelectStatement::default()
};
*self = stm.compute(ctx, opt, txn, None).await?.first();
Ok(())
}
}
}
// Ignore everything else
_ => Ok(()),
},
// No more parts so get the value
None => match self {
// Current path part is an array
Value::Array(v) => {
let futs = v.iter_mut().map(|v| v.fetch(ctx, opt, txn, path));
try_join_all(futs).await?;
Ok(())
}
// Current path part is a thing
Value::Thing(v) => {
// Clone the thing
let val = v.clone();
// Fetch the remote embedded record
let stm = SelectStatement {
expr: Fields(vec![Field::All]),
what: Values(vec![Value::from(val)]),
..SelectStatement::default()
};
*self = stm.compute(ctx, opt, txn, None).await?.first();
Ok(())
}
// Ignore everything else
_ => Ok(()),
},
}
}
}

View file

@ -36,7 +36,6 @@ impl Value {
None => Ok(Value::None),
},
Part::All => self.get(ctx, opt, txn, path.next()).await,
Part::Any => self.get(ctx, opt, txn, path.next()).await,
_ => Ok(Value::None),
},
// Current path part is an array
@ -46,10 +45,6 @@ impl Value {
let futs = v.iter().map(|v| v.get(ctx, opt, txn, path));
try_join_all(futs).await.map(Into::into)
}
Part::Any => {
let futs = v.iter().map(|v| v.get(ctx, opt, txn, path));
try_join_all(futs).await.map(Into::into)
}
Part::First => match v.first() {
Some(v) => v.get(ctx, opt, txn, path.next()).await,
None => Ok(Value::None),
@ -135,13 +130,7 @@ impl Value {
}
}
// Ignore everything else
_ => match p {
Part::Any => match path.len() {
1 => Ok(self.clone()),
_ => Ok(Value::None),
},
_ => Ok(Value::None),
},
_ => Ok(Value::None),
},
// No more parts so get the value
None => Ok(self.clone()),

View file

@ -13,6 +13,7 @@ mod del;
mod diff;
mod each;
mod every;
mod fetch;
mod first;
mod flatten;
mod generate;

280
lib/tests/fetch.rs Normal file
View file

@ -0,0 +1,280 @@
mod parse;
use parse::Parse;
use surrealdb::sql::Value;
use surrealdb::Datastore;
use surrealdb::Error;
use surrealdb::Session;
#[tokio::test]
async fn create_relate_select() -> Result<(), Error> {
let sql = "
CREATE user:tobie SET name = 'Tobie';
CREATE user:jaime SET name = 'Jaime';
CREATE product:phone SET price = 1000;
CREATE product:laptop SET price = 3000;
RELATE user:tobie->bought->product:phone SET id = bought:1, payment_method = 'VISA';
RELATE user:tobie->bought->product:laptop SET id = bought:2, payment_method = 'VISA';
RELATE user:jaime->bought->product:laptop SET id = bought:3, payment_method = 'VISA';
SELECT *, ->bought AS purchases FROM user;
SELECT *, ->bought.out.* AS products FROM user;
SELECT *, ->bought->product.* AS products FROM user;
SELECT *, ->bought AS products FROM user FETCH products;
SELECT *, ->(bought AS purchases) FROM user FETCH purchases, purchases.out;
";
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(), 12);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
id: user:tobie,
name: 'Tobie'
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
id: user:jaime,
name: 'Jaime'
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
id: product:phone,
price: 1000
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
id: product:laptop,
price: 3000
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
'id': bought:1,
'in': user:tobie,
'out': product:phone,
'payment_method': 'VISA'
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
'id': bought:2,
'in': user:tobie,
'out': product:laptop,
'payment_method': 'VISA'
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
'id': bought:3,
'in': user:jaime,
'out': product:laptop,
'payment_method': 'VISA'
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
id: user:jaime,
name: 'Jaime',
purchases: [
bought:3
]
},
{
id: user:tobie,
name: 'Tobie',
purchases: [
bought:1,
bought:2
]
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
id: user:jaime,
name: 'Jaime',
products: [
{
id: product:laptop,
price: 3000
}
]
},
{
id: user:tobie,
name: 'Tobie',
products: [
{
id: product:phone,
price: 1000
},
{
id: product:laptop,
price: 3000
}
]
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
id: user:jaime,
name: 'Jaime',
products: [
{
id: product:laptop,
price: 3000
}
]
},
{
id: user:tobie,
name: 'Tobie',
products: [
{
id: product:phone,
price: 1000
},
{
id: product:laptop,
price: 3000
}
]
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
id: user:jaime,
name: 'Jaime',
products: [
{
id: bought:3,
in: user:jaime,
out: product:laptop,
payment_method: 'VISA'
}
]
},
{
id: user:tobie,
name: 'Tobie',
products: [
{
id: bought:1,
in: user:tobie,
out: product:phone,
payment_method: 'VISA'
},
{
id: bought:2,
in: user:tobie,
out: product:laptop,
payment_method: 'VISA'
}
]
}
]",
);
assert_eq!(tmp, val);
//
let tmp = res.remove(0).result?;
let val = Value::parse(
"[
{
id: user:jaime,
name: 'Jaime',
purchases: [
{
id: bought:3,
in: user:jaime,
out: {
id: product:laptop,
price: 3000
},
payment_method: 'VISA'
}
]
},
{
id: user:tobie,
name: 'Tobie',
purchases: [
{
id: bought:1,
in: user:tobie,
out: {
id: product:phone,
price: 1000
},
payment_method: 'VISA'
},
{
id: bought:2,
in: user:tobie,
out: {
id: product:laptop,
price: 3000
},
payment_method: 'VISA'
}
]
}
]",
);
assert_eq!(tmp, val);
//
Ok(())
}