diff --git a/src/ast/mod.rs b/src/ast/mod.rs index e201f7842..8bc98ef59 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -10742,6 +10742,16 @@ pub enum TableObject { /// ``` /// [Clickhouse](https://clickhouse.com/docs/en/sql-reference/table-functions) TableFunction(Function), + + /// Table specified through a sub-query + /// Example: + /// ```sql + /// INSERT INTO + /// (SELECT employee_id, last_name, email, hire_date, job_id, salary, commission_pct FROM employees) + /// VALUES (207, 'Gregory', 'pgregory@example.com', sysdate, 'PU_CLERK', 1.2E3, NULL); + /// ``` + /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/INSERT.html#GUID-903F8043-0254-4EE9-ACC1-CB8AC0AF3423__I2126242) + TableQuery(Box), } impl fmt::Display for TableObject { @@ -10749,6 +10759,7 @@ impl fmt::Display for TableObject { match self { Self::TableName(table_name) => write!(f, "{table_name}"), Self::TableFunction(func) => write!(f, "FUNCTION {func}"), + Self::TableQuery(table_query) => write!(f, "({table_query})"), } } } diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 57d57b249..cd4e5fdf1 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -2382,6 +2382,7 @@ impl Spanned for TableObject { union_spans(segments.iter().map(|i| i.span())) } TableObject::TableFunction(func) => func.span(), + TableObject::TableQuery(query) => query.span(), } } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 8703e402c..fed81b60a 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -1247,6 +1247,13 @@ pub trait Dialect: Debug + Any { false } + /// Does the dialect support table queries in insertion? + /// + /// e.g. `SELECT INTO () ...` + fn supports_insert_table_query(&self) -> bool { + false + } + /// Does the dialect support insert formats, e.g. `INSERT INTO ... FORMAT ` fn supports_insert_format(&self) -> bool { false diff --git a/src/dialect/oracle.rs b/src/dialect/oracle.rs index dce0493d3..89cb9becb 100644 --- a/src/dialect/oracle.rs +++ b/src/dialect/oracle.rs @@ -114,4 +114,8 @@ impl Dialect for OracleDialect { fn supports_insert_table_alias(&self) -> bool { true } + + fn supports_insert_table_query(&self) -> bool { + true + } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 274449ff7..b741afb24 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -12633,6 +12633,9 @@ impl<'a> Parser<'a> { let fn_name = self.parse_object_name(false)?; self.parse_function_call(fn_name) .map(TableObject::TableFunction) + } else if self.dialect.supports_insert_table_query() && self.peek_subquery_start(true) { + self.parse_parenthesized(|p| p.parse_query()) + .map(TableObject::TableQuery) } else { self.parse_object_name(false).map(TableObject::TableName) } @@ -17353,7 +17356,7 @@ impl<'a> Parser<'a> { { (vec![], None, vec![], None, None, vec![]) } else { - let (columns, partitioned, after_columns) = if !self.peek_subquery_start() { + let (columns, partitioned, after_columns) = if !self.peek_subquery_start(false) { let columns = self.parse_parenthesized_qualified_column_list(Optional, is_mysql)?; @@ -17518,11 +17521,15 @@ impl<'a> Parser<'a> { } /// Returns true if the immediate tokens look like the - /// beginning of a subquery. `(SELECT ...` - fn peek_subquery_start(&mut self) -> bool { + /// beginning of a subquery, e.g. `(SELECT ...`. + /// + /// If `full_query == true` attempt to detect a full query with its + /// optional, leading `WITH` clause, e.g. `(WITH ...)` + fn peek_subquery_start(&mut self, full_query: bool) -> bool { let [maybe_lparen, maybe_select] = self.peek_tokens(); Token::LParen == maybe_lparen - && matches!(maybe_select, Token::Word(w) if w.keyword == Keyword::SELECT) + && matches!(maybe_select, Token::Word(w) + if w.keyword == Keyword::SELECT || (full_query && w.keyword == Keyword::WITH)) } fn parse_conflict_clause(&mut self) -> Option { diff --git a/tests/sqlparser_oracle.rs b/tests/sqlparser_oracle.rs index 35f083111..e84fb6b84 100644 --- a/tests/sqlparser_oracle.rs +++ b/tests/sqlparser_oracle.rs @@ -542,3 +542,37 @@ fn test_insert_without_alias() { if matches!(&*source, Query { body, .. } if matches!(&**body, SetExpr::Values(_))) )); } + +#[test] +fn test_insert_with_query_table() { + let oracle_dialect = oracle(); + + // a simple query (block); i.e. SELECT ... + let sql = "INSERT INTO (SELECT employee_id, last_name FROM employees) VALUES (207, 'Gregory')"; + oracle_dialect.verified_stmt(sql); + + // a full blown query; i.e. `WITH ... SELECT .. ORDER BY ...` + let sql = "INSERT INTO \ + (WITH cte AS (SELECT 1 AS id, 2 AS val FROM dual) SELECT foo_t.id, foo_t.val FROM foo_t \ + WHERE EXISTS (SELECT 1 FROM cte WHERE cte.id = foo_t.id) ORDER BY 1, 2) \ + (id, val) \ + VALUES (1000, 10101)"; + oracle_dialect.verified_stmt(sql); + + // an alias to the insert target query table + let sql = "INSERT INTO \ + (WITH cte AS (SELECT 1 AS id, 2 AS val FROM dual) SELECT foo_t.id, foo_t.val FROM foo_t \ + WHERE EXISTS (SELECT 1 FROM cte WHERE cte.id = foo_t.id)) abc \ + (id, val) \ + VALUES (1000, 10101)"; + oracle_dialect.verified_stmt(sql); + + // a query table target and a query source + let sql = "INSERT INTO (SELECT foo_t.id, foo_t.val FROM foo_t) SELECT 10, 20 FROM dual"; + oracle_dialect.verified_stmt(sql); + + // a query table target and a query source, with explicit columns + let sql = + "INSERT INTO (SELECT foo_t.id, foo_t.val FROM foo_t) (id, val) SELECT 10, 20 FROM dual"; + oracle_dialect.verified_stmt(sql); +}