diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d36e985..35d32766 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - *BREAKING:* partiql-eval: Construction of expression evaluators changed to separate binding from evaluation of expression. & implement strict eval - *BREAKING:* partiql-value: `Value` trait's `is_null_or_missing` renamed to `is_absent` +- *BREAKING:* partiql-value: `Value` trait's `coerce_to_tuple`, `coerece_to_bag`, and `coerce_to_list` methods renamed to `coerce_into_tuple`, `coerece_into_bag`, and `coerece_into_list`. +- *BREAKING:* partiql-value: `Tuple`'s `pairs` and `into_pairs` changed to return concrete `Iterator` types. - *BREAKING:* partiql-eval: `EvaluatorPlanner` construction now takes an `EvaluationMode` parameter. - *BREAKING:* partiql-eval: `like_to_re_pattern` is no longer public. - *BREAKING:* partiql-value: Box Decimals in `Value` to assure `Value` fits in 16 bytes. diff --git a/extension/partiql-extension-ion/src/decode.rs b/extension/partiql-extension-ion/src/decode.rs index 2836fb1e..497b45d1 100644 --- a/extension/partiql-extension-ion/src/decode.rs +++ b/extension/partiql-extension-ion/src/decode.rs @@ -124,6 +124,11 @@ impl<'a> Iterator for IonValueIter<'a> { fn next(&mut self) -> Option { self.inner.next() } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } } struct IonValueIterInner @@ -519,7 +524,7 @@ where let is_bag = has_annotation(reader, BAG_ANNOT); let list = decode_list(self, reader); if is_bag { - Ok(Bag::from(list?.coerce_to_list()).into()) + Ok(Bag::from(list?.coerce_into_list()).into()) } else { list } diff --git a/partiql-eval/src/eval/evaluable.rs b/partiql-eval/src/eval/evaluable.rs index 6b7d97ee..18970225 100644 --- a/partiql-eval/src/eval/evaluable.rs +++ b/partiql-eval/src/eval/evaluable.rs @@ -716,7 +716,7 @@ impl Evaluable for EvalGroupBy { EvalGroupingStrategy::GroupFull => { let mut groups: HashMap> = HashMap::new(); for v in input_value.into_iter() { - let v_as_tuple = v.coerce_to_tuple(); + let v_as_tuple = v.coerce_into_tuple(); let group = self.eval_group(&v_as_tuple, ctx); // Compute next aggregation result for each of the aggregation expressions for aggregate_expr in self.aggregate_exprs.iter_mut() { @@ -798,7 +798,7 @@ impl Evaluable for EvalPivot { let tuple: Tuple = input_value .into_iter() .filter_map(|binding| { - let binding = binding.coerce_to_tuple(); + let binding = binding.coerce_into_tuple(); let key = self.key.evaluate(&binding, ctx); if let Value::String(s) = key.as_ref() { let value = self.value.evaluate(&binding, ctx); @@ -852,7 +852,7 @@ impl Evaluable for EvalUnpivot { fn evaluate(&mut self, ctx: &dyn EvalContext) -> Value { let tuple = match self.expr.evaluate(&Tuple::new(), ctx).into_owned() { Value::Tuple(tuple) => *tuple, - other => other.coerce_to_tuple(), + other => other.coerce_into_tuple(), }; let as_key = self.as_key.as_str(); @@ -911,7 +911,7 @@ impl Evaluable for EvalFilter { let filtered = input_value .into_iter() - .map(Value::coerce_to_tuple) + .map(Value::coerce_into_tuple) .filter_map(|v| self.eval_filter(&v, ctx).then_some(v)); Value::from(filtered.collect::()) } @@ -956,7 +956,7 @@ impl Evaluable for EvalHaving { let filtered = input_value .into_iter() - .map(Value::coerce_to_tuple) + .map(Value::coerce_into_tuple) .filter_map(|v| self.eval_having(&v, ctx).then_some(v)); Value::from(filtered.collect::()) } @@ -1128,7 +1128,7 @@ impl Evaluable for EvalSelectValue { let ordered = input_value.is_ordered(); let values = input_value.into_iter().map(|v| { - let v_as_tuple = v.coerce_to_tuple(); + let v_as_tuple = v.coerce_into_tuple(); self.expr.evaluate(&v_as_tuple, ctx).into_owned() }); @@ -1166,7 +1166,7 @@ impl Evaluable for EvalSelect { let ordered = input_value.is_ordered(); let values = input_value.into_iter().map(|v| { - let v_as_tuple = v.coerce_to_tuple(); + let v_as_tuple = v.coerce_into_tuple(); let tuple_pairs = self.exprs.iter().filter_map(|(alias, expr)| { let evaluated_val = expr.evaluate(&v_as_tuple, ctx); @@ -1210,9 +1210,9 @@ impl Evaluable for EvalSelectAll { let ordered = input_value.is_ordered(); let values = input_value.into_iter().map(|val| { - val.coerce_to_tuple() + val.coerce_into_tuple() .into_values() - .flat_map(|v| v.coerce_to_tuple().into_pairs()) + .flat_map(|v| v.coerce_into_tuple().into_pairs()) .collect::() }); @@ -1244,7 +1244,7 @@ impl EvalExprQuery { impl Evaluable for EvalExprQuery { fn evaluate(&mut self, ctx: &dyn EvalContext) -> Value { - let input_value = self.input.take().unwrap_or(Value::Null).coerce_to_tuple(); + let input_value = self.input.take().unwrap_or(Value::Null).coerce_into_tuple(); self.expr.evaluate(&input_value, ctx).into_owned() } diff --git a/partiql-eval/src/eval/expr/coll.rs b/partiql-eval/src/eval/expr/coll.rs index 37c02a5e..35f25996 100644 --- a/partiql-eval/src/eval/expr/coll.rs +++ b/partiql-eval/src/eval/expr/coll.rs @@ -75,12 +75,21 @@ where { type Item = V; + #[inline] fn next(&mut self) -> Option { match self { SetQuantified::All(i) => i.next(), SetQuantified::Distinct(i) => i.next(), } } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + match self { + SetQuantified::All(i) => i.size_hint(), + SetQuantified::Distinct(i) => i.size_hint(), + } + } } /// An [`Iterator`] over a 'set' of values diff --git a/partiql-eval/src/lib.rs b/partiql-eval/src/lib.rs index a43de41d..92b1448b 100644 --- a/partiql-eval/src/lib.rs +++ b/partiql-eval/src/lib.rs @@ -182,7 +182,7 @@ mod tests { let mut bindings = MapBindings::default(); bindings.insert("data", list![Tuple::from([("lhs", lhs)])].into()); - let result = evaluate(plan, bindings).coerce_to_bag(); + let result = evaluate(plan, bindings).coerce_into_bag(); assert!(!&result.is_empty()); let expected_result = if expected_first_elem != Missing { bag!(Tuple::from([("result", expected_first_elem)])) @@ -689,7 +689,7 @@ mod tests { let mut bindings = MapBindings::default(); bindings.insert("data", list![Tuple::from([("value", value)])].into()); - let result = evaluate(plan, bindings).coerce_to_bag(); + let result = evaluate(plan, bindings).coerce_into_bag(); assert!(!&result.is_empty()); let expected_result = bag!(Tuple::from([("result", expected_first_elem)])); assert_eq!(expected_result, result); @@ -1154,7 +1154,7 @@ mod tests { let mut bindings = MapBindings::default(); bindings.insert("data", list![Tuple::from([("expr", expr)])].into()); - let result = evaluate(plan, bindings).coerce_to_bag(); + let result = evaluate(plan, bindings).coerce_into_bag(); assert!(!&result.is_empty()); assert_eq!(bag!(Tuple::from([("result", expected_first_elem)])), result); } @@ -1216,7 +1216,7 @@ mod tests { let mut bindings = MapBindings::default(); bindings.insert("data", list![Tuple::from([("lhs", lhs)])].into()); - let result = evaluate(plan, bindings).coerce_to_bag(); + let result = evaluate(plan, bindings).coerce_into_bag(); assert!(!&result.is_empty()); let expected_result = if expected_first_elem != Missing { bag!(Tuple::from([("result", expected_first_elem)])) @@ -1280,7 +1280,7 @@ mod tests { .for_each(|(i, e)| data.insert(&format!("arg{i}"), e)); bindings.insert("data", list![data].into()); - let result = evaluate(plan, bindings).coerce_to_bag(); + let result = evaluate(plan, bindings).coerce_into_bag(); assert!(!&result.is_empty()); assert_eq!(bag!(Tuple::from([("result", expected_first_elem)])), result); } diff --git a/partiql-value/src/bag.rs b/partiql-value/src/bag.rs index 6d02ccf3..cfda4d90 100644 --- a/partiql-value/src/bag.rs +++ b/partiql-value/src/bag.rs @@ -108,6 +108,7 @@ impl<'a> IntoIterator for &'a Bag { type Item = &'a Value; type IntoIter = BagIter<'a>; + #[inline] fn into_iter(self) -> Self::IntoIter { BagIter(self.0.iter()) } @@ -119,9 +120,15 @@ pub struct BagIter<'a>(slice::Iter<'a, Value>); impl<'a> Iterator for BagIter<'a> { type Item = &'a Value; + #[inline] fn next(&mut self) -> Option { self.0.next() } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } } impl IntoIterator for Bag { @@ -138,9 +145,15 @@ pub struct BagIntoIterator(vec::IntoIter); impl Iterator for BagIntoIterator { type Item = Value; + #[inline] fn next(&mut self) -> Option { self.0.next() } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } } impl Debug for Bag { diff --git a/partiql-value/src/lib.rs b/partiql-value/src/lib.rs index 02791f1d..c573569e 100644 --- a/partiql-value/src/lib.rs +++ b/partiql-value/src/lib.rs @@ -6,6 +6,7 @@ use std::borrow::Cow; use std::fmt::{Debug, Formatter}; use std::hash::Hash; +use std::iter::Once; use std::{ops, vec}; use rust_decimal::prelude::FromPrimitive; @@ -553,13 +554,25 @@ impl Value { } #[inline] - pub fn coerce_to_tuple(self) -> Tuple { + pub fn coerce_into_tuple(self) -> Tuple { match self { Value::Tuple(t) => *t, - Value::Missing => tuple![], + _ => self + .into_bindings() + .map(|(k, v)| (k.unwrap_or_else(|| "_1".to_string()), v)) + .collect(), + } + } + + #[inline] + pub fn coerce_to_tuple(&self) -> Tuple { + match self { + Value::Tuple(t) => t.as_ref().clone(), _ => { - let fresh_key = "_1"; // TODO don't hard-code 'fresh' keys - tuple![(fresh_key, self)] + let fresh = "_1".to_string(); + self.as_bindings() + .map(|(k, v)| (k.unwrap_or(&fresh), v.clone())) + .collect() } } } @@ -569,12 +582,30 @@ impl Value { if let Value::Tuple(t) = self { Cow::Borrowed(t) } else { - Cow::Owned(self.clone().coerce_to_tuple()) + Cow::Owned(self.coerce_to_tuple()) + } + } + + #[inline] + pub fn as_bindings(&self) -> BindingIter { + match self { + Value::Tuple(t) => BindingIter::Tuple(t.pairs()), + Value::Missing => BindingIter::Empty, + _ => BindingIter::Single(std::iter::once(self)), + } + } + + #[inline] + pub fn into_bindings(self) -> BindingIntoIter { + match self { + Value::Tuple(t) => BindingIntoIter::Tuple(t.into_pairs()), + Value::Missing => BindingIntoIter::Empty, + _ => BindingIntoIter::Single(std::iter::once(self)), } } #[inline] - pub fn coerce_to_bag(self) -> Bag { + pub fn coerce_into_bag(self) -> Bag { if let Value::Bag(b) = self { *b } else { @@ -587,12 +618,12 @@ impl Value { if let Value::Bag(b) = self { Cow::Borrowed(b) } else { - Cow::Owned(self.clone().coerce_to_bag()) + Cow::Owned(self.clone().coerce_into_bag()) } } #[inline] - pub fn coerce_to_list(self) -> List { + pub fn coerce_into_list(self) -> List { if let Value::List(b) = self { *b } else { @@ -605,7 +636,7 @@ impl Value { if let Value::List(l) = self { Cow::Borrowed(l) } else { - Cow::Owned(self.clone().coerce_to_list()) + Cow::Owned(self.clone().coerce_into_list()) } } @@ -629,6 +660,64 @@ impl Value { } } +#[derive(Debug, Clone)] +pub enum BindingIter<'a> { + Tuple(PairsIter<'a>), + Single(Once<&'a Value>), + Empty, +} + +impl<'a> Iterator for BindingIter<'a> { + type Item = (Option<&'a String>, &'a Value); + + #[inline] + fn next(&mut self) -> Option { + match self { + BindingIter::Tuple(t) => t.next().map(|(k, v)| (Some(k), v)), + BindingIter::Single(single) => single.next().map(|v| (None, v)), + BindingIter::Empty => None, + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + match self { + BindingIter::Tuple(t) => t.size_hint(), + BindingIter::Single(_single) => (1, Some(1)), + BindingIter::Empty => (0, Some(0)), + } + } +} + +#[derive(Debug)] +pub enum BindingIntoIter { + Tuple(PairsIntoIter), + Single(Once), + Empty, +} + +impl Iterator for BindingIntoIter { + type Item = (Option, Value); + + #[inline] + fn next(&mut self) -> Option { + match self { + BindingIntoIter::Tuple(t) => t.next().map(|(k, v)| (Some(k), v)), + BindingIntoIter::Single(single) => single.next().map(|v| (None, v)), + BindingIntoIter::Empty => None, + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + match self { + BindingIntoIter::Tuple(t) => t.size_hint(), + BindingIntoIter::Single(_single) => (1, Some(1)), + BindingIntoIter::Empty => (0, Some(0)), + } + } +} + #[derive(Debug, Clone)] pub enum ValueIter<'a> { List(ListIter<'a>), @@ -639,6 +728,7 @@ pub enum ValueIter<'a> { impl<'a> Iterator for ValueIter<'a> { type Item = &'a Value; + #[inline] fn next(&mut self) -> Option { match self { ValueIter::List(list) => list.next(), @@ -646,12 +736,22 @@ impl<'a> Iterator for ValueIter<'a> { ValueIter::Single(v) => v.take(), } } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + match self { + ValueIter::List(list) => list.size_hint(), + ValueIter::Bag(bag) => bag.size_hint(), + ValueIter::Single(_) => (1, Some(1)), + } + } } impl IntoIterator for Value { type Item = Value; type IntoIter = ValueIntoIterator; + #[inline] fn into_iter(self) -> ValueIntoIterator { match self { Value::List(list) => ValueIntoIterator::List(list.into_iter()), @@ -670,6 +770,7 @@ pub enum ValueIntoIterator { impl Iterator for ValueIntoIterator { type Item = Value; + #[inline] fn next(&mut self) -> Option { match self { ValueIntoIterator::List(list) => list.next(), @@ -677,6 +778,15 @@ impl Iterator for ValueIntoIterator { ValueIntoIterator::Single(v) => v.take(), } } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + match self { + ValueIntoIterator::List(list) => list.size_hint(), + ValueIntoIterator::Bag(bag) => bag.size_hint(), + ValueIntoIterator::Single(_) => (1, Some(1)), + } + } } // TODO make debug emit proper PartiQL notation @@ -1038,9 +1148,9 @@ mod tests { let mut pairs = tuple.pairs(); let list_val = Value::from(list); - assert_eq!(pairs.next(), Some(("list", &list_val))); + assert_eq!(pairs.next(), Some((&"list".to_string(), &list_val))); let bag_val = Value::from(bag); - assert_eq!(pairs.next(), Some(("bag", &bag_val))); + assert_eq!(pairs.next(), Some((&"bag".to_string(), &bag_val))); assert_eq!(pairs.next(), None); } diff --git a/partiql-value/src/list.rs b/partiql-value/src/list.rs index 098f599a..792468aa 100644 --- a/partiql-value/src/list.rs +++ b/partiql-value/src/list.rs @@ -119,9 +119,15 @@ pub struct ListIter<'a>(slice::Iter<'a, Value>); impl<'a> Iterator for ListIter<'a> { type Item = &'a Value; + #[inline] fn next(&mut self) -> Option { self.0.next() } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } } impl IntoIterator for List { @@ -138,9 +144,15 @@ pub struct ListIntoIterator(vec::IntoIter); impl Iterator for ListIntoIterator { type Item = Value; + #[inline] fn next(&mut self) -> Option { self.0.next() } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } } impl Debug for List { diff --git a/partiql-value/src/tuple.rs b/partiql-value/src/tuple.rs index dcca4eb7..6a46b4e4 100644 --- a/partiql-value/src/tuple.rs +++ b/partiql-value/src/tuple.rs @@ -4,7 +4,7 @@ use std::cmp::Ordering; use std::fmt::{Debug, Formatter}; use std::hash::{Hash, Hasher}; -use std::iter::zip; +use std::iter::{zip, Zip}; use std::vec; use unicase::UniCase; @@ -104,13 +104,14 @@ impl Tuple { } #[inline] - pub fn pairs(&self) -> impl Iterator + Clone { - zip(&self.attrs, &self.vals).map(|(k, v)| (k.as_str(), v)) + pub fn pairs(&self) -> PairsIter { + let attrs = self.attrs.iter(); + PairsIter(zip(attrs, self.vals.iter())) } #[inline] - pub fn into_pairs(self) -> impl Iterator { - zip(self.attrs, self.vals) + pub fn into_pairs(self) -> PairsIntoIter { + PairsIntoIter(zip(self.attrs, self.vals)) } #[inline] @@ -124,6 +125,40 @@ impl Tuple { } } +#[derive(Debug, Clone)] +pub struct PairsIter<'a>(Zip, std::slice::Iter<'a, Value>>); + +impl<'a> Iterator for PairsIter<'a> { + type Item = (&'a String, &'a Value); + + #[inline] + fn next(&mut self) -> Option { + self.0.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +#[derive(Debug, Clone)] +pub struct PairsIntoIter(Zip, std::vec::IntoIter>); + +impl Iterator for PairsIntoIter { + type Item = (String, Value); + + #[inline] + fn next(&mut self) -> Option { + self.0.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + impl From<[(&str, T); N]> for Tuple where T: Into, @@ -160,12 +195,18 @@ where impl Iterator for Tuple { type Item = (String, Value); + #[inline] fn next(&mut self) -> Option { match (self.attrs.pop(), self.vals.pop()) { (Some(attr), Some(val)) => Some((attr, val)), _ => None, } } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + (self.attrs.len(), Some(self.attrs.len())) + } } impl PartialEq for Tuple {