Visitar URL original
feat: Alternative table provider as spark data source by timsaucer · Pull Request #110 · apache/datafusion-java · GitHub
Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3cf0e81
build: consolidate Rust crates into a Cargo workspace + extract nativ…
timsaucer Jun 12, 2026
fd4dd74
build: inherit crate version/edition from workspace; publish datafusi…
timsaucer Jun 12, 2026
2e50d6e
build: drop forward-referencing RAT excludes from foundation split
timsaucer Jun 12, 2026
9d2096c
build: drop overbroad **/*.md RAT exclude
timsaucer Jun 12, 2026
2514f56
docs: add README for datafusion-jni-common
timsaucer Jun 12, 2026
3ff4fda
fix: scope foundation docs/excludes to what 1/6 ships
timsaucer Jun 12, 2026
bb023eb
build: point release scripts at workspace rust-target/ dir
timsaucer Jun 12, 2026
9e9de7d
build: mark datafusion-jni-common publish = false
timsaucer Jun 12, 2026
e87fd64
feat: add plain-C scan ABI crate over Arrow C Data/Stream
timsaucer Jun 15, 2026
2598322
feat: add scan_config / scan_request protobuf for the C scan ABI
timsaucer Jun 15, 2026
cfaac8d
build: compile scan protobufs into datafusion-scan-ffi
timsaucer Jun 15, 2026
4cded33
feat: add datafusion.listing provider decoding ScanConfig
timsaucer Jun 15, 2026
f95191d
feat: add JNI shim over the plain-C scan ABI
timsaucer Jun 15, 2026
bc91ff9
test: end-to-end JVM scan through the JNI shim
timsaucer Jun 15, 2026
436e2b3
test: projection and filter pushdown from Java
timsaucer Jun 15, 2026
d5eefee
feat: wire row limit through the scan ABI
timsaucer Jun 15, 2026
0b24d7c
feat: add Spark DataSourceV2 connector
timsaucer Jun 15, 2026
1a1d0ec
feat: columnar reader on Spark 4.0 with provided Arrow
timsaucer Jun 15, 2026
573b438
feat: push Spark limit into the scan (SupportsPushDownLimit)
timsaucer Jun 15, 2026
a378bb4
docs: add DataFusion-Spark DataSource design doc
timsaucer Jun 15, 2026
301fd13
docs: add ADBC reuse analysis to Spark design doc
timsaucer Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: add plain-C scan ABI crate over Arrow C Data/Stream
Introduce datafusion-scan-ffi: a cdylib exposing a DataFusion
TableProvider scan through extern "C" entrypoints that speak only C
primitives and the standard Arrow C Data/Stream interface
(ArrowSchema/ArrowArrayStream). No JVM/JNI dependency, so the surface is
consumable from Java (via a thin shim or FFM), Python, Go, or Rust, and
is a candidate to live closer to DataFusion proper.

This is the JNI-free reshaping of PR #103's scan logic per review
feedback on PR #104: providers are compiled in and registered by name
(approach A), filters cross as datafusion.LogicalExprNode protobufs
(shared vocabulary with datafusion-ffi/Comet), and each scanned
partition is handed back as a zero-copy FFI_ArrowArrayStream.

- abi.rs: df_scan_{schema,create,partition_count,execute_partition,
  execute,close}, df_error_free, df_scan_abi_version
- scan.rs: build -> register -> project -> filter -> plan core
- registry.rs: name-keyed provider builders
- reader.rs: SendableRecordBatchStream -> panic-safe RecordBatchReader
- include/datafusion_scan.h: the C header
- tests/roundtrip.rs: drives the ABI and re-imports the stream via the
  Arrow C Stream interface, no JVM involved (6 tests)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
  • Loading branch information
timsaucer and claude committed Jun 15, 2026
commit e87fd64d46d05dd9de1d34b60fedf59246e21aab
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ resolver = "2"
members = [
"native",
"native-common",
"native-ffi",
]

# Shared package metadata so every crate moves in lock step. Members inherit
Expand Down
62 changes: 62 additions & 0 deletions native-ffi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

[package]
name = "datafusion-scan-ffi"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
# Not published yet; this is the in-tree home of the plain-C scan ABI while it
# stabilizes. The intent is for this surface to eventually live in DataFusion
# proper (it has no JVM/JNI dependency), so keep it free of anything
# Java-specific.
publish = false

[lib]
# `cdylib` -> the shippable plain-C shared library (`libdatafusion_scan_ffi`).
# `rlib` -> lets a downstream cdylib statically link this crate, register
# its own providers, and re-export the `df_scan_*` symbols; also
# gives `cargo test` a Rust harness that round-trips the ABI with
# no JVM in sight.
crate-type = ["cdylib", "rlib"]

[features]
# A built-in in-memory provider builder registered under `datafusion.memory`,
# used by the round-trip tests and handy as a reference builder. Off by default
# so a production cdylib only carries the providers it registers itself.
demo-providers = []

[dependencies]
# The arrow C Data / C Stream interface types are the entire data plane of this
# ABI. `ffi` pulls in both `arrow::ffi` (FFI_ArrowSchema/Array) and
# `arrow::ffi_stream` (FFI_ArrowArrayStream). Same crate+version DataFusion
# links, so the types unify.
arrow = { workspace = true }
datafusion = { workspace = true }
# Pushed filters arrive as serialized `datafusion.LogicalExprNode` protobufs --
# the same vocabulary `datafusion-ffi` already uses, so the encoder is shared
# with any future Comet path.
datafusion-proto = { workspace = true }
futures = { workspace = true }
prost = { workspace = true }
tokio = { workspace = true }

[dev-dependencies]
# Round-trip tests import the produced FFI_ArrowArrayStream back into Rust via
# the same C Stream interface a Java/Python/Go consumer would use.
datafusion-scan-ffi = { path = ".", features = ["demo-providers"] }
115 changes: 115 additions & 0 deletions native-ffi/include/datafusion_scan.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

// Plain-C scan ABI over the Arrow C Data / C Stream interface.
//
// The only "rich" types crossing this boundary are the standard Arrow C
// structs `ArrowSchema` and `ArrowArrayStream` (from Arrow's abi.h), which any
// Arrow implementation can produce/consume. Everything else is C primitives
// and borrowed (ptr, len) views. No JVM/JNI types appear here, by design.

#ifndef DATAFUSION_SCAN_H
#define DATAFUSION_SCAN_H

#include <stddef.h>
#include <stdint.h>

#include "arrow/c/abi.h" // struct ArrowSchema, struct ArrowArrayStream

#ifdef __cplusplus
extern "C" {
#endif

// --- Status codes ----------------------------------------------------------
// 0 on success; nonzero classifies the failure. On error the call also writes
// a malloc'd, NUL-terminated message to *out_err (free with df_error_free).
typedef enum {
DF_OK = 0,
DF_INVALID_ARGUMENT = 1,
DF_UNKNOWN_PROVIDER = 2,
DF_PROVIDER_BUILD = 3,
DF_PLANNING = 4,
DF_EXECUTION = 5,
DF_PANIC = 6,
DF_INTERNAL = 7
} DfStatus;

// --- Borrowed input views (caller owns the memory) -------------------------
typedef struct {
const uint8_t* ptr; // UTF-8, not NUL-terminated; may be null if len == 0
size_t len;
} DfStr;

typedef struct {
const uint8_t* ptr; // may be null if len == 0
size_t len;
} DfBytes;

typedef struct {
DfStr key;
DfStr value;
} DfKeyValue;

// Opaque planned-scan handle.
typedef struct DfScanHandle DfScanHandle;

// --- Lifecycle / versioning ------------------------------------------------

// ABI major version; compare before any other call.
uint64_t df_scan_abi_version(void);

// Free a message previously written to an out_err argument (null-safe).
void df_error_free(char* err);

// --- Scan API --------------------------------------------------------------

// Probe a provider's output schema into the caller-allocated out_schema.
int32_t df_scan_schema(DfStr provider, DfBytes options, DfBytes partition,
struct ArrowSchema* out_schema, char** out_err);

// Plan a scan. On success writes an owned handle to *out_handle (release with
// df_scan_close). projection is an array of column-name DfStr (empty = all);
// filters is an array of serialized datafusion.LogicalExprNode DfBytes;
// target_partitions / batch_size <= 0 keep DataFusion defaults.
int32_t df_scan_create(DfStr provider, DfBytes options, DfBytes partition,
int32_t target_partitions, int32_t batch_size,
const DfKeyValue* config_overrides, size_t config_overrides_len,
const DfStr* projection, size_t projection_len,
const DfBytes* filters, size_t filters_len,
DfScanHandle** out_handle, char** out_err);
Comment on lines +84 to +94

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think you can do this without the C symbols (just structs, like the Arrow C Data/Stream interfaces). The reason that's nice is that symbols can be hard to guarantee uniqueness (what if you have two Rust crates that want to export table provider and they're statically linked in the same pile of crates?). This is roughly how datafusion-python works with the generated FFI from datafusion-ffi.

struct DfScanHandle {
    int32_t (*partition_count)(const DfScanHandle* self);
    // -1 partition ID for coalesced stream
    int32_t (*execute)(const DfScanHandle* self, int32_t partition,
                                  struct ArrowArrayStream* out_stream);
    const char* (*get_last_error)(struct DfScanHandle* self);
    void* private_data;
    void (*release)(struct DfScanHandle* self);
};

struct DfSimpleTableProvider {
  int32_t (*schema)(struct DFTableProvider* self, struct ArrowSchema* out_schema);
  int32_t (*scan_create)(DfStr provider, DfBytes options, DfBytes partition,
                       int32_t target_partitions, int32_t batch_size, int64_t limit,
                       const DfKeyValue* config_overrides, size_t config_overrides_len,
                       const DfStr* projection, size_t projection_len,
                       const DfBytes* filters, size_t filters_len,
                       DfScanHandle** out_handle);
  const char* (*get_last_error)(struct DFTableProvider* self);
  void* private_data;
  void (*release)(struct DFTableProvider* self);
};

For Spark, a TableProvider exporter could define an entrypoint and build a cdylib. The entrypoint would have to have a common signature (like how AdbcInitFunc works).

int32_t MyProviderInit(void* table_provider, const char* name, int32_t df_abi_version);

...but Python packages that export one of these could just work with capsules.


// Output partition count of the planned scan.
int32_t df_scan_partition_count(const DfScanHandle* handle, int32_t* out_count,
char** out_err);

// Execute one partition into the caller-allocated Arrow C Stream.
int32_t df_scan_execute_partition(const DfScanHandle* handle, int32_t partition,
struct ArrowArrayStream* out_stream, char** out_err);

// Execute the whole plan as a single coalesced Arrow C Stream.
int32_t df_scan_execute(const DfScanHandle* handle,
struct ArrowArrayStream* out_stream, char** out_err);

// Drop a planned scan (null-safe). Must not race an in-flight execute on the
// same handle.
void df_scan_close(DfScanHandle* handle);

#ifdef __cplusplus
} // extern "C"
#endif

#endif // DATAFUSION_SCAN_H
Loading