Skip to content

Commit 3b9ebdb

Browse files
committed
Move MERGE INTO FFI support to follow-up
1 parent c4c0544 commit 3b9ebdb

4 files changed

Lines changed: 12 additions & 416 deletions

File tree

datafusion/ffi/src/table_provider.rs

Lines changed: 6 additions & 238 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,18 @@ use arrow::datatypes::SchemaRef;
2222
use async_ffi::{FfiFuture, FutureExt};
2323
use async_trait::async_trait;
2424
use datafusion_catalog::{Session, TableProvider};
25+
use datafusion_common::Statistics;
2526
use datafusion_common::error::{DataFusionError, Result};
26-
use datafusion_common::{DFSchemaRef, Statistics};
2727
use datafusion_execution::TaskContext;
28-
use datafusion_expr::dml::{InsertOp, MergeIntoClause, MergeIntoOp};
28+
use datafusion_expr::dml::InsertOp;
2929
use datafusion_expr::{Expr, TableProviderFilterPushDown, TableType};
3030
use datafusion_physical_plan::ExecutionPlan;
31-
use datafusion_proto::logical_plan::from_proto::{parse_exprs, parse_merge_into_op};
32-
use datafusion_proto::logical_plan::to_proto::{
33-
serialize_exprs, serialize_merge_into_op,
34-
};
31+
use datafusion_proto::logical_plan::from_proto::parse_exprs;
32+
use datafusion_proto::logical_plan::to_proto::serialize_exprs;
3533
use datafusion_proto::logical_plan::{
3634
DefaultLogicalExtensionCodec, LogicalExtensionCodec,
3735
};
38-
use datafusion_proto::protobuf::{DfSchema, LogicalExprList, MergeIntoOpNode};
36+
use datafusion_proto::protobuf::LogicalExprList;
3937
use prost::Message;
4038

4139
use stabby::vec::Vec as SVec;
@@ -137,17 +135,6 @@ pub struct FFI_TableProvider {
137135
insert_op: FFI_InsertOp,
138136
) -> FfiFuture<FFI_Result<FFI_ExecutionPlan>>,
139137

140-
/// Merge source rows into the table. The schema and MERGE operation are
141-
/// prost-encoded to preserve logical qualifiers and expressions across the
142-
/// FFI boundary.
143-
merge_into: unsafe extern "C" fn(
144-
provider: &Self,
145-
session: FFI_SessionRef,
146-
source: &FFI_ExecutionPlan,
147-
merge_schema_serialized: SVec<u8>,
148-
merge_op_serialized: SVec<u8>,
149-
) -> FfiFuture<FFI_Result<FFI_ExecutionPlan>>,
150-
151138
/// Snapshot the provider's table-level statistics. [`FFI_Option::None`]
152139
/// corresponds to [`TableProvider::statistics`] returning `None`;
153140
/// `Some(bytes)` is a prost-encoded `datafusion_proto_common::Statistics`.
@@ -349,60 +336,6 @@ unsafe extern "C" fn insert_into_fn_wrapper(
349336
.into_ffi()
350337
}
351338

352-
unsafe extern "C" fn merge_into_fn_wrapper(
353-
provider: &FFI_TableProvider,
354-
session: FFI_SessionRef,
355-
source: &FFI_ExecutionPlan,
356-
merge_schema_serialized: SVec<u8>,
357-
merge_op_serialized: SVec<u8>,
358-
) -> FfiFuture<FFI_Result<FFI_ExecutionPlan>> {
359-
let task_ctx: Result<Arc<TaskContext>, DataFusionError> =
360-
(&provider.logical_codec.task_ctx_provider).try_into();
361-
let runtime = provider.runtime().clone();
362-
let logical_codec: Arc<dyn LogicalExtensionCodec> = (&provider.logical_codec).into();
363-
let internal_provider = Arc::clone(provider.inner());
364-
let source = source.clone();
365-
366-
async move {
367-
let mut foreign_session = None;
368-
let session = sresult_return!(
369-
session
370-
.as_local()
371-
.map(Ok::<&(dyn Session + Send + Sync), DataFusionError>)
372-
.unwrap_or_else(|| {
373-
foreign_session = Some(ForeignSession::try_from(&session)?);
374-
Ok(foreign_session.as_ref().unwrap())
375-
})
376-
);
377-
378-
let source = sresult_return!(<Arc<dyn ExecutionPlan>>::try_from(&source));
379-
let task_ctx = sresult_return!(task_ctx);
380-
381-
let merge_schema =
382-
sresult_return!(DfSchema::decode(merge_schema_serialized.as_ref()));
383-
let merge_schema: DFSchemaRef = sresult_return!(
384-
DFSchemaRef::try_from(merge_schema)
385-
.map_err(|e| DataFusionError::Plan(e.to_string()))
386-
);
387-
388-
let merge_op =
389-
sresult_return!(MergeIntoOpNode::decode(merge_op_serialized.as_ref()));
390-
let MergeIntoOp { on, clauses } = sresult_return!(
391-
parse_merge_into_op(&merge_op, task_ctx.as_ref(), logical_codec.as_ref())
392-
.map_err(|e| DataFusionError::Plan(e.to_string()))
393-
);
394-
395-
let plan = sresult_return!(
396-
internal_provider
397-
.merge_into(session, source, merge_schema, on, clauses)
398-
.await
399-
);
400-
401-
FFI_Result::Ok(FFI_ExecutionPlan::new(plan, runtime.clone()))
402-
}
403-
.into_ffi()
404-
}
405-
406339
unsafe extern "C" fn release_fn_wrapper(provider: &mut FFI_TableProvider) {
407340
unsafe {
408341
debug_assert!(!provider.private_data.is_null());
@@ -428,7 +361,6 @@ unsafe extern "C" fn clone_fn_wrapper(provider: &FFI_TableProvider) -> FFI_Table
428361
table_type: table_type_fn_wrapper,
429362
supports_filters_pushdown: provider.supports_filters_pushdown,
430363
insert_into: provider.insert_into,
431-
merge_into: provider.merge_into,
432364
statistics: statistics_fn_wrapper,
433365
logical_codec: provider.logical_codec.clone(),
434366
clone: clone_fn_wrapper,
@@ -490,7 +422,6 @@ impl FFI_TableProvider {
490422
false => None,
491423
},
492424
insert_into: insert_into_fn_wrapper,
493-
merge_into: merge_into_fn_wrapper,
494425
statistics: statistics_fn_wrapper,
495426
logical_codec,
496427
clone: clone_fn_wrapper,
@@ -646,63 +577,16 @@ impl TableProvider for ForeignTableProvider {
646577

647578
Ok(plan)
648579
}
649-
650-
async fn merge_into(
651-
&self,
652-
session: &dyn Session,
653-
source: Arc<dyn ExecutionPlan>,
654-
merge_schema: DFSchemaRef,
655-
on: Expr,
656-
clauses: Vec<MergeIntoClause>,
657-
) -> Result<Arc<dyn ExecutionPlan>> {
658-
let session = FFI_SessionRef::new(session, None, self.0.logical_codec.clone());
659-
660-
let rc = Handle::try_current().ok();
661-
let source = FFI_ExecutionPlan::new(source, rc);
662-
663-
let merge_schema = DfSchema::try_from(&merge_schema)
664-
.map_err(|e| DataFusionError::Plan(e.to_string()))?;
665-
let merge_schema_serialized: SVec<u8> =
666-
merge_schema.encode_to_vec().into_iter().collect();
667-
668-
let codec: Arc<dyn LogicalExtensionCodec> = (&self.0.logical_codec).into();
669-
let merge_op =
670-
serialize_merge_into_op(&MergeIntoOp { on, clauses }, codec.as_ref())
671-
.map_err(|e| DataFusionError::Plan(e.to_string()))?;
672-
let merge_op_serialized = merge_op.encode_to_vec().into_iter().collect();
673-
674-
let plan = unsafe {
675-
let maybe_plan = (self.0.merge_into)(
676-
&self.0,
677-
session,
678-
&source,
679-
merge_schema_serialized,
680-
merge_op_serialized,
681-
)
682-
.await;
683-
684-
<Arc<dyn ExecutionPlan>>::try_from(&df_result!(maybe_plan)?)?
685-
};
686-
687-
Ok(plan)
688-
}
689580
}
690581

691582
#[cfg(test)]
692583
mod tests {
693-
use std::sync::Mutex;
694-
695-
use arrow::datatypes::{DataType, Field, Schema};
696-
use datafusion::common::{Column, DFSchema};
697-
use datafusion::logical_expr::dml::{MergeIntoAction, MergeIntoClauseKind};
698-
use datafusion::physical_plan::empty::EmptyExec;
584+
use arrow::datatypes::Schema;
699585
use datafusion::prelude::{SessionContext, col, lit};
700586
use datafusion_execution::TaskContextProvider;
701587

702588
use super::*;
703589

704-
type CapturedMerge = (usize, usize, String, Vec<String>);
705-
706590
fn create_test_table_provider() -> Result<Arc<dyn TableProvider>> {
707591
use arrow::datatypes::Field;
708592
use datafusion::arrow::array::Float32Array;
@@ -729,122 +613,6 @@ mod tests {
729613
)?))
730614
}
731615

732-
#[derive(Debug)]
733-
struct CaptureMergeProvider {
734-
schema: SchemaRef,
735-
captured: Mutex<Option<CapturedMerge>>,
736-
}
737-
738-
#[async_trait]
739-
impl TableProvider for CaptureMergeProvider {
740-
fn schema(&self) -> SchemaRef {
741-
Arc::clone(&self.schema)
742-
}
743-
744-
fn table_type(&self) -> TableType {
745-
TableType::Base
746-
}
747-
748-
async fn scan(
749-
&self,
750-
_state: &dyn Session,
751-
_projection: Option<&Vec<usize>>,
752-
_filters: &[Expr],
753-
_limit: Option<usize>,
754-
) -> Result<Arc<dyn ExecutionPlan>> {
755-
Ok(Arc::new(EmptyExec::new(Arc::clone(&self.schema))))
756-
}
757-
758-
async fn merge_into(
759-
&self,
760-
state: &dyn Session,
761-
source: Arc<dyn ExecutionPlan>,
762-
merge_schema: DFSchemaRef,
763-
on: Expr,
764-
clauses: Vec<MergeIntoClause>,
765-
) -> Result<Arc<dyn ExecutionPlan>> {
766-
let target_id =
767-
merge_schema.index_of_column(&Column::new(Some("target"), "id"))?;
768-
let source_id =
769-
merge_schema.index_of_column(&Column::new(Some("source"), "id"))?;
770-
let physical_on = state.create_physical_expr(on, &merge_schema)?;
771-
let clauses = clauses.iter().map(|clause| format!("{clause:?}")).collect();
772-
*self.captured.lock().unwrap() =
773-
Some((target_id, source_id, format!("{physical_on:?}"), clauses));
774-
Ok(source)
775-
}
776-
}
777-
778-
#[tokio::test]
779-
async fn test_round_trip_ffi_table_provider_merge_into() -> Result<()> {
780-
let schema = Arc::new(Schema::new(vec![
781-
Field::new("id", DataType::Int32, false),
782-
Field::new("val", DataType::Int64, true),
783-
]));
784-
let provider = Arc::new(CaptureMergeProvider {
785-
schema: Arc::clone(&schema),
786-
captured: Mutex::new(None),
787-
});
788-
let ctx = Arc::new(SessionContext::new());
789-
let task_ctx_provider = Arc::clone(&ctx) as Arc<dyn TaskContextProvider>;
790-
let task_ctx_provider = FFI_TaskContextProvider::from(&task_ctx_provider);
791-
792-
let mut ffi_provider = FFI_TableProvider::new(
793-
Arc::clone(&provider) as Arc<dyn TableProvider>,
794-
true,
795-
None,
796-
task_ctx_provider,
797-
None,
798-
);
799-
ffi_provider.library_marker_id = crate::mock_foreign_marker_id;
800-
let foreign: Arc<dyn TableProvider> = (&ffi_provider).into();
801-
802-
let target_schema = DFSchema::try_from_qualified_schema("target", &schema)?;
803-
let source_schema = DFSchema::try_from_qualified_schema("source", &schema)?;
804-
let merge_schema = Arc::new(target_schema.join(&source_schema)?);
805-
let source: Arc<dyn ExecutionPlan> =
806-
Arc::new(EmptyExec::new(Arc::clone(&schema)));
807-
let clauses = vec![
808-
MergeIntoClause {
809-
kind: MergeIntoClauseKind::Matched,
810-
predicate: Some(col("target.val").is_null()),
811-
action: MergeIntoAction::Update(vec![(
812-
"val".to_string(),
813-
col("source.val"),
814-
)]),
815-
},
816-
MergeIntoClause {
817-
kind: MergeIntoClauseKind::NotMatched,
818-
predicate: None,
819-
action: MergeIntoAction::Insert {
820-
columns: vec!["id".to_string(), "val".to_string()],
821-
values: vec![col("source.id"), col("source.val")],
822-
},
823-
},
824-
];
825-
826-
foreign
827-
.merge_into(
828-
&ctx.state(),
829-
source,
830-
merge_schema,
831-
col("target.id").eq(col("source.id")),
832-
clauses,
833-
)
834-
.await?;
835-
836-
let captured = provider.captured.lock().unwrap();
837-
let (target_id, source_id, physical_on, clauses) =
838-
captured.as_ref().expect("merge_into should be called");
839-
assert_eq!((*target_id, *source_id), (0, 2));
840-
assert!(physical_on.contains("index: 0"));
841-
assert!(physical_on.contains("index: 2"));
842-
assert_eq!(clauses.len(), 2);
843-
assert!(clauses[0].contains("source"));
844-
assert!(clauses[1].contains("Insert"));
845-
Ok(())
846-
}
847-
848616
#[tokio::test]
849617
async fn test_round_trip_ffi_table_provider_scan() -> Result<()> {
850618
let provider = create_test_table_provider()?;

0 commit comments

Comments
 (0)