From d4566ff6ead7c116233676afc455f8bed4ddea80 Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Tue, 22 Feb 2022 19:05:58 +0000 Subject: [PATCH] Improve performance with query path analysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- lib/src/doc/pluck.rs | 8 +++--- lib/src/sql/idiom.rs | 15 +++++------ lib/src/sql/param.rs | 5 +++- lib/src/sql/part.rs | 17 +++++++++++++ lib/src/sql/value/array.rs | 5 ++-- lib/src/sql/value/decrement.rs | 5 ++-- lib/src/sql/value/def.rs | 21 +++++----------- lib/src/sql/value/del.rs | 46 +++++++++++++++++----------------- lib/src/sql/value/first.rs | 3 +-- lib/src/sql/value/get.rs | 33 ++++++++++++------------ lib/src/sql/value/increment.rs | 5 ++-- lib/src/sql/value/last.rs | 3 +-- lib/src/sql/value/merge.rs | 3 ++- lib/src/sql/value/object.rs | 5 ++-- lib/src/sql/value/set.rs | 35 +++++++++++++------------- 15 files changed, 113 insertions(+), 96 deletions(-) diff --git a/lib/src/doc/pluck.rs b/lib/src/doc/pluck.rs index 66fed407..74c39b18 100644 --- a/lib/src/doc/pluck.rs +++ b/lib/src/doc/pluck.rs @@ -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?; } } } diff --git a/lib/src/sql/idiom.rs b/lib/src/sql/idiom.rs index 8a9b3ba9..87b10966 100644 --- a/lib/src/sql/idiom.rs +++ b/lib/src/sql/idiom.rs @@ -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, } +impl Deref for Idiom { + type Target = [Part]; + fn deref(&self) -> &Self::Target { + self.parts.as_slice() + } +} + impl From 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(&['.', '['][..], "/") } diff --git a/lib/src/sql/param.rs b/lib/src/sql/param.rs index 39309d92..4f04bbc5 100644 --- a/lib/src/sql/param.rs +++ b/lib/src/sql/param.rs @@ -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::(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), diff --git a/lib/src/sql/part.rs b/lib/src/sql/part.rs index dc50fa6f..72519ba2 100644 --- a/lib/src/sql/part.rs +++ b/lib/src/sql/part.rs @@ -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) } diff --git a/lib/src/sql/value/array.rs b/lib/src/sql/value/array.rs index be609418..7f3e3a6d 100644 --- a/lib/src/sql/value/array.rs +++ b/lib/src/sql/value/array.rs @@ -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] diff --git a/lib/src/sql/value/decrement.rs b/lib/src/sql/value/decrement.rs index dc10f38b..c5745742 100644 --- a/lib/src/sql/value/decrement.rs +++ b/lib/src/sql/value/decrement.rs @@ -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] diff --git a/lib/src/sql/value/def.rs b/lib/src/sql/value/def.rs index e0e5b2d3..b128f92f 100644 --- a/lib/src/sql/value/def.rs +++ b/lib/src/sql/value/def.rs @@ -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 = Lazy::new(|| Idiom { - parts: vec![Part::from("id")], -}); - -static MTB: Lazy = Lazy::new(|| Idiom { - parts: vec![Part::from("meta"), Part::from("tb")], -}); - -static MID: Lazy = 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!(), diff --git a/lib/src/sql/value/del.rs b/lib/src/sql/value/del.rs index 0bf6800f..de19e15a 100644 --- a/lib/src/sql/value/del.rs +++ b/lib/src/sql/value/del.rs @@ -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] diff --git a/lib/src/sql/value/first.rs b/lib/src/sql/value/first.rs index 509e9f23..15356e42 100644 --- a/lib/src/sql/value/first.rs +++ b/lib/src/sql/value/first.rs @@ -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.get(ctx, opt, txn, &Idiom::from(vec![Part::First])).await + self.get(ctx, opt, txn, &[Part::First]).await } } diff --git a/lib/src/sql/value/get.rs b/lib/src/sql/value/get.rs index a9daf72e..a49f7aeb 100644 --- a/lib/src/sql/value/get.rs +++ b/lib/src/sql/value/get.rs @@ -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 { - 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; diff --git a/lib/src/sql/value/increment.rs b/lib/src/sql/value/increment.rs index 473caf35..b5a9f291 100644 --- a/lib/src/sql/value/increment.rs +++ b/lib/src/sql/value/increment.rs @@ -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] diff --git a/lib/src/sql/value/last.rs b/lib/src/sql/value/last.rs index bd63d07c..15db157c 100644 --- a/lib/src/sql/value/last.rs +++ b/lib/src/sql/value/last.rs @@ -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.get(ctx, opt, txn, &Idiom::from(vec![Part::Last])).await + self.get(ctx, opt, txn, &[Part::Last]).await } } diff --git a/lib/src/sql/value/merge.rs b/lib/src/sql/value/merge.rs index e7b0a5e7..cdeeeb1f 100644 --- a/lib/src/sql/value/merge.rs +++ b/lib/src/sql/value/merge.rs @@ -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(()) } diff --git a/lib/src/sql/value/object.rs b/lib/src/sql/value/object.rs index e767e8b3..5c18c78a 100644 --- a/lib/src/sql/value/object.rs +++ b/lib/src/sql/value/object.rs @@ -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] diff --git a/lib/src/sql/value/set.rs b/lib/src/sql/value/set.rs index c12e1f11..8221c0e8 100644 --- a/lib/src/sql/value/set.rs +++ b/lib/src/sql/value/set.rs @@ -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]