From 06bb8e99a7d4398cc5e94d70658f10241872cc99 Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Wed, 19 Apr 2017 13:00:14 -0700 Subject: [PATCH] Pre: Add Values to query-sql. --- query-sql/src/lib.rs | 102 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 95 insertions(+), 7 deletions(-) diff --git a/query-sql/src/lib.rs b/query-sql/src/lib.rs index 6ca151a3..15027bfd 100644 --- a/query-sql/src/lib.rs +++ b/query-sql/src/lib.rs @@ -152,6 +152,14 @@ pub enum TableOrSubquery { Table(SourceAlias), Union(Vec, TableAlias), Subquery(Box), + Values(Values, TableAlias), +} + +pub enum Values { + /// Like "VALUES (0, 1), (2, 3), ...". + Unnamed(Vec>), + /// Like "SELECT 0 AS x, SELECT 0 AS y WHERE 0 UNION ALL VALUES (0, 1), (2, 3), ...". + Named(Vec, Vec>), } pub enum FromClause { @@ -222,6 +230,7 @@ macro_rules! interpose { } } } + impl QueryFragment for ColumnOrExpression { fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { use self::ColumnOrExpression::*; @@ -391,13 +400,63 @@ impl QueryFragment for TableOrSubquery { out.push_identifier(table_alias.as_str()) }, &Subquery(ref subquery) => { - subquery.push_sql(out)?; - Ok(()) + subquery.push_sql(out) + }, + &Values(ref values, ref table_alias) => { + // XXX: does this work for Values::Unnamed? + out.push_sql("("); + values.push_sql(out)?; + out.push_sql(") AS "); + out.push_identifier(table_alias.as_str()) }, } } } +impl QueryFragment for Values { + fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { + // There are at least 3 ways to name the columns of a VALUES table: + // 1) the columns are named "", ":1", ":2", ... -- but this is undocumented. See + // http://stackoverflow.com/a/40921724. + // 2) A CTE ("WITH" statement) can declare the shape of the table, like "WITH + // table_name(column_name, ...) AS (VALUES ...)". + // 3) We can "UNION ALL" a dummy "SELECT" statement in place. + // + // We don't want to use an undocumented SQLite quirk, and we're a little concerned that some + // SQL systems will not optimize WITH statements well. It's also convenient to have an in + // place table to query, so for now we implement option 3). + if let &Values::Named(ref names, _) = self { + let alias = &names[0]; + out.push_sql("SELECT 0 AS "); + out.push_identifier(alias.as_str())?; + + for alias in &names[1..] { + out.push_sql(", 0 AS "); + out.push_identifier(alias.as_str())?; + } + + out.push_sql(" WHERE 0 UNION ALL "); + } + + let values = match self { + &Values::Named(_, ref values) => values, + &Values::Unnamed(ref values) => values, + }; + + out.push_sql("VALUES "); + + interpose!(outer, values, + { out.push_sql("("); + interpose!(inner, outer, + { out.push_typed_value(inner)? }, + { out.push_sql(", ") }); + out.push_sql(")"); + }, + { out.push_sql(", ") }); + Ok(()) + } +} + impl QueryFragment for FromClause { fn push_sql(&self, out: &mut QueryBuilder) -> BuildQueryResult { use self::FromClause::*; @@ -494,7 +553,7 @@ mod tests { DatomsTable, }; - fn build_constraint(c: Constraint) -> String { + fn build(c: &QueryFragment) -> String { let mut builder = SQLiteQueryBuilder::new(); c.push_sql(&mut builder) .map(|_| builder.finish()) @@ -524,9 +583,9 @@ mod tests { ], }; - assert_eq!("`datoms01`.v IN ()", build_constraint(none)); - assert_eq!("`datoms01`.v IN (123)", build_constraint(one)); - assert_eq!("`datoms01`.v IN (123, 456, 789)", build_constraint(three)); + assert_eq!("`datoms01`.v IN ()", build(&none)); + assert_eq!("`datoms01`.v IN (123)", build(&one)); + assert_eq!("`datoms01`.v IN (123, 456, 789)", build(&three)); } #[test] @@ -552,7 +611,36 @@ mod tests { // Two sets of parens: the outermost AND only has one child, // but still contributes parens. - assert_eq!("((123 = 456 AND 789 = 246))", build_constraint(c)); + assert_eq!("((123 = 456 AND 789 = 246))", build(&c)); + } + + #[test] + fn test_unnamed_values() { + let build = |values| build(&Values::Unnamed(values)); + assert_eq!(build(vec![vec![TypedValue::Long(1)]]), + "VALUES (1)"); + + assert_eq!(build(vec![vec![TypedValue::Boolean(false), TypedValue::Long(1)]]), + "VALUES (0, 1)"); + + assert_eq!(build(vec![vec![TypedValue::Boolean(false), TypedValue::Long(1)], + vec![TypedValue::Boolean(true), TypedValue::Long(2)]]), + "VALUES (0, 1), (1, 2)"); + } + + #[test] + fn test_named_values() { + let build = |names: Vec<_>, values| build(&Values::Named(names.into_iter().map(Variable::from_valid_name).collect(), values)); + assert_eq!(build(vec!["?a"], vec![vec![TypedValue::Long(1)]]), + "SELECT 0 AS `?a` WHERE 0 UNION ALL VALUES (1)"); + + assert_eq!(build(vec!["?a", "?b"], vec![vec![TypedValue::Boolean(false), TypedValue::Long(1)]]), + "SELECT 0 AS `?a`, 0 AS `?b` WHERE 0 UNION ALL VALUES (0, 1)"); + + assert_eq!(build(vec!["?a", "?b"], + vec![vec![TypedValue::Boolean(false), TypedValue::Long(1)], + vec![TypedValue::Boolean(true), TypedValue::Long(2)]]), + "SELECT 0 AS `?a`, 0 AS `?b` WHERE 0 UNION ALL VALUES (0, 1), (1, 2)"); } #[test]