From c1879f7be6b35ce6d41c25efe42b64bc34df9327 Mon Sep 17 00:00:00 2001 From: yfu Date: Fri, 12 Jul 2024 08:27:51 +1000 Subject: [PATCH 1/5] wip --- datafusion/sql/src/unparser/dialect.rs | 9 +++++++++ datafusion/sql/src/unparser/expr.rs | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/datafusion/sql/src/unparser/dialect.rs b/datafusion/sql/src/unparser/dialect.rs index 33eb2fd965ef6..3643e7040dfb3 100644 --- a/datafusion/sql/src/unparser/dialect.rs +++ b/datafusion/sql/src/unparser/dialect.rs @@ -15,6 +15,7 @@ // specific language governing permissions and limitations // under the License. +use datafusion_common::ScalarValue; use regex::Regex; use sqlparser::keywords::ALL_KEYWORDS; @@ -41,7 +42,15 @@ pub trait Dialect { fn use_timestamp_for_date64(&self) -> bool { false } + + fn custom_scalar_to_sql( + &self, + _: &ScalarValue, + ) -> Option> { + None + } } + pub struct DefaultDialect {} impl Dialect for DefaultDialect { diff --git a/datafusion/sql/src/unparser/expr.rs b/datafusion/sql/src/unparser/expr.rs index b978532973937..bc96f9119a3ad 100644 --- a/datafusion/sql/src/unparser/expr.rs +++ b/datafusion/sql/src/unparser/expr.rs @@ -672,6 +672,10 @@ impl Unparser<'_> { /// DataFusion ScalarValues sometimes require a ast::Expr to construct. /// For example ScalarValue::Date32(d) corresponds to the ast::Expr CAST('datestr' as DATE) fn scalar_to_sql(&self, v: &ScalarValue) -> Result { + if let Some(value) = self.dialect.custom_scalar_to_sql(v) { + return value; + } + match v { ScalarValue::Null => Ok(ast::Expr::Value(ast::Value::Null)), ScalarValue::Boolean(Some(b)) => { From 05423cb80c3aad04734b06f7676214a764a825b1 Mon Sep 17 00:00:00 2001 From: yfu Date: Fri, 12 Jul 2024 16:53:05 +1000 Subject: [PATCH 2/5] add interval style --- datafusion/sql/src/unparser/dialect.rs | 46 ++++++- datafusion/sql/src/unparser/expr.rs | 181 +++++++++++++++++++++---- 2 files changed, 197 insertions(+), 30 deletions(-) diff --git a/datafusion/sql/src/unparser/dialect.rs b/datafusion/sql/src/unparser/dialect.rs index 3643e7040dfb3..f19d151d63ddb 100644 --- a/datafusion/sql/src/unparser/dialect.rs +++ b/datafusion/sql/src/unparser/dialect.rs @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -use datafusion_common::ScalarValue; use regex::Regex; use sqlparser::keywords::ALL_KEYWORDS; @@ -43,14 +42,27 @@ pub trait Dialect { false } - fn custom_scalar_to_sql( - &self, - _: &ScalarValue, - ) -> Option> { - None + fn interval_style(&self) -> IntervalStyle { + // keep the backward compatible + IntervalStyle::PostgresVerbose } } +/// `IntervalStyle` to use for unparsing +/// +/// https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-INTERVAL-INPUT +/// different DBMS follows different standards, popular ones are: +/// postgres_verbose: '2 years 15 months 100 weeks 99 hours 123456789 milliseconds' which is +/// compatible with arrow display format, as well as duckdb +/// sql standard format is '1-2' for year-month, or '1 10:10:10.123456' for day-time +/// https://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt +#[derive(Clone)] +pub enum IntervalStyle { + PostgresVerbose, + SQLStandard, + MySQL, +} + pub struct DefaultDialect {} impl Dialect for DefaultDialect { @@ -72,6 +84,10 @@ impl Dialect for PostgreSqlDialect { fn identifier_quote_style(&self, _: &str) -> Option { Some('"') } + + fn interval_style(&self) -> IntervalStyle { + IntervalStyle::PostgresVerbose + } } pub struct MySqlDialect {} @@ -84,6 +100,10 @@ impl Dialect for MySqlDialect { fn supports_nulls_first_in_sort(&self) -> bool { false } + + fn interval_style(&self) -> IntervalStyle { + IntervalStyle::MySQL + } } pub struct SqliteDialect {} @@ -98,6 +118,7 @@ pub struct CustomDialect { identifier_quote_style: Option, supports_nulls_first_in_sort: bool, use_timestamp_for_date64: bool, + interval_style: IntervalStyle, } impl Default for CustomDialect { @@ -106,6 +127,7 @@ impl Default for CustomDialect { identifier_quote_style: None, supports_nulls_first_in_sort: true, use_timestamp_for_date64: false, + interval_style: IntervalStyle::SQLStandard, } } } @@ -132,6 +154,10 @@ impl Dialect for CustomDialect { fn use_timestamp_for_date64(&self) -> bool { self.use_timestamp_for_date64 } + + fn interval_style(&self) -> IntervalStyle { + self.interval_style.clone() + } } // create a CustomDialectBuilder @@ -139,6 +165,7 @@ pub struct CustomDialectBuilder { identifier_quote_style: Option, supports_nulls_first_in_sort: bool, use_timestamp_for_date64: bool, + interval_style: IntervalStyle, } impl CustomDialectBuilder { @@ -147,6 +174,7 @@ impl CustomDialectBuilder { identifier_quote_style: None, supports_nulls_first_in_sort: true, use_timestamp_for_date64: false, + interval_style: IntervalStyle::PostgresVerbose, } } @@ -155,6 +183,7 @@ impl CustomDialectBuilder { identifier_quote_style: self.identifier_quote_style, supports_nulls_first_in_sort: self.supports_nulls_first_in_sort, use_timestamp_for_date64: self.use_timestamp_for_date64, + interval_style: self.interval_style, } } @@ -178,4 +207,9 @@ impl CustomDialectBuilder { self.use_timestamp_for_date64 = use_timestamp_for_date64; self } + + pub fn with_interval_style(mut self, interval_style: IntervalStyle) -> Self { + self.interval_style = interval_style; + self + } } diff --git a/datafusion/sql/src/unparser/expr.rs b/datafusion/sql/src/unparser/expr.rs index bc96f9119a3ad..b4b0db58af84a 100644 --- a/datafusion/sql/src/unparser/expr.rs +++ b/datafusion/sql/src/unparser/expr.rs @@ -19,18 +19,17 @@ use arrow::datatypes::Decimal128Type; use arrow::datatypes::Decimal256Type; use arrow::datatypes::DecimalType; use arrow::util::display::array_value_to_string; -use core::fmt; -use sqlparser::ast::TimezoneInfo; -use std::{fmt::Display, vec}; - use arrow_array::{ Date32Array, Date64Array, TimestampMillisecondArray, TimestampNanosecondArray, }; use arrow_schema::DataType; +use core::fmt; +use sqlparser::ast::TimezoneInfo; use sqlparser::ast::Value::SingleQuotedString; use sqlparser::ast::{ self, Expr as AstExpr, Function, FunctionArg, Ident, Interval, UnaryOperator, }; +use std::{fmt::Display, vec}; use datafusion_common::{ internal_datafusion_err, internal_err, not_impl_err, plan_err, Column, Result, @@ -41,6 +40,7 @@ use datafusion_expr::{ Between, BinaryExpr, Case, Cast, Expr, GroupingSet, Like, Operator, TryCast, }; +use super::dialect::IntervalStyle; use super::Unparser; /// DataFusion's Exprs can represent either an `Expr` or an `OrderByExpr` @@ -672,10 +672,6 @@ impl Unparser<'_> { /// DataFusion ScalarValues sometimes require a ast::Expr to construct. /// For example ScalarValue::Date32(d) corresponds to the ast::Expr CAST('datestr' as DATE) fn scalar_to_sql(&self, v: &ScalarValue) -> Result { - if let Some(value) = self.dialect.custom_scalar_to_sql(v) { - return value; - } - match v { ScalarValue::Null => Ok(ast::Expr::Value(ast::Value::Null)), ScalarValue::Boolean(Some(b)) => { @@ -899,22 +895,7 @@ impl Unparser<'_> { ScalarValue::IntervalYearMonth(Some(_)) | ScalarValue::IntervalDayTime(Some(_)) | ScalarValue::IntervalMonthDayNano(Some(_)) => { - let wrap_array = v.to_array()?; - let Some(result) = array_value_to_string(&wrap_array, 0).ok() else { - return internal_err!( - "Unable to convert interval scalar value to string" - ); - }; - let interval = Interval { - value: Box::new(ast::Expr::Value(SingleQuotedString( - result.to_uppercase(), - ))), - leading_field: None, - leading_precision: None, - last_field: None, - fractional_seconds_precision: None, - }; - Ok(ast::Expr::Interval(interval)) + self.interval_scalar_to_sql(v) } ScalarValue::IntervalYearMonth(None) => { Ok(ast::Expr::Value(ast::Value::Null)) @@ -951,6 +932,123 @@ impl Unparser<'_> { } } + fn interval_scalar_to_sql(&self, v: &ScalarValue) -> Result { + match self.dialect.interval_style() { + IntervalStyle::PostgresVerbose => { + let wrap_array = v.to_array()?; + let Some(result) = array_value_to_string(&wrap_array, 0).ok() else { + return internal_err!( + "Unable to convert interval scalar value to string" + ); + }; + let interval = Interval { + value: Box::new(ast::Expr::Value(SingleQuotedString( + result.to_uppercase(), + ))), + leading_field: None, + leading_precision: None, + last_field: None, + fractional_seconds_precision: None, + }; + Ok(ast::Expr::Interval(interval)) + } + // If the interval standard is SQLStandard, implement a simple unparse logic + IntervalStyle::SQLStandard => match v { + ScalarValue::IntervalYearMonth(v) => { + let Some(v) = v else { + return Ok(ast::Expr::Value(ast::Value::Null)); + }; + let interval = Interval { + value: Box::new(ast::Expr::Value( + ast::Value::SingleQuotedString(v.to_string()), + )), + leading_field: Some(ast::DateTimeField::Month), + leading_precision: None, + last_field: None, + fractional_seconds_precision: None, + }; + Ok(ast::Expr::Interval(interval)) + } + ScalarValue::IntervalDayTime(v) => { + let Some(v) = v else { + return Ok(ast::Expr::Value(ast::Value::Null)); + }; + let days = v.days; + let secs = v.milliseconds / 1_000; + let mins = secs / 60; + let hours = mins / 60; + + let secs = secs - (mins * 60); + let mins = mins - (hours * 60); + + let millis = v.milliseconds % 1_000; + let interval = Interval { + value: Box::new(ast::Expr::Value( + ast::Value::SingleQuotedString(format!( + "{days} {hours}:{mins}:{secs}.{millis:3}" + )), + )), + leading_field: Some(ast::DateTimeField::Day), + leading_precision: None, + last_field: Some(ast::DateTimeField::Second), + fractional_seconds_precision: None, + }; + Ok(ast::Expr::Interval(interval)) + } + ScalarValue::IntervalMonthDayNano(v) => { + let Some(v) = v else { + return Ok(ast::Expr::Value(ast::Value::Null)); + }; + + if v.months >= 0 && v.days == 0 && v.nanoseconds == 0 { + let interval = Interval { + value: Box::new(ast::Expr::Value( + ast::Value::SingleQuotedString(v.months.to_string()), + )), + leading_field: Some(ast::DateTimeField::Month), + leading_precision: None, + last_field: None, + fractional_seconds_precision: None, + }; + Ok(ast::Expr::Interval(interval)) + } else if v.months == 0 + && v.days >= 0 + && v.nanoseconds % 1_000_000 == 0 + { + let days = v.days; + let secs = v.nanoseconds / 1_000_000_000; + let mins = secs / 60; + let hours = mins / 60; + + let secs = secs - (mins * 60); + let mins = mins - (hours * 60); + + let millis = (v.nanoseconds % 1_000_000_000) / 1_000_000; + + let interval = Interval { + value: Box::new(ast::Expr::Value( + ast::Value::SingleQuotedString(format!( + "{days} {hours}:{mins}:{secs}.{millis:03}" + )), + )), + leading_field: Some(ast::DateTimeField::Day), + leading_precision: None, + last_field: Some(ast::DateTimeField::Second), + fractional_seconds_precision: None, + }; + Ok(ast::Expr::Interval(interval)) + } else { + not_impl_err!("Unsupported IntervalMonthDayNano scalar with both Month and DayTime for IntervalStyle::SQLStandard") + } + } + _ => Ok(ast::Expr::Value(ast::Value::Null)), + }, + IntervalStyle::MySQL => { + not_impl_err!("Unsupported interval scalar for IntervalStyle::MySQL") + } + } + } + fn arrow_dtype_to_ast_dtype(&self, data_type: &DataType) -> Result { match data_type { DataType::Null => { @@ -1586,4 +1684,39 @@ mod tests { Ok(()) } + + #[test] + fn test_interval_scalar_to_expr() { + let tests = [ + (interval_month_day_nano_lit("1 MONTH"), "INTERVAL '1' MONTH"), + ( + interval_month_day_nano_lit("1.5 DAY"), + "INTERVAL '1 12:0:0.000' DAY TO SECOND", + ), + ( + interval_month_day_nano_lit("1.51234 DAY"), + "INTERVAL '1 12:17:46.176' DAY TO SECOND", + ), + ( + interval_datetime_lit("1.51234 DAY"), + "INTERVAL '1 12:17:46.176' DAY TO SECOND", + ), + (interval_year_month_lit("1 YEAR"), "INTERVAL '12' MONTH"), + ]; + + for (value, expected) in tests { + let dialect = CustomDialectBuilder::new() + .with_interval_style(IntervalStyle::SQLStandard) + .build(); + let unparser = Unparser::new(&dialect); + + let ast = unparser + .expr_to_sql(&value) + .expect("to be unparsed"); + + let actual = format!("{ast}"); + + assert_eq!(actual, expected); + } + } } From c096459e9353aa27232a03e1445e413181909876 Mon Sep 17 00:00:00 2001 From: yfu Date: Fri, 12 Jul 2024 16:53:23 +1000 Subject: [PATCH 3/5] format --- datafusion/sql/src/unparser/expr.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/datafusion/sql/src/unparser/expr.rs b/datafusion/sql/src/unparser/expr.rs index b4b0db58af84a..97451c4cc8dc2 100644 --- a/datafusion/sql/src/unparser/expr.rs +++ b/datafusion/sql/src/unparser/expr.rs @@ -1710,9 +1710,7 @@ mod tests { .build(); let unparser = Unparser::new(&dialect); - let ast = unparser - .expr_to_sql(&value) - .expect("to be unparsed"); + let ast = unparser.expr_to_sql(&value).expect("to be unparsed"); let actual = format!("{ast}"); From a9b4ee8cdd6c49e59f47adf8cbc693378fb5f75d Mon Sep 17 00:00:00 2001 From: yfu Date: Fri, 12 Jul 2024 17:29:46 +1000 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Phillip LeBlanc --- datafusion/sql/src/unparser/dialect.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/datafusion/sql/src/unparser/dialect.rs b/datafusion/sql/src/unparser/dialect.rs index f19d151d63ddb..62e68b5d7b377 100644 --- a/datafusion/sql/src/unparser/dialect.rs +++ b/datafusion/sql/src/unparser/dialect.rs @@ -43,7 +43,6 @@ pub trait Dialect { } fn interval_style(&self) -> IntervalStyle { - // keep the backward compatible IntervalStyle::PostgresVerbose } } @@ -56,7 +55,7 @@ pub trait Dialect { /// compatible with arrow display format, as well as duckdb /// sql standard format is '1-2' for year-month, or '1 10:10:10.123456' for day-time /// https://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt -#[derive(Clone)] +#[derive(Clone, Copy)] pub enum IntervalStyle { PostgresVerbose, SQLStandard, @@ -156,7 +155,7 @@ impl Dialect for CustomDialect { } fn interval_style(&self) -> IntervalStyle { - self.interval_style.clone() + self.interval_style } } From 02812f48d47e34c9b4d51a1a39df728795127422 Mon Sep 17 00:00:00 2001 From: yfu Date: Mon, 15 Jul 2024 12:02:47 +1000 Subject: [PATCH 5/5] move all intervel test together --- datafusion/sql/src/unparser/dialect.rs | 1 + datafusion/sql/src/unparser/expr.rs | 108 +++++++++++++++---------- 2 files changed, 65 insertions(+), 44 deletions(-) diff --git a/datafusion/sql/src/unparser/dialect.rs b/datafusion/sql/src/unparser/dialect.rs index 12f3dae19a704..e0553fb7a0099 100644 --- a/datafusion/sql/src/unparser/dialect.rs +++ b/datafusion/sql/src/unparser/dialect.rs @@ -247,6 +247,7 @@ impl CustomDialectBuilder { pub fn with_interval_style(mut self, interval_style: IntervalStyle) -> Self { self.interval_style = interval_style; + self } pub fn with_use_double_precision_for_float64( diff --git a/datafusion/sql/src/unparser/expr.rs b/datafusion/sql/src/unparser/expr.rs index 1dddc29f163af..d3aaa25c359d6 100644 --- a/datafusion/sql/src/unparser/expr.rs +++ b/datafusion/sql/src/unparser/expr.rs @@ -1526,30 +1526,6 @@ mod tests { ), (col("need-quoted").eq(lit(1)), r#"("need-quoted" = 1)"#), (col("need quoted").eq(lit(1)), r#"("need quoted" = 1)"#), - ( - interval_month_day_nano_lit( - "1 YEAR 1 MONTH 1 DAY 3 HOUR 10 MINUTE 20 SECOND", - ), - r#"INTERVAL '0 YEARS 13 MONS 1 DAYS 3 HOURS 10 MINS 20.000000000 SECS'"#, - ), - ( - interval_month_day_nano_lit("1.5 MONTH"), - r#"INTERVAL '0 YEARS 1 MONS 15 DAYS 0 HOURS 0 MINS 0.000000000 SECS'"#, - ), - ( - interval_month_day_nano_lit("-3 MONTH"), - r#"INTERVAL '0 YEARS -3 MONS 0 DAYS 0 HOURS 0 MINS 0.000000000 SECS'"#, - ), - ( - interval_month_day_nano_lit("1 MONTH") - .add(interval_month_day_nano_lit("1 DAY")), - r#"(INTERVAL '0 YEARS 1 MONS 0 DAYS 0 HOURS 0 MINS 0.000000000 SECS' + INTERVAL '0 YEARS 0 MONS 1 DAYS 0 HOURS 0 MINS 0.000000000 SECS')"#, - ), - ( - interval_month_day_nano_lit("1 MONTH") - .sub(interval_month_day_nano_lit("1 DAY")), - r#"(INTERVAL '0 YEARS 1 MONS 0 DAYS 0 HOURS 0 MINS 0.000000000 SECS' - INTERVAL '0 YEARS 0 MONS 1 DAYS 0 HOURS 0 MINS 0.000000000 SECS')"#, - ), ( (col("a") + col("b")) .gt(Expr::Literal(ScalarValue::Decimal128(Some(100123), 28, 3))) @@ -1571,22 +1547,6 @@ mod tests { }), r#"CAST(a AS DECIMAL(12,0))"#, ), - ( - interval_datetime_lit("10 DAY 1 HOUR 10 MINUTE 20 SECOND"), - r#"INTERVAL '0 YEARS 0 MONS 10 DAYS 1 HOURS 10 MINS 20.000 SECS'"#, - ), - ( - interval_datetime_lit("10 DAY 1.5 HOUR 10 MINUTE 20 SECOND"), - r#"INTERVAL '0 YEARS 0 MONS 10 DAYS 1 HOURS 40 MINS 20.000 SECS'"#, - ), - ( - interval_year_month_lit("1 YEAR 1 MONTH"), - r#"INTERVAL '1 YEARS 1 MONS 0 DAYS 0 HOURS 0 MINS 0.00 SECS'"#, - ), - ( - interval_year_month_lit("1.5 YEAR 1 MONTH"), - r#"INTERVAL '1 YEARS 7 MONS 0 DAYS 0 HOURS 0 MINS 0.00 SECS'"#, - ), ]; for (expr, expected) in tests { @@ -1724,25 +1684,85 @@ mod tests { #[test] fn test_interval_scalar_to_expr() { let tests = [ - (interval_month_day_nano_lit("1 MONTH"), "INTERVAL '1' MONTH"), + ( + interval_month_day_nano_lit("1 MONTH"), + IntervalStyle::SQLStandard, + "INTERVAL '1' MONTH", + ), ( interval_month_day_nano_lit("1.5 DAY"), + IntervalStyle::SQLStandard, "INTERVAL '1 12:0:0.000' DAY TO SECOND", ), ( interval_month_day_nano_lit("1.51234 DAY"), + IntervalStyle::SQLStandard, "INTERVAL '1 12:17:46.176' DAY TO SECOND", ), ( interval_datetime_lit("1.51234 DAY"), + IntervalStyle::SQLStandard, "INTERVAL '1 12:17:46.176' DAY TO SECOND", ), - (interval_year_month_lit("1 YEAR"), "INTERVAL '12' MONTH"), + ( + interval_year_month_lit("1 YEAR"), + IntervalStyle::SQLStandard, + "INTERVAL '12' MONTH", + ), + ( + interval_month_day_nano_lit( + "1 YEAR 1 MONTH 1 DAY 3 HOUR 10 MINUTE 20 SECOND", + ), + IntervalStyle::PostgresVerbose, + r#"INTERVAL '0 YEARS 13 MONS 1 DAYS 3 HOURS 10 MINS 20.000000000 SECS'"#, + ), + ( + interval_month_day_nano_lit("1.5 MONTH"), + IntervalStyle::PostgresVerbose, + r#"INTERVAL '0 YEARS 1 MONS 15 DAYS 0 HOURS 0 MINS 0.000000000 SECS'"#, + ), + ( + interval_month_day_nano_lit("-3 MONTH"), + IntervalStyle::PostgresVerbose, + r#"INTERVAL '0 YEARS -3 MONS 0 DAYS 0 HOURS 0 MINS 0.000000000 SECS'"#, + ), + ( + interval_month_day_nano_lit("1 MONTH") + .add(interval_month_day_nano_lit("1 DAY")), + IntervalStyle::PostgresVerbose, + r#"(INTERVAL '0 YEARS 1 MONS 0 DAYS 0 HOURS 0 MINS 0.000000000 SECS' + INTERVAL '0 YEARS 0 MONS 1 DAYS 0 HOURS 0 MINS 0.000000000 SECS')"#, + ), + ( + interval_month_day_nano_lit("1 MONTH") + .sub(interval_month_day_nano_lit("1 DAY")), + IntervalStyle::PostgresVerbose, + r#"(INTERVAL '0 YEARS 1 MONS 0 DAYS 0 HOURS 0 MINS 0.000000000 SECS' - INTERVAL '0 YEARS 0 MONS 1 DAYS 0 HOURS 0 MINS 0.000000000 SECS')"#, + ), + ( + interval_datetime_lit("10 DAY 1 HOUR 10 MINUTE 20 SECOND"), + IntervalStyle::PostgresVerbose, + r#"INTERVAL '0 YEARS 0 MONS 10 DAYS 1 HOURS 10 MINS 20.000 SECS'"#, + ), + ( + interval_datetime_lit("10 DAY 1.5 HOUR 10 MINUTE 20 SECOND"), + IntervalStyle::PostgresVerbose, + r#"INTERVAL '0 YEARS 0 MONS 10 DAYS 1 HOURS 40 MINS 20.000 SECS'"#, + ), + ( + interval_year_month_lit("1 YEAR 1 MONTH"), + IntervalStyle::PostgresVerbose, + r#"INTERVAL '1 YEARS 1 MONS 0 DAYS 0 HOURS 0 MINS 0.00 SECS'"#, + ), + ( + interval_year_month_lit("1.5 YEAR 1 MONTH"), + IntervalStyle::PostgresVerbose, + r#"INTERVAL '1 YEARS 7 MONS 0 DAYS 0 HOURS 0 MINS 0.00 SECS'"#, + ), ]; - for (value, expected) in tests { + for (value, style, expected) in tests { let dialect = CustomDialectBuilder::new() - .with_interval_style(IntervalStyle::SQLStandard) + .with_interval_style(style) .build(); let unparser = Unparser::new(&dialect);