Skip to content

Conversation

@timsaucer
Copy link
Member

Which issue does this PR close?

Addresses part of #18671 but does not close it.

Rationale for this change

We have a major restriction in our current FFI crates. They use protobuf encoding and decoding to pass around logical expressions. This encoding and decoding will fail when we encounter a user defined function because we only currently use the default extension codecs. This PR introduces the FFI variants for both logical and physical extension codecs. A follow on PR will enable these in our code base, on order to keep these PRs to a reasonable length for review.

In addition to the benefit to the FFI crate for passing expressions, this will also enable sending plans across FFI boundaries, which may be useful for datafusion-distributed.

What changes are included in this PR?

  • Implement FFI_LogicalExtensionCodec
  • Implement FFI_PhysicalExtensionCodec

This PR does not use these structures in the current code. That is coming as part of a later PR in an effort to keep the size of the PRs small for effective code review.

Are these changes tested?

Unit tests are added. Coverage report:

Screenshot 2025-12-03 at 4 37 23 PM

Are there any user-facing changes?

No.

@github-actions github-actions bot added the ffi Changes to the ffi crate label Dec 3, 2025
Comment on lines +50 to +58
datafusion-catalog = { workspace = true }
datafusion-common = { workspace = true }
datafusion-datasource = { workspace = true }
datafusion-execution = { workspace = true }
datafusion-expr = { workspace = true }
datafusion-functions-aggregate-common = { workspace = true }
datafusion-physical-expr = { workspace = true }
datafusion-physical-expr-common = { workspace = true }
datafusion-physical-plan = { workspace = true }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the last PR we are adding these crates in explicitly to reduce the PR size when we remove datafusion dependency.

@timsaucer timsaucer marked this pull request as ready for review December 4, 2025 14:28
@timsaucer
Copy link
Member Author

@renato2099 @comphead @paleolimbot @ntjohnson1 Here is the next up in the list of PRs for the FFI epic. It's a bit long, but portions of it are near identical since the logical and physical codec traits have a lot of overlap. This one is basically pure addition and the next PRs start to tie it all together.


#[cfg(test)]
mod tests {
pub(crate) mod tests {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the modifier change because some part of tests codebase reused elsewhere?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, so that I can reuse the EmptyExec in other unit tests, specifically in datafusion/ffi/src/proto/physical_extension_codec.rs

Copy link
Contributor

@comphead comphead left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @timsaucer I had a first glance and its good, can you please educate me on example how encoding/decoding works in this case?

I assume it is still protobuf encoding? but slightly confused of function encoding

@timsaucer
Copy link
Member Author

Thanks @timsaucer I had a first glance and its good, can you please educate me on example how encoding/decoding works in this case?

I assume it is still protobuf encoding? but slightly confused of function encoding

Yes, this is used protobuf encoding. There is an example in datafusion/proto/tests/cases/roundtrip_logical_plan.rs of how you can use extension codecs to encode and decode custom functions. All of the default functions that you get with SessionContext::new() can encode/decode without these codecs. This is just in case you want to add in some custom functions.

Copy link
Member

@paleolimbot paleolimbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool!

Mostly just optional suggestions on how to maybe make the tests easier to read 🙂 . This is great!

_table_ref: &TableReference,
schema: SchemaRef,
_ctx: &TaskContext,
) -> datafusion_common::Result<Arc<dyn TableProvider>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't matter really but I think you have Result already in scope (for these and all the tests)

Suggested change
) -> datafusion_common::Result<Arc<dyn TableProvider>> {
) -> Result<Arc<dyn TableProvider>> {

Comment on lines +397 to +403
fn try_decode_file_format(
&self,
_buf: &[u8],
_ctx: &TaskContext,
) -> Result<Arc<dyn FileFormatFactory>> {
not_impl_err!("FFI does not support decode_file_format")
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buf.push(Self::MAGIC_NUMBER);

if !node.as_any().is::<MemTable>() {
return exec_err!("TestExtensionCodec only expects Abs UDF");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this read MemTable and not Abs UDF?

Comment on lines +444 to +452
if buf[0] != Self::MAGIC_NUMBER {
return exec_err!(
"TestExtensionCodec input buffer does not start with magic number"
);
}

if buf.len() != 2 || buf[1] != Self::MAGIC_NUMBER {
return exec_err!("TestExtensionCodec unable to decode udf");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason these checks need to be separated (or alternatively, is there a reason that two identical 127s are better than a single 127 here)?

_name: &str,
buf: &[u8],
) -> datafusion_common::Result<Arc<ScalarUDF>> {
if buf[0] != Self::MAGIC_NUMBER {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be more likely to catch a typo in the code that mixed up udf/udaf/udwf if it used a different magic number for each of them.

Copy link
Contributor

@comphead comphead left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the PR overall is good to go, thanks @timsaucer and @paleolimbot for the review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ffi Changes to the ffi crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants