spark-connect

An idiomatic, SQL-first Rust client for Apache Spark Connect.
This crate is an async layer over the official
apache-spark-connect client,
whose API is synchronous. It brings the whole official API into async code and
adds a sqlx-style interface for binding
parameters safely.
Changelog
✨ Features
- ⚙️ Spark-compatible connection builder (
sc://host:portformat); - 🪶 Async execution on
tokio, for the whole official API; - 🧩 Parameterized queries, bound through the
ToLiteraltrait; - 🧾 Results as Arrow
RecordBatches, typedRows, or a row stream;
Getting Started
use SparkSession;
async
It's that simple!
🧩 Parameterized Queries
The SparkSession::query method uses the ToLiteral trait to bind each
parameter as a Spark literal, so values never reach the server as SQL text:
// This
let batches = session
.query
.bind
.bind
.execute
.await?;
// is the same as this
use ;
let batches = session
.run
.await?;
bind accepts Rust primitives, String/&str, Vec<u8> and, with the
chrono feature, NaiveDate and NaiveDateTime. Pass a
LiteralExpression
directly for decimals, arrays, maps, structs and typed nulls.
Named parameters and the rest of the official SQL API are reached through
sql_with_args,
as above.
🧰 DataFrames
The official client's DataFrame API is used as it is: transformations only
build a plan, and each action has an _async counterpart in the
prelude, run off the async executor:
use *;
let df = spark.sql?.filter;
let adults = df.count_async.await?;
let rows = df.collect_async.await?;
This crate re-exports the official crate's items - DataFrame, Column,
functions, col, lit, ... - alongside its own. Anything else in the
official API is reachable through SparkSession::run.
⚖️ Trade-offs
The official client blocks on a runtime of its own, so every call that reaches the server runs on tokio's blocking thread pool. In practice:
- Dropping a future does not cancel the query. A timed-out or
abandoned action runs to completion on the server and its result is
discarded. Use
SparkSession::interrupt_allorSparkSession::interrupt_tagto stop work on the server. - Each in-flight action holds a blocking thread. Tokio's pool allows 512 by default, far above typical Spark concurrency.
- The official synchronous actions remain callable -
df.collect()next todf.collect_async()- and panic when called from an async task. - Large results can be streamed row by row with
to_local_iterator_async.
🧠 Concepts
SparkSession- the main entry point for executing SQL queries and managing a session.SqlQueryBuilder- helper for binding parameters and executing queries.ext- async counterparts of the official types' actions.
⚙️ Requirements
- A running Spark Connect server (Spark 4.0+);
- Network access to the configured
sc://endpoint; tokioruntime;protocat build time.
Installing protoc
apache-spark-connect-proto compiles the Connect .proto files while it
builds, using the Protocol Buffers compiler:
If it is not on the PATH, point PROTOC at it:
PROTOC=/path/to/protoc
In GitHub Actions:
- uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
🔒 Example Connection Strings
sc://localhost:15002
sc://spark-cluster:15002/;user_id=francisco
sc://10.0.0.5:15002/;session_id=abc123;user_agent=my-app
📘 Learn More
apache-spark-connectAPI reference - the DataFrame, column and function APIs this crate builds on;- Apache Spark Connect documentation;
- Spark Connect client connection string.
© 2025 Francisco A. B. Sampaio. Licensed under the Apache License 2.0.
This project is not affiliated with, endorsed by, or sponsored by the Apache Software Foundation. “Apache”, “Apache Spark”, and “Spark Connect” are trademarks of the Apache Software Foundation.