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

github-bot pushed a commit to branch 
gh-readonly-queue/main/pr-2214-36e8ce602d75ebe607ec45f065cfb529f9e1ec5b
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git

commit 0b589b2555be7a29879220744124ec7b350e6bbf
Author: xitep <[email protected]>
AuthorDate: Mon Feb 23 15:06:31 2026 +0100

    [Oracle] Table alias for INSERTed table (#2214)
    
    Co-authored-by: Ifeanyi Ubah <[email protected]>
---
 src/ast/dml.rs              |  17 +++++--
 src/ast/mod.rs              |  11 +++++
 src/ast/spans.rs            |   2 +-
 src/dialect/mod.rs          |   5 ++
 src/dialect/oracle.rs       |   4 ++
 src/dialect/postgresql.rs   |   4 ++
 src/parser/mod.rs           |  33 +++++++++-----
 tests/sqlparser_oracle.rs   | 108 +++++++++++++++++++++++++++++++++++++++++++-
 tests/sqlparser_postgres.rs |  33 +++++++++-----
 9 files changed, 186 insertions(+), 31 deletions(-)

diff --git a/src/ast/dml.rs b/src/ast/dml.rs
index a0be916d..06f731c5 100644
--- a/src/ast/dml.rs
+++ b/src/ast/dml.rs
@@ -33,7 +33,8 @@ use super::{
     display_comma_separated, helpers::attached_token::AttachedToken, 
query::InputFormatClause,
     Assignment, Expr, FromTable, Ident, InsertAliases, MysqlInsertPriority, 
ObjectName, OnInsert,
     OptimizerHint, OrderByExpr, Query, SelectInto, SelectItem, Setting, 
SqliteOnConflict,
-    TableFactor, TableObject, TableWithJoins, UpdateTableFromKind, Values,
+    TableAliasWithoutColumns, TableFactor, TableObject, TableWithJoins, 
UpdateTableFromKind,
+    Values,
 };
 
 /// INSERT statement.
@@ -56,8 +57,9 @@ pub struct Insert {
     pub into: bool,
     /// TABLE
     pub table: TableObject,
-    /// table_name as foo (for PostgreSQL)
-    pub table_alias: Option<Ident>,
+    /// `table_name as foo` (for PostgreSQL)
+    /// `table_name foo` (for Oracle)
+    pub table_alias: Option<TableAliasWithoutColumns>,
     /// COLUMNS
     pub columns: Vec<Ident>,
     /// Overwrite (Hive)
@@ -125,8 +127,13 @@ pub struct Insert {
 impl Display for Insert {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         // SQLite OR conflict has a special format: INSERT OR ... INTO 
table_name
-        let table_name = if let Some(alias) = &self.table_alias {
-            format!("{0} AS {alias}", self.table)
+        let table_name = if let Some(table_alias) = &self.table_alias {
+            format!(
+                "{table} {as_keyword}{alias}",
+                table = self.table,
+                as_keyword = if table_alias.explicit { "AS " } else { "" },
+                alias = table_alias.alias
+            )
         } else {
             self.table.to_string()
         };
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index 61b0f65b..7b1e9447 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -6480,6 +6480,17 @@ pub struct InsertAliases {
     pub col_aliases: Option<Vec<Ident>>,
 }
 
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+/// Optional alias for an `INSERT` table; i.e. the table to be inserted into
+pub struct TableAliasWithoutColumns {
+    /// `true` if the aliases was explicitly introduced with the "AS" keyword
+    pub explicit: bool,
+    /// the alias name itself
+    pub alias: Ident,
+}
+
 #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index 128fe01b..b29a134b 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -1327,7 +1327,7 @@ impl Spanned for Insert {
         union_spans(
             core::iter::once(insert_token.0.span)
                 .chain(core::iter::once(table.span()))
-                .chain(table_alias.as_ref().map(|i| i.span))
+                .chain(table_alias.iter().map(|k| k.alias.span))
                 .chain(columns.iter().map(|i| i.span))
                 .chain(source.as_ref().map(|q| q.span()))
                 .chain(assignments.iter().map(|i| i.span()))
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs
index b1be1590..bcca455e 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -1240,6 +1240,11 @@ pub trait Dialect: Debug + Any {
         false
     }
 
+    /// Returns true if this dialect supports `INSERT INTO t [[AS] alias] ...`.
+    fn supports_insert_table_alias(&self) -> bool {
+        false
+    }
+
     /// Returns true if this dialect supports `SET` statements without an 
explicit
     /// assignment operator such as `=`. For example: `SET SHOWPLAN_XML ON`.
     fn supports_set_stmt_without_operator(&self) -> bool {
diff --git a/src/dialect/oracle.rs b/src/dialect/oracle.rs
index c2147eae..dce0493d 100644
--- a/src/dialect/oracle.rs
+++ b/src/dialect/oracle.rs
@@ -110,4 +110,8 @@ impl Dialect for OracleDialect {
     fn supports_comment_optimizer_hint(&self) -> bool {
         true
     }
+
+    fn supports_insert_table_alias(&self) -> bool {
+        true
+    }
 }
diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs
index b0511f6d..13bd82bf 100644
--- a/src/dialect/postgresql.rs
+++ b/src/dialect/postgresql.rs
@@ -288,4 +288,8 @@ impl Dialect for PostgreSqlDialect {
     fn supports_interval_options(&self) -> bool {
         true
     }
+
+    fn supports_insert_table_alias(&self) -> bool {
+        true
+    }
 }
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 0767e432..bb11d79c 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -17231,12 +17231,27 @@ impl<'a> Parser<'a> {
             let table = self.parse_keyword(Keyword::TABLE);
             let table_object = self.parse_table_object()?;
 
-            let table_alias =
-                if dialect_of!(self is PostgreSqlDialect) && 
self.parse_keyword(Keyword::AS) {
-                    Some(self.parse_identifier()?)
+            let table_alias = if self.dialect.supports_insert_table_alias()
+                && !self.peek_sub_query()
+                && self
+                    .peek_one_of_keywords(&[Keyword::DEFAULT, Keyword::VALUES])
+                    .is_none()
+            {
+                if self.parse_keyword(Keyword::AS) {
+                    Some(TableAliasWithoutColumns {
+                        explicit: true,
+                        alias: self.parse_identifier()?,
+                    })
                 } else {
-                    None
-                };
+                    self.maybe_parse(|parser| parser.parse_identifier())?
+                        .map(|alias| TableAliasWithoutColumns {
+                            explicit: false,
+                            alias,
+                        })
+                }
+            } else {
+                None
+            };
 
             let is_mysql = dialect_of!(self is MySqlDialect);
 
@@ -19477,14 +19492,8 @@ impl<'a> Parser<'a> {
 
     /// Returns true if the next keyword indicates a sub query, i.e. SELECT or 
WITH
     fn peek_sub_query(&mut self) -> bool {
-        if self
-            .parse_one_of_keywords(&[Keyword::SELECT, Keyword::WITH])
+        self.peek_one_of_keywords(&[Keyword::SELECT, Keyword::WITH])
             .is_some()
-        {
-            self.prev_token();
-            return true;
-        }
-        false
     }
 
     pub(crate) fn parse_show_stmt_options(&mut self) -> 
Result<ShowStatementOptions, ParserError> {
diff --git a/tests/sqlparser_oracle.rs b/tests/sqlparser_oracle.rs
index 8f7bb867..b34a9308 100644
--- a/tests/sqlparser_oracle.rs
+++ b/tests/sqlparser_oracle.rs
@@ -21,7 +21,10 @@
 use pretty_assertions::assert_eq;
 
 use sqlparser::{
-    ast::{BinaryOperator, Expr, Ident, QuoteDelimitedString, Value, 
ValueWithSpan},
+    ast::{
+        BinaryOperator, Expr, Ident, Insert, ObjectName, Query, 
QuoteDelimitedString, SetExpr,
+        Statement, TableAliasWithoutColumns, TableObject, Value, ValueWithSpan,
+    },
     dialect::OracleDialect,
     parser::ParserError,
     tokenizer::Span,
@@ -421,3 +424,106 @@ fn test_connect_by() {
           ORDER BY \"Employee\", \"Manager\", \"Pathlen\", \"Path\"",
     );
 }
+
+#[test]
+fn test_insert_with_table_alias() {
+    let oracle_dialect = oracle();
+
+    fn verify_table_name_with_alias(stmt: &Statement, exp_table_name: &str, 
exp_table_alias: &str) {
+        assert!(matches!(stmt,
+            Statement::Insert(Insert {
+                table: TableObject::TableName(table_name),
+                table_alias: Some(TableAliasWithoutColumns {
+                    explicit: false,
+                    alias: Ident {
+                        value: table_alias,
+                        quote_style: None,
+                        span: _
+                    }
+                }),
+                ..
+            })
+            if table_alias == exp_table_alias
+            && table_name == &ObjectName::from(vec![Ident {
+                value: exp_table_name.into(),
+                quote_style: None,
+                span: Span::empty(),
+            }])
+        ));
+    }
+
+    let stmt = oracle_dialect.verified_stmt(
+        "INSERT INTO foo_t t \
+         SELECT 1, 2, 3 FROM dual",
+    );
+    verify_table_name_with_alias(&stmt, "foo_t", "t");
+
+    let stmt = oracle_dialect.verified_stmt(
+        "INSERT INTO foo_t asdf (a, b, c) \
+         SELECT 1, 2, 3 FROM dual",
+    );
+    verify_table_name_with_alias(&stmt, "foo_t", "asdf");
+
+    let stmt = oracle_dialect.verified_stmt(
+        "INSERT INTO foo_t t (a, b, c) \
+         VALUES (1, 2, 3)",
+    );
+    verify_table_name_with_alias(&stmt, "foo_t", "t");
+
+    let stmt = oracle_dialect.verified_stmt(
+        "INSERT INTO foo_t t \
+         VALUES (1, 2, 3)",
+    );
+    verify_table_name_with_alias(&stmt, "foo_t", "t");
+}
+
+#[test]
+fn test_insert_without_alias() {
+    let oracle_dialect = oracle();
+
+    // check DEFAULT
+    let sql = "INSERT INTO t default SELECT 'a' FROM dual";
+    assert_eq!(
+        oracle_dialect.parse_sql_statements(sql),
+        Err(ParserError::ParserError(
+            "Expected: SELECT, VALUES, or a subquery in the query body, found: 
default".into()
+        ))
+    );
+
+    // check SELECT
+    let sql = "INSERT INTO t SELECT 'a' FROM dual";
+    let stmt = oracle_dialect.verified_stmt(sql);
+    assert!(matches!(
+        &stmt,
+        Statement::Insert(Insert {
+            table_alias: None,
+            source: Some(source),
+            ..
+        })
+        if matches!(&**source, Query { body, .. } if matches!(&**body, 
SetExpr::Select(_)))));
+
+    // check WITH
+    let sql = "INSERT INTO dual WITH w AS (SELECT 1 AS y FROM dual) SELECT y 
FROM w";
+    let stmt = oracle_dialect.verified_stmt(sql);
+    assert!(matches!(
+        &stmt,
+        Statement::Insert(Insert {
+            table_alias: None,
+            source: Some(source),
+            ..
+        })
+        if matches!(&**source, Query { body, .. } if matches!(&**body, 
SetExpr::Select(_)))));
+
+    // check VALUES
+    let sql = "INSERT INTO t VALUES (1)";
+    let stmt = oracle_dialect.verified_stmt(sql);
+    assert!(matches!(
+        stmt,
+        Statement::Insert(Insert {
+            table_alias: None,
+            source: Some(source),
+            ..
+        })
+        if matches!(&*source, Query { body, .. } if matches!(&**body, 
SetExpr::Values(_)))
+    ));
+}
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index 03517876..510f6ccc 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -5445,10 +5445,13 @@ fn test_simple_postgres_insert_with_alias() {
                 quote_style: None,
                 span: Span::empty(),
             }])),
-            table_alias: Some(Ident {
-                value: "test_table".to_string(),
-                quote_style: None,
-                span: Span::empty(),
+            table_alias: Some(TableAliasWithoutColumns {
+                explicit: true,
+                alias: Ident {
+                    value: "test_table".to_string(),
+                    quote_style: None,
+                    span: Span::empty(),
+                }
             }),
             columns: vec![
                 Ident {
@@ -5521,10 +5524,13 @@ fn test_simple_postgres_insert_with_alias() {
                 quote_style: None,
                 span: Span::empty(),
             }])),
-            table_alias: Some(Ident {
-                value: "test_table".to_string(),
-                quote_style: None,
-                span: Span::empty(),
+            table_alias: Some(TableAliasWithoutColumns {
+                explicit: true,
+                alias: Ident {
+                    value: "test_table".to_string(),
+                    quote_style: None,
+                    span: Span::empty(),
+                }
             }),
             columns: vec![
                 Ident {
@@ -5599,10 +5605,13 @@ fn test_simple_insert_with_quoted_alias() {
                 quote_style: None,
                 span: Span::empty(),
             }])),
-            table_alias: Some(Ident {
-                value: "Test_Table".to_string(),
-                quote_style: Some('"'),
-                span: Span::empty(),
+            table_alias: Some(TableAliasWithoutColumns {
+                explicit: true,
+                alias: Ident {
+                    value: "Test_Table".to_string(),
+                    quote_style: Some('"'),
+                    span: Span::empty(),
+                }
             }),
             columns: vec![
                 Ident {


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

Reply via email to