Skip to content

Commit b8e9e95

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

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;
@@ -10888,13 +10888,24 @@ pub enum TableObject {
1088810888
/// ```
1088910889
/// [Clickhouse](https://clickhouse.com/docs/en/sql-reference/table-functions)
1089010890
TableFunction(Function),
10891+
10892+
/// Table specified through a sub-query
10893+
/// Example:
10894+
/// ```sql
10895+
/// INSERT INTO
10896+
/// (SELECT employee_id, last_name, email, hire_date, job_id, salary, commission_pct FROM employees)
10897+
/// VALUES (207, 'Gregory', 'pgregory@example.com', sysdate, 'PU_CLERK', 1.2E3, NULL);
10898+
/// ```
10899+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/INSERT.html#GUID-903F8043-0254-4EE9-ACC1-CB8AC0AF3423__I2126242)
10900+
TableQuery(Box<Query>),
1089110901
}
1089210902

1089310903
impl fmt::Display for TableObject {
1089410904
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1089510905
match self {
1089610906
Self::TableName(table_name) => write!(f, "{table_name}"),
1089710907
Self::TableFunction(func) => write!(f, "FUNCTION {func}"),
10908+
Self::TableQuery(table_query) => write!(f, "({table_query})"),
1089810909
}
1089910910
}
1090010911
}

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2387,6 +2387,7 @@ impl Spanned for TableObject {
23872387
union_spans(segments.iter().map(|i| i.span()))
23882388
}
23892389
TableObject::TableFunction(func) => func.span(),
2390+
TableObject::TableQuery(query) => query.span(),
23902391
}
23912392
}
23922393
}

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
@@ -12688,6 +12688,8 @@ impl<'a> Parser<'a> {
1268812688
let fn_name = self.parse_object_name(false)?;
1268912689
self.parse_function_call(fn_name)
1269012690
.map(TableObject::TableFunction)
12691+
} else if self.dialect.supports_insert_table_query() && self.peek_subquery_start(true) {
12692+
self.parse_parenthesized(|p| p.parse_query()).map(TableObject::TableQuery)
1269112693
} else {
1269212694
self.parse_object_name(false).map(TableObject::TableName)
1269312695
}
@@ -17434,7 +17436,7 @@ impl<'a> Parser<'a> {
1743417436
{
1743517437
(vec![], None, vec![], None, None, vec![])
1743617438
} else {
17437-
let (columns, partitioned, after_columns) = if !self.peek_subquery_start() {
17439+
let (columns, partitioned, after_columns) = if !self.peek_subquery_start(false) {
1743817440
let columns =
1743917441
self.parse_parenthesized_qualified_column_list(Optional, is_mysql)?;
1744017442

@@ -17599,11 +17601,15 @@ impl<'a> Parser<'a> {
1759917601
}
1760017602

1760117603
/// Returns true if the immediate tokens look like the
17602-
/// beginning of a subquery. `(SELECT ...`
17603-
fn peek_subquery_start(&mut self) -> bool {
17604+
/// beginning of a subquery, e.g. `(SELECT ...`.
17605+
///
17606+
/// If `full_query == true` attempt to detect a full query with its
17607+
/// optional, leading `WITH` clause, e.g. `(WITH ...)`
17608+
fn peek_subquery_start(&mut self, full_query: bool) -> bool {
1760417609
let [maybe_lparen, maybe_select] = self.peek_tokens();
1760517610
Token::LParen == maybe_lparen
17606-
&& matches!(maybe_select, Token::Word(w) if w.keyword == Keyword::SELECT)
17611+
&& matches!(maybe_select, Token::Word(w)
17612+
if w.keyword == Keyword::SELECT || (full_query && w.keyword == Keyword::WITH))
1760717613
}
1760817614

1760917615
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)