Skip to content

Commit 0b589b2

Browse files
xitepiffyio
andauthored
[Oracle] Table alias for INSERTed table (#2214)
Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
1 parent 36e8ce6 commit 0b589b2

File tree

9 files changed

+186
-31
lines changed

9 files changed

+186
-31
lines changed

src/ast/dml.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ use super::{
3333
display_comma_separated, helpers::attached_token::AttachedToken, query::InputFormatClause,
3434
Assignment, Expr, FromTable, Ident, InsertAliases, MysqlInsertPriority, ObjectName, OnInsert,
3535
OptimizerHint, OrderByExpr, Query, SelectInto, SelectItem, Setting, SqliteOnConflict,
36-
TableFactor, TableObject, TableWithJoins, UpdateTableFromKind, Values,
36+
TableAliasWithoutColumns, TableFactor, TableObject, TableWithJoins, UpdateTableFromKind,
37+
Values,
3738
};
3839

3940
/// INSERT statement.
@@ -56,8 +57,9 @@ pub struct Insert {
5657
pub into: bool,
5758
/// TABLE
5859
pub table: TableObject,
59-
/// table_name as foo (for PostgreSQL)
60-
pub table_alias: Option<Ident>,
60+
/// `table_name as foo` (for PostgreSQL)
61+
/// `table_name foo` (for Oracle)
62+
pub table_alias: Option<TableAliasWithoutColumns>,
6163
/// COLUMNS
6264
pub columns: Vec<Ident>,
6365
/// Overwrite (Hive)
@@ -125,8 +127,13 @@ pub struct Insert {
125127
impl Display for Insert {
126128
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127129
// SQLite OR conflict has a special format: INSERT OR ... INTO table_name
128-
let table_name = if let Some(alias) = &self.table_alias {
129-
format!("{0} AS {alias}", self.table)
130+
let table_name = if let Some(table_alias) = &self.table_alias {
131+
format!(
132+
"{table} {as_keyword}{alias}",
133+
table = self.table,
134+
as_keyword = if table_alias.explicit { "AS " } else { "" },
135+
alias = table_alias.alias
136+
)
130137
} else {
131138
self.table.to_string()
132139
};

src/ast/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6480,6 +6480,17 @@ pub struct InsertAliases {
64806480
pub col_aliases: Option<Vec<Ident>>,
64816481
}
64826482

6483+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
6484+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
6485+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
6486+
/// Optional alias for an `INSERT` table; i.e. the table to be inserted into
6487+
pub struct TableAliasWithoutColumns {
6488+
/// `true` if the aliases was explicitly introduced with the "AS" keyword
6489+
pub explicit: bool,
6490+
/// the alias name itself
6491+
pub alias: Ident,
6492+
}
6493+
64836494
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
64846495
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
64856496
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]

src/ast/spans.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1327,7 +1327,7 @@ impl Spanned for Insert {
13271327
union_spans(
13281328
core::iter::once(insert_token.0.span)
13291329
.chain(core::iter::once(table.span()))
1330-
.chain(table_alias.as_ref().map(|i| i.span))
1330+
.chain(table_alias.iter().map(|k| k.alias.span))
13311331
.chain(columns.iter().map(|i| i.span))
13321332
.chain(source.as_ref().map(|q| q.span()))
13331333
.chain(assignments.iter().map(|i| i.span()))

src/dialect/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,6 +1240,11 @@ pub trait Dialect: Debug + Any {
12401240
false
12411241
}
12421242

1243+
/// Returns true if this dialect supports `INSERT INTO t [[AS] alias] ...`.
1244+
fn supports_insert_table_alias(&self) -> bool {
1245+
false
1246+
}
1247+
12431248
/// Returns true if this dialect supports `SET` statements without an explicit
12441249
/// assignment operator such as `=`. For example: `SET SHOWPLAN_XML ON`.
12451250
fn supports_set_stmt_without_operator(&self) -> bool {

src/dialect/oracle.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,8 @@ impl Dialect for OracleDialect {
110110
fn supports_comment_optimizer_hint(&self) -> bool {
111111
true
112112
}
113+
114+
fn supports_insert_table_alias(&self) -> bool {
115+
true
116+
}
113117
}

src/dialect/postgresql.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,4 +288,8 @@ impl Dialect for PostgreSqlDialect {
288288
fn supports_interval_options(&self) -> bool {
289289
true
290290
}
291+
292+
fn supports_insert_table_alias(&self) -> bool {
293+
true
294+
}
291295
}

src/parser/mod.rs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17231,12 +17231,27 @@ impl<'a> Parser<'a> {
1723117231
let table = self.parse_keyword(Keyword::TABLE);
1723217232
let table_object = self.parse_table_object()?;
1723317233

17234-
let table_alias =
17235-
if dialect_of!(self is PostgreSqlDialect) && self.parse_keyword(Keyword::AS) {
17236-
Some(self.parse_identifier()?)
17234+
let table_alias = if self.dialect.supports_insert_table_alias()
17235+
&& !self.peek_sub_query()
17236+
&& self
17237+
.peek_one_of_keywords(&[Keyword::DEFAULT, Keyword::VALUES])
17238+
.is_none()
17239+
{
17240+
if self.parse_keyword(Keyword::AS) {
17241+
Some(TableAliasWithoutColumns {
17242+
explicit: true,
17243+
alias: self.parse_identifier()?,
17244+
})
1723717245
} else {
17238-
None
17239-
};
17246+
self.maybe_parse(|parser| parser.parse_identifier())?
17247+
.map(|alias| TableAliasWithoutColumns {
17248+
explicit: false,
17249+
alias,
17250+
})
17251+
}
17252+
} else {
17253+
None
17254+
};
1724017255

1724117256
let is_mysql = dialect_of!(self is MySqlDialect);
1724217257

@@ -19477,14 +19492,8 @@ impl<'a> Parser<'a> {
1947719492

1947819493
/// Returns true if the next keyword indicates a sub query, i.e. SELECT or WITH
1947919494
fn peek_sub_query(&mut self) -> bool {
19480-
if self
19481-
.parse_one_of_keywords(&[Keyword::SELECT, Keyword::WITH])
19495+
self.peek_one_of_keywords(&[Keyword::SELECT, Keyword::WITH])
1948219496
.is_some()
19483-
{
19484-
self.prev_token();
19485-
return true;
19486-
}
19487-
false
1948819497
}
1948919498

1949019499
pub(crate) fn parse_show_stmt_options(&mut self) -> Result<ShowStatementOptions, ParserError> {

tests/sqlparser_oracle.rs

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121
use pretty_assertions::assert_eq;
2222

2323
use sqlparser::{
24-
ast::{BinaryOperator, Expr, Ident, QuoteDelimitedString, Value, ValueWithSpan},
24+
ast::{
25+
BinaryOperator, Expr, Ident, Insert, ObjectName, Query, QuoteDelimitedString, SetExpr,
26+
Statement, TableAliasWithoutColumns, TableObject, Value, ValueWithSpan,
27+
},
2528
dialect::OracleDialect,
2629
parser::ParserError,
2730
tokenizer::Span,
@@ -421,3 +424,106 @@ fn test_connect_by() {
421424
ORDER BY \"Employee\", \"Manager\", \"Pathlen\", \"Path\"",
422425
);
423426
}
427+
428+
#[test]
429+
fn test_insert_with_table_alias() {
430+
let oracle_dialect = oracle();
431+
432+
fn verify_table_name_with_alias(stmt: &Statement, exp_table_name: &str, exp_table_alias: &str) {
433+
assert!(matches!(stmt,
434+
Statement::Insert(Insert {
435+
table: TableObject::TableName(table_name),
436+
table_alias: Some(TableAliasWithoutColumns {
437+
explicit: false,
438+
alias: Ident {
439+
value: table_alias,
440+
quote_style: None,
441+
span: _
442+
}
443+
}),
444+
..
445+
})
446+
if table_alias == exp_table_alias
447+
&& table_name == &ObjectName::from(vec![Ident {
448+
value: exp_table_name.into(),
449+
quote_style: None,
450+
span: Span::empty(),
451+
}])
452+
));
453+
}
454+
455+
let stmt = oracle_dialect.verified_stmt(
456+
"INSERT INTO foo_t t \
457+
SELECT 1, 2, 3 FROM dual",
458+
);
459+
verify_table_name_with_alias(&stmt, "foo_t", "t");
460+
461+
let stmt = oracle_dialect.verified_stmt(
462+
"INSERT INTO foo_t asdf (a, b, c) \
463+
SELECT 1, 2, 3 FROM dual",
464+
);
465+
verify_table_name_with_alias(&stmt, "foo_t", "asdf");
466+
467+
let stmt = oracle_dialect.verified_stmt(
468+
"INSERT INTO foo_t t (a, b, c) \
469+
VALUES (1, 2, 3)",
470+
);
471+
verify_table_name_with_alias(&stmt, "foo_t", "t");
472+
473+
let stmt = oracle_dialect.verified_stmt(
474+
"INSERT INTO foo_t t \
475+
VALUES (1, 2, 3)",
476+
);
477+
verify_table_name_with_alias(&stmt, "foo_t", "t");
478+
}
479+
480+
#[test]
481+
fn test_insert_without_alias() {
482+
let oracle_dialect = oracle();
483+
484+
// check DEFAULT
485+
let sql = "INSERT INTO t default SELECT 'a' FROM dual";
486+
assert_eq!(
487+
oracle_dialect.parse_sql_statements(sql),
488+
Err(ParserError::ParserError(
489+
"Expected: SELECT, VALUES, or a subquery in the query body, found: default".into()
490+
))
491+
);
492+
493+
// check SELECT
494+
let sql = "INSERT INTO t SELECT 'a' FROM dual";
495+
let stmt = oracle_dialect.verified_stmt(sql);
496+
assert!(matches!(
497+
&stmt,
498+
Statement::Insert(Insert {
499+
table_alias: None,
500+
source: Some(source),
501+
..
502+
})
503+
if matches!(&**source, Query { body, .. } if matches!(&**body, SetExpr::Select(_)))));
504+
505+
// check WITH
506+
let sql = "INSERT INTO dual WITH w AS (SELECT 1 AS y FROM dual) SELECT y FROM w";
507+
let stmt = oracle_dialect.verified_stmt(sql);
508+
assert!(matches!(
509+
&stmt,
510+
Statement::Insert(Insert {
511+
table_alias: None,
512+
source: Some(source),
513+
..
514+
})
515+
if matches!(&**source, Query { body, .. } if matches!(&**body, SetExpr::Select(_)))));
516+
517+
// check VALUES
518+
let sql = "INSERT INTO t VALUES (1)";
519+
let stmt = oracle_dialect.verified_stmt(sql);
520+
assert!(matches!(
521+
stmt,
522+
Statement::Insert(Insert {
523+
table_alias: None,
524+
source: Some(source),
525+
..
526+
})
527+
if matches!(&*source, Query { body, .. } if matches!(&**body, SetExpr::Values(_)))
528+
));
529+
}

tests/sqlparser_postgres.rs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5445,10 +5445,13 @@ fn test_simple_postgres_insert_with_alias() {
54455445
quote_style: None,
54465446
span: Span::empty(),
54475447
}])),
5448-
table_alias: Some(Ident {
5449-
value: "test_table".to_string(),
5450-
quote_style: None,
5451-
span: Span::empty(),
5448+
table_alias: Some(TableAliasWithoutColumns {
5449+
explicit: true,
5450+
alias: Ident {
5451+
value: "test_table".to_string(),
5452+
quote_style: None,
5453+
span: Span::empty(),
5454+
}
54525455
}),
54535456
columns: vec![
54545457
Ident {
@@ -5521,10 +5524,13 @@ fn test_simple_postgres_insert_with_alias() {
55215524
quote_style: None,
55225525
span: Span::empty(),
55235526
}])),
5524-
table_alias: Some(Ident {
5525-
value: "test_table".to_string(),
5526-
quote_style: None,
5527-
span: Span::empty(),
5527+
table_alias: Some(TableAliasWithoutColumns {
5528+
explicit: true,
5529+
alias: Ident {
5530+
value: "test_table".to_string(),
5531+
quote_style: None,
5532+
span: Span::empty(),
5533+
}
55285534
}),
55295535
columns: vec![
55305536
Ident {
@@ -5599,10 +5605,13 @@ fn test_simple_insert_with_quoted_alias() {
55995605
quote_style: None,
56005606
span: Span::empty(),
56015607
}])),
5602-
table_alias: Some(Ident {
5603-
value: "Test_Table".to_string(),
5604-
quote_style: Some('"'),
5605-
span: Span::empty(),
5608+
table_alias: Some(TableAliasWithoutColumns {
5609+
explicit: true,
5610+
alias: Ident {
5611+
value: "Test_Table".to_string(),
5612+
quote_style: Some('"'),
5613+
span: Span::empty(),
5614+
}
56065615
}),
56075616
columns: vec![
56085617
Ident {

0 commit comments

Comments
 (0)