You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Is your feature request related to a problem or challenge?
LazyMemoryExec is the last built-in ExecutionPlan that datafusion-proto serializes through a central downcast_ref chain. The EPIC #23494 lists it under "Intentionally left as typed dispatch", because an ExecutionPlan-keyed hook cannot reach the generator types. This issue is the follow-up design that section calls for.
Three things follow from the chain staying where it is.
use datafusion_functions_table::generate_series::{Empty,GenSeriesArgs,GenerateSeriesTable,GenericSeriesState,TimestampValue,};
2. Encode is a downcast_ref ladder over generator types.try_from_lazy_memory_exec reads exec.generators(), takes the read lock, and tries Empty, then GenericSeriesState<i64>, then GenericSeriesState<TimestampValue>, then the date state. A generator it does not recognise returns Ok(None) and the plan silently falls through to the PhysicalExtensionCodec.
3. A new generator is silently unserializable. Adding a generator to generate_series.rs compiles, runs, and serializes to nothing, because nothing connects the two files. This is the failure mode #21835 described for PhysicalExpr and #23494 for plans.
Reproduction
LazyMemoryExec with more than one generator does not serialize at all, and the error blames DataFusion for it:
let table = GenerateSeriesTable::new(Arc::clone(&schema),GenSeriesArgs::Int64Args{start:1,end:3,step:1,include_end:true,name:"generate_series"},);let one = table.as_generator(8192)?;let two = table.as_generator(8192)?;let plan:Arc<dynExecutionPlan> =
Arc::new(LazyMemoryExec::try_new(schema,vec![one, two])?);// One generator encodes. Two do not.PhysicalPlanNode::try_from_physical_plan(plan,&DefaultPhysicalExtensionCodec{})?;
Internal error: Unsupported plan and extension codec failed with
[This feature is not implemented: PhysicalExtensionCodec is not provided]. Plan: LazyMemoryExec { … }.
This issue was likely caused by a bug in DataFusion's code. Please help us to resolve this by
filing a bug report in our issue tracker: https://github.com/apache/datafusion/issues
The cause is this line in try_from_lazy_memory_exec:
Ok(None) sends the plan on to codec.try_encode, which the default codec refuses, and the caller reports an Internal error that asks the user to file a bug. Nothing in LazyMemoryExec says it is unserializable with more than one generator.
Describe the solution you'd like
Give the generator the hook, not the plan. LazyBatchGenerator lives in datafusion-physical-plan, and the concrete generators live in datafusion-functions-table, so a hook on the generator trait puts the wire format in the same crate as the type that owns it:
An encode hook on LazyBatchGenerator, defaulting to Ok(None), matching ExecutionPlan::try_to_proto.
A decode path keyed by the generator, reached from the PhysicalPlanType::GenerateSeries arm.
LazyMemoryExec::try_to_proto then encodes its schema and asks each generator for its payload, which also removes the one-generator limit above.
Both downcast_ref ladders and the datafusion-functions-table dependency then leave datafusion-proto.
The open design question is what the decode side keys on. The GenerateSeries wire variant is a single message with a oneof over the argument types, so it is not extensible by a third party today. Two options:
Keep GenerateSeries as the only generator variant and dispatch inside datafusion-functions-table. Smallest change, keeps the wire format byte-identical, and does not make generators extensible.
Option 1 looks right until someone needs option 2.
Describe alternatives you've considered
Leave it as is. It works and the wire format is stable. The cost is that datafusion-proto keeps a dependency and two downcast_ref ladders that exist for one plan, and a new generator keeps failing silently.
Put the hook on LazyMemoryExec. This is what the EPIC rejected: datafusion-functions-table sits above datafusion-physical-plan, so LazyMemoryExec cannot name the generator types without a cycle.
Is your feature request related to a problem or challenge?
LazyMemoryExecis the last built-inExecutionPlanthatdatafusion-protoserializes through a centraldowncast_refchain. The EPIC #23494 lists it under "Intentionally left as typed dispatch", because anExecutionPlan-keyed hook cannot reach the generator types. This issue is the follow-up design that section calls for.Three things follow from the chain staying where it is.
1.
datafusion-protodepends ondatafusion-functions-tableonly for this.datafusion/proto/src/physical_plan/mod.rs:45imports five items to do it:2. Encode is a
downcast_refladder over generator types.try_from_lazy_memory_execreadsexec.generators(), takes the read lock, and triesEmpty, thenGenericSeriesState<i64>, thenGenericSeriesState<TimestampValue>, then the date state. A generator it does not recognise returnsOk(None)and the plan silently falls through to thePhysicalExtensionCodec.3. A new generator is silently unserializable. Adding a generator to
generate_series.rscompiles, runs, and serializes to nothing, because nothing connects the two files. This is the failure mode #21835 described forPhysicalExprand #23494 for plans.Reproduction
LazyMemoryExecwith more than one generator does not serialize at all, and the error blames DataFusion for it:The cause is this line in
try_from_lazy_memory_exec:Ok(None)sends the plan on tocodec.try_encode, which the default codec refuses, and the caller reports anInternalerror that asks the user to file a bug. Nothing inLazyMemoryExecsays it is unserializable with more than one generator.Describe the solution you'd like
Give the generator the hook, not the plan.
LazyBatchGeneratorlives indatafusion-physical-plan, and the concrete generators live indatafusion-functions-table, so a hook on the generator trait puts the wire format in the same crate as the type that owns it:LazyBatchGenerator, defaulting toOk(None), matchingExecutionPlan::try_to_proto.PhysicalPlanType::GenerateSeriesarm.LazyMemoryExec::try_to_protothen encodes its schema and asks each generator for its payload, which also removes the one-generator limit above.Both
downcast_refladders and thedatafusion-functions-tabledependency then leavedatafusion-proto.The open design question is what the decode side keys on. The
GenerateSerieswire variant is a single message with aoneofover the argument types, so it is not extensible by a third party today. Two options:GenerateSeriesas the only generator variant and dispatch insidedatafusion-functions-table. Smallest change, keeps the wire format byte-identical, and does not make generators extensible.ExecutionPlans to decode via a per-type registry instead ofPhysicalExtensionCodec#24625. Larger, and it makes third-party generators serializable, which nothing asks for yet.Option 1 looks right until someone needs option 2.
Describe alternatives you've considered
datafusion-protokeeps a dependency and twodowncast_refladders that exist for one plan, and a new generator keeps failing silently.LazyMemoryExec. This is what the EPIC rejected:datafusion-functions-tablesits abovedatafusion-physical-plan, soLazyMemoryExeccannot name the generator types without a cycle.Additional context
PhysicalExprequivalent: [EPIC] PortPhysicalExprimplementations to usetry_from_proto/try_to_proto#22418.ExecutionPlans to decode via a per-type registry instead ofPhysicalExtensionCodec#24625 and feat: allow extensionExecutionPlans to decode via a per-type registry instead ofPhysicalExtensionCodec#24631.