Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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))]
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
5 changes: 5 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/oracle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,8 @@ impl Dialect for OracleDialect {
fn supports_comment_optimizer_hint(&self) -> bool {
true
}

fn supports_insert_table_alias(&self) -> bool {
true
}
}
4 changes: 4 additions & 0 deletions src/dialect/postgresql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,4 +288,8 @@ impl Dialect for PostgreSqlDialect {
fn supports_interval_options(&self) -> bool {
true
}

fn supports_insert_table_alias(&self) -> bool {
true
}
}
33 changes: 21 additions & 12 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17229,12 +17229,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);

Expand Down Expand Up @@ -19475,14 +19490,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
108 changes: 107 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 @@ -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(_)))
));
}
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
Loading