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

goldmedal pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git


The following commit(s) were added to refs/heads/main by this push:
     new 304488d348 chore(deps): Update sqlparser to `0.54.0` (#14255)
304488d348 is described below

commit 304488d348ad2c952ce24f93064a81046155da79
Author: Andrew Lamb <[email protected]>
AuthorDate: Wed Feb 5 10:33:13 2025 -0500

    chore(deps): Update sqlparser to `0.54.0` (#14255)
    
    * chore(deps): Update sqlparser to `0.54.0`
    
    * Update for API changes
    
    * Turn multi-object name into an error
    
    * Add test for unsupported join
    
    * Update datafusion/sql/src/planner.rs
    
    Co-authored-by: Jax Liu <[email protected]>
    
    ---------
    
    Co-authored-by: Jax Liu <[email protected]>
---
 Cargo.toml                                      |   2 +-
 datafusion-cli/Cargo.lock                       |   5 +-
 datafusion/common/src/column.rs                 |  10 +-
 datafusion/expr/src/logical_plan/statement.rs   |   1 +
 datafusion/sql/src/expr/identifier.rs           |   2 +-
 datafusion/sql/src/expr/mod.rs                  | 224 +++++++++++++++---------
 datafusion/sql/src/parser.rs                    |   6 +-
 datafusion/sql/src/planner.rs                   |   9 +-
 datafusion/sql/src/relation/join.rs             |  23 ++-
 datafusion/sql/src/set_expr.rs                  |   3 +
 datafusion/sql/src/statement.rs                 |  51 +++++-
 datafusion/sql/src/unparser/ast.rs              |   1 +
 datafusion/sql/src/unparser/dialect.rs          |   9 +-
 datafusion/sql/src/unparser/expr.rs             |   9 +-
 datafusion/sql/src/unparser/plan.rs             |  10 +-
 datafusion/sql/tests/sql_integration.rs         |   2 +-
 datafusion/sqllogictest/test_files/interval.slt |  23 +--
 datafusion/sqllogictest/test_files/joins.slt    |   4 +
 18 files changed, 256 insertions(+), 138 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 0952c17887..8d25478ab5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -150,7 +150,7 @@ recursive = "0.1.1"
 regex = "1.8"
 rstest = "0.24.0"
 serde_json = "1"
-sqlparser = { version = "0.53.0", features = ["visitor"] }
+sqlparser = { version = "0.54.0", features = ["visitor"] }
 tempfile = "3"
 tokio = { version = "1.43", features = ["macros", "rt", "sync"] }
 url = "2.5.4"
diff --git a/datafusion-cli/Cargo.lock b/datafusion-cli/Cargo.lock
index b4e3336188..e320b2ffc8 100644
--- a/datafusion-cli/Cargo.lock
+++ b/datafusion-cli/Cargo.lock
@@ -3798,11 +3798,12 @@ checksum = 
"6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
 
 [[package]]
 name = "sqlparser"
-version = "0.53.0"
+version = "0.54.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "05a528114c392209b3264855ad491fcce534b94a38771b0a0b97a79379275ce8"
+checksum = "c66e3b7374ad4a6af849b08b3e7a6eda0edbd82f0fd59b57e22671bf16979899"
 dependencies = [
  "log",
+ "recursive",
  "sqlparser_derive",
 ]
 
diff --git a/datafusion/common/src/column.rs b/datafusion/common/src/column.rs
index 9973a72e7b..05e2dff0bd 100644
--- a/datafusion/common/src/column.rs
+++ b/datafusion/common/src/column.rs
@@ -84,7 +84,11 @@ impl Column {
         }
     }
 
-    fn from_idents(idents: &mut Vec<String>) -> Option<Self> {
+    /// Create a Column from multiple normalized identifiers
+    ///
+    /// For example, `foo.bar` would be represented as a two element vector
+    /// `["foo", "bar"]`
+    fn from_idents(mut idents: Vec<String>) -> Option<Self> {
         let (relation, name) = match idents.len() {
             1 => (None, idents.remove(0)),
             2 => (
@@ -126,7 +130,7 @@ impl Column {
     /// where `"foo.BAR"` would be parsed to a reference to column named 
`foo.BAR`
     pub fn from_qualified_name(flat_name: impl Into<String>) -> Self {
         let flat_name = flat_name.into();
-        Self::from_idents(&mut parse_identifiers_normalized(&flat_name, 
false)).unwrap_or(
+        Self::from_idents(parse_identifiers_normalized(&flat_name, 
false)).unwrap_or(
             Self {
                 relation: None,
                 name: flat_name,
@@ -138,7 +142,7 @@ impl Column {
     /// Deserialize a fully qualified name string into a column preserving 
column text case
     pub fn from_qualified_name_ignore_case(flat_name: impl Into<String>) -> 
Self {
         let flat_name = flat_name.into();
-        Self::from_idents(&mut parse_identifiers_normalized(&flat_name, 
true)).unwrap_or(
+        Self::from_idents(parse_identifiers_normalized(&flat_name, 
true)).unwrap_or(
             Self {
                 relation: None,
                 name: flat_name,
diff --git a/datafusion/expr/src/logical_plan/statement.rs 
b/datafusion/expr/src/logical_plan/statement.rs
index 93be04c275..82acebee3d 100644
--- a/datafusion/expr/src/logical_plan/statement.rs
+++ b/datafusion/expr/src/logical_plan/statement.rs
@@ -153,6 +153,7 @@ pub enum TransactionIsolationLevel {
     ReadCommitted,
     RepeatableRead,
     Serializable,
+    Snapshot,
 }
 
 /// Indicator that the following statements should be committed or rolled back 
atomically
diff --git a/datafusion/sql/src/expr/identifier.rs 
b/datafusion/sql/src/expr/identifier.rs
index 130cdf083d..ab5c550691 100644
--- a/datafusion/sql/src/expr/identifier.rs
+++ b/datafusion/sql/src/expr/identifier.rs
@@ -92,7 +92,7 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
         }
     }
 
-    pub(super) fn sql_compound_identifier_to_expr(
+    pub(crate) fn sql_compound_identifier_to_expr(
         &self,
         ids: Vec<Ident>,
         schema: &DFSchema,
diff --git a/datafusion/sql/src/expr/mod.rs b/datafusion/sql/src/expr/mod.rs
index 951e81c1fd..de753da895 100644
--- a/datafusion/sql/src/expr/mod.rs
+++ b/datafusion/sql/src/expr/mod.rs
@@ -21,14 +21,14 @@ use datafusion_expr::planner::{
     PlannerResult, RawBinaryExpr, RawDictionaryExpr, RawFieldAccessExpr,
 };
 use sqlparser::ast::{
-    BinaryOperator, CastFormat, CastKind, DataType as SQLDataType, 
DictionaryField,
-    Expr as SQLExpr, ExprWithAlias as SQLExprWithAlias, MapEntry, StructField, 
Subscript,
-    TrimWhereField, Value,
+    AccessExpr, BinaryOperator, CastFormat, CastKind, DataType as SQLDataType,
+    DictionaryField, Expr as SQLExpr, ExprWithAlias as SQLExprWithAlias, 
MapEntry,
+    StructField, Subscript, TrimWhereField, Value,
 };
 
 use datafusion_common::{
-    internal_datafusion_err, internal_err, not_impl_err, plan_err, DFSchema, 
Result,
-    ScalarValue,
+    internal_datafusion_err, internal_err, not_impl_err, plan_err, Column, 
DFSchema,
+    Result, ScalarValue,
 };
 
 use datafusion_expr::expr::ScalarFunction;
@@ -238,14 +238,14 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
                 self.sql_identifier_to_expr(id, schema, planner_context)
             }
 
-            SQLExpr::MapAccess { .. } => {
-                not_impl_err!("Map Access")
-            }
-
             // <expr>["foo"], <expr>[4] or <expr>[4:5]
-            SQLExpr::Subscript { expr, subscript } => {
-                self.sql_subscript_to_expr(*expr, subscript, schema, 
planner_context)
-            }
+            SQLExpr::CompoundFieldAccess { root, access_chain } => self
+                .sql_compound_field_access_to_expr(
+                    *root,
+                    access_chain,
+                    schema,
+                    planner_context,
+                ),
 
             SQLExpr::CompoundIdentifier(ids) => {
                 self.sql_compound_identifier_to_expr(ids, schema, 
planner_context)
@@ -982,84 +982,146 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
         Ok(Expr::Cast(Cast::new(Box::new(expr), dt)))
     }
 
-    fn sql_subscript_to_expr(
+    fn sql_compound_field_access_to_expr(
         &self,
-        expr: SQLExpr,
-        subscript: Box<Subscript>,
+        root: SQLExpr,
+        access_chain: Vec<AccessExpr>,
         schema: &DFSchema,
         planner_context: &mut PlannerContext,
     ) -> Result<Expr> {
-        let expr = self.sql_expr_to_logical_expr(expr, schema, 
planner_context)?;
-
-        let field_access = match *subscript {
-            Subscript::Index { index } => {
-                // index can be a name, in which case it is a named field 
access
-                match index {
-                    SQLExpr::Value(
-                        Value::SingleQuotedString(s) | 
Value::DoubleQuotedString(s),
-                    ) => GetFieldAccess::NamedStructField {
-                        name: ScalarValue::from(s),
-                    },
-                    SQLExpr::JsonAccess { .. } => {
-                        return not_impl_err!("JsonAccess");
+        let mut root = self.sql_expr_to_logical_expr(root, schema, 
planner_context)?;
+        let fields = access_chain
+            .into_iter()
+            .map(|field| match field {
+                AccessExpr::Subscript(subscript) => {
+                    match subscript {
+                        Subscript::Index { index } => {
+                            // index can be a name, in which case it is a 
named field access
+                            match index {
+                                SQLExpr::Value(
+                                    Value::SingleQuotedString(s)
+                                    | Value::DoubleQuotedString(s),
+                                ) => Ok(Some(GetFieldAccess::NamedStructField {
+                                    name: ScalarValue::from(s),
+                                })),
+                                SQLExpr::JsonAccess { .. } => {
+                                    not_impl_err!("JsonAccess")
+                                }
+                                // otherwise treat like a list index
+                                _ => Ok(Some(GetFieldAccess::ListIndex {
+                                    key: 
Box::new(self.sql_expr_to_logical_expr(
+                                        index,
+                                        schema,
+                                        planner_context,
+                                    )?),
+                                })),
+                            }
+                        }
+                        Subscript::Slice {
+                            lower_bound,
+                            upper_bound,
+                            stride,
+                        } => {
+                            // Means access like [:2]
+                            let lower_bound = if let Some(lower_bound) = 
lower_bound {
+                                self.sql_expr_to_logical_expr(
+                                    lower_bound,
+                                    schema,
+                                    planner_context,
+                                )
+                            } else {
+                                not_impl_err!("Slice subscript requires a 
lower bound")
+                            }?;
+
+                            // means access like [2:]
+                            let upper_bound = if let Some(upper_bound) = 
upper_bound {
+                                self.sql_expr_to_logical_expr(
+                                    upper_bound,
+                                    schema,
+                                    planner_context,
+                                )
+                            } else {
+                                not_impl_err!("Slice subscript requires an 
upper bound")
+                            }?;
+
+                            // stride, default to 1
+                            let stride = if let Some(stride) = stride {
+                                self.sql_expr_to_logical_expr(
+                                    stride,
+                                    schema,
+                                    planner_context,
+                                )?
+                            } else {
+                                lit(1i64)
+                            };
+
+                            Ok(Some(GetFieldAccess::ListRange {
+                                start: Box::new(lower_bound),
+                                stop: Box::new(upper_bound),
+                                stride: Box::new(stride),
+                            }))
+                        }
                     }
-                    // otherwise treat like a list index
-                    _ => GetFieldAccess::ListIndex {
-                        key: Box::new(self.sql_expr_to_logical_expr(
-                            index,
-                            schema,
-                            planner_context,
-                        )?),
-                    },
                 }
-            }
-            Subscript::Slice {
-                lower_bound,
-                upper_bound,
-                stride,
-            } => {
-                // Means access like [:2]
-                let lower_bound = if let Some(lower_bound) = lower_bound {
-                    self.sql_expr_to_logical_expr(lower_bound, schema, 
planner_context)
-                } else {
-                    not_impl_err!("Slice subscript requires a lower bound")
-                }?;
-
-                // means access like [2:]
-                let upper_bound = if let Some(upper_bound) = upper_bound {
-                    self.sql_expr_to_logical_expr(upper_bound, schema, 
planner_context)
-                } else {
-                    not_impl_err!("Slice subscript requires an upper bound")
-                }?;
-
-                // stride, default to 1
-                let stride = if let Some(stride) = stride {
-                    self.sql_expr_to_logical_expr(stride, schema, 
planner_context)?
-                } else {
-                    lit(1i64)
-                };
-
-                GetFieldAccess::ListRange {
-                    start: Box::new(lower_bound),
-                    stop: Box::new(upper_bound),
-                    stride: Box::new(stride),
+                AccessExpr::Dot(expr) => {
+                    let expr =
+                        self.sql_expr_to_logical_expr(expr, schema, 
planner_context)?;
+                    match expr {
+                        Expr::Column(Column {
+                            name,
+                            relation,
+                            spans,
+                        }) => {
+                            if let Some(relation) = &relation {
+                                // If the first part of the dot access is a 
column reference, we should
+                                // check if the column is from the same table 
as the root expression.
+                                // If it is, we should replace the root 
expression with the column reference.
+                                // Otherwise, we should treat the dot access 
as a named field access.
+                                if relation.table() == 
root.schema_name().to_string() {
+                                    root = Expr::Column(Column {
+                                        name,
+                                        relation: Some(relation.clone()),
+                                        spans,
+                                    });
+                                    Ok(None)
+                                } else {
+                                    plan_err!(
+                                        "table name mismatch: {} != {}",
+                                        relation.table(),
+                                        root.schema_name()
+                                    )
+                                }
+                            } else {
+                                Ok(Some(GetFieldAccess::NamedStructField {
+                                    name: ScalarValue::from(name),
+                                }))
+                            }
+                        }
+                        _ => not_impl_err!(
+                            "Dot access not supported for non-column expr: 
{expr:?}"
+                        ),
+                    }
                 }
-            }
-        };
+            })
+            .collect::<Result<Vec<_>>>()?;
 
-        let mut field_access_expr = RawFieldAccessExpr { expr, field_access };
-        for planner in self.context_provider.get_expr_planners() {
-            match planner.plan_field_access(field_access_expr, schema)? {
-                PlannerResult::Planned(expr) => return Ok(expr),
-                PlannerResult::Original(expr) => {
-                    field_access_expr = expr;
+        fields
+            .into_iter()
+            .flatten()
+            .try_fold(root, |expr, field_access| {
+                let mut field_access_expr = RawFieldAccessExpr { expr, 
field_access };
+                for planner in self.context_provider.get_expr_planners() {
+                    match planner.plan_field_access(field_access_expr, 
schema)? {
+                        PlannerResult::Planned(expr) => return Ok(expr),
+                        PlannerResult::Original(expr) => {
+                            field_access_expr = expr;
+                        }
+                    }
                 }
-            }
-        }
-
-        not_impl_err!(
-            "GetFieldAccess not supported by ExprPlanner: 
{field_access_expr:?}"
-        )
+                not_impl_err!(
+                    "GetFieldAccess not supported by ExprPlanner: 
{field_access_expr:?}"
+                )
+            })
     }
 }
 
diff --git a/datafusion/sql/src/parser.rs b/datafusion/sql/src/parser.rs
index cc42ec1bf3..9725166b8a 100644
--- a/datafusion/sql/src/parser.rs
+++ b/datafusion/sql/src/parser.rs
@@ -571,7 +571,7 @@ impl<'a> DFParser<'a> {
 
         loop {
             if let Token::Word(_) = self.parser.peek_token().token {
-                let identifier = self.parser.parse_identifier(false)?;
+                let identifier = self.parser.parse_identifier()?;
                 partitions.push(identifier.to_string());
             } else {
                 return self.expected("partition name", 
self.parser.peek_token());
@@ -674,7 +674,7 @@ impl<'a> DFParser<'a> {
     }
 
     fn parse_column_def(&mut self) -> Result<ColumnDef, ParserError> {
-        let name = self.parser.parse_identifier(false)?;
+        let name = self.parser.parse_identifier()?;
         let data_type = self.parser.parse_data_type()?;
         let collation = if self.parser.parse_keyword(Keyword::COLLATE) {
             Some(self.parser.parse_object_name(false)?)
@@ -684,7 +684,7 @@ impl<'a> DFParser<'a> {
         let mut options = vec![];
         loop {
             if self.parser.parse_keyword(Keyword::CONSTRAINT) {
-                let name = Some(self.parser.parse_identifier(false)?);
+                let name = Some(self.parser.parse_identifier()?);
                 if let Some(option) = 
self.parser.parse_optional_column_option()? {
                     options.push(ColumnOptionDef { name, option });
                 } else {
diff --git a/datafusion/sql/src/planner.rs b/datafusion/sql/src/planner.rs
index f22ff6d94f..85d428cae8 100644
--- a/datafusion/sql/src/planner.rs
+++ b/datafusion/sql/src/planner.rs
@@ -451,7 +451,10 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> {
             SQLDataType::UnsignedBigInt(_) | SQLDataType::UnsignedInt8(_) => 
Ok(DataType::UInt64),
             SQLDataType::Float(_) => Ok(DataType::Float32),
             SQLDataType::Real | SQLDataType::Float4 => Ok(DataType::Float32),
-            SQLDataType::Double | SQLDataType::DoublePrecision | 
SQLDataType::Float8 => Ok(DataType::Float64),
+            SQLDataType::Double(ExactNumberInfo::None) | 
SQLDataType::DoublePrecision | SQLDataType::Float8 => Ok(DataType::Float64),
+            
SQLDataType::Double(ExactNumberInfo::Precision(_)|ExactNumberInfo::PrecisionAndScale(_,
 _)) => {
+                not_impl_err!("Unsupported SQL type (precision/scale not 
supported) {sql_type}")
+            }
             SQLDataType::Char(_)
             | SQLDataType::Text
             | SQLDataType::String(_) => Ok(DataType::Utf8),
@@ -587,7 +590,9 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> {
             | SQLDataType::MediumText
             | SQLDataType::LongText
             | SQLDataType::Bit(_)
-            |SQLDataType::BitVarying(_)
+            | SQLDataType::BitVarying(_)
+            // BigQuery UDFs
+            | SQLDataType::AnyType
             => not_impl_err!(
                 "Unsupported SQL type {sql_type:?}"
             ),
diff --git a/datafusion/sql/src/relation/join.rs 
b/datafusion/sql/src/relation/join.rs
index 75f39792bc..88665401dc 100644
--- a/datafusion/sql/src/relation/join.rs
+++ b/datafusion/sql/src/relation/join.rs
@@ -18,7 +18,9 @@
 use crate::planner::{ContextProvider, PlannerContext, SqlToRel};
 use datafusion_common::{not_impl_err, Column, Result};
 use datafusion_expr::{JoinType, LogicalPlan, LogicalPlanBuilder};
-use sqlparser::ast::{Join, JoinConstraint, JoinOperator, TableFactor, 
TableWithJoins};
+use sqlparser::ast::{
+    Join, JoinConstraint, JoinOperator, ObjectName, TableFactor, 
TableWithJoins,
+};
 use std::collections::HashSet;
 
 impl<S: ContextProvider> SqlToRel<'_, S> {
@@ -123,11 +125,22 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
                     .join_on(right, join_type, Some(expr))?
                     .build()
             }
-            JoinConstraint::Using(idents) => {
-                let keys: Vec<Column> = idents
+            JoinConstraint::Using(object_names) => {
+                let keys = object_names
                     .into_iter()
-                    .map(|x| 
Column::from_name(self.ident_normalizer.normalize(x)))
-                    .collect();
+                    .map(|object_name| {
+                        let ObjectName(mut object_names) = object_name;
+                        if object_names.len() != 1 {
+                            not_impl_err!(
+                                "Invalid identifier in USING clause. Expected 
single identifier, got {}", ObjectName(object_names)
+                            )
+                        } else {
+                            let id = object_names.swap_remove(0);
+                            Ok(self.ident_normalizer.normalize(id))
+                        }
+                    })
+                    .collect::<Result<Vec<_>>>()?;
+
                 LogicalPlanBuilder::from(left)
                     .join_using(right, join_type, keys)?
                     .build()
diff --git a/datafusion/sql/src/set_expr.rs b/datafusion/sql/src/set_expr.rs
index 290c017478..3ddbe373ec 100644
--- a/datafusion/sql/src/set_expr.rs
+++ b/datafusion/sql/src/set_expr.rs
@@ -133,6 +133,9 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
             (SetOperator::Except, false) => {
                 LogicalPlanBuilder::except(left_plan, right_plan, false)
             }
+            (SetOperator::Minus, _) => {
+                not_impl_err!("MINUS Set Operator not implemented")
+            }
         }
     }
 }
diff --git a/datafusion/sql/src/statement.rs b/datafusion/sql/src/statement.rs
index dfd3a4fd76..83c91ecde6 100644
--- a/datafusion/sql/src/statement.rs
+++ b/datafusion/sql/src/statement.rs
@@ -56,7 +56,7 @@ use datafusion_expr::{
 };
 use sqlparser::ast::{
     self, BeginTransactionKind, NullsDistinctOption, ShowStatementIn,
-    ShowStatementOptions, SqliteOnConflict,
+    ShowStatementOptions, SqliteOnConflict, TableObject, UpdateTableFromKind,
 };
 use sqlparser::ast::{
     Assignment, AssignmentTarget, ColumnDef, CreateIndex, CreateTable,
@@ -497,6 +497,7 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
                 if_not_exists,
                 temporary,
                 to,
+                params,
             } => {
                 if materialized {
                     return not_impl_err!("Materialized views not supported")?;
@@ -532,6 +533,7 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
                     if_not_exists,
                     temporary,
                     to,
+                    params,
                 };
                 let sql = stmt.to_string();
                 let Statement::CreateView {
@@ -818,7 +820,6 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
             Statement::Insert(Insert {
                 or,
                 into,
-                table_name,
                 columns,
                 overwrite,
                 source,
@@ -832,7 +833,17 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
                 mut replace_into,
                 priority,
                 insert_alias,
+                assignments,
+                has_table_keyword,
+                settings,
+                format_clause,
             }) => {
+                let table_name = match table {
+                    TableObject::TableName(table_name) => table_name,
+                    TableObject::TableFunction(_) => {
+                        return not_impl_err!("INSERT INTO Table functions not 
supported")
+                    }
+                };
                 if let Some(or) = or {
                     match or {
                         SqliteOnConflict::Replace => replace_into = true,
@@ -845,9 +856,6 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
                 if !after_columns.is_empty() {
                     plan_err!("After-columns clause not supported")?;
                 }
-                if table {
-                    plan_err!("Table clause not supported")?;
-                }
                 if on.is_some() {
                     plan_err!("Insert-on clause not supported")?;
                 }
@@ -873,7 +881,18 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
                 if insert_alias.is_some() {
                     plan_err!("Inserts with an alias not supported")?;
                 }
-                let _ = into; // optional keyword doesn't change behavior
+                if !assignments.is_empty() {
+                    plan_err!("Inserts with assignments not supported")?;
+                }
+                if settings.is_some() {
+                    plan_err!("Inserts with settings not supported")?;
+                }
+                if format_clause.is_some() {
+                    plan_err!("Inserts with format clause not supported")?;
+                }
+                // optional keywords don't change behavior
+                let _ = into;
+                let _ = has_table_keyword;
                 self.insert_to_plan(table_name, columns, source, overwrite, 
replace_into)
             }
             Statement::Update {
@@ -884,6 +903,11 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
                 returning,
                 or,
             } => {
+                let from =
+                    from.map(|update_table_from_kind| match 
update_table_from_kind {
+                        UpdateTableFromKind::BeforeSet(from) => from,
+                        UpdateTableFromKind::AfterSet(from) => from,
+                    });
                 if returning.is_some() {
                     plan_err!("Update-returning clause not yet supported")?;
                 }
@@ -969,6 +993,9 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
                     ast::TransactionIsolationLevel::Serializable => {
                         TransactionIsolationLevel::Serializable
                     }
+                    ast::TransactionIsolationLevel::Snapshot => {
+                        TransactionIsolationLevel::Snapshot
+                    }
                 };
                 let access_mode = match access_mode {
                     ast::TransactionAccessMode::ReadOnly => {
@@ -984,7 +1011,17 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
                 });
                 Ok(LogicalPlan::Statement(statement))
             }
-            Statement::Commit { chain } => {
+            Statement::Commit {
+                chain,
+                end,
+                modifier,
+            } => {
+                if end {
+                    return not_impl_err!("COMMIT AND END not supported");
+                };
+                if let Some(modifier) = modifier {
+                    return not_impl_err!("COMMIT {modifier} not supported");
+                };
                 let statement = PlanStatement::TransactionEnd(TransactionEnd {
                     conclusion: TransactionConclusion::Commit,
                     chain,
diff --git a/datafusion/sql/src/unparser/ast.rs 
b/datafusion/sql/src/unparser/ast.rs
index e320a4510e..6d77c01ea8 100644
--- a/datafusion/sql/src/unparser/ast.rs
+++ b/datafusion/sql/src/unparser/ast.rs
@@ -466,6 +466,7 @@ impl TableRelationBuilder {
             partitions: self.partitions.clone(),
             with_ordinality: false,
             json_path: None,
+            sample: None,
         })
     }
     fn create_empty() -> Self {
diff --git a/datafusion/sql/src/unparser/dialect.rs 
b/datafusion/sql/src/unparser/dialect.rs
index 830435fd01..adfb7a0d0c 100644
--- a/datafusion/sql/src/unparser/dialect.rs
+++ b/datafusion/sql/src/unparser/dialect.rs
@@ -17,6 +17,7 @@
 
 use std::{collections::HashMap, sync::Arc};
 
+use super::{utils::character_length_to_sql, utils::date_part_to_sql, Unparser};
 use arrow_schema::TimeUnit;
 use datafusion_common::Result;
 use datafusion_expr::Expr;
@@ -29,8 +30,6 @@ use sqlparser::{
     keywords::ALL_KEYWORDS,
 };
 
-use super::{utils::character_length_to_sql, utils::date_part_to_sql, Unparser};
-
 pub type ScalarFnToSqlHandler =
     Box<dyn Fn(&Unparser, &[Expr]) -> Result<Option<ast::Expr>> + Send + Sync>;
 
@@ -65,7 +64,7 @@ pub trait Dialect: Send + Sync {
     /// Does the dialect use DOUBLE PRECISION to represent Float64 rather than 
DOUBLE?
     /// E.g. Postgres uses DOUBLE PRECISION instead of DOUBLE
     fn float64_ast_dtype(&self) -> ast::DataType {
-        ast::DataType::Double
+        ast::DataType::Double(ast::ExactNumberInfo::None)
     }
 
     /// The SQL type to use for Arrow Utf8 unparsing
@@ -526,7 +525,7 @@ impl Default for CustomDialect {
             supports_nulls_first_in_sort: true,
             use_timestamp_for_date64: false,
             interval_style: IntervalStyle::SQLStandard,
-            float64_ast_dtype: ast::DataType::Double,
+            float64_ast_dtype: 
ast::DataType::Double(ast::ExactNumberInfo::None),
             utf8_cast_dtype: ast::DataType::Varchar(None),
             large_utf8_cast_dtype: ast::DataType::Text,
             date_field_extract_style: DateFieldExtractStyle::DatePart,
@@ -718,7 +717,7 @@ impl CustomDialectBuilder {
             supports_nulls_first_in_sort: true,
             use_timestamp_for_date64: false,
             interval_style: IntervalStyle::PostgresVerbose,
-            float64_ast_dtype: ast::DataType::Double,
+            float64_ast_dtype: 
ast::DataType::Double(ast::ExactNumberInfo::None),
             utf8_cast_dtype: ast::DataType::Varchar(None),
             large_utf8_cast_dtype: ast::DataType::Text,
             date_field_extract_style: DateFieldExtractStyle::DatePart,
diff --git a/datafusion/sql/src/unparser/expr.rs 
b/datafusion/sql/src/unparser/expr.rs
index 5fad68cf46..51ae73e03e 100644
--- a/datafusion/sql/src/unparser/expr.rs
+++ b/datafusion/sql/src/unparser/expr.rs
@@ -544,9 +544,9 @@ impl Unparser<'_> {
         }
         let array = self.expr_to_sql(&args[0])?;
         let index = self.expr_to_sql(&args[1])?;
-        Ok(ast::Expr::Subscript {
-            expr: Box::new(array),
-            subscript: Box::new(Subscript::Index { index }),
+        Ok(ast::Expr::CompoundFieldAccess {
+            root: Box::new(array),
+            access_chain: vec![ast::AccessExpr::Subscript(Subscript::Index { 
index })],
         })
     }
 
@@ -1667,6 +1667,7 @@ mod tests {
     use datafusion_functions_nested::map::map;
     use datafusion_functions_window::rank::rank_udwf;
     use datafusion_functions_window::row_number::row_number_udwf;
+    use sqlparser::ast::ExactNumberInfo;
 
     use crate::unparser::dialect::{
         CharacterLengthStyle, CustomDialect, CustomDialectBuilder, 
DateFieldExtractStyle,
@@ -2184,7 +2185,7 @@ mod tests {
     #[test]
     fn custom_dialect_float64_ast_dtype() -> Result<()> {
         for (float64_ast_dtype, identifier) in [
-            (ast::DataType::Double, "DOUBLE"),
+            (ast::DataType::Double(ExactNumberInfo::None), "DOUBLE"),
             (ast::DataType::DoublePrecision, "DOUBLE PRECISION"),
         ] {
             let dialect = CustomDialectBuilder::new()
diff --git a/datafusion/sql/src/unparser/plan.rs 
b/datafusion/sql/src/unparser/plan.rs
index 368131751e..0fa203c60b 100644
--- a/datafusion/sql/src/unparser/plan.rs
+++ b/datafusion/sql/src/unparser/plan.rs
@@ -1089,7 +1089,7 @@ impl Unparser<'_> {
         &self,
         join_conditions: &[(Expr, Expr)],
     ) -> Option<ast::JoinConstraint> {
-        let mut idents = Vec::with_capacity(join_conditions.len());
+        let mut object_names = Vec::with_capacity(join_conditions.len());
         for (left, right) in join_conditions {
             match (left, right) {
                 (
@@ -1104,14 +1104,18 @@ impl Unparser<'_> {
                         spans: _,
                     }),
                 ) if left_name == right_name => {
-                    
idents.push(self.new_ident_quoted_if_needs(left_name.to_string()));
+                    // For example, if the join condition `t1.id = t2.id`
+                    // this is represented as two columns like `[t1.id, t2.id]`
+                    // This code forms `id` (without relation name)
+                    let ident = 
self.new_ident_quoted_if_needs(left_name.to_string());
+                    object_names.push(ast::ObjectName(vec![ident]));
                 }
                 // USING is only valid with matching column names; arbitrary 
expressions
                 // are not allowed
                 _ => return None,
             }
         }
-        Some(ast::JoinConstraint::Using(idents))
+        Some(ast::JoinConstraint::Using(object_names))
     }
 
     /// Convert a join constraint and associated conditions and filter to a 
SQL AST node
diff --git a/datafusion/sql/tests/sql_integration.rs 
b/datafusion/sql/tests/sql_integration.rs
index b9502c1520..6a0db3888f 100644
--- a/datafusion/sql/tests/sql_integration.rs
+++ b/datafusion/sql/tests/sql_integration.rs
@@ -3673,7 +3673,7 @@ fn test_non_prepare_statement_should_infer_types() {
 
 #[test]
 #[should_panic(
-    expected = "Expected: [NOT] NULL or TRUE|FALSE or [NOT] DISTINCT FROM 
after IS, found: $1"
+    expected = "Expected: [NOT] NULL | TRUE | FALSE | DISTINCT | [form] 
NORMALIZED FROM after IS, found: $1"
 )]
 fn test_prepare_statement_to_plan_panic_is_param() {
     let sql = "PREPARE my_plan(INT) AS SELECT id, age  FROM person WHERE age 
is $1";
diff --git a/datafusion/sqllogictest/test_files/interval.slt 
b/datafusion/sqllogictest/test_files/interval.slt
index db453adf12..1ef3048ddc 100644
--- a/datafusion/sqllogictest/test_files/interval.slt
+++ b/datafusion/sqllogictest/test_files/interval.slt
@@ -25,27 +25,10 @@ select
 Interval(MonthDayNano) Interval(MonthDayNano)
 
 
-## This is incredibly confusing but document it in tests:
-#
-# years is parsed as a column name
-# year is parsed as part of the interval type.
-#
-# postgres=# select interval '5' year;
-#  interval
-# ----------
-#  5 years
-# (1 row)
-#
-# postgres=# select interval '5' years;
-#   years
-# ----------
-#  00:00:05
-# (1 row)
 query ?
 select interval '5' years
 ----
-5.000000000 secs
-
+60 mons
 
 # check all different kinds of intervals
 query ?
@@ -61,7 +44,7 @@ select interval '5' month
 query ?
 select interval '5' months
 ----
-5.000000000 secs
+5 mons
 
 query ?
 select interval '5' week
@@ -83,7 +66,7 @@ select interval '5' hour
 query ?
 select interval '5' hours
 ----
-5.000000000 secs
+5 hours
 
 query ?
 select interval '5' minute
diff --git a/datafusion/sqllogictest/test_files/joins.slt 
b/datafusion/sqllogictest/test_files/joins.slt
index ac02aeb6fe..f16b2dbb2d 100644
--- a/datafusion/sqllogictest/test_files/joins.slt
+++ b/datafusion/sqllogictest/test_files/joins.slt
@@ -2623,6 +2623,10 @@ SELECT t1.c1, t2.c2 FROM test_partition_table t1 JOIN 
test_partition_table t2 US
 0 9
 0 10
 
+# left_join_using_qualified (snowflake syntax)
+query error DataFusion error: This feature is not implemented: Invalid 
identifier in USING clause\. Expected single identifier, got t2\.c2
+SELECT t1.c1, t2.c2 FROM test_partition_table t1 JOIN test_partition_table t2 
USING (t2.c2) ORDER BY t2.c2;
+
 # left_join_using_join_key_projection
 query III
 SELECT t1.c1, t1.c2, t2.c2 FROM test_partition_table t1 JOIN 
test_partition_table t2 USING (c2) ORDER BY t2.c2


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to