Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10742,13 +10742,24 @@ 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<Query>),
}

impl fmt::Display for TableObject {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
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})"),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,13 @@ pub trait Dialect: Debug + Any {
false
}

/// Does the dialect support table queries in insertion?
///
/// e.g. `SELECT INTO (<query>) ...`
fn supports_insert_table_query(&self) -> bool {
false
}

/// Does the dialect support insert formats, e.g. `INSERT INTO ... FORMAT <format>`
fn supports_insert_format(&self) -> bool {
false
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/oracle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,8 @@ impl Dialect for OracleDialect {
fn supports_insert_table_alias(&self) -> bool {
true
}

fn supports_insert_table_query(&self) -> bool {
true
}
}
15 changes: 11 additions & 4 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)?;

Expand Down Expand Up @@ -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<SqliteOnConflict> {
Expand Down
34 changes: 34 additions & 0 deletions tests/sqlparser_oracle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}