This is an automated email from the ASF dual-hosted git repository.
github-bot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
The following commit(s) were added to refs/heads/main by this push:
new 0b589b25 [Oracle] Table alias for INSERTed table (#2214)
0b589b25 is described below
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]