Skip to content

Commit 5150514

Browse files
committed
Add Snowflake multi-table INSERT support
Add support for Snowflake's multi-table INSERT statement, including: 1. Unconditional multi-table insert: INSERT ALL INTO t1 INTO t2 SELECT ... FROM src 2. Conditional multi-table insert with WHEN clauses: INSERT ALL/FIRST WHEN cond THEN INTO t1 ELSE INTO t2 SELECT ... FROM src 3. INSERT OVERWRITE support for multi-table insert 4. INTO clause with optional column list and VALUES clause See: https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table
1 parent 0cf85d3 commit 5150514

File tree

7 files changed

+689
-4
lines changed

7 files changed

+689
-4
lines changed

src/ast/dml.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,80 @@ pub struct Insert {
9090
///
9191
/// [ClickHouse formats JSON insert](https://clickhouse.com/docs/en/interfaces/formats#json-inserting-data)
9292
pub format_clause: Option<InputFormatClause>,
93+
/// For multi-table insert: `INSERT FIRST` vs `INSERT ALL`
94+
///
95+
/// When `true`, this is an `INSERT FIRST` statement (only the first matching WHEN clause is executed).
96+
/// When `false` with non-empty `clauses`, this is an `INSERT ALL` statement.
97+
///
98+
/// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table>
99+
pub insert_first: bool,
100+
/// For multi-table insert: additional INTO clauses (unconditional)
101+
///
102+
/// Used for `INSERT ALL INTO t1 INTO t2 ... SELECT ...`
103+
///
104+
/// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table>
105+
pub multi_table_into_clauses: Vec<MultiTableInsertIntoClause>,
106+
/// For conditional multi-table insert: WHEN clauses
107+
///
108+
/// Used for `INSERT ALL/FIRST WHEN cond THEN INTO t1 ... SELECT ...`
109+
///
110+
/// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table>
111+
pub multi_table_when_clauses: Vec<MultiTableInsertWhenClause>,
112+
/// For conditional multi-table insert: ELSE clause
113+
///
114+
/// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table>
115+
pub multi_table_else_clause: Option<Vec<MultiTableInsertIntoClause>>,
93116
}
94117

95118
impl Display for Insert {
96119
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120+
// Check if this is a Snowflake multi-table insert
121+
let is_multi_table = !self.multi_table_into_clauses.is_empty()
122+
|| !self.multi_table_when_clauses.is_empty();
123+
124+
if is_multi_table {
125+
// Snowflake multi-table INSERT format
126+
write!(f, "INSERT")?;
127+
if self.overwrite {
128+
write!(f, " OVERWRITE")?;
129+
}
130+
if self.insert_first {
131+
write!(f, " FIRST")?;
132+
} else {
133+
write!(f, " ALL")?;
134+
}
135+
136+
// Unconditional multi-table insert: INTO clauses directly after ALL
137+
for into_clause in &self.multi_table_into_clauses {
138+
SpaceOrNewline.fmt(f)?;
139+
write!(f, "{}", into_clause)?;
140+
}
141+
142+
// Conditional multi-table insert: WHEN clauses
143+
for when_clause in &self.multi_table_when_clauses {
144+
SpaceOrNewline.fmt(f)?;
145+
write!(f, "{}", when_clause)?;
146+
}
147+
148+
// ELSE clause
149+
if let Some(else_clauses) = &self.multi_table_else_clause {
150+
SpaceOrNewline.fmt(f)?;
151+
write!(f, "ELSE")?;
152+
for into_clause in else_clauses {
153+
SpaceOrNewline.fmt(f)?;
154+
write!(f, "{}", into_clause)?;
155+
}
156+
}
157+
158+
// Source query
159+
if let Some(source) = &self.source {
160+
SpaceOrNewline.fmt(f)?;
161+
source.fmt(f)?;
162+
}
163+
return Ok(());
164+
}
165+
166+
// Standard INSERT format
97167
let table_name = if let Some(alias) = &self.table_alias {
98168
format!("{0} AS {alias}", self.table)
99169
} else {
@@ -644,3 +714,92 @@ impl fmt::Display for OutputClause {
644714
}
645715
}
646716
}
717+
718+
/// A WHEN clause in a conditional multi-table INSERT.
719+
///
720+
/// Syntax:
721+
/// ```sql
722+
/// WHEN n1 > 100 THEN
723+
/// INTO t1
724+
/// INTO t2 (c1, c2) VALUES (n1, n2)
725+
/// ```
726+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
727+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
728+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
729+
pub struct MultiTableInsertWhenClause {
730+
/// The condition for this WHEN clause
731+
pub condition: Expr,
732+
/// The INTO clauses to execute when the condition is true
733+
pub into_clauses: Vec<MultiTableInsertIntoClause>,
734+
}
735+
736+
impl Display for MultiTableInsertWhenClause {
737+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
738+
write!(f, "WHEN {} THEN", self.condition)?;
739+
for into_clause in &self.into_clauses {
740+
SpaceOrNewline.fmt(f)?;
741+
write!(f, "{}", into_clause)?;
742+
}
743+
Ok(())
744+
}
745+
}
746+
747+
/// An INTO clause in a multi-table INSERT.
748+
///
749+
/// Syntax:
750+
/// ```sql
751+
/// INTO <target_table> [ ( <target_col_name> [ , ... ] ) ] [ VALUES ( { <source_col_name> | DEFAULT | NULL } [ , ... ] ) ]
752+
/// ```
753+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
754+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
755+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
756+
pub struct MultiTableInsertIntoClause {
757+
/// The target table
758+
pub table_name: ObjectName,
759+
/// The target columns (optional)
760+
pub columns: Vec<Ident>,
761+
/// The VALUES clause (optional)
762+
pub values: Option<MultiTableInsertValues>,
763+
}
764+
765+
impl Display for MultiTableInsertIntoClause {
766+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
767+
write!(f, "INTO {}", self.table_name)?;
768+
if !self.columns.is_empty() {
769+
write!(f, " ({})", display_comma_separated(&self.columns))?;
770+
}
771+
if let Some(values) = &self.values {
772+
write!(f, " VALUES ({})", display_comma_separated(&values.values))?;
773+
}
774+
Ok(())
775+
}
776+
}
777+
778+
/// The VALUES clause in a multi-table INSERT INTO clause.
779+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
780+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
781+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
782+
pub struct MultiTableInsertValues {
783+
/// The values to insert (can be column references, DEFAULT, or NULL)
784+
pub values: Vec<MultiTableInsertValue>,
785+
}
786+
787+
/// A value in a multi-table INSERT VALUES clause.
788+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
789+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
790+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
791+
pub enum MultiTableInsertValue {
792+
/// A column reference or expression from the source
793+
Expr(Expr),
794+
/// The DEFAULT keyword
795+
Default,
796+
}
797+
798+
impl Display for MultiTableInsertValue {
799+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
800+
match self {
801+
MultiTableInsertValue::Expr(expr) => write!(f, "{}", expr),
802+
MultiTableInsertValue::Default => write!(f, "DEFAULT"),
803+
}
804+
}
805+
}

src/ast/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ pub use self::ddl::{
8181
};
8282
pub use self::dml::{
8383
Delete, Insert, Merge, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr,
84-
MergeInsertKind, MergeUpdateExpr, OutputClause, Update,
84+
MergeInsertKind, MergeUpdateExpr, MultiTableInsertIntoClause, MultiTableInsertValue,
85+
MultiTableInsertValues, MultiTableInsertWhenClause, OutputClause, Update,
8586
};
8687
pub use self::operator::{BinaryOperator, UnaryOperator};
8788
pub use self::query::{

src/ast/spans.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,6 +1274,10 @@ impl Spanned for Insert {
12741274
assignments,
12751275
settings: _, // todo, clickhouse specific
12761276
format_clause: _, // todo, clickhouse specific
1277+
insert_first: _, // snowflake multi-table insert
1278+
multi_table_into_clauses: _, // snowflake multi-table insert
1279+
multi_table_when_clauses: _, // snowflake multi-table insert
1280+
multi_table_else_clause: _, // snowflake multi-table insert
12771281
} = self;
12781282

12791283
union_spans(

0 commit comments

Comments
 (0)