Skip to content

Commit 75f6f4b

Browse files
authored
Snowflake: Add multi table insert support (apache#2148)
1 parent 03f00cd commit 75f6f4b

File tree

7 files changed

+733
-15
lines changed

7 files changed

+733
-15
lines changed

src/ast/dml.rs

Lines changed: 188 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,35 @@ pub struct Insert {
9696
///
9797
/// [ClickHouse formats JSON insert](https://clickhouse.com/docs/en/interfaces/formats#json-inserting-data)
9898
pub format_clause: Option<InputFormatClause>,
99+
/// For Snowflake multi-table insert: specifies the type (`ALL` or `FIRST`)
100+
///
101+
/// - `None` means this is a regular single-table INSERT
102+
/// - `Some(All)` means `INSERT ALL` (all matching WHEN clauses are executed)
103+
/// - `Some(First)` means `INSERT FIRST` (only the first matching WHEN clause is executed)
104+
///
105+
/// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table>
106+
pub multi_table_insert_type: Option<MultiTableInsertType>,
107+
/// For multi-table insert: additional INTO clauses (unconditional)
108+
///
109+
/// Used for `INSERT ALL INTO t1 INTO t2 ... SELECT ...`
110+
///
111+
/// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table>
112+
pub multi_table_into_clauses: Vec<MultiTableInsertIntoClause>,
113+
/// For conditional multi-table insert: WHEN clauses
114+
///
115+
/// Used for `INSERT ALL/FIRST WHEN cond THEN INTO t1 ... SELECT ...`
116+
///
117+
/// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table>
118+
pub multi_table_when_clauses: Vec<MultiTableInsertWhenClause>,
119+
/// For conditional multi-table insert: ELSE clause
120+
///
121+
/// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table>
122+
pub multi_table_else_clause: Option<Vec<MultiTableInsertIntoClause>>,
99123
}
100124

101125
impl Display for Insert {
102126
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127+
// SQLite OR conflict has a special format: INSERT OR ... INTO table_name
103128
let table_name = if let Some(alias) = &self.table_alias {
104129
format!("{0} AS {alias}", self.table)
105130
} else {
@@ -126,29 +151,46 @@ impl Display for Insert {
126151
write!(f, " {hint}")?;
127152
}
128153
if let Some(priority) = self.priority {
129-
write!(f, " {priority}",)?;
154+
write!(f, " {priority}")?;
130155
}
131156

132-
write!(
133-
f,
134-
"{ignore}{over}{int}{tbl} {table_name} ",
135-
table_name = table_name,
136-
ignore = if self.ignore { " IGNORE" } else { "" },
137-
over = if self.overwrite { " OVERWRITE" } else { "" },
138-
int = if self.into { " INTO" } else { "" },
139-
tbl = if self.has_table_keyword { " TABLE" } else { "" },
140-
)?;
157+
if self.ignore {
158+
write!(f, " IGNORE")?;
159+
}
160+
161+
if self.overwrite {
162+
write!(f, " OVERWRITE")?;
163+
}
164+
165+
if let Some(insert_type) = &self.multi_table_insert_type {
166+
write!(f, " {}", insert_type)?;
167+
}
168+
169+
if self.into {
170+
write!(f, " INTO")?;
171+
}
172+
173+
if self.has_table_keyword {
174+
write!(f, " TABLE")?;
175+
}
176+
177+
if !table_name.is_empty() {
178+
write!(f, " {table_name} ")?;
179+
}
141180
}
181+
142182
if !self.columns.is_empty() {
143183
write!(f, "({})", display_comma_separated(&self.columns))?;
144184
SpaceOrNewline.fmt(f)?;
145185
}
186+
146187
if let Some(ref parts) = self.partitioned {
147188
if !parts.is_empty() {
148189
write!(f, "PARTITION ({})", display_comma_separated(parts))?;
149190
SpaceOrNewline.fmt(f)?;
150191
}
151192
}
193+
152194
if !self.after_columns.is_empty() {
153195
write!(f, "({})", display_comma_separated(&self.after_columns))?;
154196
SpaceOrNewline.fmt(f)?;
@@ -159,7 +201,31 @@ impl Display for Insert {
159201
SpaceOrNewline.fmt(f)?;
160202
}
161203

204+
for into_clause in &self.multi_table_into_clauses {
205+
SpaceOrNewline.fmt(f)?;
206+
write!(f, "{}", into_clause)?;
207+
}
208+
209+
for when_clause in &self.multi_table_when_clauses {
210+
SpaceOrNewline.fmt(f)?;
211+
write!(f, "{}", when_clause)?;
212+
}
213+
214+
if let Some(else_clauses) = &self.multi_table_else_clause {
215+
SpaceOrNewline.fmt(f)?;
216+
write!(f, "ELSE")?;
217+
for into_clause in else_clauses {
218+
SpaceOrNewline.fmt(f)?;
219+
write!(f, "{}", into_clause)?;
220+
}
221+
}
222+
162223
if let Some(source) = &self.source {
224+
if !self.multi_table_into_clauses.is_empty()
225+
|| !self.multi_table_when_clauses.is_empty()
226+
{
227+
SpaceOrNewline.fmt(f)?;
228+
}
163229
source.fmt(f)?;
164230
} else if !self.assignments.is_empty() {
165231
write!(f, "SET")?;
@@ -189,6 +255,7 @@ impl Display for Insert {
189255
f.write_str("RETURNING")?;
190256
indented_list(f, returning)?;
191257
}
258+
192259
Ok(())
193260
}
194261
}
@@ -695,3 +762,114 @@ impl fmt::Display for OutputClause {
695762
}
696763
}
697764
}
765+
766+
/// A WHEN clause in a conditional multi-table INSERT.
767+
///
768+
/// Syntax:
769+
/// ```sql
770+
/// WHEN n1 > 100 THEN
771+
/// INTO t1
772+
/// INTO t2 (c1, c2) VALUES (n1, n2)
773+
/// ```
774+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
775+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
776+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
777+
pub struct MultiTableInsertWhenClause {
778+
/// The condition for this WHEN clause
779+
pub condition: Expr,
780+
/// The INTO clauses to execute when the condition is true
781+
pub into_clauses: Vec<MultiTableInsertIntoClause>,
782+
}
783+
784+
impl Display for MultiTableInsertWhenClause {
785+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
786+
write!(f, "WHEN {} THEN", self.condition)?;
787+
for into_clause in &self.into_clauses {
788+
SpaceOrNewline.fmt(f)?;
789+
write!(f, "{}", into_clause)?;
790+
}
791+
Ok(())
792+
}
793+
}
794+
795+
/// An INTO clause in a multi-table INSERT.
796+
///
797+
/// Syntax:
798+
/// ```sql
799+
/// INTO <target_table> [ ( <target_col_name> [ , ... ] ) ] [ VALUES ( { <source_col_name> | DEFAULT | NULL } [ , ... ] ) ]
800+
/// ```
801+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
802+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
803+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
804+
pub struct MultiTableInsertIntoClause {
805+
/// The target table
806+
pub table_name: ObjectName,
807+
/// The target columns (optional)
808+
pub columns: Vec<Ident>,
809+
/// The VALUES clause (optional)
810+
pub values: Option<MultiTableInsertValues>,
811+
}
812+
813+
impl Display for MultiTableInsertIntoClause {
814+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
815+
write!(f, "INTO {}", self.table_name)?;
816+
if !self.columns.is_empty() {
817+
write!(f, " ({})", display_comma_separated(&self.columns))?;
818+
}
819+
if let Some(values) = &self.values {
820+
write!(f, " VALUES ({})", display_comma_separated(&values.values))?;
821+
}
822+
Ok(())
823+
}
824+
}
825+
826+
/// The VALUES clause in a multi-table INSERT INTO clause.
827+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
828+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
829+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
830+
pub struct MultiTableInsertValues {
831+
/// The values to insert (can be column references, DEFAULT, or NULL)
832+
pub values: Vec<MultiTableInsertValue>,
833+
}
834+
835+
/// A value in a multi-table INSERT VALUES clause.
836+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
837+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
838+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
839+
pub enum MultiTableInsertValue {
840+
/// A column reference or expression from the source
841+
Expr(Expr),
842+
/// The DEFAULT keyword
843+
Default,
844+
}
845+
846+
impl Display for MultiTableInsertValue {
847+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
848+
match self {
849+
MultiTableInsertValue::Expr(expr) => write!(f, "{}", expr),
850+
MultiTableInsertValue::Default => write!(f, "DEFAULT"),
851+
}
852+
}
853+
}
854+
855+
/// The type of multi-table INSERT statement(Snowflake).
856+
///
857+
/// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table>
858+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
859+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
860+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
861+
pub enum MultiTableInsertType {
862+
/// `INSERT ALL` - all matching WHEN clauses are executed
863+
All,
864+
/// `INSERT FIRST` - only the first matching WHEN clause is executed
865+
First,
866+
}
867+
868+
impl Display for MultiTableInsertType {
869+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
870+
match self {
871+
MultiTableInsertType::All => write!(f, "ALL"),
872+
MultiTableInsertType::First => write!(f, "FIRST"),
873+
}
874+
}
875+
}

src/ast/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ pub use self::ddl::{
8383
};
8484
pub use self::dml::{
8585
Delete, Insert, Merge, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr,
86-
MergeInsertKind, MergeUpdateExpr, OutputClause, Update,
86+
MergeInsertKind, MergeUpdateExpr, MultiTableInsertIntoClause, MultiTableInsertType,
87+
MultiTableInsertValue, MultiTableInsertValues, MultiTableInsertWhenClause, OutputClause,
88+
Update,
8789
};
8890
pub use self::operator::{BinaryOperator, UnaryOperator};
8991
pub use self::query::{

src/ast/spans.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,8 +1312,12 @@ impl Spanned for Insert {
13121312
priority: _, // todo, mysql specific
13131313
insert_alias: _, // todo, mysql specific
13141314
assignments,
1315-
settings: _, // todo, clickhouse specific
1316-
format_clause: _, // todo, clickhouse specific
1315+
settings: _, // todo, clickhouse specific
1316+
format_clause: _, // todo, clickhouse specific
1317+
multi_table_insert_type: _, // snowflake multi-table insert
1318+
multi_table_into_clauses: _, // snowflake multi-table insert
1319+
multi_table_when_clauses: _, // snowflake multi-table insert
1320+
multi_table_else_clause: _, // snowflake multi-table insert
13171321
} = self;
13181322

13191323
union_spans(

0 commit comments

Comments
 (0)