Improve performance with query path analysis

Instead of creating a new Vec<_> for every embedded path part, we now use a reference when calling get/set/del on a value’s data. This means we aren’t creating and copying the Vec items each and every time we traverse down a path.
This commit is contained in:
Tobie Morgan Hitchcock 2022-02-22 19:05:58 +00:00
parent cf707bf5e3
commit d4566ff6ea
15 changed files with 113 additions and 96 deletions

View file

@ -47,11 +47,11 @@ impl<'a> Document<'a> {
Field::All => (),
Field::Alone(v) => {
let x = v.compute(ctx, opt, txn, Some(&self.current)).await?;
out.set(ctx, opt, txn, &v.to_idiom(), x).await?;
out.set(ctx, opt, txn, v.to_idiom().as_ref(), x).await?;
}
Field::Alias(v, i) => {
let x = v.compute(ctx, opt, txn, Some(&self.current)).await?;
out.set(ctx, opt, txn, &i, x).await?;
out.set(ctx, opt, txn, i, x).await?;
}
}
}
@ -69,11 +69,11 @@ impl<'a> Document<'a> {
Field::All => (),
Field::Alone(v) => {
let x = v.compute(ctx, opt, txn, Some(&self.current)).await?;
out.set(ctx, opt, txn, &v.to_idiom(), x).await?;
out.set(ctx, opt, txn, v.to_idiom().as_ref(), x).await?;
}
Field::Alias(v, i) => {
let x = v.compute(ctx, opt, txn, Some(&self.current)).await?;
out.set(ctx, opt, txn, &i, x).await?;
out.set(ctx, opt, txn, i, x).await?;
}
}
}

View file

@ -11,6 +11,7 @@ use nom::multi::many0;
use nom::multi::separated_list1;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::Deref;
use std::str;
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize)]
@ -32,6 +33,13 @@ pub struct Idiom {
pub parts: Vec<Part>,
}
impl Deref for Idiom {
type Target = [Part];
fn deref(&self) -> &Self::Target {
self.parts.as_slice()
}
}
impl From<String> for Idiom {
fn from(v: String) -> Self {
Idiom {
@ -55,13 +63,6 @@ impl Idiom {
Idiom::from(p)
}
pub fn next(&self) -> Idiom {
match self.parts.len() {
0 => Idiom::from(vec![]),
_ => Idiom::from(self.parts[1..].to_vec()),
}
}
pub fn to_path(&self) -> String {
format!("/{}", self).replace(']', "").replace(&['.', '['][..], "/")
}

View file

@ -5,6 +5,7 @@ use crate::err::Error;
use crate::sql::error::IResult;
use crate::sql::idiom;
use crate::sql::idiom::Idiom;
use crate::sql::part::Next;
use crate::sql::part::Part;
use crate::sql::value::Value;
use nom::bytes::complete::tag;
@ -39,10 +40,12 @@ impl Param {
Some(Part::Field(v)) => match ctx.value::<Value>(v.name.clone()) {
// The base variable exists
Some(v) => {
// Get the path parts
let pth: &[Part] = &self.name;
// Process the paramater value
let res = v.compute(ctx, opt, txn, doc).await?;
// Return the desired field
res.get(ctx, opt, txn, &self.name.next()).await
res.get(ctx, opt, txn, pth.next()).await
}
// The base variable does not exist
None => Ok(Value::None),

View file

@ -87,6 +87,23 @@ impl fmt::Display for Part {
}
}
// ------------------------------
pub trait Next<'a> {
fn next(&'a self) -> &[Part];
}
impl<'a> Next<'a> for &'a [Part] {
fn next(&'a self) -> &'a [Part] {
match self.len() {
0 => &[],
_ => &self[1..],
}
}
}
// ------------------------------
pub fn part(i: &str) -> IResult<&str, Part> {
alt((all, last, index, field, graph, filter))(i)
}

View file

@ -3,7 +3,7 @@ use crate::dbs::Runtime;
use crate::dbs::Transaction;
use crate::err::Error;
use crate::sql::array::Array;
use crate::sql::idiom::Idiom;
use crate::sql::part::Part;
use crate::sql::value::Value;
impl Value {
@ -12,7 +12,7 @@ impl Value {
ctx: &Runtime,
opt: &Options,
txn: &Transaction,
path: &Idiom,
path: &[Part],
) -> Result<(), Error> {
let val = Value::from(Array::default());
self.set(ctx, opt, txn, path, val).await
@ -24,6 +24,7 @@ mod tests {
use super::*;
use crate::dbs::test::mock;
use crate::sql::idiom::Idiom;
use crate::sql::test::Parse;
#[tokio::test]

View file

@ -2,8 +2,8 @@ use crate::dbs::Options;
use crate::dbs::Runtime;
use crate::dbs::Transaction;
use crate::err::Error;
use crate::sql::idiom::Idiom;
use crate::sql::number::Number;
use crate::sql::part::Part;
use crate::sql::value::Value;
impl Value {
@ -12,7 +12,7 @@ impl Value {
ctx: &Runtime,
opt: &Options,
txn: &Transaction,
path: &Idiom,
path: &[Part],
val: Value,
) -> Result<(), Error> {
match self.get(ctx, opt, txn, path).await? {
@ -40,6 +40,7 @@ mod tests {
use super::*;
use crate::dbs::test::mock;
use crate::sql::idiom::Idiom;
use crate::sql::test::Parse;
#[tokio::test]

View file

@ -2,23 +2,14 @@ use crate::dbs::Options;
use crate::dbs::Runtime;
use crate::dbs::Transaction;
use crate::err::Error;
use crate::sql::idiom::Idiom;
use crate::sql::part::Part;
use crate::sql::thing::Thing;
use crate::sql::value::Value;
use once_cell::sync::Lazy;
static RID: Lazy<Idiom> = Lazy::new(|| Idiom {
parts: vec![Part::from("id")],
});
static MTB: Lazy<Idiom> = Lazy::new(|| Idiom {
parts: vec![Part::from("meta"), Part::from("tb")],
});
static MID: Lazy<Idiom> = Lazy::new(|| Idiom {
parts: vec![Part::from("meta"), Part::from("id")],
});
static RID: Lazy<[Part; 1]> = Lazy::new(|| [Part::from("id")]);
static MTB: Lazy<[Part; 2]> = Lazy::new(|| [Part::from("meta"), Part::from("tb")]);
static MID: Lazy<[Part; 2]> = Lazy::new(|| [Part::from("meta"), Part::from("id")]);
impl Value {
pub async fn def(
@ -32,9 +23,9 @@ impl Value {
Some(id) => {
let id = id.clone();
let md = id.clone();
self.set(ctx, opt, txn, &RID, id.into()).await?;
self.set(ctx, opt, txn, &MTB, md.tb.into()).await?;
self.set(ctx, opt, txn, &MID, md.id.into()).await?;
self.set(ctx, opt, txn, RID.as_ref(), id.into()).await?;
self.set(ctx, opt, txn, MTB.as_ref(), md.tb.into()).await?;
self.set(ctx, opt, txn, MID.as_ref(), md.id.into()).await?;
Ok(())
}
None => unreachable!(),

View file

@ -3,7 +3,7 @@ use crate::dbs::Runtime;
use crate::dbs::Transaction;
use crate::err::Error;
use crate::sql::array::Abolish;
use crate::sql::idiom::Idiom;
use crate::sql::part::Next;
use crate::sql::part::Part;
use crate::sql::value::Value;
use async_recursion::async_recursion;
@ -18,20 +18,20 @@ impl Value {
ctx: &Runtime,
opt: &Options,
txn: &Transaction,
path: &Idiom,
path: &[Part],
) -> Result<(), Error> {
match path.parts.first() {
match path.first() {
// Get the current path part
Some(p) => match self {
// Current path part is an object
Value::Object(v) => match p {
Part::Field(f) => match path.parts.len() {
Part::Field(f) => match path.len() {
1 => {
v.remove(&f.name);
Ok(())
}
_ => match v.value.get_mut(&f.name) {
Some(v) if v.is_some() => v.del(ctx, opt, txn, &path.next()).await,
Some(v) if v.is_some() => v.del(ctx, opt, txn, path.next()).await,
_ => Ok(()),
},
},
@ -39,19 +39,19 @@ impl Value {
},
// Current path part is an array
Value::Array(v) => match p {
Part::All => match path.parts.len() {
Part::All => match path.len() {
1 => {
v.value.clear();
Ok(())
}
_ => {
let pth = path.next();
let fut = v.value.iter_mut().map(|v| v.del(&ctx, opt, txn, &pth));
try_join_all(fut).await?;
let path = path.next();
let futs = v.value.iter_mut().map(|v| v.del(&ctx, opt, txn, path));
try_join_all(futs).await?;
Ok(())
}
},
Part::First => match path.parts.len() {
Part::First => match path.len() {
1 => {
if v.value.len().gt(&0) {
v.value.remove(0);
@ -59,11 +59,11 @@ impl Value {
Ok(())
}
_ => match v.value.first_mut() {
Some(v) => v.del(ctx, opt, txn, &path.next()).await,
Some(v) => v.del(ctx, opt, txn, path.next()).await,
None => Ok(()),
},
},
Part::Last => match path.parts.len() {
Part::Last => match path.len() {
1 => {
if v.value.len().gt(&0) {
v.value.remove(v.value.len() - 1);
@ -71,25 +71,25 @@ impl Value {
Ok(())
}
_ => match v.value.last_mut() {
Some(v) => v.del(ctx, opt, txn, &path.next()).await,
Some(v) => v.del(ctx, opt, txn, path.next()).await,
None => Ok(()),
},
},
Part::Index(i) => match path.parts.len() {
Part::Index(i) => match path.len() {
1 => {
if v.value.len().gt(&i.to_usize()) {
v.value.remove(i.to_usize());
}
Ok(())
}
_ => match path.parts.len() {
_ => match path.len() {
_ => match v.value.get_mut(i.to_usize()) {
Some(v) => v.del(ctx, opt, txn, &path.next()).await,
Some(v) => v.del(ctx, opt, txn, path.next()).await,
None => Ok(()),
},
},
},
Part::Where(w) => match path.parts.len() {
Part::Where(w) => match path.len() {
1 => {
let mut m = HashMap::new();
for (i, v) in v.value.iter().enumerate() {
@ -101,27 +101,26 @@ impl Value {
Ok(())
}
_ => {
let pth = path.next();
let path = path.next();
for v in &mut v.value {
if w.compute(ctx, opt, txn, Some(&v)).await?.is_truthy() {
v.del(ctx, opt, txn, &pth).await?;
v.del(ctx, opt, txn, path).await?;
}
}
Ok(())
}
},
_ => match path.parts.len() {
_ => match path.len() {
1 => {
v.value.clear();
Ok(())
}
_ => {
let fut = v.value.iter_mut().map(|v| v.del(&ctx, opt, txn, &path));
try_join_all(fut).await?;
let futs = v.value.iter_mut().map(|v| v.del(&ctx, opt, txn, path));
try_join_all(futs).await?;
Ok(())
}
},
_ => Ok(()),
},
// Ignore everything else
_ => Ok(()),
@ -137,6 +136,7 @@ mod tests {
use super::*;
use crate::dbs::test::mock;
use crate::sql::idiom::Idiom;
use crate::sql::test::Parse;
#[tokio::test]

View file

@ -2,7 +2,6 @@ use crate::dbs::Options;
use crate::dbs::Runtime;
use crate::dbs::Transaction;
use crate::err::Error;
use crate::sql::idiom::Idiom;
use crate::sql::part::Part;
use crate::sql::value::Value;
@ -13,6 +12,6 @@ impl Value {
opt: &Options,
txn: &Transaction,
) -> Result<Self, Error> {
self.get(ctx, opt, txn, &Idiom::from(vec![Part::First])).await
self.get(ctx, opt, txn, &[Part::First]).await
}
}

View file

@ -3,7 +3,7 @@ use crate::dbs::Runtime;
use crate::dbs::Transaction;
use crate::err::Error;
use crate::sql::field::{Field, Fields};
use crate::sql::idiom::Idiom;
use crate::sql::part::Next;
use crate::sql::part::Part;
use crate::sql::statements::select::SelectStatement;
use crate::sql::value::{Value, Values};
@ -18,15 +18,15 @@ impl Value {
ctx: &Runtime,
opt: &Options,
txn: &Transaction,
path: &Idiom,
path: &[Part],
) -> Result<Self, Error> {
match path.parts.first() {
match path.first() {
// Get the current path part
Some(p) => match self {
// Current path part is an object
Value::Object(v) => match p {
Part::Field(f) => match v.value.get(&f.name) {
Some(v) => v.get(ctx, opt, txn, &path.next()).await,
Some(v) => v.get(ctx, opt, txn, path.next()).await,
None => Ok(Value::None),
},
_ => Ok(Value::None),
@ -34,39 +34,39 @@ impl Value {
// Current path part is an array
Value::Array(v) => match p {
Part::All => {
let pth = path.next();
let fut = v.value.iter().map(|v| v.get(&ctx, opt, txn, &pth));
try_join_all(fut).await.map(|v| v.into())
let path = path.next();
let futs = v.value.iter().map(|v| v.get(&ctx, opt, txn, path));
try_join_all(futs).await.map(|v| v.into())
}
Part::First => match v.value.first() {
Some(v) => v.get(ctx, opt, txn, &path.next()).await,
Some(v) => v.get(ctx, opt, txn, path.next()).await,
None => Ok(Value::None),
},
Part::Last => match v.value.last() {
Some(v) => v.get(ctx, opt, txn, &path.next()).await,
Some(v) => v.get(ctx, opt, txn, path.next()).await,
None => Ok(Value::None),
},
Part::Index(i) => match v.value.get(i.to_usize()) {
Some(v) => v.get(ctx, opt, txn, &path.next()).await,
Some(v) => v.get(ctx, opt, txn, path.next()).await,
None => Ok(Value::None),
},
Part::Where(w) => {
let pth = path.next();
let path = path.next();
let mut a = Vec::new();
for v in &v.value {
if w.compute(ctx, opt, txn, Some(&v)).await?.is_truthy() {
a.push(v.get(ctx, opt, txn, &pth).await?)
a.push(v.get(ctx, opt, txn, path).await?)
}
}
Ok(a.into())
}
_ => {
let fut = v.value.iter().map(|v| v.get(&ctx, opt, txn, &path));
try_join_all(fut).await.map(|v| v.into())
let futs = v.value.iter().map(|v| v.get(&ctx, opt, txn, path));
try_join_all(futs).await.map(|v| v.into())
}
},
// Current path part is a thing
Value::Thing(v) => match path.parts.len() {
Value::Thing(v) => match path.len() {
// No remote embedded fields, so just return this
0 => Ok(Value::Thing(v.clone())),
// Remote embedded field, so fetch the thing
@ -80,7 +80,7 @@ impl Value {
.await?
.first(ctx, opt, txn)
.await?
.get(ctx, opt, txn, &path)
.get(ctx, opt, txn, path)
.await
}
},
@ -98,6 +98,7 @@ mod tests {
use super::*;
use crate::dbs::test::mock;
use crate::sql::idiom::Idiom;
use crate::sql::test::Parse;
use crate::sql::thing::Thing;

View file

@ -2,8 +2,8 @@ use crate::dbs::Options;
use crate::dbs::Runtime;
use crate::dbs::Transaction;
use crate::err::Error;
use crate::sql::idiom::Idiom;
use crate::sql::number::Number;
use crate::sql::part::Part;
use crate::sql::value::Value;
impl Value {
@ -12,7 +12,7 @@ impl Value {
ctx: &Runtime,
opt: &Options,
txn: &Transaction,
path: &Idiom,
path: &[Part],
val: Value,
) -> Result<(), Error> {
match self.get(ctx, opt, txn, path).await? {
@ -41,6 +41,7 @@ mod tests {
use super::*;
use crate::dbs::test::mock;
use crate::sql::idiom::Idiom;
use crate::sql::test::Parse;
#[tokio::test]

View file

@ -2,7 +2,6 @@ use crate::dbs::Options;
use crate::dbs::Runtime;
use crate::dbs::Transaction;
use crate::err::Error;
use crate::sql::idiom::Idiom;
use crate::sql::part::Part;
use crate::sql::value::Value;
@ -13,6 +12,6 @@ impl Value {
opt: &Options,
txn: &Transaction,
) -> Result<Self, Error> {
self.get(ctx, opt, txn, &Idiom::from(vec![Part::Last])).await
self.get(ctx, opt, txn, &[Part::Last]).await
}
}

View file

@ -2,6 +2,7 @@ use crate::dbs::Options;
use crate::dbs::Runtime;
use crate::dbs::Transaction;
use crate::err::Error;
use crate::sql::part::Part;
use crate::sql::value::Value;
impl Value {
@ -15,7 +16,7 @@ impl Value {
match val.compute(ctx, opt, txn, Some(self)).await? {
Value::Object(v) => {
for (k, v) in v.value.into_iter() {
self.set(ctx, opt, txn, &k.into(), v).await?;
self.set(ctx, opt, txn, &[Part::from(k)], v).await?;
}
Ok(())
}

View file

@ -2,8 +2,8 @@ use crate::dbs::Options;
use crate::dbs::Runtime;
use crate::dbs::Transaction;
use crate::err::Error;
use crate::sql::idiom::Idiom;
use crate::sql::object::Object;
use crate::sql::part::Part;
use crate::sql::value::Value;
impl Value {
@ -12,7 +12,7 @@ impl Value {
ctx: &Runtime,
opt: &Options,
txn: &Transaction,
path: &Idiom,
path: &[Part],
) -> Result<(), Error> {
let val = Value::from(Object::default());
self.set(ctx, opt, txn, path, val).await
@ -24,6 +24,7 @@ mod tests {
use super::*;
use crate::dbs::test::mock;
use crate::sql::idiom::Idiom;
use crate::sql::test::Parse;
#[tokio::test]

View file

@ -2,7 +2,7 @@ use crate::dbs::Options;
use crate::dbs::Runtime;
use crate::dbs::Transaction;
use crate::err::Error;
use crate::sql::idiom::Idiom;
use crate::sql::part::Next;
use crate::sql::part::Part;
use crate::sql::value::Value;
use async_recursion::async_recursion;
@ -16,19 +16,19 @@ impl Value {
ctx: &Runtime,
opt: &Options,
txn: &Transaction,
path: &Idiom,
path: &[Part],
val: Value,
) -> Result<(), Error> {
match path.parts.first() {
match path.first() {
// Get the current path part
Some(p) => match self {
// Current path part is an object
Value::Object(v) => match p {
Part::Field(f) => match v.value.get_mut(&f.name) {
Some(v) if v.is_some() => v.set(ctx, opt, txn, &path.next(), val).await,
Some(v) if v.is_some() => v.set(ctx, opt, txn, path.next(), val).await,
_ => {
let mut obj = Value::base();
obj.set(ctx, opt, txn, &path.next(), val).await?;
obj.set(ctx, opt, txn, path.next(), val).await?;
v.insert(&f.name, obj);
Ok(())
}
@ -38,37 +38,37 @@ impl Value {
// Current path part is an array
Value::Array(v) => match p {
Part::All => {
let pth = path.next();
let fut =
v.value.iter_mut().map(|v| v.set(ctx, opt, txn, &pth, val.clone()));
try_join_all(fut).await?;
let path = path.next();
let futs =
v.value.iter_mut().map(|v| v.set(ctx, opt, txn, path, val.clone()));
try_join_all(futs).await?;
Ok(())
}
Part::First => match v.value.first_mut() {
Some(v) => v.set(ctx, opt, txn, &path.next(), val).await,
Some(v) => v.set(ctx, opt, txn, path.next(), val).await,
None => Ok(()),
},
Part::Last => match v.value.last_mut() {
Some(v) => v.set(ctx, opt, txn, &path.next(), val).await,
Some(v) => v.set(ctx, opt, txn, path.next(), val).await,
None => Ok(()),
},
Part::Index(i) => match v.value.get_mut(i.to_usize()) {
Some(v) => v.set(ctx, opt, txn, &path.next(), val).await,
Some(v) => v.set(ctx, opt, txn, path.next(), val).await,
None => Ok(()),
},
Part::Where(w) => {
let pth = path.next();
let path = path.next();
for v in &mut v.value {
if w.compute(ctx, opt, txn, Some(&v)).await?.is_truthy() {
v.set(ctx, opt, txn, &pth, val.clone()).await?;
v.set(ctx, opt, txn, path, val.clone()).await?;
}
}
Ok(())
}
_ => {
let fut =
v.value.iter_mut().map(|v| v.set(ctx, opt, txn, &path, val.clone()));
try_join_all(fut).await?;
let futs =
v.value.iter_mut().map(|v| v.set(ctx, opt, txn, path, val.clone()));
try_join_all(futs).await?;
Ok(())
}
},
@ -99,6 +99,7 @@ mod tests {
use super::*;
use crate::dbs::test::mock;
use crate::sql::idiom::Idiom;
use crate::sql::test::Parse;
#[tokio::test]