From a9fd03d36a376f04219ad9b9370ca79a3a61d8cf Mon Sep 17 00:00:00 2001 From: Andi Cuko Date: Wed, 12 Jun 2024 14:45:08 +0200 Subject: [PATCH 1/7] ok --- src/expr/sql.rs | 26 +++++++++++++++++++++++++- src/sql/expr.rs | 6 ++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/expr/sql.rs b/src/expr/sql.rs index a7586354..ffd15a01 100644 --- a/src/expr/sql.rs +++ b/src/expr/sql.rs @@ -583,7 +583,7 @@ impl From<&Expr> for ast::Expr { #[cfg(test)] mod tests { use super::*; - use crate::sql::parse_expr; + use crate::{hierarchy::Hierarchy, sql::parse_expr, WithoutContext}; use std::convert::TryFrom; #[test] @@ -1488,4 +1488,28 @@ mod tests { println!("ast::expr = {gen_expr}"); assert_eq!(gen_expr, ast_expr); } + + #[test] + fn test_expr_from_value() { + let ast_expr: ast::Expr = parse_expr("True").unwrap(); + println!("\nast::expr = {ast_expr}"); + let expr = Expr::try_from(ast_expr.with(&Hierarchy::empty())).unwrap(); + println!("expr = {}", expr); + + let ast_expr: ast::Expr = parse_expr("1").unwrap(); + println!("\nast::expr = {ast_expr}"); + let expr = Expr::try_from(ast_expr.with(&Hierarchy::empty())).unwrap(); + println!("expr = {}", expr); + + let ast_expr: ast::Expr = parse_expr("Null").unwrap(); + println!("\nast::expr = {ast_expr}"); + let expr = Expr::try_from(ast_expr.with(&Hierarchy::empty())).unwrap(); + println!("expr = {}", expr); + + let ast_expr: ast::Expr = parse_expr(" 'some_string' ").unwrap(); + println!("\nast::expr = {ast_expr}"); + let expr = Expr::try_from(ast_expr.with(&Hierarchy::empty())).unwrap(); + println!("expr = {}", expr); + } + } diff --git a/src/sql/expr.rs b/src/sql/expr.rs index d2f5aeb9..fc1f5dea 100644 --- a/src/sql/expr.rs +++ b/src/sql/expr.rs @@ -920,8 +920,8 @@ impl<'a> Visitor<'a, Result> for TryIntoExprVisitor<'a> { ast::Value::NationalStringLiteral(_) => todo!(), ast::Value::HexStringLiteral(_) => todo!(), ast::Value::DoubleQuotedString(_) => todo!(), - ast::Value::Boolean(_) => todo!(), - ast::Value::Null => todo!(), + ast::Value::Boolean(b) => Expr::val(*b), + ast::Value::Null => Expr::val(None), ast::Value::Placeholder(_) => todo!(), ast::Value::DollarQuotedString(_) => todo!(), ast::Value::SingleQuotedByteStringLiteral(_) => todo!(), @@ -1443,6 +1443,8 @@ mod tests { println!("\nast::expr = {ast_expr}"); let expr = Expr::try_from(ast_expr.with(&Hierarchy::empty())).unwrap(); println!("expr = {}", expr); + + assert_eq!( ast::Expr::from(&expr).to_string(), String::from( From df44c55ec1a1f8012b42355081b704cb0da6ca4d Mon Sep 17 00:00:00 2001 From: Andi Cuko Date: Wed, 12 Jun 2024 15:22:31 +0200 Subject: [PATCH 2/7] fix translator --- src/dialect_translation/mod.rs | 7 ++++++- src/dialect_translation/postgresql.rs | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/dialect_translation/mod.rs b/src/dialect_translation/mod.rs index eb019e37..d2c61e25 100644 --- a/src/dialect_translation/mod.rs +++ b/src/dialect_translation/mod.rs @@ -327,6 +327,7 @@ macro_rules! relation_to_query_tranlator_trait_constructor { } fn expr(&self, expr: &expr::Expr) -> ast::Expr { + println!("DEBUG: EXPR {}", expr); match expr { expr::Expr::Column(ident) => self.column(ident), expr::Expr::Value(value) => self.value(value), @@ -346,6 +347,7 @@ macro_rules! relation_to_query_tranlator_trait_constructor { } fn value(&self, value: &expr::Value) -> ast::Expr { + println!("DEBUG: VAL {}", value); match value { expr::Value::Unit(_) => ast::Expr::Value(ast::Value::Null), expr::Value::Boolean(b) => ast::Expr::Value(ast::Value::Boolean(**b)), @@ -362,7 +364,10 @@ macro_rules! relation_to_query_tranlator_trait_constructor { expr::Value::Bytes(_) => todo!(), expr::Value::Struct(_) => todo!(), expr::Value::Union(_) => todo!(), - expr::Value::Optional(_) => todo!(), + expr::Value::Optional(optional_val) => match optional_val.as_deref() { + Some(arg) => self.value(arg), + None => ast::Expr::Value(ast::Value::Null), + }, expr::Value::List(l) => ast::Expr::Tuple( l.to_vec() .iter() diff --git a/src/dialect_translation/postgresql.rs b/src/dialect_translation/postgresql.rs index 80196b82..020d032a 100644 --- a/src/dialect_translation/postgresql.rs +++ b/src/dialect_translation/postgresql.rs @@ -169,4 +169,26 @@ mod tests { .map(ToString::to_string); Ok(()) } + + #[test] + fn test_relation_to_query_with_null_field() -> Result<()> { + let mut database = postgresql::test_database(); + let relations = database.relations(); + let query_str = + r#"SELECT CASE WHEN (1) > (2) THEN 1 ELSE NULL END AS "_PRIVACY_UNIT_", a AS a FROM table_1"#; + let translator = PostgreSqlTranslator; + let query = parse_with_dialect(query_str, translator.dialect())?; + let query_with_relation = QueryWithRelations::new(&query, &relations); + let relation = Relation::try_from((query_with_relation, translator))?; + println!("\n {} \n", relation); + let rel_with_traslator = RelationWithTranslator(&relation, translator); + let translated = ast::Query::from(rel_with_traslator); + print!("{}", translated); + _ = database + .query(translated.to_string().as_str()) + .unwrap() + .iter() + .map(ToString::to_string); + Ok(()) + } } From 4b30cdc88a262f867b1307cc07cd6ac090b730e8 Mon Sep 17 00:00:00 2001 From: Andi Cuko Date: Mon, 17 Jun 2024 18:29:00 +0200 Subject: [PATCH 3/7] ok --- src/dialect_translation/mod.rs | 2 -- src/rewriting/composition.rs | 4 ++++ src/sql/mod.rs | 31 +++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/dialect_translation/mod.rs b/src/dialect_translation/mod.rs index d2c61e25..51a75d0d 100644 --- a/src/dialect_translation/mod.rs +++ b/src/dialect_translation/mod.rs @@ -327,7 +327,6 @@ macro_rules! relation_to_query_tranlator_trait_constructor { } fn expr(&self, expr: &expr::Expr) -> ast::Expr { - println!("DEBUG: EXPR {}", expr); match expr { expr::Expr::Column(ident) => self.column(ident), expr::Expr::Value(value) => self.value(value), @@ -347,7 +346,6 @@ macro_rules! relation_to_query_tranlator_trait_constructor { } fn value(&self, value: &expr::Value) -> ast::Expr { - println!("DEBUG: VAL {}", value); match value { expr::Value::Unit(_) => ast::Expr::Value(ast::Value::Null), expr::Value::Boolean(b) => ast::Expr::Value(ast::Value::Boolean(**b)), diff --git a/src/rewriting/composition.rs b/src/rewriting/composition.rs index edadca66..191b697a 100644 --- a/src/rewriting/composition.rs +++ b/src/rewriting/composition.rs @@ -261,6 +261,10 @@ mod tests { ("b", DataType::float_interval(-2., 2.)), ("c", DataType::float()), ("d", DataType::float_interval(0., 1.)), + ("e", DataType::float()), + ("f", DataType::float_interval(-2., 2.)), + ("g", DataType::float()), + ("h", DataType::float_interval(0., 1.)), ] .into_iter() .collect(); diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 99598782..72ffd16b 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -105,6 +105,8 @@ pub use relation::{parse, parse_with_dialect}; #[cfg(test)] mod tests { + use itertools::Itertools; + use super::*; use crate::{ ast, @@ -297,4 +299,33 @@ mod tests { let relation = Relation::try_from(qwr).unwrap(); relation.display_dot().unwrap(); } + + #[test] + fn test_parsing_many_times() { + let mut database = postgresql::test_database(); + let query: &str = r#" + SELECT * FROM table_1 t1 JOIN table_2 t2 ON t1.d = t2.x + "#; + let mut query = parse(query).unwrap(); + println!("QUERY: {}", query); + let binding = database.relations(); + + for i in 0..5 { + let qwr = query.with(&binding); + let relation = Relation::try_from(qwr).unwrap(); + relation.display_dot().unwrap(); + let query_str: &str = &ast::Query::from(&relation).to_string(); + println!("\n{}\n", query_str); + + _ = &database + .query(query_str) + .unwrap() + .iter() + .map(ToString::to_string) + .join("\n"); + + query = parse(query_str).unwrap(); + } + + } } From 82c0690d80ceeb97cf09d26047b617424eec7a8a Mon Sep 17 00:00:00 2001 From: Andi Cuko Date: Wed, 19 Jun 2024 14:40:38 +0200 Subject: [PATCH 4/7] ok --- src/data_type/function.rs | 45 ++++++++++++++++++++++++++- src/dialect_translation/mod.rs | 4 ++- src/dialect_translation/mssql.rs | 13 +++++--- src/dialect_translation/postgresql.rs | 3 +- src/expr/function.rs | 4 +++ src/expr/implementation.rs | 1 + src/expr/mod.rs | 1 + src/expr/sql.rs | 14 ++++++++- src/sql/expr.rs | 4 +-- src/sql/mod.rs | 45 +++++++++++++++++++++++++-- src/sql/relation.rs | 5 +-- 11 files changed, 123 insertions(+), 16 deletions(-) diff --git a/src/data_type/function.rs b/src/data_type/function.rs index ffe399f7..2210c918 100644 --- a/src/data_type/function.rs +++ b/src/data_type/function.rs @@ -1944,6 +1944,17 @@ pub fn extract_year() -> impl Function { )) } +pub fn extract_epoch() -> impl Function { + Polymorphic::from(( + Pointwise::univariate(data_type::DateTime::default(), DataType::integer(), |a| { + (a.and_utc().timestamp() as i64).into() + }), + Pointwise::univariate(data_type::Duration::default(), DataType::integer(), |a| { + (a.num_seconds()).into() + }), + )) +} + pub fn extract_month() -> impl Function { Polymorphic::from(( Pointwise::univariate( @@ -2449,7 +2460,8 @@ pub fn count_distinct() -> impl Function { Aggregate::from( DataType::Any, |values| (values.iter().cloned().collect::>().len() as i64).into(), - |(_dt, size)| Ok(data_type::Integer::from_interval(1, *size.max().unwrap())), + |(_dt, size)| Ok(size), // count(distinct x) can be 0 + //Ok(data_type::Integer::from_interval(1, *size.max().unwrap()))}, ), // Optional implementation Aggregate::from( @@ -4316,6 +4328,37 @@ mod tests { #[test] fn test_extract() { + // epoch + println!("\nTest extract_epoch"); + let fun = extract_epoch(); + println!("type = {}", fun); + println!("domain = {}", fun.domain()); + println!("co_domain = {}", fun.co_domain()); + println!("data_type = {}", fun.data_type()); + + let set = DataType::date_time_values([ + NaiveDate::from_ymd_opt(2016, 7, 8) + .unwrap() + .and_hms_opt(9, 10, 11) + .unwrap(), + NaiveDate::from_ymd_opt(2026, 7, 8) + .unwrap() + .and_hms_opt(9, 15, 11) + .unwrap(), + ]); + + let im = fun.super_image(&set).unwrap(); + println!("im({}) = {}", set, im); + assert!(im == DataType::integer_values([1467969011, 1783502111])); + + let set = DataType::duration_values([ + chrono::Duration::hours(24), + chrono::Duration::seconds(100), + ]); + let im = fun.super_image(&set).unwrap(); + println!("im({}) = {}", set, im); + assert!(im == DataType::integer_values([86400, 100])); + // year println!("\nTest extract_year"); let fun = extract_year(); diff --git a/src/dialect_translation/mod.rs b/src/dialect_translation/mod.rs index 51a75d0d..eee16880 100644 --- a/src/dialect_translation/mod.rs +++ b/src/dialect_translation/mod.rs @@ -362,7 +362,7 @@ macro_rules! relation_to_query_tranlator_trait_constructor { expr::Value::Bytes(_) => todo!(), expr::Value::Struct(_) => todo!(), expr::Value::Union(_) => todo!(), - expr::Value::Optional(optional_val) => match optional_val.as_deref() { + expr::Value::Optional(optional_val) => match optional_val.as_deref() { Some(arg) => self.value(arg), None => ast::Expr::Value(ast::Value::Null), }, @@ -438,6 +438,7 @@ macro_rules! relation_to_query_tranlator_trait_constructor { CastAsTime, Sign, Unhex, + ExtractEpoch, ExtractYear, ExtractMonth, ExtractDay, @@ -557,6 +558,7 @@ macro_rules! relation_to_query_tranlator_trait_constructor { CastAsTime, Sign, Unhex, + ExtractEpoch, ExtractYear, ExtractMonth, ExtractDay, diff --git a/src/dialect_translation/mssql.rs b/src/dialect_translation/mssql.rs index d83f0e3c..e6d4157c 100644 --- a/src/dialect_translation/mssql.rs +++ b/src/dialect_translation/mssql.rs @@ -135,10 +135,15 @@ impl RelationToQueryTranslator for MsSqlTranslator { function_builder("CEILING", vec![arg], false) } - // fn from_extract_epoch(&self, expr: &expr::Expr) -> ast::Expr { - // //EXTRACT(EPOCH FROM col1) is not supported yet - // todo!() - // } + fn extract_epoch(&self, expr: &expr::Expr) -> ast::Expr { + let arg = self.expr(expr); + let second = ast::Expr::Identifier(ast::Ident { + value: "SECOND".to_string(), + quote_style: None, + }); + let unix = ast::Expr::Value(ast::Value::SingleQuotedString("19700101".to_string())); + function_builder("DATEDIFF", vec![second, unix, arg], false) + } // used during onboarding in order to have datetime with a proper format. // This is not needed when we will remove the cast in string of the datetime diff --git a/src/dialect_translation/postgresql.rs b/src/dialect_translation/postgresql.rs index 020d032a..9e06c355 100644 --- a/src/dialect_translation/postgresql.rs +++ b/src/dialect_translation/postgresql.rs @@ -174,8 +174,7 @@ mod tests { fn test_relation_to_query_with_null_field() -> Result<()> { let mut database = postgresql::test_database(); let relations = database.relations(); - let query_str = - r#"SELECT CASE WHEN (1) > (2) THEN 1 ELSE NULL END AS "_PRIVACY_UNIT_", a AS a FROM table_1"#; + let query_str = r#"SELECT CASE WHEN (1) > (2) THEN 1 ELSE NULL END AS "_PRIVACY_UNIT_", a AS a FROM table_1"#; let translator = PostgreSqlTranslator; let query = parse_with_dialect(query_str, translator.dialect())?; let query_with_relation = QueryWithRelations::new(&query, &relations); diff --git a/src/expr/function.rs b/src/expr/function.rs index 2986c96d..9163b815 100644 --- a/src/expr/function.rs +++ b/src/expr/function.rs @@ -76,6 +76,7 @@ pub enum Function { CurrentDate, CurrentTime, CurrentTimestamp, + ExtractEpoch, ExtractYear, ExtractMonth, ExtractDay, @@ -173,6 +174,7 @@ impl Function { | Function::CastAsTime | Function::Sign | Function::Unhex + | Function::ExtractEpoch | Function::ExtractYear | Function::ExtractMonth | Function::ExtractDay @@ -276,6 +278,7 @@ impl Function { | Function::Floor | Function::Sign | Function::Unhex + | Function::ExtractEpoch | Function::ExtractYear | Function::ExtractMonth | Function::ExtractDay @@ -431,6 +434,7 @@ impl fmt::Display for Function { Function::CastAsTime => "cast_as_time", Function::Sign => "sign", Function::Unhex => "unhex", + Function::ExtractEpoch => "extract_epoch", Function::ExtractYear => "extract_year", Function::ExtractMonth => "extract_month", Function::ExtractDay => "extract_day", diff --git a/src/expr/implementation.rs b/src/expr/implementation.rs index 6379dc88..74939f91 100644 --- a/src/expr/implementation.rs +++ b/src/expr/implementation.rs @@ -109,6 +109,7 @@ function_implementations!( RegexpContains, Encode, Decode, + ExtractEpoch, ExtractYear, ExtractMonth, ExtractDay, diff --git a/src/expr/mod.rs b/src/expr/mod.rs index e22cb88b..60573c29 100644 --- a/src/expr/mod.rs +++ b/src/expr/mod.rs @@ -317,6 +317,7 @@ impl_unary_function_constructors!( Floor, Sign, Unhex, + ExtractEpoch, ExtractYear, ExtractMonth, ExtractDay, diff --git a/src/expr/sql.rs b/src/expr/sql.rs index ffd15a01..e1bc89eb 100644 --- a/src/expr/sql.rs +++ b/src/expr/sql.rs @@ -337,6 +337,10 @@ impl<'a> expr::Visitor<'a, ast::Expr> for FromExprVisitor { null_treatment: None, within_group: vec![], }), + expr::function::Function::ExtractEpoch => ast::Expr::Extract { + field: ast::DateTimeField::Epoch, + expr: arguments[0].clone().into(), + }, expr::function::Function::ExtractYear => ast::Expr::Extract { field: ast::DateTimeField::Year, expr: arguments[0].clone().into(), @@ -1209,6 +1213,15 @@ mod tests { #[test] fn test_extract() { + // EXTRACT(EPOCH FROM col1) + let str_expr = "extract(epoch from col1)"; + let ast_expr: ast::Expr = parse_expr(str_expr).unwrap(); + let expr = Expr::try_from(&ast_expr).unwrap(); + println!("expr = {}", expr); + let gen_expr = ast::Expr::from(&expr); + println!("ast::expr = {gen_expr}"); + assert_eq!(gen_expr, ast_expr); + // EXTRACT(YEAR FROM col1) let str_expr = "extract(year from col1)"; let ast_expr: ast::Expr = parse_expr(str_expr).unwrap(); @@ -1511,5 +1524,4 @@ mod tests { let expr = Expr::try_from(ast_expr.with(&Hierarchy::empty())).unwrap(); println!("expr = {}", expr); } - } diff --git a/src/sql/expr.rs b/src/sql/expr.rs index fc1f5dea..145ebe2b 100644 --- a/src/sql/expr.rs +++ b/src/sql/expr.rs @@ -1296,6 +1296,7 @@ impl<'a> Visitor<'a, Result> for TryIntoExprVisitor<'a> { fn extract(&self, field: &'a ast::DateTimeField, expr: Result) -> Result { Ok(match field { + ast::DateTimeField::Epoch => Expr::extract_epoch(expr.clone()?), ast::DateTimeField::Year => Expr::extract_year(expr.clone()?), ast::DateTimeField::Month => Expr::extract_month(expr.clone()?), ast::DateTimeField::Week(_) => Expr::extract_week(expr.clone()?), @@ -1443,8 +1444,7 @@ mod tests { println!("\nast::expr = {ast_expr}"); let expr = Expr::try_from(ast_expr.with(&Hierarchy::empty())).unwrap(); println!("expr = {}", expr); - - + assert_eq!( ast::Expr::from(&expr).to_string(), String::from( diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 72ffd16b..66fa48a4 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -105,6 +105,8 @@ pub use relation::{parse, parse_with_dialect}; #[cfg(test)] mod tests { + use std::sync::Arc; + use itertools::Itertools; use super::*; @@ -112,8 +114,10 @@ mod tests { ast, builder::With, display::Dot, + hierarchy::Hierarchy, io::{postgresql, Database}, relation::{Relation, Variant as _}, + DataType, Ready as _, }; #[test] @@ -300,6 +304,42 @@ mod tests { relation.display_dot().unwrap(); } + #[test] + fn test_parse_queries_on_inf_sized_table() { + let table: Arc = Arc::new( + crate::relation::TableBuilder::new() + .path(["table_1"]) + .name("table_1") + .size(9223372036854775807) + .schema( + crate::relation::Schema::empty() + .with(("a", DataType::float_interval(0., 10.))) + .with(("b", DataType::optional(DataType::float_interval(-1., 1.)))) + .with(( + "c", + DataType::date_interval( + chrono::NaiveDate::from_ymd_opt(1980, 12, 06).unwrap(), + chrono::NaiveDate::from_ymd_opt(2023, 12, 06).unwrap(), + ), + )) + .with(("d", DataType::integer_interval(0, 10))) + .with(("sarus_privacy_unit", DataType::optional(DataType::id()))), + ) + .build(), + ); + let relations: Hierarchy> = Hierarchy::from([(vec!["table_1"], table)]); + + let query_str: &str = r#" + SELECT 0.1 * COUNT(DISTINCT sarus_privacy_unit) FROM table_1 + "#; + + let query = parse(query_str).unwrap(); + println!("QUERY: {}", query); + let qwr = query.with(&relations); + let relation = Relation::try_from(qwr).unwrap(); + relation.display_dot().unwrap() + } + #[test] fn test_parsing_many_times() { let mut database = postgresql::test_database(); @@ -309,8 +349,8 @@ mod tests { let mut query = parse(query).unwrap(); println!("QUERY: {}", query); let binding = database.relations(); - - for i in 0..5 { + + for _i in 0..5 { let qwr = query.with(&binding); let relation = Relation::try_from(qwr).unwrap(); relation.display_dot().unwrap(); @@ -326,6 +366,5 @@ mod tests { query = parse(query_str).unwrap(); } - } } diff --git a/src/sql/relation.rs b/src/sql/relation.rs index 83f81806..edbedc61 100644 --- a/src/sql/relation.rs +++ b/src/sql/relation.rs @@ -503,7 +503,6 @@ impl<'a, T: QueryToRelationTranslator + Copy + Clone> VisitedQueryRelations<'a, .iter() .map(|(p, i)| (i.deref(), p.last().unwrap().clone())) .collect(); - for field in from.schema().iter() { let field_name = field.name().to_string(); let alias = new_aliases @@ -539,7 +538,6 @@ impl<'a, T: QueryToRelationTranslator + Copy + Clone> VisitedQueryRelations<'a, let (named_expr_from_select, new_columns) = self.try_named_expr_columns_from_select_items(columns, select_items, &from)?; named_exprs.extend(named_expr_from_select.into_iter()); - // Prepare the GROUP BY let group_by = match group_by { ast::GroupByExpr::All => todo!(), @@ -548,6 +546,7 @@ impl<'a, T: QueryToRelationTranslator + Copy + Clone> VisitedQueryRelations<'a, .map(|e| self.translator.try_expr(e, columns)) .collect::>>()?, }; + // If the GROUP BY contains aliases, then replace them by the corresponding expression in `named_exprs`. // Note that we mimic postgres behavior and support only GROUP BY alias column (no other expressions containing aliases are allowed) // The aliases cannot be used in HAVING @@ -564,6 +563,7 @@ impl<'a, T: QueryToRelationTranslator + Copy + Clone> VisitedQueryRelations<'a, _ => x, }) .collect::>(); + // Add the having in named_exprs let having = if let Some(expr) = having { let having_name = namer::name_from_content(FIELD, &expr); @@ -644,6 +644,7 @@ impl<'a, T: QueryToRelationTranslator + Copy + Clone> VisitedQueryRelations<'a, } // preserve old columns while composing with new ones let columns = &columns.clone().with(columns.and_then(new_columns)); + Ok(RelationWithColumns::new( Arc::new(relation), columns.clone(), From 769ff9994587e0539e74dcd43cac82ac571a853f Mon Sep 17 00:00:00 2001 From: Andi Cuko Date: Wed, 19 Jun 2024 16:57:31 +0200 Subject: [PATCH 5/7] fix translator --- src/dialect_translation/mod.rs | 47 ++++++++++++++++++++------- src/dialect_translation/postgresql.rs | 21 ++++++++++++ 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/src/dialect_translation/mod.rs b/src/dialect_translation/mod.rs index eee16880..33bc3f68 100644 --- a/src/dialect_translation/mod.rs +++ b/src/dialect_translation/mod.rs @@ -58,6 +58,20 @@ macro_rules! unary_function_ast_constructor { } } +/// Constructors for creating trait functions with default implementations for generating AST extract expressions +macro_rules! extract_ast_expression_constructor { + ($( $enum:ident ),*) => { + paste! { + $( + fn [](&self, expr: &expr::Expr) -> ast::Expr { + let ast_expr = self.expr(expr); + extract_builder(ast_expr, ast::DateTimeField::$enum) + } + )* + } + } +} + /// Constructors for creating trait functions with default implementations for generating AST nary function expressions macro_rules! nary_function_ast_constructor { ($( $enum:ident ),*) => { @@ -558,17 +572,6 @@ macro_rules! relation_to_query_tranlator_trait_constructor { CastAsTime, Sign, Unhex, - ExtractEpoch, - ExtractYear, - ExtractMonth, - ExtractDay, - ExtractHour, - ExtractMinute, - ExtractSecond, - ExtractMicrosecond, - ExtractMillisecond, - ExtractDow, - ExtractWeek, Dayname, UnixTimestamp, Quarter, @@ -612,6 +615,24 @@ macro_rules! relation_to_query_tranlator_trait_constructor { Concat ); + extract_ast_expression_constructor!( + Epoch, + Year, + Month, + Day, + Dow, + Hour, + Minute, + Second, + Microsecond, + Millisecond + ); + + fn extract_week(&self, expr: &expr::Expr)-> ast::Expr { + let ast_expr=self.expr(expr); + extract_builder(ast_expr, ast::DateTimeField::Week(None)) + } + fn cast_as_text(&self, expr: &expr::Expr) -> ast::Expr { let ast_expr = self.expr(expr); cast_builder(ast_expr, ast::DataType::Text) @@ -875,6 +896,10 @@ fn unary_op_builder(op: ast::UnaryOperator, expr: ast::Expr) -> ast::Expr { } } +fn extract_builder(expr: ast::Expr, datetime_field: ast::DateTimeField) -> ast::Expr { + ast::Expr::Extract { field: datetime_field, expr: Box::new(expr) } +} + pub struct RelationWithTranslator<'a, T: RelationToQueryTranslator>(pub &'a Relation, pub T); impl<'a, T: RelationToQueryTranslator> From> for ast::Query { diff --git a/src/dialect_translation/postgresql.rs b/src/dialect_translation/postgresql.rs index 9e06c355..a79a0699 100644 --- a/src/dialect_translation/postgresql.rs +++ b/src/dialect_translation/postgresql.rs @@ -190,4 +190,25 @@ mod tests { .map(ToString::to_string); Ok(()) } + + #[test] + fn test_extract() -> Result<()> { + let mut database = postgresql::test_database(); + let relations = database.relations(); + let query_str = r#"SELECT extract(EPOCH FROM c) as my_epoch, extract(YEAR FROM c) as my_year, extract(WEEK FROM c) as my_week, extract(DOW FROM c) as my_dow FROM table_1"#; + let translator = PostgreSqlTranslator; + let query = parse_with_dialect(query_str, translator.dialect())?; + let query_with_relation = QueryWithRelations::new(&query, &relations); + let relation = Relation::try_from((query_with_relation, translator))?; + println!("\n {} \n", relation); + let rel_with_traslator = RelationWithTranslator(&relation, translator); + let translated = ast::Query::from(rel_with_traslator); + print!("{}", translated); + _ = database + .query(translated.to_string().as_str()) + .unwrap() + .iter() + .map(ToString::to_string); + Ok(()) + } } From 6d30ca3b2565b365890a8e48b98eea5f4db58163 Mon Sep 17 00:00:00 2001 From: Andi Cuko Date: Thu, 20 Jun 2024 16:50:30 +0200 Subject: [PATCH 6/7] version and changelog --- CHANGELOG.md | 8 ++++++++ Cargo.toml | 2 +- src/data_type/function.rs | 3 +-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index befb5de4..52eaf55b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [0.9.20] - 2024-06-20 +### Added +- support for extract_epoch function +- support for NULL and Boolean values in the SELECT when parsing a query + +### Changed +- Polymorphic properties of count(distinct ) are set as those of count + ## [0.9.19] - 2024-05-23 ### Added - parsing of CTEs with column alias diff --git a/Cargo.toml b/Cargo.toml index 0d6fa3bd..c4721b1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["Nicolas Grislain "] name = "qrlew" -version = "0.9.19" +version = "0.9.20" edition = "2021" description = "Sarus Qrlew Engine" documentation = "https://docs.rs/qrlew" diff --git a/src/data_type/function.rs b/src/data_type/function.rs index 2210c918..7b786daf 100644 --- a/src/data_type/function.rs +++ b/src/data_type/function.rs @@ -2460,8 +2460,7 @@ pub fn count_distinct() -> impl Function { Aggregate::from( DataType::Any, |values| (values.iter().cloned().collect::>().len() as i64).into(), - |(_dt, size)| Ok(size), // count(distinct x) can be 0 - //Ok(data_type::Integer::from_interval(1, *size.max().unwrap()))}, + |(_dt, size)| Ok(size), ), // Optional implementation Aggregate::from( From eaa8636509c174419d7d9fc5ee4e3aa180bf88a7 Mon Sep 17 00:00:00 2001 From: Andi Cuko Date: Thu, 20 Jun 2024 18:18:24 +0200 Subject: [PATCH 7/7] ok --- src/data_type/function.rs | 19 +++++++++++-------- src/dialect_translation/mod.rs | 11 +++++++---- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/data_type/function.rs b/src/data_type/function.rs index 7b786daf..81c8d751 100644 --- a/src/data_type/function.rs +++ b/src/data_type/function.rs @@ -1945,14 +1945,17 @@ pub fn extract_year() -> impl Function { } pub fn extract_epoch() -> impl Function { - Polymorphic::from(( - Pointwise::univariate(data_type::DateTime::default(), DataType::integer(), |a| { - (a.and_utc().timestamp() as i64).into() - }), - Pointwise::univariate(data_type::Duration::default(), DataType::integer(), |a| { - (a.num_seconds()).into() - }), - )) + Polymorphic::default() + .with(PartitionnedMonotonic::univariate( + data_type::DateTime::default(), + |a| { + (a.and_utc().timestamp() as i64).clamp(::min(), ::max()) + }, + )) + .with(PartitionnedMonotonic::univariate( + data_type::Duration::default(), + |a| (a.num_seconds()).clamp(::min(), ::max()), + )) } pub fn extract_month() -> impl Function { diff --git a/src/dialect_translation/mod.rs b/src/dialect_translation/mod.rs index 33bc3f68..d974b22a 100644 --- a/src/dialect_translation/mod.rs +++ b/src/dialect_translation/mod.rs @@ -628,8 +628,8 @@ macro_rules! relation_to_query_tranlator_trait_constructor { Millisecond ); - fn extract_week(&self, expr: &expr::Expr)-> ast::Expr { - let ast_expr=self.expr(expr); + fn extract_week(&self, expr: &expr::Expr) -> ast::Expr { + let ast_expr = self.expr(expr); extract_builder(ast_expr, ast::DateTimeField::Week(None)) } @@ -897,8 +897,11 @@ fn unary_op_builder(op: ast::UnaryOperator, expr: ast::Expr) -> ast::Expr { } fn extract_builder(expr: ast::Expr, datetime_field: ast::DateTimeField) -> ast::Expr { - ast::Expr::Extract { field: datetime_field, expr: Box::new(expr) } -} + ast::Expr::Extract { + field: datetime_field, + expr: Box::new(expr), + } +} pub struct RelationWithTranslator<'a, T: RelationToQueryTranslator>(pub &'a Relation, pub T);