From c1bd87dd1dfc18d33f537a941359b1278064c4d2 Mon Sep 17 00:00:00 2001 From: Martin Ek Date: Fri, 29 Sep 2017 18:15:23 +0200 Subject: [PATCH 01/16] Initial arithmetic support in Project --- src/flow/core/data.rs | 153 +++++++++++++++++++++++++++++++++++++++- src/mir/mod.rs | 1 + src/ops/project.rs | 157 +++++++++++++++++++++++++++++++++++++----- tests/lib.rs | 6 +- 4 files changed, 297 insertions(+), 20 deletions(-) diff --git a/src/flow/core/data.rs b/src/flow/core/data.rs index 6b919f1f0..e31cd6bb5 100644 --- a/src/flow/core/data.rs +++ b/src/flow/core/data.rs @@ -8,7 +8,7 @@ use nom_sql::Literal; use serde_json::Value; use std::hash::{Hash, Hasher}; -use std::ops::{Deref, DerefMut}; +use std::ops::{Add, Deref, DerefMut, Div, Mul, Sub}; use std::fmt; const TINYTEXT_WIDTH: usize = 15; @@ -202,6 +202,17 @@ impl Into for DataType { } } +impl Into for DataType { + fn into(self) -> f64 { + match self { + DataType::Real(i, f) => i as f64 + (f as f64) / 1000_000_000.0, + DataType::Int(i) => i as f64, + DataType::BigInt(i) => i as f64, + _ => unreachable!(), + } + } +} + impl From for DataType { fn from(s: String) -> Self { let len = s.as_bytes().len(); @@ -225,6 +236,94 @@ impl<'a> From<&'a str> for DataType { } } +impl Add for DataType { + type Output = DataType; + + fn add(self, other: DataType) -> DataType { + match (self, other) { + (DataType::Int(first), DataType::Int(second)) => (first + second).into(), + (DataType::BigInt(first), DataType::BigInt(second)) => (first + second).into(), + (DataType::Int(first), DataType::BigInt(second)) => ((first as i64) + second).into(), + (DataType::BigInt(first), DataType::Int(second)) => (first + (second as i64)).into(), + + (first @ DataType::Int(..), second @ DataType::Real(..)) | + (first @ DataType::Real(..), second @ DataType::Int(..)) | + (first @ DataType::Real(..), second @ DataType::Real(..)) => { + let a: f64 = first.into(); + let b: f64 = second.into(); + (a + b).into() + } + (first, second) => panic!(format!("cannot add a {} and {}", first, second)), + } + } +} + +impl Sub for DataType { + type Output = DataType; + + fn sub(self, other: DataType) -> DataType { + match (self, other) { + (DataType::Int(first), DataType::Int(second)) => (first - second).into(), + (DataType::BigInt(first), DataType::BigInt(second)) => (first - second).into(), + (DataType::Int(first), DataType::BigInt(second)) => ((first as i64) - second).into(), + (DataType::BigInt(first), DataType::Int(second)) => (first - (second as i64)).into(), + + (first @ DataType::Int(..), second @ DataType::Real(..)) | + (first @ DataType::Real(..), second @ DataType::Int(..)) | + (first @ DataType::Real(..), second @ DataType::Real(..)) => { + let a: f64 = first.into(); + let b: f64 = second.into(); + (a - b).into() + } + (first, second) => panic!(format!("cannot subtract a {} and {}", first, second)), + } + } +} + +impl Mul for DataType { + type Output = DataType; + + fn mul(self, other: DataType) -> DataType { + match (self, other) { + (DataType::Int(first), DataType::Int(second)) => (first * second).into(), + (DataType::BigInt(first), DataType::BigInt(second)) => (first * second).into(), + (DataType::Int(first), DataType::BigInt(second)) => ((first as i64) * second).into(), + (DataType::BigInt(first), DataType::Int(second)) => (first * (second as i64)).into(), + + (first @ DataType::Int(..), second @ DataType::Real(..)) | + (first @ DataType::Real(..), second @ DataType::Int(..)) | + (first @ DataType::Real(..), second @ DataType::Real(..)) => { + let a: f64 = first.into(); + let b: f64 = second.into(); + (a * b).into() + } + (first, second) => panic!(format!("cannot multiply a {} and {}", first, second)), + } + } +} + +impl Div for DataType { + type Output = DataType; + + fn div(self, other: DataType) -> DataType { + match (self, other) { + (DataType::Int(first), DataType::Int(second)) => (first / second).into(), + (DataType::BigInt(first), DataType::BigInt(second)) => (first / second).into(), + (DataType::Int(first), DataType::BigInt(second)) => ((first as i64) / second).into(), + (DataType::BigInt(first), DataType::Int(second)) => (first / (second as i64)).into(), + + (first @ DataType::Int(..), second @ DataType::Real(..)) | + (first @ DataType::Real(..), second @ DataType::Int(..)) | + (first @ DataType::Real(..), second @ DataType::Real(..)) => { + let a: f64 = first.into(); + let b: f64 = second.into(); + (a / b).into() + } + (first, second) => panic!(format!("cannot divide a {} and {}", first, second)), + } + } +} + impl fmt::Debug for DataType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -442,6 +541,58 @@ mod tests { assert_eq!(c.to_json(), json!(-0.012345678)); } + #[test] + fn real_to_float() { + let original = 2.5; + let data_type: DataType = original.into(); + let converted: f64 = data_type.into(); + assert_eq!(original, converted); + } + + #[test] + fn add_data_types() { + assert_eq!(DataType::from(1) + DataType::from(2), 3.into()); + assert_eq!(DataType::from(1.5) + DataType::from(2), (3.5).into()); + assert_eq!(DataType::from(2) + DataType::from(1.5), (3.5).into()); + assert_eq!(DataType::from(1.5) + DataType::from(2.5), (4.0).into()); + assert_eq!(DataType::BigInt(1) + DataType::BigInt(2), 3.into()); + assert_eq!(DataType::from(1) + DataType::BigInt(2), 3.into()); + assert_eq!(DataType::BigInt(2) + DataType::from(1), 3.into()); + } + + #[test] + fn subtract_data_types() { + assert_eq!(DataType::from(2) - DataType::from(1), 1.into()); + assert_eq!(DataType::from(3.5) - DataType::from(2), (1.5).into()); + assert_eq!(DataType::from(2) - DataType::from(1.5), (0.5).into()); + assert_eq!(DataType::from(3.5) - DataType::from(2.0), (1.5).into()); + assert_eq!(DataType::BigInt(1) - DataType::BigInt(2), (-1).into()); + assert_eq!(DataType::from(1) - DataType::BigInt(2), (-1).into()); + assert_eq!(DataType::BigInt(2) - DataType::from(1), 1.into()); + } + + #[test] + fn multiply_data_types() { + assert_eq!(DataType::from(2) * DataType::from(1), 2.into()); + assert_eq!(DataType::from(3.5) * DataType::from(2), (7.0).into()); + assert_eq!(DataType::from(2) * DataType::from(1.5), (3.0).into()); + assert_eq!(DataType::from(3.5) * DataType::from(2.0), (7.0).into()); + assert_eq!(DataType::BigInt(1) * DataType::BigInt(2), 2.into()); + assert_eq!(DataType::from(1) * DataType::BigInt(2), 2.into()); + assert_eq!(DataType::BigInt(2) * DataType::from(1), 2.into()); + } + + #[test] + fn divide_data_types() { + assert_eq!(DataType::from(2) / DataType::from(1), 2.into()); + assert_eq!(DataType::from(7.5) / DataType::from(2), (3.75).into()); + assert_eq!(DataType::from(7) / DataType::from(2.5), (2.8).into()); + assert_eq!(DataType::from(3.5) / DataType::from(2.0), (1.75).into()); + assert_eq!(DataType::BigInt(4) / DataType::BigInt(2), 2.into()); + assert_eq!(DataType::from(4) / DataType::BigInt(2), 2.into()); + assert_eq!(DataType::BigInt(4) / DataType::from(2), 2.into()); + } + #[test] fn data_type_debug() { let tiny_text: DataType = "hi".into(); diff --git a/src/mir/mod.rs b/src/mir/mod.rs index c2912f920..7814d2140 100644 --- a/src/mir/mod.rs +++ b/src/mir/mod.rs @@ -1570,6 +1570,7 @@ fn make_project_node( parent_na, projected_column_ids.as_slice(), Some(literal_values), + None, ), ); FlowNode::New(n) diff --git a/src/ops/project.rs b/src/ops/project.rs index 5d9d5b28e..0db0f46c8 100644 --- a/src/ops/project.rs +++ b/src/ops/project.rs @@ -2,22 +2,51 @@ use std::collections::HashMap; use flow::prelude::*; +// TODO: use the ArithmeticOperator from nom-sql +#[derive(Debug, Clone, Serialize, Deserialize)] +enum ArithmeticOperator { + Add, + Subtract, + Multiply, + Divide, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +enum ArithmeticBase { + Column(usize), + Literal(DataType), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ArithmeticExpression { + op: ArithmeticOperator, + left: ArithmeticBase, + right: ArithmeticBase, +} + /// Permutes or omits columns from its source node, or adds additional literal value columns. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Project { us: Option, emit: Option>, additional: Option>, + expressions: Option>, src: IndexPair, cols: usize, } impl Project { /// Construct a new permuter operator. - pub fn new(src: NodeIndex, emit: &[usize], additional: Option>) -> Project { + pub fn new( + src: NodeIndex, + emit: &[usize], + additional: Option>, + expressions: Option>, + ) -> Project { Project { emit: Some(emit.into()), additional: additional, + expressions: expressions, src: src.into(), cols: 0, us: None, @@ -34,6 +63,25 @@ impl Project { self.emit.as_ref().map_or(col, |emit| emit[col]) } } + + fn eval_expression(&self, expression: &ArithmeticExpression, record: &Record) -> DataType { + let left = match expression.left { + ArithmeticBase::Column(i) => &record[i], + ArithmeticBase::Literal(ref data) => data, + }.clone(); + + let right = match expression.right { + ArithmeticBase::Column(i) => &record[i], + ArithmeticBase::Literal(ref data) => data, + }.clone(); + + match expression.op { + ArithmeticOperator::Add => left + right, + ArithmeticOperator::Subtract => left - right, + ArithmeticOperator::Multiply => left * right, + ArithmeticOperator::Divide => left / right, + } + } } impl Ingredient for Project { @@ -89,10 +137,20 @@ impl Ingredient for Project { for i in e { new_r.push(r[*i].clone()); } + match self.expressions { + Some(ref e) => { + for i in e { + new_r.push(self.eval_expression(i, r)); + } + } + None => (), + } match self.additional { - Some(ref a) => for i in a { - new_r.push(i.clone()); - }, + Some(ref a) => { + for i in a { + new_r.push(i.clone()); + } + } None => (), } **r = new_r; @@ -115,17 +173,24 @@ impl Ingredient for Project { fn description(&self) -> String { let emit_cols = match self.emit.as_ref() { None => "*".into(), - Some(emit) => match self.additional { - None => emit.iter() - .map(|e| e.to_string()) - .collect::>() - .join(", "), - Some(ref add) => emit.iter() - .map(|e| e.to_string()) - .chain(add.iter().map(|e| format!("lit: {}", e.to_string()))) - .collect::>() - .join(", "), - }, + // TODO: print out arithmetic expressions as well: + Some(emit) => { + match self.additional { + None => { + emit.iter() + .map(|e| e.to_string()) + .collect::>() + .join(", ") + } + Some(ref add) => { + emit.iter() + .map(|e| e.to_string()) + .chain(add.iter().map(|e| format!("lit: {}", e.to_string()))) + .collect::>() + .join(", ") + } + } + } }; format!("π[{}]", emit_cols) } @@ -159,12 +224,32 @@ mod tests { g.set_op( "permute", &["x", "y", "z"], - Project::new(s.as_global(), &permutation[..], additional), + Project::new(s.as_global(), &permutation[..], additional, None), materialized, ); g } + fn setup_arithmetic(op: ArithmeticOperator) -> ops::test::MockGraph { + let expression = ArithmeticExpression { + left: ArithmeticBase::Column(0), + right: ArithmeticBase::Column(1), + op: op, + }; + + let mut g = ops::test::MockGraph::new(); + let s = g.add_base("source", &["x", "y", "z"]); + + let permutation = vec![0, 1]; + g.set_op( + "permute", + &["x", "y", "z"], + Project::new(s.as_global(), &permutation[..], None, Some(vec![expression])), + false, + ); + g + } + #[test] fn it_describes() { let p = setup(false, false, true); @@ -227,6 +312,46 @@ mod tests { ); } + #[test] + fn it_forwards_addition_arithmetic() { + let mut p = setup_arithmetic(ArithmeticOperator::Add); + let rec = vec![10.into(), 20.into()]; + assert_eq!( + p.narrow_one_row(rec, false), + vec![vec![10.into(), 20.into(), 30.into()]].into() + ); + } + + #[test] + fn it_forwards_subtraction_arithmetic() { + let mut p = setup_arithmetic(ArithmeticOperator::Subtract); + let rec = vec![10.into(), 20.into()]; + assert_eq!( + p.narrow_one_row(rec, false), + vec![vec![10.into(), 20.into(), (-10).into()]].into() + ); + } + + #[test] + fn it_forwards_multiplication_arithmetic() { + let mut p = setup_arithmetic(ArithmeticOperator::Multiply); + let rec = vec![10.into(), 20.into()]; + assert_eq!( + p.narrow_one_row(rec, false), + vec![vec![10.into(), 20.into(), 200.into()]].into() + ); + } + + #[test] + fn it_forwards_division_arithmetic() { + let mut p = setup_arithmetic(ArithmeticOperator::Divide); + let rec = vec![10.into(), 2.into()]; + assert_eq!( + p.narrow_one_row(rec, false), + vec![vec![10.into(), 2.into(), 5.into()]].into() + ); + } + #[test] fn it_suggests_indices() { let me = 1.into(); diff --git a/tests/lib.rs b/tests/lib.rs index 8a0fca599..c27fb8a2a 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -771,7 +771,7 @@ fn migrate_added_columns() { let b = mig.add_ingredient( "x", &["c", "b"], - distributary::Project::new(a, &[2, 0], None), + distributary::Project::new(a, &[2, 0], None, None), ); mig.maintain(b, 1); mig.commit(); @@ -902,7 +902,7 @@ fn key_on_added() { let b = mig.add_ingredient( "x", &["c", "b"], - distributary::Project::new(a, &[2, 1], None), + distributary::Project::new(a, &[2, 1], None, None), ); mig.maintain(b, 0); mig.commit(); @@ -1046,7 +1046,7 @@ fn full_aggregation_with_bogokey() { let bogo = mig.add_ingredient( "bogo", &["x", "bogo"], - distributary::Project::new(base, &[0], Some(vec![0.into()])), + distributary::Project::new(base, &[0], Some(vec![0.into()]), None), ); let agg = mig.add_ingredient( "agg", From a2f71406c430c920f9e602f4f8b638cfd8b21f5a Mon Sep 17 00:00:00 2001 From: Martin Ek Date: Fri, 29 Sep 2017 18:36:25 +0200 Subject: [PATCH 02/16] Use a macro for arithmetic DataType operations --- src/flow/core/data.rs | 86 +++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 56 deletions(-) diff --git a/src/flow/core/data.rs b/src/flow/core/data.rs index e31cd6bb5..09aa63c77 100644 --- a/src/flow/core/data.rs +++ b/src/flow/core/data.rs @@ -236,25 +236,33 @@ impl<'a> From<&'a str> for DataType { } } -impl Add for DataType { - type Output = DataType; - - fn add(self, other: DataType) -> DataType { - match (self, other) { - (DataType::Int(first), DataType::Int(second)) => (first + second).into(), - (DataType::BigInt(first), DataType::BigInt(second)) => (first + second).into(), - (DataType::Int(first), DataType::BigInt(second)) => ((first as i64) + second).into(), - (DataType::BigInt(first), DataType::Int(second)) => (first + (second as i64)).into(), +// Performs an arithmetic operation on two numeric DataTypes, +// returning a new DataType as the result. +macro_rules! arithmetic_operation ( + ($op:tt, $first:ident, $second:ident) => ( + match ($first, $second) { + (DataType::Int(a), DataType::Int(b)) => (a $op b).into(), + (DataType::BigInt(a), DataType::BigInt(b)) => (a $op b).into(), + (DataType::Int(a), DataType::BigInt(b)) => ((a as i64) $op b).into(), + (DataType::BigInt(a), DataType::Int(b)) => (a $op (b as i64)).into(), (first @ DataType::Int(..), second @ DataType::Real(..)) | (first @ DataType::Real(..), second @ DataType::Int(..)) | (first @ DataType::Real(..), second @ DataType::Real(..)) => { let a: f64 = first.into(); let b: f64 = second.into(); - (a + b).into() + (a $op b).into() } - (first, second) => panic!(format!("cannot add a {} and {}", first, second)), + (first, second) => panic!(format!("can't {} a {:?} and {:?}", stringify!($op), first, second)), } + ); +); + +impl Add for DataType { + type Output = DataType; + + fn add(self, other: DataType) -> DataType { + arithmetic_operation!(+, self, other) } } @@ -262,21 +270,7 @@ impl Sub for DataType { type Output = DataType; fn sub(self, other: DataType) -> DataType { - match (self, other) { - (DataType::Int(first), DataType::Int(second)) => (first - second).into(), - (DataType::BigInt(first), DataType::BigInt(second)) => (first - second).into(), - (DataType::Int(first), DataType::BigInt(second)) => ((first as i64) - second).into(), - (DataType::BigInt(first), DataType::Int(second)) => (first - (second as i64)).into(), - - (first @ DataType::Int(..), second @ DataType::Real(..)) | - (first @ DataType::Real(..), second @ DataType::Int(..)) | - (first @ DataType::Real(..), second @ DataType::Real(..)) => { - let a: f64 = first.into(); - let b: f64 = second.into(); - (a - b).into() - } - (first, second) => panic!(format!("cannot subtract a {} and {}", first, second)), - } + arithmetic_operation!(-, self, other) } } @@ -284,21 +278,7 @@ impl Mul for DataType { type Output = DataType; fn mul(self, other: DataType) -> DataType { - match (self, other) { - (DataType::Int(first), DataType::Int(second)) => (first * second).into(), - (DataType::BigInt(first), DataType::BigInt(second)) => (first * second).into(), - (DataType::Int(first), DataType::BigInt(second)) => ((first as i64) * second).into(), - (DataType::BigInt(first), DataType::Int(second)) => (first * (second as i64)).into(), - - (first @ DataType::Int(..), second @ DataType::Real(..)) | - (first @ DataType::Real(..), second @ DataType::Int(..)) | - (first @ DataType::Real(..), second @ DataType::Real(..)) => { - let a: f64 = first.into(); - let b: f64 = second.into(); - (a * b).into() - } - (first, second) => panic!(format!("cannot multiply a {} and {}", first, second)), - } + arithmetic_operation!(*, self, other) } } @@ -306,21 +286,7 @@ impl Div for DataType { type Output = DataType; fn div(self, other: DataType) -> DataType { - match (self, other) { - (DataType::Int(first), DataType::Int(second)) => (first / second).into(), - (DataType::BigInt(first), DataType::BigInt(second)) => (first / second).into(), - (DataType::Int(first), DataType::BigInt(second)) => ((first as i64) / second).into(), - (DataType::BigInt(first), DataType::Int(second)) => (first / (second as i64)).into(), - - (first @ DataType::Int(..), second @ DataType::Real(..)) | - (first @ DataType::Real(..), second @ DataType::Int(..)) | - (first @ DataType::Real(..), second @ DataType::Real(..)) => { - let a: f64 = first.into(); - let b: f64 = second.into(); - (a / b).into() - } - (first, second) => panic!(format!("cannot divide a {} and {}", first, second)), - } + arithmetic_operation!(/, self, other) } } @@ -593,6 +559,14 @@ mod tests { assert_eq!(DataType::BigInt(4) / DataType::from(2), 2.into()); } + #[test] + #[should_panic(expected = "can't + a TinyText(\"hi\") and Int(5)")] + fn add_invalid_types() { + let a: DataType = "hi".into(); + let b: DataType = 5.into(); + a + b; + } + #[test] fn data_type_debug() { let tiny_text: DataType = "hi".into(); From a9eeb9242e96897ed74200c961d0dd6192156852 Mon Sep 17 00:00:00 2001 From: Martin Ek Date: Fri, 29 Sep 2017 18:50:11 +0200 Subject: [PATCH 03/16] Add tests for literal arithmetic --- src/ops/project.rs | 68 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/src/ops/project.rs b/src/ops/project.rs index 0db0f46c8..b25eb84d8 100644 --- a/src/ops/project.rs +++ b/src/ops/project.rs @@ -230,13 +230,7 @@ mod tests { g } - fn setup_arithmetic(op: ArithmeticOperator) -> ops::test::MockGraph { - let expression = ArithmeticExpression { - left: ArithmeticBase::Column(0), - right: ArithmeticBase::Column(1), - op: op, - }; - + fn setup_arithmetic(expression: ArithmeticExpression) -> ops::test::MockGraph { let mut g = ops::test::MockGraph::new(); let s = g.add_base("source", &["x", "y", "z"]); @@ -244,12 +238,27 @@ mod tests { g.set_op( "permute", &["x", "y", "z"], - Project::new(s.as_global(), &permutation[..], None, Some(vec![expression])), + Project::new( + s.as_global(), + &permutation[..], + None, + Some(vec![expression]), + ), false, ); g } + fn setup_column_arithmetic(op: ArithmeticOperator) -> ops::test::MockGraph { + let expression = ArithmeticExpression { + left: ArithmeticBase::Column(0), + right: ArithmeticBase::Column(1), + op: op, + }; + + setup_arithmetic(expression) + } + #[test] fn it_describes() { let p = setup(false, false, true); @@ -314,7 +323,7 @@ mod tests { #[test] fn it_forwards_addition_arithmetic() { - let mut p = setup_arithmetic(ArithmeticOperator::Add); + let mut p = setup_column_arithmetic(ArithmeticOperator::Add); let rec = vec![10.into(), 20.into()]; assert_eq!( p.narrow_one_row(rec, false), @@ -324,7 +333,7 @@ mod tests { #[test] fn it_forwards_subtraction_arithmetic() { - let mut p = setup_arithmetic(ArithmeticOperator::Subtract); + let mut p = setup_column_arithmetic(ArithmeticOperator::Subtract); let rec = vec![10.into(), 20.into()]; assert_eq!( p.narrow_one_row(rec, false), @@ -334,7 +343,7 @@ mod tests { #[test] fn it_forwards_multiplication_arithmetic() { - let mut p = setup_arithmetic(ArithmeticOperator::Multiply); + let mut p = setup_column_arithmetic(ArithmeticOperator::Multiply); let rec = vec![10.into(), 20.into()]; assert_eq!( p.narrow_one_row(rec, false), @@ -344,7 +353,7 @@ mod tests { #[test] fn it_forwards_division_arithmetic() { - let mut p = setup_arithmetic(ArithmeticOperator::Divide); + let mut p = setup_column_arithmetic(ArithmeticOperator::Divide); let rec = vec![10.into(), 2.into()]; assert_eq!( p.narrow_one_row(rec, false), @@ -352,6 +361,41 @@ mod tests { ); } + #[test] + fn it_forwards_arithmetic_w_literals() { + let number: DataType = 40.into(); + let expression = ArithmeticExpression { + left: ArithmeticBase::Column(0), + right: ArithmeticBase::Literal(number), + op: ArithmeticOperator::Multiply, + }; + + let mut p = setup_arithmetic(expression); + let rec = vec![10.into(), 0.into()]; + assert_eq!( + p.narrow_one_row(rec, false), + vec![vec![10.into(), 0.into(), 400.into()]].into() + ); + } + + #[test] + fn it_forwards_arithmetic_w_only_literals() { + let a: DataType = 80.into(); + let b: DataType = 40.into(); + let expression = ArithmeticExpression { + left: ArithmeticBase::Literal(a), + right: ArithmeticBase::Literal(b), + op: ArithmeticOperator::Divide, + }; + + let mut p = setup_arithmetic(expression); + let rec = vec![0.into(), 0.into()]; + assert_eq!( + p.narrow_one_row(rec, false), + vec![vec![0.into(), 0.into(), 2.into()]].into() + ); + } + #[test] fn it_suggests_indices() { let me = 1.into(); From af2832f4d61620f771969967c0bd95f25fc4d69b Mon Sep 17 00:00:00 2001 From: Martin Ek Date: Sun, 1 Oct 2017 15:16:33 +0200 Subject: [PATCH 04/16] Handle ArithmeticExpressions from nom-sql --- Cargo.lock | 6 +-- Cargo.toml | 2 +- src/mir/mod.rs | 40 ++++++++++++-- src/mir/reuse.rs | 1 + src/mir/visualize.rs | 3 ++ src/ops/project.rs | 67 ++++++++++++----------- src/sql/mir.rs | 30 ++++++++++- src/sql/mod.rs | 2 + src/sql/passes/count_star_rewrite.rs | 1 + src/sql/passes/implied_tables.rs | 11 +++- src/sql/passes/star_expansion.rs | 1 + src/sql/query_graph.rs | 40 +++++++++++++- tests/lib.rs | 81 +++++++++++++++++++++++++++- 13 files changed, 241 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index add311be4..e58932ba6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,7 +22,7 @@ dependencies = [ "memcached-rs 0.1.2 (git+https://github.com/jonhoo/memcached-rs.git?branch=expose-multi)", "mysql 12.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", - "nom_sql 0.0.1 (git+https://github.com/ms705/nom-sql.git?rev=7758136babe72f470b7ea9fea384fca4f47db647)", + "nom_sql 0.0.1 (git+https://github.com/ms705/nom-sql.git?rev=b01998fc34a5d473387987110724a395298fc6c0)", "petgraph 0.4.5 (git+https://github.com/fintelia/petgraph?branch=serde)", "rand 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -812,7 +812,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "nom_sql" version = "0.0.1" -source = "git+https://github.com/ms705/nom-sql.git?rev=7758136babe72f470b7ea9fea384fca4f47db647#7758136babe72f470b7ea9fea384fca4f47db647" +source = "git+https://github.com/ms705/nom-sql.git?rev=b01998fc34a5d473387987110724a395298fc6c0#b01998fc34a5d473387987110724a395298fc6c0" dependencies = [ "nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1621,7 +1621,7 @@ dependencies = [ "checksum nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2c5afeb0198ec7be8569d666644b574345aad2e95a53baf3a532da3e0f3fb32" "checksum nodrop 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "52cd74cd09beba596430cc6e3091b74007169a56246e1262f0ba451ea95117b2" "checksum nom 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce" -"checksum nom_sql 0.0.1 (git+https://github.com/ms705/nom-sql.git?rev=7758136babe72f470b7ea9fea384fca4f47db647)" = "" +"checksum nom_sql 0.0.1 (git+https://github.com/ms705/nom-sql.git?rev=b01998fc34a5d473387987110724a395298fc6c0)" = "" "checksum num 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "a311b77ebdc5dd4cf6449d81e4135d9f0e3b153839ac90e648a8ef538f923525" "checksum num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "d1452e8b06e448a07f0e6ebb0bb1d92b8890eea63288c0b627331d53514d0fba" "checksum num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "7485fcc84f85b4ecd0ea527b14189281cf27d60e583ae65ebc9c088b13dffe01" diff --git a/Cargo.toml b/Cargo.toml index 30f334c9b..c24be5e46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ vec_map = { version = "0.8.0", features = ["eders"] } hurdles = "1.0.0" arrayvec = "0.4.0" -nom_sql = { git = "https://github.com/ms705/nom-sql.git", rev = "7758136babe72f470b7ea9fea384fca4f47db647"} +nom_sql = { git = "https://github.com/ms705/nom-sql.git", rev = "b01998fc34a5d473387987110724a395298fc6c0"} # for benchmarks # cli diff --git a/src/mir/mod.rs b/src/mir/mod.rs index 7814d2140..a173f1f7a 100644 --- a/src/mir/mod.rs +++ b/src/mir/mod.rs @@ -1,4 +1,4 @@ -use nom_sql::{Column, ColumnConstraint, ColumnSpecification, Operator, OrderType}; +use nom_sql::{ArithmeticBase, ArithmeticExpression, Column, ColumnConstraint, ColumnSpecification, Operator, OrderType}; use std::cell::RefCell; use std::collections::HashMap; use std::fmt::{Debug, Display, Error, Formatter}; @@ -12,7 +12,7 @@ use ops::grouped::aggregate::Aggregation as AggregationKind; use ops::grouped::extremum::Extremum as ExtremumKind; use ops::join::{Join, JoinType}; use ops::latest::Latest; -use ops::project::Project; +use ops::project::{Project, ProjectExpression, ProjectExpressionBase}; use sql::QueryFlowParts; pub mod reuse; @@ -644,6 +644,7 @@ impl MirNode { MirNodeType::Project { ref emit, ref literals, + ref arithmetic, } => { assert_eq!(self.ancestors.len(), 1); let parent = self.ancestors[0].clone(); @@ -652,6 +653,7 @@ impl MirNode { parent, self.columns.as_slice(), emit, + arithmetic, literals, mig, ) @@ -765,6 +767,7 @@ pub enum MirNodeType { /// emit columns Project { emit: Vec, + arithmetic: Vec<(String, ArithmeticExpression)>, literals: Vec<(String, DataType)>, }, /// emit columns @@ -943,11 +946,13 @@ impl MirNodeType { MirNodeType::Project { emit: ref our_emit, literals: ref our_literals, + arithmetic: ref our_arithmetic, } => match *other { MirNodeType::Project { ref emit, ref literals, - } => our_emit == emit && our_literals == literals, + ref arithmetic, + } => our_emit == emit && our_literals == literals && our_arithmetic == arithmetic, _ => false, }, MirNodeType::Reuse { node: ref us } => { @@ -1165,6 +1170,7 @@ impl Debug for MirNodeType { MirNodeType::Project { ref emit, ref literals, + ref arithmetic, } => write!( f, "π [{}{}]", @@ -1184,6 +1190,8 @@ impl Debug for MirNodeType { } else { format!("") } + + // TODO: format arithmetic ), MirNodeType::Reuse { ref node } => write!( f, @@ -1546,11 +1554,26 @@ fn make_latest_node( FlowNode::New(na) } +// Converts a nom_sql::ArithmeticBase into a project::ProjectExpressionBase: +fn generate_projection_base(parent: &MirNodeRef, base: &ArithmeticBase) -> ProjectExpressionBase { + match *base { + ArithmeticBase::Column(ref column) => { + let column_id = parent.borrow().column_id_for_column(column); + ProjectExpressionBase::Column(column_id) + } + ArithmeticBase::Scalar(ref literal) => { + let data: DataType = literal.into(); + ProjectExpressionBase::Literal(data) + } + } +} + fn make_project_node( name: &str, parent: MirNodeRef, columns: &[Column], emit: &Vec, + arithmetic: &Vec<(String, ArithmeticExpression)>, literals: &Vec<(String, DataType)>, mig: &mut Migration, ) -> FlowNode { @@ -1563,6 +1586,15 @@ fn make_project_node( let (_, literal_values): (Vec<_>, Vec<_>) = literals.iter().cloned().unzip(); + let projected_arithmetic: Vec = arithmetic + .iter() + .map(|&(_, ref e)| ProjectExpression::new( + e.op.clone(), + generate_projection_base(&parent, &e.left), + generate_projection_base(&parent, &e.right), + )) + .collect(); + let n = mig.add_ingredient( String::from(name), column_names.as_slice(), @@ -1570,7 +1602,7 @@ fn make_project_node( parent_na, projected_column_ids.as_slice(), Some(literal_values), - None, + Some(projected_arithmetic), ), ); FlowNode::New(n) diff --git a/src/mir/reuse.rs b/src/mir/reuse.rs index b97b0c8b9..7f36dc5cb 100644 --- a/src/mir/reuse.rs +++ b/src/mir/reuse.rs @@ -310,6 +310,7 @@ mod tests { vec![Column::from("aa")], MirNodeType::Project { emit: vec![Column::from("aa")], + arithmetic: vec![], literals: vec![], }, vec![c.clone()], diff --git a/src/mir/visualize.rs b/src/mir/visualize.rs index 5dc177619..49cf905f1 100644 --- a/src/mir/visualize.rs +++ b/src/mir/visualize.rs @@ -215,6 +215,7 @@ impl GraphViz for MirNodeType { MirNodeType::Project { ref emit, ref literals, + ref arithmetic, } => { write!( out, @@ -235,6 +236,8 @@ impl GraphViz for MirNodeType { } else { format!("") } + + // TODO(ekmartin): handle arithmetic )?; } MirNodeType::Reuse { ref node } => { diff --git a/src/ops/project.rs b/src/ops/project.rs index b25eb84d8..9869dc2fe 100644 --- a/src/ops/project.rs +++ b/src/ops/project.rs @@ -1,36 +1,43 @@ +use nom_sql::ArithmeticOperator; use std::collections::HashMap; use flow::prelude::*; -// TODO: use the ArithmeticOperator from nom-sql #[derive(Debug, Clone, Serialize, Deserialize)] -enum ArithmeticOperator { - Add, - Subtract, - Multiply, - Divide, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -enum ArithmeticBase { +pub enum ProjectExpressionBase { Column(usize), Literal(DataType), } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ArithmeticExpression { +pub struct ProjectExpression { op: ArithmeticOperator, - left: ArithmeticBase, - right: ArithmeticBase, + left: ProjectExpressionBase, + right: ProjectExpressionBase, +} + +impl ProjectExpression { + pub fn new( + op: ArithmeticOperator, + left: ProjectExpressionBase, + right: ProjectExpressionBase, + ) -> ProjectExpression { + ProjectExpression { + op: op, + left: left, + right: right, + } + } } + /// Permutes or omits columns from its source node, or adds additional literal value columns. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Project { us: Option, emit: Option>, additional: Option>, - expressions: Option>, + expressions: Option>, src: IndexPair, cols: usize, } @@ -41,7 +48,7 @@ impl Project { src: NodeIndex, emit: &[usize], additional: Option>, - expressions: Option>, + expressions: Option>, ) -> Project { Project { emit: Some(emit.into()), @@ -64,15 +71,15 @@ impl Project { } } - fn eval_expression(&self, expression: &ArithmeticExpression, record: &Record) -> DataType { + fn eval_expression(&self, expression: &ProjectExpression, record: &Record) -> DataType { let left = match expression.left { - ArithmeticBase::Column(i) => &record[i], - ArithmeticBase::Literal(ref data) => data, + ProjectExpressionBase::Column(i) => &record[i], + ProjectExpressionBase::Literal(ref data) => data, }.clone(); let right = match expression.right { - ArithmeticBase::Column(i) => &record[i], - ArithmeticBase::Literal(ref data) => data, + ProjectExpressionBase::Column(i) => &record[i], + ProjectExpressionBase::Literal(ref data) => data, }.clone(); match expression.op { @@ -230,7 +237,7 @@ mod tests { g } - fn setup_arithmetic(expression: ArithmeticExpression) -> ops::test::MockGraph { + fn setup_arithmetic(expression: ProjectExpression) -> ops::test::MockGraph { let mut g = ops::test::MockGraph::new(); let s = g.add_base("source", &["x", "y", "z"]); @@ -250,9 +257,9 @@ mod tests { } fn setup_column_arithmetic(op: ArithmeticOperator) -> ops::test::MockGraph { - let expression = ArithmeticExpression { - left: ArithmeticBase::Column(0), - right: ArithmeticBase::Column(1), + let expression = ProjectExpression { + left: ProjectExpressionBase::Column(0), + right: ProjectExpressionBase::Column(1), op: op, }; @@ -364,9 +371,9 @@ mod tests { #[test] fn it_forwards_arithmetic_w_literals() { let number: DataType = 40.into(); - let expression = ArithmeticExpression { - left: ArithmeticBase::Column(0), - right: ArithmeticBase::Literal(number), + let expression = ProjectExpression { + left: ProjectExpressionBase::Column(0), + right: ProjectExpressionBase::Literal(number), op: ArithmeticOperator::Multiply, }; @@ -382,9 +389,9 @@ mod tests { fn it_forwards_arithmetic_w_only_literals() { let a: DataType = 80.into(); let b: DataType = 40.into(); - let expression = ArithmeticExpression { - left: ArithmeticBase::Literal(a), - right: ArithmeticBase::Literal(b), + let expression = ProjectExpression { + left: ProjectExpressionBase::Literal(a), + right: ProjectExpressionBase::Literal(b), op: ArithmeticOperator::Divide, }; diff --git a/src/sql/mir.rs b/src/sql/mir.rs index 401355c17..2d0592154 100644 --- a/src/sql/mir.rs +++ b/src/sql/mir.rs @@ -5,7 +5,7 @@ use mir::{GroupedNodeType, MirNode, MirNodeType}; pub use mir::{FlowNode, MirNodeRef, MirQuery}; use ops::join::JoinType; -use nom_sql::{Column, ColumnSpecification, ConditionBase, ConditionExpression, ConditionTree, +use nom_sql::{ArithmeticExpression, Column, ColumnSpecification, ConditionBase, ConditionExpression, ConditionTree, Literal, Operator, SqlQuery, TableKey}; use nom_sql::{LimitClause, OrderClause, SelectStatement}; use sql::query_graph::{OutputColumn, QueryGraph, QueryGraphEdge, JoinRef}; @@ -166,6 +166,7 @@ impl SqlToMirConverter { MirNodeType::Project { emit: columns.clone(), literals: vec![], + arithmetic: vec![], }, vec![parent.clone()], vec![], @@ -720,6 +721,7 @@ impl SqlToMirConverter { name, parent, vec![fn_col], + vec![], vec![(String::from("grp"), DataType::from(0 as i32))], ) } @@ -729,10 +731,13 @@ impl SqlToMirConverter { name: &str, parent_node: MirNodeRef, proj_cols: Vec<&Column>, + arithmetic: Vec<(String, ArithmeticExpression)>, literals: Vec<(String, DataType)>, ) -> MirNodeRef { //assert!(proj_cols.iter().all(|c| c.table == parent_name)); + // TODO(ekmartin): group these two together + let arithmetic_names: Vec = arithmetic.iter().map(|&(ref n, _)| n.clone()).collect(); let literal_names: Vec = literals.iter().map(|&(ref n, _)| n.clone()).collect(); let fields = proj_cols .clone() @@ -754,6 +759,14 @@ impl SqlToMirConverter { function: None, } })) + .chain(arithmetic_names.into_iter().map(|n| { + Column { + name: n, + alias: None, + table: Some(String::from(name)), + function: None, + } + })) .collect(); // remove aliases from emit columns because they are later compared to parent node columns @@ -778,6 +791,7 @@ impl SqlToMirConverter { MirNodeType::Project { emit: emit_cols, literals: literals, + arithmetic: arithmetic, }, vec![parent_node.clone()], vec![], @@ -1314,6 +1328,7 @@ impl SqlToMirConverter { let mut projected_columns: Vec<&Column> = qg.columns .iter() .filter_map(|oc| match *oc { + OutputColumn::Arithmetic(_) => None, OutputColumn::Data(ref c) => Some(c), OutputColumn::Literal(_) => None, }) @@ -1323,9 +1338,20 @@ impl SqlToMirConverter { projected_columns.push(pc); } } + let projected_arithmetic: Vec<(String, ArithmeticExpression)> = qg.columns + .iter() + .filter_map(|oc| match *oc { + OutputColumn::Arithmetic(ref ac) => { + Some((ac.name.clone(), ac.expression.clone())) + }, + OutputColumn::Data(_) => None, + OutputColumn::Literal(_) => None, + }) + .collect(); let projected_literals: Vec<(String, DataType)> = qg.columns .iter() .filter_map(|oc| match *oc { + OutputColumn::Arithmetic(_) => None, OutputColumn::Data(_) => None, OutputColumn::Literal(ref lc) => { Some((lc.name.clone(), DataType::from(&lc.value))) @@ -1335,7 +1361,7 @@ impl SqlToMirConverter { let ident = format!("q_{:x}_n{}", qg.signature().hash, new_node_count); let leaf_project_node = - self.make_project_node(&ident, final_node, projected_columns, projected_literals); + self.make_project_node(&ident, final_node, projected_columns, projected_arithmetic, projected_literals); nodes_added.push(leaf_project_node.clone()); // We always materialize leaves of queries (at least currently), so add a diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 84b1d9170..9eeda3961 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -192,6 +192,8 @@ impl SqlIncorporator { // GROUP BY clause if qg.columns.iter().all(|c| match *c { OutputColumn::Literal(_) => true, + // TODO(ekmartin): is this okay? + OutputColumn::Arithmetic(_) => true, OutputColumn::Data(ref dc) => dc.function.is_none(), }) { // QGs are identical, except for parameters (or their order) diff --git a/src/sql/passes/count_star_rewrite.rs b/src/sql/passes/count_star_rewrite.rs index 8c32fa28c..c9ad9bfd0 100644 --- a/src/sql/passes/count_star_rewrite.rs +++ b/src/sql/passes/count_star_rewrite.rs @@ -96,6 +96,7 @@ impl CountStarRewrite for SqlQuery { &mut FieldExpression::All => panic!(err), &mut FieldExpression::AllInTable(_) => panic!(err), &mut FieldExpression::Literal(_) => (), + &mut FieldExpression::Arithmetic(_) => (), &mut FieldExpression::Col(ref mut c) => { rewrite_count_star(c, &tables, &avoid_cols) } diff --git a/src/sql/passes/implied_tables.rs b/src/sql/passes/implied_tables.rs index 9393a68e9..e71964053 100644 --- a/src/sql/passes/implied_tables.rs +++ b/src/sql/passes/implied_tables.rs @@ -1,4 +1,4 @@ -use nom_sql::{Column, ConditionExpression, ConditionTree, FieldExpression, JoinRightSide, +use nom_sql::{ArithmeticBase, Column, ConditionExpression, ConditionTree, FieldExpression, JoinRightSide, SqlQuery, Table}; use std::collections::HashMap; @@ -174,6 +174,15 @@ impl ImpliedTableExpansion for SqlQuery { &mut FieldExpression::All => panic!(err), &mut FieldExpression::AllInTable(_) => panic!(err), &mut FieldExpression::Literal(_) => (), + &mut FieldExpression::Arithmetic(ref mut e) => { + if let ArithmeticBase::Column(ref mut c) = e.left { + *c = expand_columns(c.clone(), &tables); + } + + if let ArithmeticBase::Column(ref mut c) = e.right { + *c = expand_columns(c.clone(), &tables); + } + } &mut FieldExpression::Col(ref mut f) => { *f = expand_columns(f.clone(), &tables); } diff --git a/src/sql/passes/star_expansion.rs b/src/sql/passes/star_expansion.rs index c19e97a6b..2defd1c37 100644 --- a/src/sql/passes/star_expansion.rs +++ b/src/sql/passes/star_expansion.rs @@ -37,6 +37,7 @@ impl StarExpansion for SqlQuery { let v: Vec<_> = expand_table(t).collect(); v.into_iter() } + FieldExpression::Arithmetic(a) => vec![FieldExpression::Arithmetic(a)].into_iter(), FieldExpression::Literal(l) => vec![FieldExpression::Literal(l)].into_iter(), FieldExpression::Col(c) => vec![FieldExpression::Col(c)].into_iter(), }) diff --git a/src/sql/query_graph.rs b/src/sql/query_graph.rs index c89d94bf5..d8360fb1f 100644 --- a/src/sql/query_graph.rs +++ b/src/sql/query_graph.rs @@ -1,5 +1,5 @@ -use nom_sql::{Column, ConditionBase, ConditionExpression, ConditionTree, FieldExpression, - JoinConstraint, JoinOperator, JoinRightSide, Literal, Operator}; +use nom_sql::{ArithmeticExpression, Column, ConditionBase, ConditionExpression, ConditionTree, + FieldExpression, JoinConstraint, JoinOperator, JoinRightSide, Literal, Operator}; use nom_sql::SelectStatement; use nom_sql::ConditionExpression::*; @@ -18,15 +18,28 @@ pub struct LiteralColumn { pub value: Literal, } +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct ArithmeticColumn { + pub name: String, + pub table: Option, + pub expression: ArithmeticExpression, +} + #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum OutputColumn { Data(Column), + Arithmetic(ArithmeticColumn), Literal(LiteralColumn), } impl Ord for OutputColumn { fn cmp(&self, other: &OutputColumn) -> Ordering { match *self { + OutputColumn::Arithmetic(ArithmeticColumn { + ref name, + ref table, + .. + }) | OutputColumn::Data(Column { ref name, ref table, @@ -37,6 +50,11 @@ impl Ord for OutputColumn { ref table, .. }) => match *other { + OutputColumn::Arithmetic(ArithmeticColumn { + name: ref other_name, + table: ref other_table, + .. + }) | OutputColumn::Data(Column { name: ref other_name, table: ref other_table, @@ -62,6 +80,11 @@ impl Ord for OutputColumn { impl PartialOrd for OutputColumn { fn partial_cmp(&self, other: &OutputColumn) -> Option { match *self { + OutputColumn::Arithmetic(ArithmeticColumn { + ref name, + ref table, + .. + }) | OutputColumn::Data(Column { ref name, ref table, @@ -72,6 +95,11 @@ impl PartialOrd for OutputColumn { ref table, .. }) => match *other { + OutputColumn::Arithmetic(ArithmeticColumn { + name: ref other_name, + table: ref other_table, + .. + }) | OutputColumn::Data(Column { name: ref other_name, table: ref other_table, @@ -420,6 +448,7 @@ pub fn to_query_graph(st: &SelectStatement) -> Result { // No need to do anything for literals here, as they aren't associated with a // relation (and thus have no QGN) FieldExpression::Literal(_) => None, + FieldExpression::Arithmetic(_) => None, FieldExpression::Col(ref c) => { match c.table.as_ref() { None => { @@ -654,6 +683,13 @@ pub fn to_query_graph(st: &SelectStatement) -> Result { value: l.clone(), })); } + FieldExpression::Arithmetic(ref a) => { + qg.columns.push(OutputColumn::Arithmetic(ArithmeticColumn { + name: String::from("arithmetic"), + table: None, + expression: a.clone(), + })); + } FieldExpression::Col(ref c) => { match c.function { None => (), // we've already dealt with this column as part of some relation diff --git a/tests/lib.rs b/tests/lib.rs index c27fb8a2a..c7d992800 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -288,7 +288,7 @@ fn it_works_deletion() { fn it_works_with_sql_recipe() { let mut g = distributary::Blender::new(); let sql = " - CREATE Table Car (id int, brand varchar(255), PRIMARY KEY(id)); + CREATE TABLE Car (id int, brand varchar(255), PRIMARY KEY(id)); CountCars: SELECT COUNT(*) FROM Car WHERE brand = ?; "; @@ -319,6 +319,85 @@ fn it_works_with_sql_recipe() { assert_eq!(result[0][0], 2.into()); } +#[test] +fn it_works_with_simple_arithmetic() { + let mut g = distributary::Blender::new(); + let sql = " + CREATE TABLE Car (id int, price int, PRIMARY KEY(id)); + CarPrice: SELECT 2 * price FROM Car WHERE id = ?; + "; + + let recipe = { + let mut mig = g.start_migration(); + let mut recipe = distributary::Recipe::from_str(&sql, None).unwrap(); + recipe.activate(&mut mig, false).unwrap(); + mig.commit(); + recipe + }; + + let car_index = recipe.node_addr_for("Car").unwrap(); + let count_index = recipe.node_addr_for("CarPrice").unwrap(); + let mut mutator = g.get_mutator(car_index); + let getter = g.get_getter(count_index).unwrap(); + let id: distributary::DataType = 1.into(); + let price: distributary::DataType = 123.into(); + mutator.put(vec![id.clone(), price]).unwrap(); + + // Let writes propagate: + thread::sleep(time::Duration::from_millis(SETTLE_TIME_MS)); + + // Retrieve the result of the count query: + let result = getter.lookup(&id, true).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0][1], 246.into()); +} + +#[test] +fn it_works_with_join_arithmetic() { + let mut g = distributary::Blender::new(); + let sql = " + CREATE TABLE Car (car_id int, price_id int, PRIMARY KEY(car_id)); + CREATE TABLE Price (price_id int, price int, PRIMARY KEY(price_id)); + CREATE TABLE Sales (sales_id int, price_id int, fraction float, PRIMARY KEY(sales_id)); + CarPrice: SELECT price * fraction FROM Car \ + JOIN Price ON Car.price_id = Price.price_id \ + JOIN Sales ON Price.price_id = Sales.price_id \ + WHERE car_id = ?; + "; + + let recipe = { + let mut mig = g.start_migration(); + let mut recipe = distributary::Recipe::from_str(&sql, None).unwrap(); + recipe.activate(&mut mig, false).unwrap(); + mig.commit(); + recipe + }; + + let car_index = recipe.node_addr_for("Car").unwrap(); + let price_index = recipe.node_addr_for("Price").unwrap(); + let sales_index = recipe.node_addr_for("Sales").unwrap(); + let query_index = recipe.node_addr_for("CarPrice").unwrap(); + let mut car_mutator = g.get_mutator(car_index); + let mut price_mutator = g.get_mutator(price_index); + let mut sales_mutator = g.get_mutator(sales_index); + let getter = g.get_getter(query_index).unwrap(); + let id = 1; + let price = 123; + let fraction = 0.7; + car_mutator.put(vec![id.into(), id.into()]).unwrap(); + price_mutator.put(vec![id.into(), price.into()]).unwrap(); + sales_mutator.put(vec![id.into(), id.into(), fraction.into()]).unwrap(); + + // Let writes propagate: + thread::sleep(time::Duration::from_millis(SETTLE_TIME_MS)); + + // Retrieve the result of the count query: + let result = getter.lookup(&id.into(), true).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0][1], (price as f64 * fraction).into()); +} + + #[test] fn votes() { use distributary::{Aggregation, Base, Join, JoinType, Union}; From ddb704309486539fd1812fa71177e1583dfba9b7 Mon Sep 17 00:00:00 2001 From: Martin Ek Date: Sun, 1 Oct 2017 15:26:11 +0200 Subject: [PATCH 05/16] Add a FLOAT_PRECISION constant --- src/flow/core/data.rs | 5 +++-- tests/lib.rs | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/flow/core/data.rs b/src/flow/core/data.rs index 09aa63c77..30943a4c9 100644 --- a/src/flow/core/data.rs +++ b/src/flow/core/data.rs @@ -11,6 +11,7 @@ use std::hash::{Hash, Hasher}; use std::ops::{Add, Deref, DerefMut, Div, Mul, Sub}; use std::fmt; +const FLOAT_PRECISION: f64 = 1000_000_000.0; const TINYTEXT_WIDTH: usize = 15; /// The main type used for user data throughout the codebase. @@ -128,7 +129,7 @@ impl From for DataType { } let mut i = f.trunc() as i32; - let mut frac = (f.fract() * 1000_000_000.0).round() as i32; + let mut frac = (f.fract() * FLOAT_PRECISION).round() as i32; if frac == 1000_000_000 { i += 1; frac = 0; @@ -205,7 +206,7 @@ impl Into for DataType { impl Into for DataType { fn into(self) -> f64 { match self { - DataType::Real(i, f) => i as f64 + (f as f64) / 1000_000_000.0, + DataType::Real(i, f) => i as f64 + (f as f64) / FLOAT_PRECISION, DataType::Int(i) => i as f64, DataType::BigInt(i) => i as f64, _ => unreachable!(), diff --git a/tests/lib.rs b/tests/lib.rs index c7d992800..6269a6de5 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -397,7 +397,6 @@ fn it_works_with_join_arithmetic() { assert_eq!(result[0][1], (price as f64 * fraction).into()); } - #[test] fn votes() { use distributary::{Aggregation, Base, Join, JoinType, Union}; From 8978a11869a01fb93a93870ef9e5b27b1cd59842 Mon Sep 17 00:00:00 2001 From: Martin Ek Date: Sun, 1 Oct 2017 15:57:53 +0200 Subject: [PATCH 06/16] Handle arithmetic and literal names together --- src/sql/mir.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/sql/mir.rs b/src/sql/mir.rs index 2d0592154..583442cf8 100644 --- a/src/sql/mir.rs +++ b/src/sql/mir.rs @@ -736,9 +736,12 @@ impl SqlToMirConverter { ) -> MirNodeRef { //assert!(proj_cols.iter().all(|c| c.table == parent_name)); - // TODO(ekmartin): group these two together - let arithmetic_names: Vec = arithmetic.iter().map(|&(ref n, _)| n.clone()).collect(); - let literal_names: Vec = literals.iter().map(|&(ref n, _)| n.clone()).collect(); + let names: Vec = literals + .iter() + .map(|&(ref n, _)| n.clone()) + .chain(arithmetic.iter().map(|&(ref n, _)| n.clone())) + .collect(); + let fields = proj_cols .clone() .into_iter() @@ -751,15 +754,7 @@ impl SqlToMirConverter { }, None => c.clone(), }) - .chain(literal_names.into_iter().map(|n| { - Column { - name: n, - alias: None, - table: Some(String::from(name)), - function: None, - } - })) - .chain(arithmetic_names.into_iter().map(|n| { + .chain(names.into_iter().map(|n| { Column { name: n, alias: None, From 72f6dfc3149d04b09f6dc22c7a35fef8880f284a Mon Sep 17 00:00:00 2001 From: Martin Ek Date: Sun, 1 Oct 2017 18:40:35 +0200 Subject: [PATCH 07/16] Add computed columns if they're used in arithmetic expressions --- src/sql/query_graph.rs | 45 ++++++++++++++++++++++++++++-------------- tests/lib.rs | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/src/sql/query_graph.rs b/src/sql/query_graph.rs index d8360fb1f..410a714ce 100644 --- a/src/sql/query_graph.rs +++ b/src/sql/query_graph.rs @@ -1,5 +1,6 @@ -use nom_sql::{ArithmeticExpression, Column, ConditionBase, ConditionExpression, ConditionTree, - FieldExpression, JoinConstraint, JoinOperator, JoinRightSide, Literal, Operator}; +use nom_sql::{ArithmeticBase, ArithmeticExpression, Column, ConditionBase, ConditionExpression, + ConditionTree, FieldExpression, JoinConstraint, JoinOperator, JoinRightSide, + Literal, Operator}; use nom_sql::SelectStatement; use nom_sql::ConditionExpression::*; @@ -669,6 +670,24 @@ pub fn to_query_graph(st: &SelectStatement) -> Result { } } + // Adds a computed column to the query graph if the given column has a function: + let add_computed_column = |query_graph: &mut QueryGraph, column: &Column| { + match column.function { + None => (), // we've already dealt with this column as part of some relation + Some(_) => { + // add a special node representing the computed columns; if it already + // exists, add another computed column to it + let n = query_graph.relations + .entry(String::from("computed_columns")) + .or_insert_with( + || new_node(String::from("computed_columns"), vec![], st), + ); + + n.columns.push(column.clone()); + } + } + }; + // 4. Add query graph nodes for any computed columns, which won't be represented in the // nodes corresponding to individual relations. for field in st.fields.iter() { @@ -684,6 +703,14 @@ pub fn to_query_graph(st: &SelectStatement) -> Result { })); } FieldExpression::Arithmetic(ref a) => { + if let ArithmeticBase::Column(ref c) = a.left { + add_computed_column(&mut qg, c); + } + + if let ArithmeticBase::Column(ref c) = a.right { + add_computed_column(&mut qg, c); + } + qg.columns.push(OutputColumn::Arithmetic(ArithmeticColumn { name: String::from("arithmetic"), table: None, @@ -691,19 +718,7 @@ pub fn to_query_graph(st: &SelectStatement) -> Result { })); } FieldExpression::Col(ref c) => { - match c.function { - None => (), // we've already dealt with this column as part of some relation - Some(_) => { - // add a special node representing the computed columns; if it already - // exists, add another computed column to it - let n = qg.relations - .entry(String::from("computed_columns")) - .or_insert_with( - || new_node(String::from("computed_columns"), vec![], st), - ); - n.columns.push(c.clone()); - } - } + add_computed_column(&mut qg, c); qg.columns.push(OutputColumn::Data(c.clone())); } } diff --git a/tests/lib.rs b/tests/lib.rs index 6269a6de5..e783df8fe 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -397,6 +397,41 @@ fn it_works_with_join_arithmetic() { assert_eq!(result[0][1], (price as f64 * fraction).into()); } +#[test] +fn it_works_with_function_arithmetic() { + let mut g = distributary::Blender::new(); + let sql = " + CREATE TABLE Bread (id int, price int, PRIMARY KEY(id)); + Price: SELECT 2 * COUNT(price) FROM Bread WHERE id = ?; + "; + + let recipe = { + let mut mig = g.start_migration(); + let mut recipe = distributary::Recipe::from_str(&sql, None).unwrap(); + recipe.activate(&mut mig, false).unwrap(); + mig.commit(); + recipe + }; + + let bread_index = recipe.node_addr_for("Bread").unwrap(); + let query_index = recipe.node_addr_for("Price").unwrap(); + let mut mutator = g.get_mutator(bread_index); + let getter = g.get_getter(query_index).unwrap(); + let max_price = 20; + for (i, price) in (10..max_price).enumerate() { + let id: distributary::DataType = (i as i32).into(); + mutator.put(vec![id, price.into()]).unwrap(); + } + + // Let writes propagate: + thread::sleep(time::Duration::from_millis(SETTLE_TIME_MS)); + + // Retrieve the result of the count query: + let result = getter.lookup(&1.into(), true).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0][1], 2.into()); +} + #[test] fn votes() { use distributary::{Aggregation, Base, Join, JoinType, Union}; From 059ffaffcf46e77986f24835931a12742acae894 Mon Sep 17 00:00:00 2001 From: Martin Ek Date: Sun, 1 Oct 2017 19:32:08 +0200 Subject: [PATCH 08/16] Implement formatting for arithmetic expressions --- src/mir/mod.rs | 18 +++++++++--- src/mir/visualize.rs | 16 +++++++++-- src/ops/project.rs | 68 ++++++++++++++++++++++++++++++++------------ src/sql/mod.rs | 24 ++++++++++++++++ 4 files changed, 101 insertions(+), 25 deletions(-) diff --git a/src/mir/mod.rs b/src/mir/mod.rs index a173f1f7a..ce16bb117 100644 --- a/src/mir/mod.rs +++ b/src/mir/mod.rs @@ -1173,11 +1173,23 @@ impl Debug for MirNodeType { ref arithmetic, } => write!( f, - "π [{}{}]", + "π [{}{}{}]", emit.iter() .map(|c| c.name.as_str()) .collect::>() .join(", "), + if arithmetic.len() > 0 { + format!( + ", {}", + arithmetic + .iter() + .map(|&(ref n, ref e)| format!("{}: {:?}", n, e)) + .collect::>() + .join(", ") + ) + } else { + format!("") + }, if literals.len() > 0 { format!( ", lit: {}", @@ -1189,9 +1201,7 @@ impl Debug for MirNodeType { ) } else { format!("") - } - - // TODO: format arithmetic + }, ), MirNodeType::Reuse { ref node } => write!( f, diff --git a/src/mir/visualize.rs b/src/mir/visualize.rs index 49cf905f1..e822e2364 100644 --- a/src/mir/visualize.rs +++ b/src/mir/visualize.rs @@ -219,11 +219,23 @@ impl GraphViz for MirNodeType { } => { write!( out, - "π: {}{}", + "π: {}{}{}", emit.iter() .map(|c| c.name.as_str()) .collect::>() .join(", "), + if arithmetic.len() > 0 { + format!( + ", {}", + arithmetic + .iter() + .map(|&(ref n, ref e)| format!("{}: {:?}", n, e)) + .collect::>() + .join(", ") + ) + } else { + format!("") + }, if literals.len() > 0 { format!( ", lit: {}", @@ -236,8 +248,6 @@ impl GraphViz for MirNodeType { } else { format!("") } - - // TODO(ekmartin): handle arithmetic )?; } MirNodeType::Reuse { ref node } => { diff --git a/src/ops/project.rs b/src/ops/project.rs index 9869dc2fe..6ae84e03d 100644 --- a/src/ops/project.rs +++ b/src/ops/project.rs @@ -1,4 +1,5 @@ use nom_sql::ArithmeticOperator; +use std::fmt; use std::collections::HashMap; use flow::prelude::*; @@ -30,6 +31,28 @@ impl ProjectExpression { } } +impl fmt::Display for ProjectExpressionBase { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + ProjectExpressionBase::Column(u) => write!(f, "{}", u), + ProjectExpressionBase::Literal(ref l) => write!(f, "(lit: {})", l), + } + } +} + +impl fmt::Display for ProjectExpression { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let op = match self.op { + ArithmeticOperator::Add => "+", + ArithmeticOperator::Subtract => "-", + ArithmeticOperator::Divide => "/", + ArithmeticOperator::Multiply => "*", + }; + + write!(f, "{} {} {}", self.left, op, self.right) + } +} + /// Permutes or omits columns from its source node, or adds additional literal value columns. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -178,28 +201,31 @@ impl Ingredient for Project { } fn description(&self) -> String { - let emit_cols = match self.emit.as_ref() { - None => "*".into(), - // TODO: print out arithmetic expressions as well: + let mut emit_cols = vec![]; + match self.emit.as_ref() { + None => emit_cols.push("*".to_string()), Some(emit) => { - match self.additional { - None => { - emit.iter() - .map(|e| e.to_string()) - .collect::>() - .join(", ") - } - Some(ref add) => { - emit.iter() - .map(|e| e.to_string()) - .chain(add.iter().map(|e| format!("lit: {}", e.to_string()))) - .collect::>() - .join(", ") - } + emit_cols.extend(emit.iter().map(|e| e.to_string()).collect::>()); + + if let Some(ref arithmetic) = self.expressions { + emit_cols.extend( + arithmetic + .iter() + .map(|e| format!("{}", e)) + .collect::>(), + ); + } + + if let Some(ref add) = self.additional { + emit_cols.extend( + add.iter() + .map(|e| format!("lit: {}", e.to_string())) + .collect::>(), + ); } } }; - format!("π[{}]", emit_cols) + format!("π[{}]", emit_cols.join(", ")) } fn parent_columns(&self, column: usize) -> Vec<(NodeIndex, Option)> { @@ -272,6 +298,12 @@ mod tests { assert_eq!(p.node().description(), "π[2, 0, lit: \"hello\", lit: 42]"); } + #[test] + fn it_describes_arithmetic() { + let mut p = setup_column_arithmetic(ArithmeticOperator::Add); + assert_eq!(p.node().description(), "π[0, 1, 0 + 1]"); + } + #[test] fn it_describes_all() { let p = setup(false, true, false); diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 9eeda3961..935fc3462 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -1304,6 +1304,30 @@ mod tests { assert_eq!(edge.description(), format!("π[1, lit: 1]")); } + #[test] + fn it_incorporates_arithmetic_projection() { + // set up graph + let mut g = Blender::new(); + let mut inc = SqlIncorporator::default(); + let mut mig = g.start_migration(); + + assert!( + inc.add_query( + "CREATE TABLE users (id int, age int);", + None, + &mut mig + ).is_ok() + ); + + let res = inc.add_query("SELECT 2 * users.age FROM users;", None, &mut mig); + assert!(res.is_ok()); + + // leaf view node + let edge = get_node(&inc, &mig, &res.unwrap().name); + assert_eq!(edge.fields(), &["arithmetic"]); + assert_eq!(edge.description(), format!("π[(lit: 2) * 1]")); + } + #[test] fn it_incorporates_join_with_nested_query() { let mut g = Blender::new(); From 811ca80e0091ad66be18a0c9cfb0759550ff0452 Mon Sep 17 00:00:00 2001 From: Martin Ek Date: Sun, 1 Oct 2017 20:21:36 +0200 Subject: [PATCH 09/16] Only re-use arithmetic expressions without functions --- src/sql/mod.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 935fc3462..b673aea94 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -9,7 +9,7 @@ use flow::Migration; use flow::prelude::NodeIndex; use mir::reuse as mir_reuse; use nom_sql::parser as sql_parser; -use nom_sql::{Column, SqlQuery}; +use nom_sql::{ArithmeticBase, Column, SqlQuery}; use nom_sql::SelectStatement; use self::mir::{MirNodeRef, MirQuery, SqlToMirConverter}; use self::reuse::{ReuseConfig, ReuseConfigType}; @@ -192,8 +192,18 @@ impl SqlIncorporator { // GROUP BY clause if qg.columns.iter().all(|c| match *c { OutputColumn::Literal(_) => true, - // TODO(ekmartin): is this okay? - OutputColumn::Arithmetic(_) => true, + OutputColumn::Arithmetic(ref ac) => { + let mut is_function = false; + if let ArithmeticBase::Column(ref c) = ac.expression.left { + is_function = is_function || c.function.is_some(); + } + + if let ArithmeticBase::Column(ref c) = ac.expression.right { + is_function = is_function || c.function.is_some(); + } + + !is_function + }, OutputColumn::Data(ref dc) => dc.function.is_none(), }) { // QGs are identical, except for parameters (or their order) From 688e85fc033e9217369405208714f01c14d7dd71 Mon Sep 17 00:00:00 2001 From: Martin Ek Date: Sun, 1 Oct 2017 23:30:05 +0200 Subject: [PATCH 10/16] Use MAX in arithmetic test --- tests/lib.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/lib.rs b/tests/lib.rs index e783df8fe..d0b54b628 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -402,7 +402,7 @@ fn it_works_with_function_arithmetic() { let mut g = distributary::Blender::new(); let sql = " CREATE TABLE Bread (id int, price int, PRIMARY KEY(id)); - Price: SELECT 2 * COUNT(price) FROM Bread WHERE id = ?; + Price: SELECT 2 * MAX(price) FROM Bread; "; let recipe = { @@ -418,18 +418,19 @@ fn it_works_with_function_arithmetic() { let mut mutator = g.get_mutator(bread_index); let getter = g.get_getter(query_index).unwrap(); let max_price = 20; - for (i, price) in (10..max_price).enumerate() { - let id: distributary::DataType = (i as i32).into(); - mutator.put(vec![id, price.into()]).unwrap(); + for (i, price) in (10..max_price + 1).enumerate() { + let id = (i + 1) as i32; + mutator.put(vec![id.into(), price.into()]).unwrap(); } // Let writes propagate: thread::sleep(time::Duration::from_millis(SETTLE_TIME_MS)); // Retrieve the result of the count query: - let result = getter.lookup(&1.into(), true).unwrap(); + let key = distributary::DataType::BigInt(max_price * 2); + let result = getter.lookup(&key, true).unwrap(); assert_eq!(result.len(), 1); - assert_eq!(result[0][1], 2.into()); + assert_eq!(result[0][0], key); } #[test] From a492cb094b8391039770b2e0eaae628cf6a08423 Mon Sep 17 00:00:00 2001 From: Martin Ek Date: Mon, 2 Oct 2017 14:54:30 +0200 Subject: [PATCH 11/16] Use g.migrate instead of start_migration --- src/ops/project.rs | 2 +- src/sql/mod.rs | 30 +++++++++++++++--------------- tests/lib.rs | 24 +++++++++--------------- 3 files changed, 25 insertions(+), 31 deletions(-) diff --git a/src/ops/project.rs b/src/ops/project.rs index 6ae84e03d..94c29391b 100644 --- a/src/ops/project.rs +++ b/src/ops/project.rs @@ -300,7 +300,7 @@ mod tests { #[test] fn it_describes_arithmetic() { - let mut p = setup_column_arithmetic(ArithmeticOperator::Add); + let p = setup_column_arithmetic(ArithmeticOperator::Add); assert_eq!(p.node().description(), "π[0, 1, 0 + 1]"); } diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 9f7a1fa0b..7824cf8d4 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -1276,23 +1276,23 @@ mod tests { // set up graph let mut g = Blender::new(); let mut inc = SqlIncorporator::default(); - let mut mig = g.start_migration(); - - assert!( - inc.add_query( - "CREATE TABLE users (id int, age int);", - None, - &mut mig - ).is_ok() - ); + g.migrate(|mig| { + assert!( + inc.add_query( + "CREATE TABLE users (id int, age int);", + None, + mig + ).is_ok() + ); - let res = inc.add_query("SELECT 2 * users.age FROM users;", None, &mut mig); - assert!(res.is_ok()); + let res = inc.add_query("SELECT 2 * users.age FROM users;", None, mig); + assert!(res.is_ok()); - // leaf view node - let edge = get_node(&inc, &mig, &res.unwrap().name); - assert_eq!(edge.fields(), &["arithmetic"]); - assert_eq!(edge.description(), format!("π[(lit: 2) * 1]")); + // leaf view node + let edge = get_node(&inc, mig, &res.unwrap().name); + assert_eq!(edge.fields(), &["arithmetic"]); + assert_eq!(edge.description(), format!("π[(lit: 2) * 1]")); + }); } #[test] diff --git a/tests/lib.rs b/tests/lib.rs index a78c7c4f8..96e186091 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -314,13 +314,11 @@ fn it_works_with_simple_arithmetic() { CarPrice: SELECT 2 * price FROM Car WHERE id = ?; "; - let recipe = { - let mut mig = g.start_migration(); + let recipe = g.migrate(|mig| { let mut recipe = distributary::Recipe::from_str(&sql, None).unwrap(); - recipe.activate(&mut mig, false).unwrap(); - mig.commit(); + recipe.activate(mig, false).unwrap(); recipe - }; + }); let car_index = recipe.node_addr_for("Car").unwrap(); let count_index = recipe.node_addr_for("CarPrice").unwrap(); @@ -352,13 +350,11 @@ fn it_works_with_join_arithmetic() { WHERE car_id = ?; "; - let recipe = { - let mut mig = g.start_migration(); + let recipe = g.migrate(|mig| { let mut recipe = distributary::Recipe::from_str(&sql, None).unwrap(); - recipe.activate(&mut mig, false).unwrap(); - mig.commit(); + recipe.activate(mig, false).unwrap(); recipe - }; + }); let car_index = recipe.node_addr_for("Car").unwrap(); let price_index = recipe.node_addr_for("Price").unwrap(); @@ -392,13 +388,11 @@ fn it_works_with_function_arithmetic() { Price: SELECT 2 * MAX(price) FROM Bread; "; - let recipe = { - let mut mig = g.start_migration(); + let recipe = g.migrate(|mig| { let mut recipe = distributary::Recipe::from_str(&sql, None).unwrap(); - recipe.activate(&mut mig, false).unwrap(); - mig.commit(); + recipe.activate(mig, false).unwrap(); recipe - }; + }); let bread_index = recipe.node_addr_for("Bread").unwrap(); let query_index = recipe.node_addr_for("Price").unwrap(); From 292566ef929a4374f08a379adfaefa22a3d096ed Mon Sep 17 00:00:00 2001 From: Martin Ek Date: Mon, 2 Oct 2017 14:57:44 +0200 Subject: [PATCH 12/16] &*mig -> mig --- src/sql/mod.rs | 90 +++++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 7824cf8d4..be2d8450e 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -720,7 +720,7 @@ mod tests { // Should have two nodes: source and "users" base table let ncount = mig.graph().node_count(); assert_eq!(ncount, 2); - assert_eq!(get_node(&inc, &*mig, "users").name(), "users"); + assert_eq!(get_node(&inc, mig, "users").name(), "users"); assert!( "SELECT users.id from users;" @@ -755,9 +755,9 @@ mod tests { ); // Should have source and "users" base table node assert_eq!(mig.graph().node_count(), 2); - assert_eq!(get_node(&inc, &*mig, "users").name(), "users"); - assert_eq!(get_node(&inc, &*mig, "users").fields(), &["id", "name"]); - assert_eq!(get_node(&inc, &*mig, "users").description(), "B"); + assert_eq!(get_node(&inc, mig, "users").name(), "users"); + assert_eq!(get_node(&inc, mig, "users").fields(), &["id", "name"]); + assert_eq!(get_node(&inc, mig, "users").description(), "B"); // Establish a base write type for "articles" assert!( @@ -769,12 +769,12 @@ mod tests { ); // Should have source and "users" base table node assert_eq!(mig.graph().node_count(), 3); - assert_eq!(get_node(&inc, &*mig, "articles").name(), "articles"); + assert_eq!(get_node(&inc, mig, "articles").name(), "articles"); assert_eq!( - get_node(&inc, &*mig, "articles").fields(), + get_node(&inc, mig, "articles").fields(), &["id", "author", "title"] ); - assert_eq!(get_node(&inc, &*mig, "articles").description(), "B"); + assert_eq!(get_node(&inc, mig, "articles").description(), "B"); // Try a simple equi-JOIN query let q = "SELECT users.name, articles.title \ @@ -788,13 +788,13 @@ mod tests { &[&Column::from("articles.title"), &Column::from("users.name")], ); // join node - let new_join_view = get_node(&inc, &*mig, &format!("q_{:x}_n0", qid)); + let new_join_view = get_node(&inc, mig, &format!("q_{:x}_n0", qid)); assert_eq!( new_join_view.fields(), &["id", "author", "title", "id", "name"] ); // leaf node - let new_leaf_view = get_node(&inc, &*mig, &q.unwrap().name); + let new_leaf_view = get_node(&inc, mig, &q.unwrap().name); assert_eq!(new_leaf_view.fields(), &["name", "title"]); assert_eq!(new_leaf_view.description(), format!("π[4, 2]")); }); @@ -813,9 +813,9 @@ mod tests { ); // Should have source and "users" base table node assert_eq!(mig.graph().node_count(), 2); - assert_eq!(get_node(&inc, &*mig, "users").name(), "users"); - assert_eq!(get_node(&inc, &*mig, "users").fields(), &["id", "name"]); - assert_eq!(get_node(&inc, &*mig, "users").description(), "B"); + assert_eq!(get_node(&inc, mig, "users").name(), "users"); + assert_eq!(get_node(&inc, mig, "users").fields(), &["id", "name"]); + assert_eq!(get_node(&inc, mig, "users").description(), "B"); // Try a simple query let res = inc.add_query( @@ -831,11 +831,11 @@ mod tests { &[&Column::from("users.name")], ); // filter node - let filter = get_node(&inc, &*mig, &format!("q_{:x}_n0_p0_f0", qid)); + let filter = get_node(&inc, mig, &format!("q_{:x}_n0_p0_f0", qid)); assert_eq!(filter.fields(), &["id", "name"]); assert_eq!(filter.description(), format!("σ[f0 = 42]")); // leaf view node - let edge = get_node(&inc, &*mig, &res.unwrap().name); + let edge = get_node(&inc, mig, &res.unwrap().name); assert_eq!(edge.fields(), &["name"]); assert_eq!(edge.description(), format!("π[1]")); }); @@ -854,9 +854,9 @@ mod tests { ); // Should have source and "users" base table node assert_eq!(mig.graph().node_count(), 2); - assert_eq!(get_node(&inc, &*mig, "votes").name(), "votes"); - assert_eq!(get_node(&inc, &*mig, "votes").fields(), &["aid", "userid"]); - assert_eq!(get_node(&inc, &*mig, "votes").description(), "B"); + assert_eq!(get_node(&inc, mig, "votes").name(), "votes"); + assert_eq!(get_node(&inc, mig, "votes").fields(), &["aid", "userid"]); + assert_eq!(get_node(&inc, mig, "votes").description(), "B"); // Try a simple COUNT function let res = inc.add_query( @@ -886,11 +886,11 @@ mod tests { }, ], ); - let agg_view = get_node(&inc, &*mig, &format!("q_{:x}_n0", qid)); + let agg_view = get_node(&inc, mig, &format!("q_{:x}_n0", qid)); assert_eq!(agg_view.fields(), &["aid", "votes"]); assert_eq!(agg_view.description(), format!("|*| γ[0]")); // check edge view - let edge_view = get_node(&inc, &*mig, &res.unwrap().name); + let edge_view = get_node(&inc, mig, &res.unwrap().name); assert_eq!(edge_view.fields(), &["votes"]); assert_eq!(edge_view.description(), format!("π[1]")); }); @@ -938,9 +938,9 @@ mod tests { ); // Should have source and "users" base table node assert_eq!(mig.graph().node_count(), 2); - assert_eq!(get_node(&inc, &*mig, "users").name(), "users"); - assert_eq!(get_node(&inc, &*mig, "users").fields(), &["id", "name"]); - assert_eq!(get_node(&inc, &*mig, "users").description(), "B"); + assert_eq!(get_node(&inc, mig, "users").name(), "users"); + assert_eq!(get_node(&inc, mig, "users").fields(), &["id", "name"]); + assert_eq!(get_node(&inc, mig, "users").description(), "B"); // Add a new query let res = inc.add_query("SELECT id, name FROM users WHERE users.id = 42;", None, mig); @@ -976,12 +976,12 @@ mod tests { ); // Should have source and "users" base table node assert_eq!(mig.graph().node_count(), 2); - assert_eq!(get_node(&inc, &*mig, "users").name(), "users"); + assert_eq!(get_node(&inc, mig, "users").name(), "users"); assert_eq!( - get_node(&inc, &*mig, "users").fields(), + get_node(&inc, mig, "users").fields(), &["id", "name", "address"] ); - assert_eq!(get_node(&inc, &*mig, "users").description(), "B"); + assert_eq!(get_node(&inc, mig, "users").description(), "B"); // Add a new query let res = inc.add_query("SELECT id, name FROM users WHERE users.id = ?;", None, mig); @@ -1002,7 +1002,7 @@ mod tests { assert_eq!(mig.graph().node_count(), ncount + 2); // only the identity node is returned in the vector of new nodes assert_eq!(qfp.new_nodes.len(), 1); - assert_eq!(get_node(&inc, &*mig, &qfp.name).description(), "≡"); + assert_eq!(get_node(&inc, mig, &qfp.name).description(), "≡"); // we should be based off the identity as our leaf let id_node = qfp.new_nodes.iter().next().unwrap(); assert_eq!(qfp.query_leaf, *id_node); @@ -1022,7 +1022,7 @@ mod tests { // only the projection node is returned in the vector of new nodes assert_eq!(qfp.new_nodes.len(), 1); assert_eq!( - get_node(&inc, &*mig, &qfp.name).description(), + get_node(&inc, mig, &qfp.name).description(), "π[0, 1, 2]" ); // we should be based off the new projection as our leaf @@ -1044,9 +1044,9 @@ mod tests { ); // Should have source and "users" base table node assert_eq!(mig.graph().node_count(), 2); - assert_eq!(get_node(&inc, &*mig, "votes").name(), "votes"); - assert_eq!(get_node(&inc, &*mig, "votes").fields(), &["aid", "userid"]); - assert_eq!(get_node(&inc, &*mig, "votes").description(), "B"); + assert_eq!(get_node(&inc, mig, "votes").name(), "votes"); + assert_eq!(get_node(&inc, mig, "votes").fields(), &["aid", "userid"]); + assert_eq!(get_node(&inc, mig, "votes").description(), "B"); // Try a simple COUNT function without a GROUP BY clause let res = inc.add_query("SELECT COUNT(votes.userid) AS count FROM votes;", None, mig); assert!(res.is_ok()); @@ -1069,16 +1069,16 @@ mod tests { }, ], ); - let proj_helper_view = get_node(&inc, &*mig, &format!("q_{:x}_n0_prj_hlpr", qid)); + let proj_helper_view = get_node(&inc, mig, &format!("q_{:x}_n0_prj_hlpr", qid)); assert_eq!(proj_helper_view.fields(), &["userid", "grp"]); assert_eq!(proj_helper_view.description(), format!("π[1, lit: 0]")); // check aggregation view - let agg_view = get_node(&inc, &*mig, &format!("q_{:x}_n0", qid)); + let agg_view = get_node(&inc, mig, &format!("q_{:x}_n0", qid)); assert_eq!(agg_view.fields(), &["grp", "count"]); assert_eq!(agg_view.description(), format!("|*| γ[1]")); // check edge view -- note that it's not actually currently possible to read from // this for a lack of key (the value would be the key) - let edge_view = get_node(&inc, &*mig, &res.unwrap().name); + let edge_view = get_node(&inc, mig, &res.unwrap().name); assert_eq!(edge_view.fields(), &["count"]); assert_eq!(edge_view.description(), format!("π[1]")); }); @@ -1097,9 +1097,9 @@ mod tests { ); // Should have source and "users" base table node assert_eq!(mig.graph().node_count(), 2); - assert_eq!(get_node(&inc, &*mig, "votes").name(), "votes"); - assert_eq!(get_node(&inc, &*mig, "votes").fields(), &["userid", "aid"]); - assert_eq!(get_node(&inc, &*mig, "votes").description(), "B"); + assert_eq!(get_node(&inc, mig, "votes").name(), "votes"); + assert_eq!(get_node(&inc, mig, "votes").fields(), &["userid", "aid"]); + assert_eq!(get_node(&inc, mig, "votes").description(), "B"); // Try a simple COUNT function without a GROUP BY clause let res = inc.add_query( "SELECT COUNT(*) AS count FROM votes GROUP BY votes.userid;", @@ -1123,12 +1123,12 @@ mod tests { }, ], ); - let agg_view = get_node(&inc, &*mig, &format!("q_{:x}_n0", qid)); + let agg_view = get_node(&inc, mig, &format!("q_{:x}_n0", qid)); assert_eq!(agg_view.fields(), &["userid", "count"]); assert_eq!(agg_view.description(), format!("|*| γ[0]")); // check edge view -- note that it's not actually currently possible to read from // this for a lack of key (the value would be the key) - let edge_view = get_node(&inc, &*mig, &res.unwrap().name); + let edge_view = get_node(&inc, mig, &res.unwrap().name); assert_eq!(edge_view.fields(), &["count"]); assert_eq!(edge_view.description(), format!("π[1]")); }); @@ -1181,7 +1181,7 @@ mod tests { // XXX(malte): non-deterministic join ordering make it difficult to assert on the join // views // leaf view - let leaf_view = get_node(&inc, &*mig, "q_3"); + let leaf_view = get_node(&inc, mig, "q_3"); assert_eq!(leaf_view.fields(), &["name", "title", "uid"]); }); } @@ -1232,20 +1232,20 @@ mod tests { &Column::from("votes.uid"), ], ); - let join1_view = get_node(&inc, &*mig, &format!("q_{:x}_n0", qid)); + let join1_view = get_node(&inc, mig, &format!("q_{:x}_n0", qid)); // articles join votes assert_eq!( join1_view.fields(), &["aid", "title", "author", "id", "name"] ); - let join2_view = get_node(&inc, &*mig, &format!("q_{:x}_n1", qid)); + let join2_view = get_node(&inc, mig, &format!("q_{:x}_n1", qid)); // join1_view join users assert_eq!( join2_view.fields(), &["aid", "title", "author", "id", "name", "aid", "uid"] ); // leaf view - let leaf_view = get_node(&inc, &*mig, "q_3"); + let leaf_view = get_node(&inc, mig, "q_3"); assert_eq!(leaf_view.fields(), &["name", "title", "uid"]); }); } @@ -1265,7 +1265,7 @@ mod tests { assert!(res.is_ok()); // leaf view node - let edge = get_node(&inc, &*mig, &res.unwrap().name); + let edge = get_node(&inc, mig, &res.unwrap().name); assert_eq!(edge.fields(), &["name", "literal"]); assert_eq!(edge.description(), format!("π[1, lit: 1]")); }); @@ -1330,13 +1330,13 @@ mod tests { ], ); // join node - let new_join_view = get_node(&inc, &*mig, &format!("q_{:x}_n0", qid)); + let new_join_view = get_node(&inc, mig, &format!("q_{:x}_n0", qid)); assert_eq!( new_join_view.fields(), &["id", "name", "id", "author", "title"] ); // leaf node - let new_leaf_view = get_node(&inc, &*mig, &q.unwrap().name); + let new_leaf_view = get_node(&inc, mig, &q.unwrap().name); assert_eq!(new_leaf_view.fields(), &["name", "title"]); assert_eq!(new_leaf_view.description(), format!("π[1, 4]")); }); From 70340d3685e1292cbd890bf96bcc6c72911747e1 Mon Sep 17 00:00:00 2001 From: Martin Ek Date: Mon, 2 Oct 2017 23:04:39 +0200 Subject: [PATCH 13/16] rustfmt --- src/flow/core/data.rs | 9 ++++++++- src/mir/to_flow.rs | 14 ++++++++------ src/ops/project.rs | 16 ++++++---------- src/sql/mir.rs | 15 ++++++++++----- src/sql/mod.rs | 14 ++++---------- src/sql/passes/implied_tables.rs | 4 ++-- src/sql/passes/star_expansion.rs | 4 +++- src/sql/query_graph.rs | 7 +++---- tests/lib.rs | 4 +++- 9 files changed, 47 insertions(+), 40 deletions(-) diff --git a/src/flow/core/data.rs b/src/flow/core/data.rs index f98eacbdb..4634ba1c1 100644 --- a/src/flow/core/data.rs +++ b/src/flow/core/data.rs @@ -254,7 +254,14 @@ macro_rules! arithmetic_operation ( let b: f64 = second.into(); (a $op b).into() } - (first, second) => panic!(format!("can't {} a {:?} and {:?}", stringify!($op), first, second)), + (first, second) => panic!( + format!( + "can't {} a {:?} and {:?}", + stringify!($op), + first, + second, + ) + ), } ); ); diff --git a/src/mir/to_flow.rs b/src/mir/to_flow.rs index 0eb6af47b..f60d527ec 100644 --- a/src/mir/to_flow.rs +++ b/src/mir/to_flow.rs @@ -393,13 +393,15 @@ pub(crate) fn make_project_node( let (_, literal_values): (Vec<_>, Vec<_>) = literals.iter().cloned().unzip(); - let projected_arithmetic: Vec = arithmetic + let projected_arithmetic: Vec = arithmetic .iter() - .map(|&(_, ref e)| ProjectExpression::new( - e.op.clone(), - generate_projection_base(&parent, &e.left), - generate_projection_base(&parent, &e.right), - )) + .map(|&(_, ref e)| { + ProjectExpression::new( + e.op.clone(), + generate_projection_base(&parent, &e.left), + generate_projection_base(&parent, &e.right), + ) + }) .collect(); let n = mig.add_ingredient( diff --git a/src/ops/project.rs b/src/ops/project.rs index 94c29391b..f737fc8db 100644 --- a/src/ops/project.rs +++ b/src/ops/project.rs @@ -168,19 +168,15 @@ impl Ingredient for Project { new_r.push(r[*i].clone()); } match self.expressions { - Some(ref e) => { - for i in e { - new_r.push(self.eval_expression(i, r)); - } - } + Some(ref e) => for i in e { + new_r.push(self.eval_expression(i, r)); + }, None => (), } match self.additional { - Some(ref a) => { - for i in a { - new_r.push(i.clone()); - } - } + Some(ref a) => for i in a { + new_r.push(i.clone()); + }, None => (), } **r = new_r; diff --git a/src/sql/mir.rs b/src/sql/mir.rs index 9841085c2..bbce4521d 100644 --- a/src/sql/mir.rs +++ b/src/sql/mir.rs @@ -7,8 +7,8 @@ use mir::query::MirQuery; pub use mir::to_flow::FlowNode; use ops::join::JoinType; -use nom_sql::{ArithmeticExpression, Column, ColumnSpecification, ConditionBase, ConditionExpression, ConditionTree, - Literal, Operator, SqlQuery, TableKey}; +use nom_sql::{ArithmeticExpression, Column, ColumnSpecification, ConditionBase, + ConditionExpression, ConditionTree, Literal, Operator, SqlQuery, TableKey}; use nom_sql::{LimitClause, OrderClause, SelectStatement}; use sql::query_graph::{JoinRef, OutputColumn, QueryGraph, QueryGraphEdge}; @@ -1344,7 +1344,7 @@ impl SqlToMirConverter { .filter_map(|oc| match *oc { OutputColumn::Arithmetic(ref ac) => { Some((ac.name.clone(), ac.expression.clone())) - }, + } OutputColumn::Data(_) => None, OutputColumn::Literal(_) => None, }) @@ -1361,8 +1361,13 @@ impl SqlToMirConverter { .collect(); let ident = format!("q_{:x}_n{}", qg.signature().hash, new_node_count); - let leaf_project_node = - self.make_project_node(&ident, final_node, projected_columns, projected_arithmetic, projected_literals); + let leaf_project_node = self.make_project_node( + &ident, + final_node, + projected_columns, + projected_arithmetic, + projected_literals, + ); nodes_added.push(leaf_project_node.clone()); // We always materialize leaves of queries (at least currently), so add a diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 1f3d9a69b..a7030b997 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -204,7 +204,7 @@ impl SqlIncorporator { } !is_function - }, + } OutputColumn::Data(ref dc) => dc.function.is_none(), }) { // QGs are identical, except for parameters (or their order) @@ -1021,10 +1021,7 @@ mod tests { assert_eq!(mig.graph().node_count(), ncount + 2); // only the projection node is returned in the vector of new nodes assert_eq!(qfp.new_nodes.len(), 1); - assert_eq!( - get_node(&inc, mig, &qfp.name).description(), - "π[0, 1, 2]" - ); + assert_eq!(get_node(&inc, mig, &qfp.name).description(), "π[0, 1, 2]"); // we should be based off the new projection as our leaf let id_node = qfp.new_nodes.iter().next().unwrap(); assert_eq!(qfp.query_leaf, *id_node); @@ -1278,11 +1275,8 @@ mod tests { let mut inc = SqlIncorporator::default(); g.migrate(|mig| { assert!( - inc.add_query( - "CREATE TABLE users (id int, age int);", - None, - mig - ).is_ok() + inc.add_query("CREATE TABLE users (id int, age int);", None, mig) + .is_ok() ); let res = inc.add_query("SELECT 2 * users.age FROM users;", None, mig); diff --git a/src/sql/passes/implied_tables.rs b/src/sql/passes/implied_tables.rs index e71964053..573f3cb2b 100644 --- a/src/sql/passes/implied_tables.rs +++ b/src/sql/passes/implied_tables.rs @@ -1,5 +1,5 @@ -use nom_sql::{ArithmeticBase, Column, ConditionExpression, ConditionTree, FieldExpression, JoinRightSide, - SqlQuery, Table}; +use nom_sql::{ArithmeticBase, Column, ConditionExpression, ConditionTree, FieldExpression, + JoinRightSide, SqlQuery, Table}; use std::collections::HashMap; diff --git a/src/sql/passes/star_expansion.rs b/src/sql/passes/star_expansion.rs index 2defd1c37..2aa1af1c6 100644 --- a/src/sql/passes/star_expansion.rs +++ b/src/sql/passes/star_expansion.rs @@ -37,7 +37,9 @@ impl StarExpansion for SqlQuery { let v: Vec<_> = expand_table(t).collect(); v.into_iter() } - FieldExpression::Arithmetic(a) => vec![FieldExpression::Arithmetic(a)].into_iter(), + FieldExpression::Arithmetic(a) => { + vec![FieldExpression::Arithmetic(a)].into_iter() + } FieldExpression::Literal(l) => vec![FieldExpression::Literal(l)].into_iter(), FieldExpression::Col(c) => vec![FieldExpression::Col(c)].into_iter(), }) diff --git a/src/sql/query_graph.rs b/src/sql/query_graph.rs index 570c8212e..4945a9300 100644 --- a/src/sql/query_graph.rs +++ b/src/sql/query_graph.rs @@ -676,11 +676,10 @@ pub fn to_query_graph(st: &SelectStatement) -> Result { Some(_) => { // add a special node representing the computed columns; if it already // exists, add another computed column to it - let n = query_graph.relations + let n = query_graph + .relations .entry(String::from("computed_columns")) - .or_insert_with( - || new_node(String::from("computed_columns"), vec![], st), - ); + .or_insert_with(|| new_node(String::from("computed_columns"), vec![], st)); n.columns.push(column.clone()); } diff --git a/tests/lib.rs b/tests/lib.rs index 96e186091..cac939096 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -369,7 +369,9 @@ fn it_works_with_join_arithmetic() { let fraction = 0.7; car_mutator.put(vec![id.into(), id.into()]).unwrap(); price_mutator.put(vec![id.into(), price.into()]).unwrap(); - sales_mutator.put(vec![id.into(), id.into(), fraction.into()]).unwrap(); + sales_mutator + .put(vec![id.into(), id.into(), fraction.into()]) + .unwrap(); // Let writes propagate: thread::sleep(time::Duration::from_millis(SETTLE_TIME_MS)); From 436333cce567b3c50e253fa44ba9f621c23a576a Mon Sep 17 00:00:00 2001 From: Martin Ek Date: Tue, 3 Oct 2017 03:10:00 +0200 Subject: [PATCH 14/16] thread::sleep(...) -> sleep() --- tests/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/lib.rs b/tests/lib.rs index c3f00f43a..830483da9 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -345,7 +345,7 @@ fn it_works_with_simple_arithmetic() { mutator.put(vec![id.clone(), price]).unwrap(); // Let writes propagate: - thread::sleep(time::Duration::from_millis(SETTLE_TIME_MS)); + sleep(); // Retrieve the result of the count query: let result = getter.lookup(&id, true).unwrap(); @@ -390,7 +390,7 @@ fn it_works_with_join_arithmetic() { .unwrap(); // Let writes propagate: - thread::sleep(time::Duration::from_millis(SETTLE_TIME_MS)); + sleep(); // Retrieve the result of the count query: let result = getter.lookup(&id.into(), true).unwrap(); @@ -423,7 +423,7 @@ fn it_works_with_function_arithmetic() { } // Let writes propagate: - thread::sleep(time::Duration::from_millis(SETTLE_TIME_MS)); + sleep(); // Retrieve the result of the count query: let key = distributary::DataType::BigInt(max_price * 2); From 85044bf28c9bb83bbe2b907bfdfaf7cf0b6d0c84 Mon Sep 17 00:00:00 2001 From: Martin Ek Date: Wed, 4 Oct 2017 17:16:19 +0200 Subject: [PATCH 15/16] len() > 0 -> is_empty() --- src/mir/node.rs | 12 ++++++------ src/mir/visualize.rs | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/mir/node.rs b/src/mir/node.rs index e664f951a..34b865ee8 100644 --- a/src/mir/node.rs +++ b/src/mir/node.rs @@ -1007,7 +1007,9 @@ impl Debug for MirNodeType { .map(|c| c.name.as_str()) .collect::>() .join(", "), - if arithmetic.len() > 0 { + if arithmetic.is_empty() { + format!("") + } else { format!( ", {}", arithmetic @@ -1016,10 +1018,10 @@ impl Debug for MirNodeType { .collect::>() .join(", ") ) - } else { - format!("") }, - if literals.len() > 0 { + if literals.is_empty() { + format!("") + } else { format!( ", lit: {}", literals @@ -1028,8 +1030,6 @@ impl Debug for MirNodeType { .collect::>() .join(", ") ) - } else { - format!("") }, ), MirNodeType::Reuse { ref node } => write!( diff --git a/src/mir/visualize.rs b/src/mir/visualize.rs index c62e1a6fb..5310c64f6 100644 --- a/src/mir/visualize.rs +++ b/src/mir/visualize.rs @@ -225,7 +225,9 @@ impl GraphViz for MirNodeType { .map(|c| c.name.as_str()) .collect::>() .join(", "), - if arithmetic.len() > 0 { + if arithmetic.is_empty() { + format!("") + } else { format!( ", {}", arithmetic @@ -234,10 +236,10 @@ impl GraphViz for MirNodeType { .collect::>() .join(", ") ) - } else { - format!("") }, - if literals.len() > 0 { + if literals.is_empty() { + format!("") + } else { format!( ", lit: {}", literals @@ -246,8 +248,6 @@ impl GraphViz for MirNodeType { .collect::>() .join(", ") ) - } else { - format!("") } )?; } From 28a9dadb8743cc9d18e19b7140ac83a8fdee5b39 Mon Sep 17 00:00:00 2001 From: Martin Ek Date: Wed, 4 Oct 2017 17:21:45 +0200 Subject: [PATCH 16/16] Add a test for multiple arithmetic expressions --- tests/lib.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/lib.rs b/tests/lib.rs index 830483da9..50e207bd9 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -353,6 +353,39 @@ fn it_works_with_simple_arithmetic() { assert_eq!(result[0][1], 246.into()); } +#[test] +fn it_works_with_multiple_arithmetic_expressions() { + let mut g = distributary::Blender::new(); + let sql = " + CREATE TABLE Car (id int, price int, PRIMARY KEY(id)); + CarPrice: SELECT 10 * 10, 2 * price, 10 * price, FROM Car WHERE id = ?; + "; + + let recipe = g.migrate(|mig| { + let mut recipe = distributary::Recipe::from_str(&sql, None).unwrap(); + recipe.activate(mig, false).unwrap(); + recipe + }); + + let car_index = recipe.node_addr_for("Car").unwrap(); + let count_index = recipe.node_addr_for("CarPrice").unwrap(); + let mut mutator = g.get_mutator(car_index); + let getter = g.get_getter(count_index).unwrap(); + let id: distributary::DataType = 1.into(); + let price: distributary::DataType = 123.into(); + mutator.put(vec![id.clone(), price]).unwrap(); + + // Let writes propagate: + sleep(); + + // Retrieve the result of the count query: + let result = getter.lookup(&id, true).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0][1], 100.into()); + assert_eq!(result[0][2], 246.into()); + assert_eq!(result[0][3], 1230.into()); +} + #[test] fn it_works_with_join_arithmetic() { let mut g = distributary::Blender::new();