Implement expressions in index operators (#4756)
This commit is contained in:
parent
82636d43fa
commit
912cafd3c5
11 changed files with 629 additions and 313 deletions
|
@ -250,9 +250,10 @@ impl Fields {
|
||||||
_ => {
|
_ => {
|
||||||
let expr = expr.compute(stk, ctx, opt, Some(doc)).await?;
|
let expr = expr.compute(stk, ctx, opt, Some(doc)).await?;
|
||||||
// Check if this is a single VALUE field expression
|
// Check if this is a single VALUE field expression
|
||||||
match self.single().is_some() {
|
if self.single().is_some() {
|
||||||
false => out.set(stk, ctx, opt, name.as_ref(), expr).await?,
|
out = expr;
|
||||||
true => out = expr,
|
} else {
|
||||||
|
out.set(stk, ctx, opt, name.as_ref(), expr).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,9 +116,7 @@ impl Idiom {
|
||||||
pub(crate) fn simplify(&self) -> Idiom {
|
pub(crate) fn simplify(&self) -> Idiom {
|
||||||
self.0
|
self.0
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&p| {
|
.filter(|&p| matches!(p, Part::Field(_) | Part::Start(_) | Part::Graph(_)))
|
||||||
matches!(p, Part::Field(_) | Part::Start(_) | Part::Value(_) | Part::Graph(_))
|
|
||||||
})
|
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into()
|
.into()
|
||||||
|
|
|
@ -26,6 +26,84 @@ pub struct Range {
|
||||||
pub end: Bound<Value>,
|
pub end: Bound<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Range {
|
||||||
|
pub fn slice<'a, T>(&self, s: &'a [T]) -> Option<&'a [T]> {
|
||||||
|
let r = match self.end {
|
||||||
|
Bound::Included(ref x) => {
|
||||||
|
let Value::Number(ref x) = x else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let x = x.to_usize();
|
||||||
|
s.get(..=x)?
|
||||||
|
}
|
||||||
|
Bound::Excluded(ref x) => {
|
||||||
|
let Value::Number(ref x) = x else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let x = x.to_usize();
|
||||||
|
s.get(..x)?
|
||||||
|
}
|
||||||
|
Bound::Unbounded => s,
|
||||||
|
};
|
||||||
|
let r = match self.beg {
|
||||||
|
Bound::Included(ref x) => {
|
||||||
|
let Value::Number(ref x) = x else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let x = x.to_usize();
|
||||||
|
r.get(x..)?
|
||||||
|
}
|
||||||
|
Bound::Excluded(ref x) => {
|
||||||
|
let Value::Number(ref x) = x else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let x = x.to_usize().saturating_add(1);
|
||||||
|
r.get(x..)?
|
||||||
|
}
|
||||||
|
Bound::Unbounded => r,
|
||||||
|
};
|
||||||
|
Some(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slice_mut<'a, T>(&self, s: &'a mut [T]) -> Option<&'a mut [T]> {
|
||||||
|
let r = match self.end {
|
||||||
|
Bound::Included(ref x) => {
|
||||||
|
let Value::Number(ref x) = x else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let x = x.to_usize();
|
||||||
|
s.get_mut(..x)?
|
||||||
|
}
|
||||||
|
Bound::Excluded(ref x) => {
|
||||||
|
let Value::Number(ref x) = x else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let x = x.to_usize();
|
||||||
|
s.get_mut(..=x)?
|
||||||
|
}
|
||||||
|
Bound::Unbounded => s,
|
||||||
|
};
|
||||||
|
let r = match self.beg {
|
||||||
|
Bound::Included(ref x) => {
|
||||||
|
let Value::Number(ref x) = x else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let x = x.to_usize();
|
||||||
|
r.get_mut(x..)?
|
||||||
|
}
|
||||||
|
Bound::Excluded(ref x) => {
|
||||||
|
let Value::Number(ref x) = x else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let x = x.to_usize().saturating_add(1);
|
||||||
|
r.get_mut(x..)?
|
||||||
|
}
|
||||||
|
Bound::Unbounded => r,
|
||||||
|
};
|
||||||
|
Some(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FromStr for Range {
|
impl FromStr for Range {
|
||||||
type Err = ();
|
type Err = ();
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
|
|
@ -19,10 +19,12 @@ use std::sync::Arc;
|
||||||
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct SelectStatement {
|
pub struct SelectStatement {
|
||||||
|
/// The foo,bar part in SELECT foo,bar FROM baz.
|
||||||
pub expr: Fields,
|
pub expr: Fields,
|
||||||
pub omit: Option<Idioms>,
|
pub omit: Option<Idioms>,
|
||||||
#[revision(start = 2)]
|
#[revision(start = 2)]
|
||||||
pub only: bool,
|
pub only: bool,
|
||||||
|
/// The baz part in SELECT foo,bar FROM baz.
|
||||||
pub what: Values,
|
pub what: Values,
|
||||||
pub with: Option<With>,
|
pub with: Option<With>,
|
||||||
pub cond: Option<Cond>,
|
pub cond: Option<Cond>,
|
||||||
|
|
|
@ -11,7 +11,6 @@ use futures::future::try_join_all;
|
||||||
use reblessive::tree::Stk;
|
use reblessive::tree::Stk;
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
/// Was marked recursive
|
|
||||||
pub(crate) async fn fetch(
|
pub(crate) async fn fetch(
|
||||||
&mut self,
|
&mut self,
|
||||||
stk: &mut Stk,
|
stk: &mut Stk,
|
||||||
|
@ -19,152 +18,222 @@ impl Value {
|
||||||
opt: &Options,
|
opt: &Options,
|
||||||
path: &[Part],
|
path: &[Part],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
match path.first() {
|
let mut this = self;
|
||||||
// Get the current path part
|
let mut iter = path.iter();
|
||||||
Some(p) => match self {
|
let mut prev = path;
|
||||||
// Current path part is an object
|
|
||||||
Value::Object(v) => match p {
|
// Loop over the path.
|
||||||
Part::Graph(_) => match v.rid() {
|
// If the we just need to select a sub section of a value we update this to point to the
|
||||||
Some(v) => {
|
// new subsection of the value. Otherwise we call into fetch again and then immediately
|
||||||
let mut v = Value::Thing(v);
|
// return.
|
||||||
stk.run(|stk| v.fetch(stk, ctx, opt, path.next())).await
|
// If we encounter a idiom application which does not make sense, like `(1).foo` just
|
||||||
|
// return Ok(())
|
||||||
|
while let Some(p) = iter.next() {
|
||||||
|
match p {
|
||||||
|
Part::Graph(g) => match this {
|
||||||
|
Value::Object(o) => {
|
||||||
|
let Some(v) = o.rid() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut v = Value::Thing(v);
|
||||||
|
return stk.run(|stk| v.fetch(stk, ctx, opt, iter.as_slice())).await;
|
||||||
|
}
|
||||||
|
Value::Thing(x) => {
|
||||||
|
let stm = SelectStatement {
|
||||||
|
expr: Fields(vec![Field::All], false),
|
||||||
|
what: Values(vec![Value::from(Edges {
|
||||||
|
from: x.clone(),
|
||||||
|
dir: g.dir.clone(),
|
||||||
|
what: g.what.clone(),
|
||||||
|
})]),
|
||||||
|
cond: g.cond.clone(),
|
||||||
|
..SelectStatement::default()
|
||||||
|
};
|
||||||
|
*this = stm
|
||||||
|
.compute(stk, ctx, opt, None)
|
||||||
|
.await?
|
||||||
|
.all()
|
||||||
|
.get(stk, ctx, opt, None, path.next())
|
||||||
|
.await?
|
||||||
|
.flatten()
|
||||||
|
.ok()?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Value::Array(x) => {
|
||||||
|
// apply this path to every entry of the array.
|
||||||
|
stk.scope(|scope| {
|
||||||
|
let futs =
|
||||||
|
x.iter_mut().map(|v| scope.run(|stk| v.fetch(stk, ctx, opt, prev)));
|
||||||
|
try_join_all(futs)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
// break her to be comp
|
||||||
|
_ => return Ok(()),
|
||||||
|
},
|
||||||
|
Part::Field(f) => match this {
|
||||||
|
Value::Object(o) => {
|
||||||
|
let Some(x) = o.get_mut(f.0.as_str()) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
this = x;
|
||||||
|
}
|
||||||
|
Value::Array(x) => {
|
||||||
|
// apply this path to every entry of the array.
|
||||||
|
stk.scope(|scope| {
|
||||||
|
let futs =
|
||||||
|
x.iter_mut().map(|v| scope.run(|stk| v.fetch(stk, ctx, opt, prev)));
|
||||||
|
try_join_all(futs)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
},
|
||||||
|
Part::Index(i) => match this {
|
||||||
|
Value::Object(v) => {
|
||||||
|
let Some(x) = v.get_mut(&i.to_string()) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
this = x;
|
||||||
|
}
|
||||||
|
Value::Array(v) => {
|
||||||
|
let Some(x) = v.get_mut(i.to_usize()) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
this = x;
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
},
|
||||||
|
Part::Value(v) => {
|
||||||
|
let v = v.compute(stk, ctx, opt, None).await?;
|
||||||
|
match this {
|
||||||
|
Value::Object(obj) => {
|
||||||
|
let Some(x) = obj.get_mut(v.coerce_to_string()?.as_str()) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
this = x;
|
||||||
}
|
}
|
||||||
None => Ok(()),
|
Value::Array(array) => {
|
||||||
},
|
if let Value::Range(x) = v {
|
||||||
Part::Field(f) => match v.get_mut(f as &str) {
|
let Some(range) = x.slice(array) else {
|
||||||
Some(v) => stk.run(|stk| v.fetch(stk, ctx, opt, path.next())).await,
|
return Ok(());
|
||||||
None => Ok(()),
|
};
|
||||||
},
|
let mut range = Value::Array(range.to_vec().into());
|
||||||
Part::Index(i) => match v.get_mut(&i.to_string()) {
|
return stk
|
||||||
Some(v) => stk.run(|stk| v.fetch(stk, ctx, opt, path.next())).await,
|
.run(|stk| range.fetch(stk, ctx, opt, iter.as_slice()))
|
||||||
None => Ok(()),
|
.await;
|
||||||
},
|
}
|
||||||
Part::All => stk.run(|stk| self.fetch(stk, ctx, opt, path.next())).await,
|
let Some(x) = array.get_mut(v.coerce_to_u64()? as usize) else {
|
||||||
Part::Destructure(p) => {
|
return Ok(());
|
||||||
|
};
|
||||||
|
this = x;
|
||||||
|
}
|
||||||
|
_ => return Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Part::Destructure(p) => match this {
|
||||||
|
Value::Array(x) => {
|
||||||
|
// apply this path to every entry of the array.
|
||||||
|
stk.scope(|scope| {
|
||||||
|
let futs =
|
||||||
|
x.iter_mut().map(|v| scope.run(|stk| v.fetch(stk, ctx, opt, prev)));
|
||||||
|
try_join_all(futs)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Value::Object(_) => {
|
||||||
for p in p.iter() {
|
for p in p.iter() {
|
||||||
let path = [(p.path().as_slice()), path].concat();
|
let mut destructure_path = p.path();
|
||||||
stk.run(|stk| self.fetch(stk, ctx, opt, &path)).await?;
|
destructure_path.extend_from_slice(path);
|
||||||
|
stk.run(|stk| this.fetch(stk, ctx, opt, &destructure_path)).await?;
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
_ => return Ok(()),
|
||||||
|
},
|
||||||
|
Part::All => match this {
|
||||||
|
Value::Object(_) => {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Value::Array(x) => {
|
||||||
|
let next_path = iter.as_slice();
|
||||||
|
// no need to spawn all those futures if their is no more paths to
|
||||||
|
// calculate
|
||||||
|
if next_path.is_empty() {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
_ => Ok(()),
|
|
||||||
},
|
|
||||||
// Current path part is an array
|
|
||||||
Value::Array(v) => match p {
|
|
||||||
Part::All => {
|
|
||||||
let path = path.next();
|
|
||||||
stk.scope(|scope| {
|
stk.scope(|scope| {
|
||||||
let futs =
|
let futs = x
|
||||||
v.iter_mut().map(|v| scope.run(|stk| v.fetch(stk, ctx, opt, path)));
|
.iter_mut()
|
||||||
|
.map(|v| scope.run(|stk| v.fetch(stk, ctx, opt, next_path)));
|
||||||
try_join_all(futs)
|
try_join_all(futs)
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
return Ok(());
|
||||||
}
|
}
|
||||||
Part::First => match v.first_mut() {
|
_ => break,
|
||||||
Some(v) => stk.run(|stk| v.fetch(stk, ctx, opt, path.next())).await,
|
},
|
||||||
None => Ok(()),
|
Part::First => match this {
|
||||||
},
|
Value::Array(x) => {
|
||||||
Part::Last => match v.last_mut() {
|
let Some(x) = x.first_mut() else {
|
||||||
Some(v) => stk.run(|stk| v.fetch(stk, ctx, opt, path.next())).await,
|
return Ok(());
|
||||||
None => Ok(()),
|
};
|
||||||
},
|
this = x;
|
||||||
Part::Index(i) => match v.get_mut(i.to_usize()) {
|
}
|
||||||
Some(v) => stk.run(|stk| v.fetch(stk, ctx, opt, path.next())).await,
|
_ => return Ok(()),
|
||||||
None => Ok(()),
|
},
|
||||||
},
|
Part::Last => match this {
|
||||||
Part::Where(w) => {
|
Value::Array(x) => {
|
||||||
let path = path.next();
|
let Some(x) = x.last_mut() else {
|
||||||
for v in v.iter_mut() {
|
return Ok(());
|
||||||
let cur = v.clone().into();
|
};
|
||||||
if w.compute(stk, ctx, opt, Some(&cur)).await?.is_truthy() {
|
this = x;
|
||||||
stk.run(|stk| v.fetch(stk, ctx, opt, path)).await?;
|
}
|
||||||
|
_ => return Ok(()),
|
||||||
|
},
|
||||||
|
Part::Where(w) => match this {
|
||||||
|
Value::Array(x) => {
|
||||||
|
for v in x.iter_mut() {
|
||||||
|
let doc = v.clone().into();
|
||||||
|
if w.compute(stk, ctx, opt, Some(&doc)).await?.is_truthy() {
|
||||||
|
stk.run(|stk| v.fetch(stk, ctx, opt, iter.as_slice())).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
stk.scope(|scope| {
|
|
||||||
let futs =
|
|
||||||
v.iter_mut().map(|v| scope.run(|stk| v.fetch(stk, ctx, opt, path)));
|
|
||||||
try_join_all(futs)
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
_ => return Ok(()),
|
||||||
},
|
},
|
||||||
// Current path part is a thing
|
_ => break,
|
||||||
Value::Thing(v) => {
|
}
|
||||||
// Clone the thing
|
prev = iter.as_slice();
|
||||||
let val = v.clone();
|
}
|
||||||
// Fetch the remote embedded record
|
|
||||||
match p {
|
// If the final value is on of following types we still need to compute it.
|
||||||
// This is a graph traversal expression
|
match this {
|
||||||
Part::Graph(g) => {
|
Value::Array(v) => {
|
||||||
let stm = SelectStatement {
|
stk.scope(|scope| {
|
||||||
expr: Fields(vec![Field::All], false),
|
let futs = v.iter_mut().map(|v| scope.run(|stk| v.fetch(stk, ctx, opt, path)));
|
||||||
what: Values(vec![Value::from(Edges {
|
try_join_all(futs)
|
||||||
from: val,
|
})
|
||||||
dir: g.dir.clone(),
|
.await?;
|
||||||
what: g.what.clone(),
|
Ok(())
|
||||||
})]),
|
}
|
||||||
cond: g.cond.clone(),
|
Value::Thing(v) => {
|
||||||
..SelectStatement::default()
|
// Clone the thing
|
||||||
};
|
let val = v.clone();
|
||||||
*self = stm
|
// Fetch the remote embedded record
|
||||||
.compute(stk, ctx, opt, None)
|
let stm = SelectStatement {
|
||||||
.await?
|
expr: Fields(vec![Field::All], false),
|
||||||
.all()
|
what: Values(vec![Value::from(val)]),
|
||||||
.get(stk, ctx, opt, None, path.next())
|
..SelectStatement::default()
|
||||||
.await?
|
};
|
||||||
.flatten()
|
*this = stm.compute(stk, ctx, opt, None).await?.first();
|
||||||
.ok()?;
|
Ok(())
|
||||||
Ok(())
|
}
|
||||||
}
|
_ => Ok(()),
|
||||||
// This is a remote field expression
|
|
||||||
_ => {
|
|
||||||
let stm = SelectStatement {
|
|
||||||
expr: Fields(vec![Field::All], false),
|
|
||||||
what: Values(vec![Value::from(val)]),
|
|
||||||
..SelectStatement::default()
|
|
||||||
};
|
|
||||||
*self = stm.compute(stk, ctx, opt, 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) => {
|
|
||||||
stk.scope(|scope| {
|
|
||||||
let futs =
|
|
||||||
v.iter_mut().map(|v| scope.run(|stk| v.fetch(stk, ctx, opt, 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], false),
|
|
||||||
what: Values(vec![Value::from(val)]),
|
|
||||||
..SelectStatement::default()
|
|
||||||
};
|
|
||||||
*self = stm.compute(stk, ctx, opt, None).await?.first();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
// Ignore everything else
|
|
||||||
_ => Ok(()),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -233,6 +233,21 @@ impl Value {
|
||||||
Some(v) => stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await,
|
Some(v) => stk.run(|stk| v.get(stk, ctx, opt, doc, path.next())).await,
|
||||||
None => Ok(Value::None),
|
None => Ok(Value::None),
|
||||||
},
|
},
|
||||||
|
Value::Range(r) => {
|
||||||
|
if let Some(range) = r.slice(v.as_slice()) {
|
||||||
|
let path = path.next();
|
||||||
|
stk.scope(|scope| {
|
||||||
|
let futs = range
|
||||||
|
.iter()
|
||||||
|
.map(|v| scope.run(|stk| v.get(stk, ctx, opt, doc, path)));
|
||||||
|
try_join_all_buffered(futs)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map(Into::into)
|
||||||
|
} else {
|
||||||
|
Ok(Value::None)
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => stk.run(|stk| Value::None.get(stk, ctx, opt, doc, path.next())).await,
|
_ => stk.run(|stk| Value::None.get(stk, ctx, opt, doc, path.next())).await,
|
||||||
},
|
},
|
||||||
Part::Method(name, args) => {
|
Part::Method(name, args) => {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
|
use std::collections::btree_map::Entry;
|
||||||
|
|
||||||
use crate::ctx::Context;
|
use crate::ctx::Context;
|
||||||
use crate::dbs::Options;
|
use crate::dbs::Options;
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::exe::try_join_all_buffered;
|
use crate::exe::try_join_all_buffered;
|
||||||
use crate::sql::part::Next;
|
|
||||||
use crate::sql::part::Part;
|
use crate::sql::part::Part;
|
||||||
use crate::sql::value::Value;
|
use crate::sql::value::Value;
|
||||||
|
use crate::sql::Object;
|
||||||
use reblessive::tree::Stk;
|
use reblessive::tree::Stk;
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
|
@ -19,162 +21,254 @@ impl Value {
|
||||||
path: &[Part],
|
path: &[Part],
|
||||||
val: Value,
|
val: Value,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
match path.first() {
|
if path.is_empty() {
|
||||||
// Get the current value at path
|
*self = val;
|
||||||
Some(p) => match self {
|
return Ok(());
|
||||||
// Current value at path is an object
|
|
||||||
Value::Object(v) => match p {
|
|
||||||
Part::Graph(g) => match v.get_mut(g.to_raw().as_str()) {
|
|
||||||
Some(v) if v.is_some() => {
|
|
||||||
stk.run(|stk| v.set(stk, ctx, opt, path.next(), val)).await
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let mut obj = Value::base();
|
|
||||||
stk.run(|stk| obj.set(stk, ctx, opt, path.next(), val)).await?;
|
|
||||||
v.insert(g.to_raw(), obj);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Part::Field(f) => match v.get_mut(f.as_str()) {
|
|
||||||
Some(v) if v.is_some() => {
|
|
||||||
stk.run(|stk| v.set(stk, ctx, opt, path.next(), val)).await
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let mut obj = Value::base();
|
|
||||||
stk.run(|stk| obj.set(stk, ctx, opt, path.next(), val)).await?;
|
|
||||||
v.insert(f.to_raw(), obj);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Part::Index(i) => match v.get_mut(&i.to_string()) {
|
|
||||||
Some(v) if v.is_some() => {
|
|
||||||
stk.run(|stk| v.set(stk, ctx, opt, path.next(), val)).await
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let mut obj = Value::base();
|
|
||||||
stk.run(|stk| obj.set(stk, ctx, opt, path.next(), val)).await?;
|
|
||||||
v.insert(i.to_string(), obj);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Part::Value(x) => match stk.run(|stk| x.compute(stk, ctx, opt, None)).await? {
|
|
||||||
Value::Strand(f) => match v.get_mut(f.as_str()) {
|
|
||||||
Some(v) if v.is_some() => {
|
|
||||||
stk.run(|stk| v.set(stk, ctx, opt, path.next(), val)).await
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let mut obj = Value::base();
|
|
||||||
stk.run(|stk| obj.set(stk, ctx, opt, path.next(), val)).await?;
|
|
||||||
v.insert(f.to_raw(), obj);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => Ok(()),
|
|
||||||
},
|
|
||||||
_ => Ok(()),
|
|
||||||
},
|
|
||||||
// Current value at path is an array
|
|
||||||
Value::Array(v) => match p {
|
|
||||||
Part::All => {
|
|
||||||
let path = path.next();
|
|
||||||
|
|
||||||
stk.scope(|scope| {
|
|
||||||
let futs = v
|
|
||||||
.iter_mut()
|
|
||||||
.map(|v| scope.run(|stk| v.set(stk, ctx, opt, path, val.clone())));
|
|
||||||
try_join_all_buffered(futs)
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Part::First => match v.first_mut() {
|
|
||||||
Some(v) => stk.run(|stk| v.set(stk, ctx, opt, path.next(), val)).await,
|
|
||||||
None => Ok(()),
|
|
||||||
},
|
|
||||||
Part::Last => match v.last_mut() {
|
|
||||||
Some(v) => stk.run(|stk| v.set(stk, ctx, opt, path.next(), val)).await,
|
|
||||||
None => Ok(()),
|
|
||||||
},
|
|
||||||
Part::Index(i) => match v.get_mut(i.to_usize()) {
|
|
||||||
Some(v) => stk.run(|stk| v.set(stk, ctx, opt, path.next(), val)).await,
|
|
||||||
None => Ok(()),
|
|
||||||
},
|
|
||||||
Part::Where(w) => match path.next().first() {
|
|
||||||
Some(Part::Index(_)) => {
|
|
||||||
let mut a = Vec::new();
|
|
||||||
let mut p = Vec::new();
|
|
||||||
// Store the elements and positions to update
|
|
||||||
for (i, o) in v.iter_mut().enumerate() {
|
|
||||||
let cur = o.clone().into();
|
|
||||||
if w.compute(stk, ctx, opt, Some(&cur)).await?.is_truthy() {
|
|
||||||
a.push(o.clone());
|
|
||||||
p.push(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Convert the matched elements array to a value
|
|
||||||
let mut a = Value::from(a);
|
|
||||||
// Set the new value on the matches elements
|
|
||||||
stk.run(|stk| a.set(stk, ctx, opt, path.next(), val.clone())).await?;
|
|
||||||
// Push the new values into the original array
|
|
||||||
for (i, p) in p.into_iter().enumerate() {
|
|
||||||
v[p] = a.pick(&[Part::Index(i.into())]);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let path = path.next();
|
|
||||||
for v in v.iter_mut() {
|
|
||||||
let cur = v.clone().into();
|
|
||||||
if w.compute(stk, ctx, opt, Some(&cur)).await?.is_truthy() {
|
|
||||||
stk.run(|stk| v.set(stk, ctx, opt, path, val.clone())).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Part::Value(x) => match x.compute(stk, ctx, opt, None).await? {
|
|
||||||
Value::Number(i) => match v.get_mut(i.to_usize()) {
|
|
||||||
Some(v) => stk.run(|stk| v.set(stk, ctx, opt, path.next(), val)).await,
|
|
||||||
None => Ok(()),
|
|
||||||
},
|
|
||||||
_ => Ok(()),
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
stk.scope(|scope| {
|
|
||||||
let futs = v
|
|
||||||
.iter_mut()
|
|
||||||
.map(|v| scope.run(|stk| v.set(stk, ctx, opt, path, val.clone())));
|
|
||||||
try_join_all_buffered(futs)
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Current value at path is a record
|
|
||||||
Value::Thing(_) => {
|
|
||||||
*self = Value::base();
|
|
||||||
stk.run(|stk| self.set(stk, ctx, opt, path, val)).await
|
|
||||||
}
|
|
||||||
// Current value at path is empty
|
|
||||||
Value::Null => {
|
|
||||||
*self = Value::base();
|
|
||||||
stk.run(|stk| self.set(stk, ctx, opt, path, val)).await
|
|
||||||
}
|
|
||||||
// Current value at path is empty
|
|
||||||
Value::None => {
|
|
||||||
*self = Value::base();
|
|
||||||
stk.run(|stk| self.set(stk, ctx, opt, path, val)).await
|
|
||||||
}
|
|
||||||
// Ignore everything else
|
|
||||||
_ => Ok(()),
|
|
||||||
},
|
|
||||||
// No more parts so set the value
|
|
||||||
None => {
|
|
||||||
*self = val;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut iter = path.iter();
|
||||||
|
let mut place = self;
|
||||||
|
let mut prev = path;
|
||||||
|
|
||||||
|
// Index forward trying to find the location where to insert the value
|
||||||
|
// Whenever we hit an existing path in the value we update place to point to the new value.
|
||||||
|
// If we hit a dead end, we then assign the into that dead end. If any path is not yet
|
||||||
|
// matched we use that to create an object to assign.
|
||||||
|
while let Some(p) = iter.next() {
|
||||||
|
match place {
|
||||||
|
Value::Thing(_) | Value::Null | Value::None => {
|
||||||
|
// any index is guaranteed to fail so just assign to this place.
|
||||||
|
return Self::assign(stk, ctx, opt, place, val, prev).await;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
match p {
|
||||||
|
Part::Graph(g) => {
|
||||||
|
match place {
|
||||||
|
Value::Object(obj) => match obj.entry(g.to_raw()) {
|
||||||
|
Entry::Vacant(x) => {
|
||||||
|
let v = x.insert(Value::None);
|
||||||
|
return Self::assign(stk, ctx, opt, v, val, iter.as_slice()).await;
|
||||||
|
}
|
||||||
|
Entry::Occupied(x) => {
|
||||||
|
place = x.into_mut();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Value::Array(arr) => {
|
||||||
|
// Apply to all entries of the array
|
||||||
|
stk.scope(|scope| {
|
||||||
|
let futs = arr.iter_mut().map(|v| {
|
||||||
|
scope.run(|stk| v.set(stk, ctx, opt, prev, val.clone()))
|
||||||
|
});
|
||||||
|
try_join_all_buffered(futs)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
_ => return Ok(()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Part::Field(f) => {
|
||||||
|
match place {
|
||||||
|
Value::Object(obj) => match obj.entry(f.0.clone()) {
|
||||||
|
Entry::Vacant(x) => {
|
||||||
|
let v = x.insert(Value::None);
|
||||||
|
return Self::assign(stk, ctx, opt, v, val, iter.as_slice()).await;
|
||||||
|
}
|
||||||
|
Entry::Occupied(x) => {
|
||||||
|
place = x.into_mut();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Value::Array(arr) => {
|
||||||
|
// Apply to all entries of the array
|
||||||
|
stk.scope(|scope| {
|
||||||
|
let futs = arr.iter_mut().map(|v| {
|
||||||
|
scope.run(|stk| v.set(stk, ctx, opt, prev, val.clone()))
|
||||||
|
});
|
||||||
|
try_join_all_buffered(futs)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
_ => return Ok(()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Part::Index(f) => match place {
|
||||||
|
Value::Object(obj) => match obj.entry(f.to_string()) {
|
||||||
|
Entry::Vacant(x) => {
|
||||||
|
let v = x.insert(Value::None);
|
||||||
|
return Self::assign(stk, ctx, opt, v, val, prev).await;
|
||||||
|
}
|
||||||
|
Entry::Occupied(x) => {
|
||||||
|
place = x.into_mut();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Value::Array(arr) => {
|
||||||
|
if let Some(x) = arr.get_mut(f.to_usize()) {
|
||||||
|
place = x
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Ok(()),
|
||||||
|
},
|
||||||
|
Part::Value(x) => {
|
||||||
|
let v = stk.run(|stk| x.compute(stk, ctx, opt, None)).await?;
|
||||||
|
|
||||||
|
match place {
|
||||||
|
Value::Object(obj) => {
|
||||||
|
let v = match v {
|
||||||
|
Value::Strand(x) => x.0.clone(),
|
||||||
|
x => x.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match obj.entry(v) {
|
||||||
|
Entry::Vacant(x) => {
|
||||||
|
let v = x.insert(Value::None);
|
||||||
|
return Self::assign(stk, ctx, opt, v, val, prev).await;
|
||||||
|
}
|
||||||
|
Entry::Occupied(x) => {
|
||||||
|
place = x.into_mut();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Array(arr) => match v {
|
||||||
|
Value::Range(x) => {
|
||||||
|
if let Some(v) = x.slice_mut(arr) {
|
||||||
|
let path = iter.as_slice();
|
||||||
|
stk.scope(|scope| {
|
||||||
|
let futs = v.iter_mut().map(|v| {
|
||||||
|
scope.run(|stk| v.set(stk, ctx, opt, path, val.clone()))
|
||||||
|
});
|
||||||
|
try_join_all_buffered(futs)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Number(i) => {
|
||||||
|
if let Some(v) = arr.get_mut(i.to_usize()) {
|
||||||
|
place = v;
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Ok(()),
|
||||||
|
},
|
||||||
|
_ => return Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Part::First => {
|
||||||
|
let Value::Array(arr) = place else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let Some(x) = arr.first_mut() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
place = x
|
||||||
|
}
|
||||||
|
Part::Last => {
|
||||||
|
let Value::Array(arr) = place else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let Some(x) = arr.last_mut() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
place = x
|
||||||
|
}
|
||||||
|
Part::All => {
|
||||||
|
let Value::Array(arr) = place else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = iter.as_slice();
|
||||||
|
stk.scope(|scope| {
|
||||||
|
let futs = arr
|
||||||
|
.iter_mut()
|
||||||
|
.map(|v| scope.run(|stk| v.set(stk, ctx, opt, path, val.clone())));
|
||||||
|
try_join_all_buffered(futs)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Part::Where(w) => {
|
||||||
|
let Value::Array(arr) = place else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
if let Some(Part::Index(_)) = iter.as_slice().first() {
|
||||||
|
let mut a = Vec::new();
|
||||||
|
let mut p = Vec::new();
|
||||||
|
// Store the elements and positions to update
|
||||||
|
for (i, o) in arr.iter_mut().enumerate() {
|
||||||
|
let cur = o.clone().into();
|
||||||
|
if w.compute(stk, ctx, opt, Some(&cur)).await?.is_truthy() {
|
||||||
|
a.push(o.clone());
|
||||||
|
p.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Convert the matched elements array to a value
|
||||||
|
let mut a = Value::from(a);
|
||||||
|
// Set the new value on the matches elements
|
||||||
|
stk.run(|stk| a.set(stk, ctx, opt, iter.as_slice(), val.clone())).await?;
|
||||||
|
// Push the new values into the original array
|
||||||
|
for (i, p) in p.into_iter().enumerate() {
|
||||||
|
arr[p] = a.pick(&[Part::Index(i.into())]);
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
for v in arr.iter_mut() {
|
||||||
|
let cur = v.clone().into();
|
||||||
|
if w.compute(stk, ctx, opt, Some(&cur)).await?.is_truthy() {
|
||||||
|
stk.run(|stk| v.set(stk, ctx, opt, iter.as_slice(), val.clone()))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Ok(()),
|
||||||
|
}
|
||||||
|
prev = iter.as_slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
*place = val;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn assign(
|
||||||
|
stk: &mut Stk,
|
||||||
|
ctx: &Context,
|
||||||
|
opt: &Options,
|
||||||
|
place: &mut Value,
|
||||||
|
mut val: Value,
|
||||||
|
path: &[Part],
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
for p in path.iter().rev() {
|
||||||
|
let name = match p {
|
||||||
|
Part::Graph(x) => x.to_raw(),
|
||||||
|
Part::Field(f) => f.0.clone(),
|
||||||
|
Part::Index(i) => i.to_string(),
|
||||||
|
Part::Value(x) => {
|
||||||
|
let v = stk.run(|stk| x.compute(stk, ctx, opt, None)).await?;
|
||||||
|
match v {
|
||||||
|
Value::Strand(x) => x.0,
|
||||||
|
Value::Number(x) => x.to_string(),
|
||||||
|
Value::Range(x) => x.to_string(),
|
||||||
|
_ => return Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Ok(()),
|
||||||
|
};
|
||||||
|
let mut object = Object::default();
|
||||||
|
object.insert(name, val);
|
||||||
|
val = object.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
*place = val;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -346,25 +346,18 @@ impl Parser<'_> {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
Part::Last
|
Part::Last
|
||||||
}
|
}
|
||||||
t!("+") | TokenKind::Digits | TokenKind::Glued(Glued::Number) => {
|
|
||||||
Part::Index(self.next_token_value()?)
|
|
||||||
}
|
|
||||||
t!("-") => {
|
|
||||||
if let TokenKind::Digits = self.peek_whitespace1().kind {
|
|
||||||
unexpected!(self, peek,"$, * or a number", => "An index can't be negative.");
|
|
||||||
}
|
|
||||||
unexpected!(self, peek, "$, * or a number");
|
|
||||||
}
|
|
||||||
t!("?") | t!("WHERE") => {
|
t!("?") | t!("WHERE") => {
|
||||||
self.pop_peek();
|
self.pop_peek();
|
||||||
let value = ctx.run(|ctx| self.parse_value_field(ctx)).await?;
|
let value = ctx.run(|ctx| self.parse_value_field(ctx)).await?;
|
||||||
Part::Where(value)
|
Part::Where(value)
|
||||||
}
|
}
|
||||||
t!("$param") => Part::Value(Value::Param(self.next_token_value()?)),
|
|
||||||
TokenKind::Qoute(_x) => Part::Value(Value::Strand(self.next_token_value()?)),
|
|
||||||
_ => {
|
_ => {
|
||||||
let idiom = self.parse_basic_idiom(ctx).await?;
|
let value = ctx.run(|ctx| self.parse_value_inherit(ctx)).await?;
|
||||||
Part::Value(Value::Idiom(idiom))
|
if let Value::Number(x) = value {
|
||||||
|
Part::Index(x)
|
||||||
|
} else {
|
||||||
|
Part::Value(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.expect_closing_delimiter(t!("]"), start)?;
|
self.expect_closing_delimiter(t!("]"), start)?;
|
||||||
|
|
|
@ -22,7 +22,7 @@ macro_rules! unexpected {
|
||||||
$crate::syn::error::bail!("Unexpected whitespace, expected token {} to continue",$expected, @__found.span$( $($t)* )?)
|
$crate::syn::error::bail!("Unexpected whitespace, expected token {} to continue",$expected, @__found.span$( $($t)* )?)
|
||||||
}
|
}
|
||||||
x => {
|
x => {
|
||||||
$crate::syn::error::bail!("Unexpected token {}, expected {}",x,$expected, @__found.span$( $($t)* )?)
|
$crate::syn::error::bail!("Unexpected token `{}`, expected {}",x,$expected, @__found.span$( $($t)* )?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
|
|
|
@ -4,15 +4,37 @@ use reblessive::Stack;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
sql::{
|
sql::{
|
||||||
Array, Constant, Id, Idiom, Number, Object, Part, Query, Statement, Statements, Strand,
|
Array, Constant, Expression, Geometry, Id, Ident, Idiom, Number, Object, Operator, Part,
|
||||||
Thing, Value,
|
Query, Statement, Statements, Strand, Thing, Value,
|
||||||
},
|
},
|
||||||
syn::parser::{mac::test_parse, Parser},
|
syn::parser::{mac::test_parse, Parser},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_index_expression() {
|
||||||
|
let value = test_parse!(parse_value_table, "a[1 + 1]").unwrap();
|
||||||
|
let Value::Idiom(x) = value else {
|
||||||
|
panic!("not the right value type");
|
||||||
|
};
|
||||||
|
assert_eq!(x.0[0], Part::Field(Ident("a".to_string())));
|
||||||
|
assert_eq!(
|
||||||
|
x.0[1],
|
||||||
|
Part::Value(Value::Expression(Box::new(Expression::Binary {
|
||||||
|
l: Value::Number(Number::Int(1)),
|
||||||
|
o: Operator::Add,
|
||||||
|
r: Value::Number(Number::Int(1)),
|
||||||
|
})))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_coordinate() {
|
fn parse_coordinate() {
|
||||||
test_parse!(parse_value_table, "(1.88, -18.0)").unwrap();
|
let coord = test_parse!(parse_value_table, "(1.88, -18.0)").unwrap();
|
||||||
|
let Value::Geometry(Geometry::Point(x)) = coord else {
|
||||||
|
panic!("not the right value");
|
||||||
|
};
|
||||||
|
assert_eq!(x.x(), 1.88);
|
||||||
|
assert_eq!(x.y(), -18.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -12,3 +12,47 @@ async fn idiom_chain_part_optional() -> Result<(), Error> {
|
||||||
Test::new(sql).await?.expect_val("false")?.expect_val("None")?;
|
Test::new(sql).await?.expect_val("false")?.expect_val("None")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn idiom_index_expression() -> Result<(), Error> {
|
||||||
|
let sql = r#"
|
||||||
|
[1,2,3,4][1 + 1];
|
||||||
|
"#;
|
||||||
|
Test::new(sql).await?.expect_val("3")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn idiom_index_call() -> Result<(), Error> {
|
||||||
|
let sql = r#"
|
||||||
|
DEFINE FUNCTION fn::foo() {
|
||||||
|
return 1 + 1;
|
||||||
|
};
|
||||||
|
RETURN [1,2,3,4][fn::foo()];
|
||||||
|
"#;
|
||||||
|
Test::new(sql).await?.expect_val("None")?.expect_val("3")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn idiom_index_range() -> Result<(), Error> {
|
||||||
|
let sql = r#"
|
||||||
|
[1,2,3,4][1..2];
|
||||||
|
[1,2,3,4][1..=2];
|
||||||
|
[1,2,3,4][1>..=2];
|
||||||
|
[1,2,3,4][1>..];
|
||||||
|
[1,2,3,4][1..];
|
||||||
|
[1,2,3,4][..2];
|
||||||
|
[1,2,3,4][..=2];
|
||||||
|
"#;
|
||||||
|
Test::new(sql)
|
||||||
|
.await?
|
||||||
|
.expect_val("[2]")?
|
||||||
|
.expect_val("[2,3]")?
|
||||||
|
.expect_val("[3]")?
|
||||||
|
.expect_val("[3,4]")?
|
||||||
|
.expect_val("[2,3,4]")?
|
||||||
|
.expect_val("[1,2]")?
|
||||||
|
.expect_val("[1,2,3]")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue