You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by gi...@apache.org on 2026/04/19 14:09:58 UTC

(datafusion) 01/01: fix(unparser): make `BigQueryDialect` more robust (#21296)

This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-21296-fd882fbf207c52c1a512d9a53706b264c19e90ee
in repository https://gitbox.apache.org/repos/asf/datafusion.git

commit 7e1a7104b62d568afe3a02cc7aecb96dd265c238
Author: Sergei Grebnov <se...@gmail.com>
AuthorDate: Sun Apr 19 17:09:36 2026 +0300

    fix(unparser): make `BigQueryDialect` more robust (#21296)
    
    ## Which issue does this PR close?
    
    PR improves `BigQueryDialect` dialect to make generated SQL
    `BigQuery`-compatible (fix execution errors).
    
    ## What changes are included in this PR?
    
    Eight `Dialect` trait overrides added to `BigQueryDialect`:
    
    
    https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/data-types
    
    1. `date_field_extract_style` → `Extract` +
    `scalar_function_to_sql_overrides`
    
    BigQuery does not support `date_part()`. TPC-H Q7, Q8, Q9 fail with
    `Function not found: date_part`.
    
    | Before (error) | After |
    |---|---|
    | `date_part('YEAR', l_shipdate)` | `EXTRACT(YEAR FROM l_shipdate)` |
    
    2. `interval_style` → `SQLStandard`
    
    BigQuery does not support PostgreSQL-style interval abbreviations. TPC-H
    Q4, Q20 fail with `Syntax error: Unexpected ")"`.
    
    | Before (error) | After |
    |---|---|
    | `INTERVAL '3 MONS'` | `INTERVAL '3' MONTH` |
    
    3. `float64_ast_dtype` → `Float64`
    
    BigQuery does not support `DOUBLE`. Fails with `Type not found: DOUBLE`.
    
    | Before (error) | After |
    |---|---|
    | `CAST(a AS DOUBLE)` | `CAST(a AS FLOAT64)` |
    
    4. `supports_column_alias_in_table_alias` → `false`
    
    BigQuery does not support column aliases in table alias definitions.
    Fails with `Expected ")" but got "("`.
    
    | Before (error) | After |
    |---|---|
    | `SELECT c.key FROM (...) AS c(key)` | `SELECT c.key FROM (SELECT
    o_orderkey AS key FROM orders) AS c` |
    
    5. `utf8_cast_dtype` + `large_utf8_cast_dtype` → `String`
    
    BigQuery does not support `VARCHAR`/`TEXT`. Fails with `Type not found:
    VARCHAR`, `Type not found: Text`.
    
    | Before (error) | After |
    |---|---|
    | `CAST(a AS VARCHAR)` | `CAST(a AS STRING)` |
    | `CAST(a AS TEXT)` | `CAST(a AS STRING)` |
    
    6. ~`int64_cast_dtype` → `Int64`~
    
    
    7. `timestamp_cast_dtype` → `Timestamp` (no timezone qualifier)
    
    
    https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/data-types#timestamp_type
    
    BigQuery does not support `TIMESTAMP WITH TIME ZONE`. Fails with `Syntax
    error: Expected ')' or keyword FORMAT but got keyword WITH`. `TIMESTAMP`
    should be used (preserves time zone information)/
    
    | Before (error) | After |
    |---|---|
    | `CAST(a AS TIMESTAMP WITH TIME ZONE)` | `CAST(a AS TIMESTAMP)` |
    
    ## Are these changes tested?
    
    Yes. Added `test_bigquery_dialect_overrides` unit test covering all
    eight overrides, verified against BigQuery before and after.
    
    ## Are there any user-facing changes?
    
    No API changes. `BigQueryDialect` now generates valid BigQuery SQL for
    the affected expressions.
---
 datafusion/sql/src/unparser/dialect.rs | 45 +++++++++++++++++++++++++++++++
 datafusion/sql/src/unparser/expr.rs    | 49 ++++++++++++++++++++++++++++++++++
 2 files changed, 94 insertions(+)

diff --git a/datafusion/sql/src/unparser/dialect.rs b/datafusion/sql/src/unparser/dialect.rs
index 194f443749..05438a4785 100644
--- a/datafusion/sql/src/unparser/dialect.rs
+++ b/datafusion/sql/src/unparser/dialect.rs
@@ -664,6 +664,51 @@ impl Dialect for BigQueryDialect {
     fn unnest_as_table_factor(&self) -> bool {
         true
     }
+
+    fn supports_column_alias_in_table_alias(&self) -> bool {
+        false
+    }
+
+    fn float64_ast_dtype(&self) -> ast::DataType {
+        ast::DataType::Float64
+    }
+
+    fn utf8_cast_dtype(&self) -> ast::DataType {
+        ast::DataType::String(None)
+    }
+
+    fn large_utf8_cast_dtype(&self) -> ast::DataType {
+        ast::DataType::String(None)
+    }
+
+    fn timestamp_cast_dtype(
+        &self,
+        _time_unit: &TimeUnit,
+        _tz: &Option<Arc<str>>,
+    ) -> ast::DataType {
+        ast::DataType::Timestamp(None, TimezoneInfo::None)
+    }
+
+    fn date_field_extract_style(&self) -> DateFieldExtractStyle {
+        DateFieldExtractStyle::Extract
+    }
+
+    fn interval_style(&self) -> IntervalStyle {
+        IntervalStyle::SQLStandard
+    }
+
+    fn scalar_function_to_sql_overrides(
+        &self,
+        unparser: &Unparser,
+        func_name: &str,
+        args: &[Expr],
+    ) -> Result<Option<ast::Expr>> {
+        if func_name == "date_part" {
+            return date_part_to_sql(unparser, self.date_field_extract_style(), args);
+        }
+
+        Ok(None)
+    }
 }
 
 impl BigQueryDialect {
diff --git a/datafusion/sql/src/unparser/expr.rs b/datafusion/sql/src/unparser/expr.rs
index 5954e29718..4885dde077 100644
--- a/datafusion/sql/src/unparser/expr.rs
+++ b/datafusion/sql/src/unparser/expr.rs
@@ -3526,4 +3526,53 @@ mod tests {
         }
         Ok(())
     }
+
+    #[test]
+    fn test_bigquery_dialect_overrides() -> Result<()> {
+        let bigquery_dialect: Arc<dyn Dialect> = Arc::new(BigQueryDialect::new());
+        let unparser = Unparser::new(bigquery_dialect.as_ref());
+
+        // date_field_extract_style: EXTRACT instead of date_part
+        let expr = Expr::ScalarFunction(ScalarFunction {
+            func: Arc::new(ScalarUDF::new_from_impl(
+                datafusion_functions::datetime::date_part::DatePartFunc::new(),
+            )),
+            args: vec![lit("YEAR"), col("date_col")],
+        });
+        let actual = format!("{}", unparser.expr_to_sql(&expr)?);
+        assert_eq!(actual, "EXTRACT(YEAR FROM `date_col`)");
+
+        // interval_style: SQL standard instead of PostgresVerbose
+        let expr = interval_year_month_lit("3 months");
+        let actual = format!("{}", unparser.expr_to_sql(&expr)?);
+        assert_eq!(actual, "INTERVAL '3' MONTH");
+
+        // float64_ast_dtype: FLOAT64 instead of DOUBLE
+        let expr = cast(col("a"), DataType::Float64);
+        let actual = format!("{}", unparser.expr_to_sql(&expr)?);
+        assert_eq!(actual, "CAST(`a` AS FLOAT64)");
+
+        // supports_column_alias_in_table_alias: false
+        assert!(!bigquery_dialect.supports_column_alias_in_table_alias());
+
+        // utf8_cast_dtype: STRING instead of VARCHAR
+        let expr = cast(col("a"), DataType::Utf8);
+        let actual = format!("{}", unparser.expr_to_sql(&expr)?);
+        assert_eq!(actual, "CAST(`a` AS STRING)");
+
+        // large_utf8_cast_dtype: STRING instead of TEXT
+        let expr = cast(col("a"), DataType::LargeUtf8);
+        let actual = format!("{}", unparser.expr_to_sql(&expr)?);
+        assert_eq!(actual, "CAST(`a` AS STRING)");
+
+        // timestamp_cast_dtype: TIMESTAMP (no WITH TIME ZONE)
+        let expr = cast(
+            col("a"),
+            DataType::Timestamp(TimeUnit::Microsecond, Some("+00:00".into())),
+        );
+        let actual = format!("{}", unparser.expr_to_sql(&expr)?);
+        assert_eq!(actual, "CAST(`a` AS TIMESTAMP)");
+
+        Ok(())
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org