Skip to content
Open
17 changes: 12 additions & 5 deletions src/ast/dml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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()
};
Expand Down
11 changes: 11 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6456,6 +6456,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))]
Expand Down
2 changes: 1 addition & 1 deletion src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
Expand Down
11 changes: 11 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,17 @@ pub trait Dialect: Debug + Any {
false
}

/// Returns true if this dialect supports `INSERT INTO t AS alias ...`.
fn supports_insert_table_explicit_alias(&self) -> bool {
false
}

/// Returns true if this dialect supports `INSERT INTO t alias ...` with
/// `alias` _not_ preceded by the "AS" keyword.
fn supports_insert_table_implicit_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 {
Expand Down
7 changes: 7 additions & 0 deletions src/dialect/oracle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,11 @@ impl Dialect for OracleDialect {
fn supports_comment_optimizer_hint(&self) -> bool {
true
}

/// Supports insert table aliases (but with no preceding "AS" keyword)
///
/// See <https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/INSERT.html#GUID-903F8043-0254-4EE9-ACC1-CB8AC0AF3423__GUID-AC239E95-0DD5-4F4B-A849-55C87840D0E6>
fn supports_insert_table_implicit_alias(&self) -> bool {
true
}
}
7 changes: 7 additions & 0 deletions src/dialect/postgresql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,4 +288,11 @@ impl Dialect for PostgreSqlDialect {
fn supports_interval_options(&self) -> bool {
true
}

/// [Postgres] supports insert table aliases with an explicit "AS" keyword.
///
/// [Postgres]: https://www.postgresql.org/docs/17/sql-insert.html
fn supports_insert_table_explicit_alias(&self) -> bool {
true
}
}
35 changes: 22 additions & 13 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17166,12 +17166,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()?)
} else {
None
};
let table_alias = if self.dialect.supports_insert_table_implicit_alias()
&& !self.peek_sub_query()
&& self
.peek_one_of_keywords(&[Keyword::AS, Keyword::DEFAULT, Keyword::VALUES])
.is_none()
{
self.maybe_parse(|parser| parser.parse_identifier())?
.map(|alias| TableAliasWithoutColumns {
explicit: false,
alias,
})
} else if self.dialect.supports_insert_table_explicit_alias()
&& self.parse_keyword(Keyword::AS)
{
Some(TableAliasWithoutColumns {
explicit: true,
alias: self.parse_identifier()?,
})
} else {
None
};

let is_mysql = dialect_of!(self is MySqlDialect);

Expand Down Expand Up @@ -19412,14 +19427,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> {
Expand Down
118 changes: 117 additions & 1 deletion tests/sqlparser_oracle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -414,3 +417,116 @@ 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 AS
let sql = "INSERT INTO AS 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);
dbg!(&stmt);
assert!(matches!(
stmt,
Statement::Insert(Insert {
table_alias: None,
source: Some(source),
..
})
if matches!(&*source, Query { body, .. } if matches!(&**body, SetExpr::Values(_)))
));
}
33 changes: 21 additions & 12 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down