Skip to content

Commit 50921b1

Browse files
authored
[Oracle] Support for INSERT INTO (<sub-query>) ... (#2276)
1 parent b3e176d commit 50921b1

File tree

6 files changed

+99
-3
lines changed

6 files changed

+99
-3
lines changed

src/ast/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,9 @@ impl Dialect for OracleDialect {
114114
fn supports_insert_table_alias(&self) -> bool {
115115
true
116116
}
117+
118+
/// See <https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/INSERT.html#GUID-903F8043-0254-4EE9-ACC1-CB8AC0AF3423__I2126242>
119+
fn supports_insert_table_query(&self) -> bool {
120+
true
121+
}
117122
}

src/parser/mod.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12688,6 +12688,9 @@ 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_or_cte_start() {
12692+
self.parse_parenthesized(|p| p.parse_query())
12693+
.map(TableObject::TableQuery)
1269112694
} else {
1269212695
self.parse_object_name(false).map(TableObject::TableName)
1269312696
}
@@ -17601,9 +17604,44 @@ impl<'a> Parser<'a> {
1760117604
/// Returns true if the immediate tokens look like the
1760217605
/// beginning of a subquery. `(SELECT ...`
1760317606
fn peek_subquery_start(&mut self) -> bool {
17604-
let [maybe_lparen, maybe_select] = self.peek_tokens();
17605-
Token::LParen == maybe_lparen
17606-
&& matches!(maybe_select, Token::Word(w) if w.keyword == Keyword::SELECT)
17607+
matches!(
17608+
self.peek_tokens_ref(),
17609+
[
17610+
TokenWithSpan {
17611+
token: Token::LParen,
17612+
..
17613+
},
17614+
TokenWithSpan {
17615+
token: Token::Word(Word {
17616+
keyword: Keyword::SELECT,
17617+
..
17618+
}),
17619+
..
17620+
},
17621+
]
17622+
)
17623+
}
17624+
17625+
/// Returns true if the immediate tokens look like the
17626+
/// beginning of a subquery possibly preceded by CTEs;
17627+
/// i.e. `(WITH ...` or `(SELECT ...`.
17628+
fn peek_subquery_or_cte_start(&mut self) -> bool {
17629+
matches!(
17630+
self.peek_tokens_ref(),
17631+
[
17632+
TokenWithSpan {
17633+
token: Token::LParen,
17634+
..
17635+
},
17636+
TokenWithSpan {
17637+
token: Token::Word(Word {
17638+
keyword: Keyword::SELECT | Keyword::WITH,
17639+
..
17640+
}),
17641+
..
17642+
},
17643+
]
17644+
)
1760717645
}
1760817646

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

tests/sqlparser_common.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13525,6 +13525,40 @@ fn insert_into_with_parentheses() {
1352513525
dialects.verified_stmt(r#"INSERT INTO t1 ("select", name) (SELECT t2.name FROM t2)"#);
1352613526
}
1352713527

13528+
#[test]
13529+
fn test_insert_with_query_table() {
13530+
let dialects = all_dialects_where(|d| d.supports_insert_table_query());
13531+
13532+
// a simple query (block); i.e. SELECT ...
13533+
let sql = "INSERT INTO (SELECT employee_id, last_name FROM employees) VALUES (207, 'Gregory')";
13534+
dialects.verified_stmt(sql);
13535+
13536+
// a full blown query; i.e. `WITH ... SELECT .. ORDER BY ...`
13537+
let sql = "INSERT INTO \
13538+
(WITH cte AS (SELECT 1 AS id, 2 AS val FROM dual) SELECT foo_t.id, foo_t.val FROM foo_t \
13539+
WHERE EXISTS (SELECT 1 FROM cte WHERE cte.id = foo_t.id) ORDER BY 1, 2) \
13540+
(id, val) \
13541+
VALUES (1000, 10101)";
13542+
dialects.verified_stmt(sql);
13543+
13544+
// an alias to the insert target query table
13545+
let sql = "INSERT INTO \
13546+
(WITH cte AS (SELECT 1 AS id, 2 AS val FROM dual) SELECT foo_t.id, foo_t.val FROM foo_t \
13547+
WHERE EXISTS (SELECT 1 FROM cte WHERE cte.id = foo_t.id)) abc \
13548+
(id, val) \
13549+
VALUES (1000, 10101)";
13550+
dialects.verified_stmt(sql);
13551+
13552+
// a query table target and a query source
13553+
let sql = "INSERT INTO (SELECT foo_t.id, foo_t.val FROM foo_t) SELECT 10, 20 FROM dual";
13554+
dialects.verified_stmt(sql);
13555+
13556+
// a query table target and a query source, with explicit columns
13557+
let sql =
13558+
"INSERT INTO (SELECT foo_t.id, foo_t.val FROM foo_t) (id, val) SELECT 10, 20 FROM dual";
13559+
dialects.verified_stmt(sql);
13560+
}
13561+
1352813562
#[test]
1352913563
fn parse_odbc_scalar_function() {
1353013564
let select = verified_only_select("SELECT {fn my_func(1, 2)}");

0 commit comments

Comments
 (0)