@@ -25,7 +25,7 @@ use arrow::datatypes::{DataType, Field, Schema};
2525use datafusion_common:: file_options:: file_type:: FileType ;
2626use datafusion_common:: { DFSchemaRef , TableReference } ;
2727
28- use crate :: { LogicalPlan , TableSource } ;
28+ use crate :: { Expr , LogicalPlan , TableSource } ;
2929
3030/// Operator that copies the contents of a database to file(s)
3131#[ derive( Clone ) ]
@@ -239,6 +239,8 @@ pub enum WriteOp {
239239 Ctas ,
240240 /// `TRUNCATE` operation
241241 Truncate ,
242+ /// `MERGE INTO` operation
243+ MergeInto ( MergeIntoOp ) ,
242244}
243245
244246impl WriteOp {
@@ -250,6 +252,7 @@ impl WriteOp {
250252 WriteOp :: Update => "Update" ,
251253 WriteOp :: Ctas => "Ctas" ,
252254 WriteOp :: Truncate => "Truncate" ,
255+ WriteOp :: MergeInto ( _) => "MergeInto" ,
253256 }
254257 }
255258}
@@ -291,6 +294,62 @@ impl Display for InsertOp {
291294 }
292295}
293296
297+ /// Describes a MERGE INTO operation's parameters.
298+ ///
299+ /// This is carried inside `WriteOp::MergeInto` and contains
300+ /// the ON condition and WHEN clauses that the TableProvider
301+ /// needs to execute the merge.
302+ #[ derive( Debug , Clone , PartialEq , Eq , PartialOrd , Hash ) ]
303+ pub struct MergeIntoOp {
304+ /// The join condition from `ON <expr>`.
305+ /// Kept as a general logical Expr; downstream providers
306+ /// (e.g., Iceberg) can decompose into column pairs if needed.
307+ pub on : Expr ,
308+ /// The WHEN clauses, in the order they appeared in the SQL.
309+ pub clauses : Vec < MergeIntoClause > ,
310+ }
311+
312+ /// A single WHEN clause within a MERGE INTO statement.
313+ #[ derive( Debug , Clone , PartialEq , Eq , PartialOrd , Hash ) ]
314+ pub struct MergeIntoClause {
315+ /// Whether this fires on matched or unmatched rows.
316+ pub kind : MergeIntoClauseKind ,
317+ /// Optional additional predicate (`AND <expr>`).
318+ pub predicate : Option < Expr > ,
319+ /// The action to take.
320+ pub action : MergeIntoAction ,
321+ }
322+
323+ /// Which rows a MERGE WHEN clause applies to.
324+ #[ derive( Debug , Clone , Copy , PartialEq , Eq , PartialOrd , Hash ) ]
325+ pub enum MergeIntoClauseKind {
326+ /// WHEN MATCHED
327+ Matched ,
328+ /// WHEN NOT MATCHED (synonymous with NOT MATCHED BY TARGET)
329+ NotMatched ,
330+ /// WHEN NOT MATCHED BY TARGET
331+ NotMatchedByTarget ,
332+ /// WHEN NOT MATCHED BY SOURCE
333+ NotMatchedBySource ,
334+ }
335+
336+ /// The action for a single WHEN clause.
337+ #[ derive( Debug , Clone , PartialEq , Eq , PartialOrd , Hash ) ]
338+ pub enum MergeIntoAction {
339+ /// UPDATE SET col1 = expr1, col2 = expr2, ...
340+ /// Stored as (column_name, value_expr) pairs.
341+ Update ( Vec < ( String , Expr ) > ) ,
342+ /// INSERT (col1, col2, ...) VALUES (expr1, expr2, ...)
343+ /// `columns` may be empty (meaning all columns).
344+ /// `values` contains one expression per inserted column.
345+ Insert {
346+ columns : Vec < String > ,
347+ values : Vec < Expr > ,
348+ } ,
349+ /// DELETE (no additional data needed)
350+ Delete ,
351+ }
352+
294353fn make_count_schema ( ) -> DFSchemaRef {
295354 Arc :: new (
296355 Schema :: new ( vec ! [ Field :: new( "count" , DataType :: UInt64 , false ) ] )
0 commit comments