Skip to content

Commit d709dcc

Browse files
committed
[Oracle] Support for INSERT INTO (<sub-query>) ...
1 parent 3fa7114 commit d709dcc

File tree

6 files changed

+72
-9
lines changed

6 files changed

+72
-9
lines changed

src/ast/mod.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ pub use self::trigger::{
117117
};
118118

119119
pub use self::value::{
120-
escape_double_quote_string, escape_quoted_string, DateTimeField, DollarQuotedString,
121-
NormalizationForm, QuoteDelimitedString, TrimWhereField, Value, ValueWithSpan,
120+
DateTimeField, DollarQuotedString, NormalizationForm, QuoteDelimitedString, TrimWhereField,
121+
Value, ValueWithSpan, escape_double_quote_string, escape_quoted_string,
122122
};
123123

124124
use crate::ast::helpers::key_value_options::KeyValueOptions;
@@ -10742,13 +10742,24 @@ pub enum TableObject {
1074210742
/// ```
1074310743
/// [Clickhouse](https://clickhouse.com/docs/en/sql-reference/table-functions)
1074410744
TableFunction(Function),
10745+
10746+
/// Table specified through a sub-query
10747+
/// Example:
10748+
/// ```sql
10749+
/// INSERT INTO
10750+
/// (SELECT employee_id, last_name, email, hire_date, job_id, salary, commission_pct FROM employees)
10751+
/// VALUES (207, 'Gregory', 'pgregory@example.com', sysdate, 'PU_CLERK', 1.2E3, NULL);
10752+
/// ```
10753+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/INSERT.html#GUID-903F8043-0254-4EE9-ACC1-CB8AC0AF3423__I2126242)
10754+
TableQuery(Box<Query>),
1074510755
}
1074610756

1074710757
impl fmt::Display for TableObject {
1074810758
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1074910759
match self {
1075010760
Self::TableName(table_name) => write!(f, "{table_name}"),
1075110761
Self::TableFunction(func) => write!(f, "FUNCTION {func}"),
10762+
Self::TableQuery(table_query) => write!(f, "({table_query})"),
1075210763
}
1075310764
}
1075410765
}

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2382,6 +2382,7 @@ impl Spanned for TableObject {
23822382
union_spans(segments.iter().map(|i| i.span()))
23832383
}
23842384
TableObject::TableFunction(func) => func.span(),
2385+
TableObject::TableQuery(query) => query.span(),
23852386
}
23862387
}
23872388
}

src/dialect/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,6 +1247,13 @@ pub trait Dialect: Debug + Any {
12471247
false
12481248
}
12491249

1250+
/// Does the dialect support table queries in insertion?
1251+
///
1252+
/// e.g. `SELECT INTO (<query>) ...`
1253+
fn supports_insert_table_query(&self) -> bool {
1254+
false
1255+
}
1256+
12501257
/// Does the dialect support insert formats, e.g. `INSERT INTO ... FORMAT <format>`
12511258
fn supports_insert_format(&self) -> bool {
12521259
false

src/dialect/oracle.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,8 @@ impl Dialect for OracleDialect {
114114
fn supports_insert_table_alias(&self) -> bool {
115115
true
116116
}
117+
118+
fn supports_insert_table_query(&self) -> bool {
119+
true
120+
}
117121
}

src/parser/mod.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12633,6 +12633,8 @@ impl<'a> Parser<'a> {
1263312633
let fn_name = self.parse_object_name(false)?;
1263412634
self.parse_function_call(fn_name)
1263512635
.map(TableObject::TableFunction)
12636+
} else if self.dialect.supports_insert_table_query() && self.peek_subquery_start(true) {
12637+
self.parse_parenthesized(|p| p.parse_query()).map(TableObject::TableQuery)
1263612638
} else {
1263712639
self.parse_object_name(false).map(TableObject::TableName)
1263812640
}
@@ -17353,7 +17355,7 @@ impl<'a> Parser<'a> {
1735317355
{
1735417356
(vec![], None, vec![], None, None, vec![])
1735517357
} else {
17356-
let (columns, partitioned, after_columns) = if !self.peek_subquery_start() {
17358+
let (columns, partitioned, after_columns) = if !self.peek_subquery_start(false) {
1735717359
let columns =
1735817360
self.parse_parenthesized_qualified_column_list(Optional, is_mysql)?;
1735917361

@@ -17518,11 +17520,15 @@ impl<'a> Parser<'a> {
1751817520
}
1751917521

1752017522
/// Returns true if the immediate tokens look like the
17521-
/// beginning of a subquery. `(SELECT ...`
17522-
fn peek_subquery_start(&mut self) -> bool {
17523+
/// beginning of a subquery, e.g. `(SELECT ...`.
17524+
///
17525+
/// If `full_query == true` attempt to detect a full query with its
17526+
/// optional, leading `WITH` clause, e.g. `(WITH ...)`
17527+
fn peek_subquery_start(&mut self, full_query: bool) -> bool {
1752317528
let [maybe_lparen, maybe_select] = self.peek_tokens();
1752417529
Token::LParen == maybe_lparen
17525-
&& matches!(maybe_select, Token::Word(w) if w.keyword == Keyword::SELECT)
17530+
&& matches!(maybe_select, Token::Word(w)
17531+
if w.keyword == Keyword::SELECT || (full_query && w.keyword == Keyword::WITH))
1752617532
}
1752717533

1752817534
fn parse_conflict_clause(&mut self) -> Option<SqliteOnConflict> {

tests/sqlparser_oracle.rs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ use pretty_assertions::assert_eq;
2222

2323
use sqlparser::{
2424
ast::{
25-
BinaryOperator, Expr, Ident, Insert, ObjectName, Query, QuoteDelimitedString, SetExpr,
26-
Statement, TableAliasWithoutColumns, TableObject, Value, ValueWithSpan,
25+
BinaryOperator, Expr, Ident, Insert, ObjectName, ObjectNamePart, Query,
26+
QuoteDelimitedString, Select, SelectItem, SetExpr, Statement, TableAliasWithoutColumns,
27+
TableFactor, TableObject, TableWithJoins, Value, ValueWithSpan,
2728
},
2829
dialect::OracleDialect,
2930
parser::ParserError,
3031
tokenizer::Span,
3132
};
32-
use test_utils::{all_dialects_where, expr_from_projection, number, TestedDialects};
33+
use test_utils::{TestedDialects, all_dialects_where, expr_from_projection, number};
3334

3435
mod test_utils;
3536

@@ -542,3 +543,36 @@ fn test_insert_without_alias() {
542543
if matches!(&*source, Query { body, .. } if matches!(&**body, SetExpr::Values(_)))
543544
));
544545
}
546+
547+
#[test]
548+
fn test_insert_with_query_table() {
549+
let oracle_dialect = oracle();
550+
551+
// a simple query (block); i.e. SELECT ...
552+
let sql = "INSERT INTO (SELECT employee_id, last_name FROM employees) VALUES (207, 'Gregory')";
553+
oracle_dialect.verified_stmt(sql);
554+
555+
// a full blown query; i.e. `WITH ... SELECT .. ORDER BY ...`
556+
let sql = "INSERT INTO \
557+
(WITH cte AS (SELECT 1 AS id, 2 AS val FROM dual) SELECT foo_t.id, foo_t.val FROM foo_t \
558+
WHERE EXISTS (SELECT 1 FROM cte WHERE cte.id = foo_t.id) ORDER BY 1, 2) \
559+
(id, val) \
560+
VALUES (1000, 10101)";
561+
oracle_dialect.verified_stmt(sql);
562+
563+
// an alias to the insert target query table
564+
let sql = "INSERT INTO \
565+
(WITH cte AS (SELECT 1 AS id, 2 AS val FROM dual) SELECT foo_t.id, foo_t.val FROM foo_t \
566+
WHERE EXISTS (SELECT 1 FROM cte WHERE cte.id = foo_t.id)) abc \
567+
(id, val) \
568+
VALUES (1000, 10101)";
569+
oracle_dialect.verified_stmt(sql);
570+
571+
// a query table target and a query source
572+
let sql = "INSERT INTO (SELECT foo_t.id, foo_t.val FROM foo_t) SELECT 10, 20 FROM dual";
573+
oracle_dialect.verified_stmt(sql);
574+
575+
// a query table target and a query source, with explicit columns
576+
let sql = "INSERT INTO (SELECT foo_t.id, foo_t.val FROM foo_t) (id, val) SELECT 10, 20 FROM dual";
577+
oracle_dialect.verified_stmt(sql);
578+
}

0 commit comments

Comments
 (0)