parent
79b5ef6411
commit
7cd8bab75c
6 changed files with 430 additions and 38 deletions
|
@ -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?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
144
lib/src/sql/value/fetch.rs
Normal 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(()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()),
|
||||
|
|
|
@ -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
280
lib/tests/fetch.rs
Normal 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(())
|
||||
}
|
Loading…
Reference in a new issue