Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
888da2c
feat(builders): support literals for all Substrait types + registry.i…
nielspardon Jul 2, 2026
ec452ab
refactor(narwhals)!: rename substrait.dataframe module to substrait.n…
nielspardon Jul 2, 2026
13eb715
feat(api): ergonomic native DataFrame/Expr API with full function and…
nielspardon Jul 2, 2026
9cebc71
docs(examples): add api_example, consolidate Narwhals example
nielspardon Jul 2, 2026
157cc31
feat(builders): support join post-filter and offset-only fetch
nielspardon Jul 8, 2026
7e15946
feat(expr): add CASE, IN, modulo/power, and comparison helpers
nielspardon Jul 8, 2026
1184caa
feat(api): expand the DataFrame with set ops, joins, sort, fetch, ren…
nielspardon Jul 8, 2026
f07c897
feat(builders): aggregate grouping sets, measure filters, DISTINCT an…
nielspardon Jul 8, 2026
84b7092
feat(api): rollup/cube/grouping_sets and aggregate FILTER/DISTINCT/or…
nielspardon Jul 8, 2026
98159ee
feat(builders): add virtual-table, local-files and extension-table reads
nielspardon Jul 8, 2026
30ad50c
feat(api): from_records and file/extension read sources
nielspardon Jul 8, 2026
7e16c7b
feat(builders): add subquery expression builders (scalar/EXISTS/IN/AN…
nielspardon Jul 8, 2026
0d6b246
feat(expr): nested field access and subquery expressions
nielspardon Jul 8, 2026
ed5ff58
feat(builders): parameterize write op; add DDL and Update builders
nielspardon Jul 8, 2026
11e7e8b
feat(api): write op, create/drop table+view, and update_table
nielspardon Jul 8, 2026
6de4fc8
feat(expr): window functions via Expr.over(partition_by/order_by/frame)
nielspardon Jul 8, 2026
c23c217
feat(builders): ExpandRel builder + expand schema inference
nielspardon Jul 8, 2026
8b9a45e
feat(api): DataFrame.unpivot (ExpandRel)
nielspardon Jul 8, 2026
f176066
feat(type-inference): resolve ReferenceRel schema via a build-context…
nielspardon Jul 8, 2026
f8dcf77
feat(api): DataFrame.cache() for shared subplans (CTEs)
nielspardon Jul 8, 2026
eff4111
feat(builders): nested-loop join + exchange builders and schema infer…
nielspardon Jul 8, 2026
c19740a
feat(api): nested_loop_join, repartition, broadcast
nielspardon Jul 8, 2026
11b6cd6
build(deps)!: bump substrait packages 0.86.0 -> 0.94.0; drop removed …
nielspardon Jul 8, 2026
4f71e7d
build(deps): bump substrait packages 0.94.0 -> 0.96.0 (latest); migra…
nielspardon Jul 8, 2026
3c7f9ae
feat(builders): TopN and execution-context-variable builders (0.96)
nielspardon Jul 8, 2026
db59a01
feat(api): top_n verb and current_timestamp/date/timezone
nielspardon Jul 8, 2026
c212038
feat(api): DataFrame.hint() for RelCommon.Hint annotations
nielspardon Jul 8, 2026
b9e1bf0
feat(type-inference): infer lambda func types and match func<> signat…
nielspardon Jul 8, 2026
b19be06
feat(expr): higher-order list functions (list_transform / list_filter)
nielspardon Jul 8, 2026
1016e32
feat(builders): hash/merge joins, extension-single, function options,…
nielspardon Jul 9, 2026
d952505
feat(api): equi-join & extension verbs, parameter(), user_defined, fu…
nielspardon Jul 9, 2026
318cfae
feat(builders): user-defined extension relations via detail ABCs
nielspardon Jul 9, 2026
67e99d4
feat(api): extension-relation verbs (extension_leaf / extension / ext…
nielspardon Jul 9, 2026
e6e27b1
feat(type-inference): correlated subqueries via an outer-schema stack
nielspardon Jul 9, 2026
541abdd
feat(api): sub.outer() for correlated subquery references
nielspardon Jul 9, 2026
70cf1d5
fix(display): drop removed legacy timestamp literal branches
nielspardon Jul 9, 2026
6bd9745
fix(api): correct schema inference and literal/join edge cases
nielspardon Jul 9, 2026
cbfdc4c
refactor(api): group ergonomic API under substrait.dataframe
nielspardon Jul 9, 2026
10f70c2
style: sort imports and wrap long line after substrait.dataframe move
nielspardon Jul 9, 2026
58aeaa7
feat(api): decimal support for arithmetic and comparison operators
nielspardon Jul 10, 2026
fb1c49b
refactor(api): centralize ReferenceRel construction in a plan.referen…
nielspardon Jul 13, 2026
dee6b8b
refactor(api): drop cache()/reference from this PR (track as #211)
nielspardon Jul 13, 2026
81c4723
test: inline the _built wrapper in test_literals
nielspardon Jul 13, 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
82 changes: 69 additions & 13 deletions examples/dataframe_example.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,73 @@
import substrait.dataframe as sdf
from substrait.builders.plan import read_named_table
from substrait.builders.type import boolean, i64, named_struct, struct
from substrait.extension_registry import ExtensionRegistry
"""Example usage of the ergonomic `substrait.dataframe` facade.

registry = ExtensionRegistry(load_default_extensions=True)
Compare this with `builder_example.py`, which builds the same kinds of plans
with the lower-level `substrait.builders.*` functions.
"""

ns = named_struct(
names=["id", "is_applicable"],
struct=struct(types=[i64(nullable=False), boolean()], nullable=False),
)
import substrait.dataframe as sub
from substrait.utils.display import pretty_print_plan

table = read_named_table("example_table", ns)

frame = sdf.DataFrame(read_named_table("example_table", ns))
frame = frame.select(sdf.col("id"))
print(frame.to_substrait(registry))
def filter_select_example():
"""read -> filter -> with_columns -> select, with operator expressions."""
plan = (
sub.read_named_table(
"people", {"id": sub.i64, "age": sub.i64, "name": sub.string}
)
.filter((sub.col("age") > 25) & sub.col("name").is_not_null())
.with_columns(next_year=sub.col("age") + 1)
.select("id", "name", "next_year")
.to_plan()
)
pretty_print_plan(plan, use_colors=True)


def aggregate_example():
"""group_by().agg() with the named-function namespace `f`.

Note the explicit nullability: ``region`` is required, ``amount`` nullable.
``amount > 0`` also shows literal coercion -- the int ``0`` is typed to match
the fp64 column so the comparison overload resolves.
"""
plan = (
sub.read_named_table(
"sales", {"region": sub.string.non_null, "amount": sub.fp64}
)
.filter(sub.col("amount") > 0)
.group_by("region")
.agg(
sub.f.sum(sub.col("amount")).alias("total"),
sub.f.count(sub.col("amount")).alias("n"),
)
.to_plan()
)
pretty_print_plan(plan, use_colors=True)


def join_example():
"""Join two tables and project across the combined schema."""
customers = sub.read_named_table(
"customers", {"cust_id": sub.i64, "name": sub.string}
)
orders = sub.read_named_table(
"orders", {"order_id": sub.i64, "cust_ref": sub.i64, "amount": sub.fp64}
)
plan = (
customers.join(
orders, on=sub.col("cust_id") == sub.col("cust_ref"), how="inner"
)
.select("name", "amount")
.sort("amount", descending=True)
.limit(10)
.to_plan()
)
pretty_print_plan(plan, use_colors=True)


if __name__ == "__main__":
print("=== filter / with_columns / select ===")
filter_select_example()
print("\n=== group_by / agg ===")
aggregate_example()
print("\n=== join / sort / limit ===")
join_example()
20 changes: 11 additions & 9 deletions examples/narwhals_example.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
# Install duckdb and pyarrow before running this example
# Example of the `substrait.narwhals` integration layer: drive Substrait plan
# construction through Narwhals (`nw.from_native`), so backend-agnostic Narwhals
# code compiles to a Substrait plan.
#
# For building plans directly (without Narwhals), see `dataframe_example.py`,
# which uses the Substrait-native DataFrame in `substrait.dataframe`.
#
# /// script
# dependencies = [
# "narwhals==2.9.0",
Expand All @@ -9,7 +15,7 @@
import narwhals as nw
from narwhals.typing import FrameT

import substrait.dataframe as sdf
import substrait.narwhals as sn
from substrait.builders.plan import read_named_table
from substrait.builders.type import boolean, i64, named_struct, struct
from substrait.extension_registry import ExtensionRegistry
Expand All @@ -21,15 +27,11 @@
struct=struct(types=[i64(nullable=False), boolean()], nullable=False),
)

table = read_named_table("example_table", ns)


lazy_frame: FrameT = nw.from_native(
sdf.DataFrame(read_named_table("example_table", ns))
)
# Wrap the Substrait Narwhals backend and drive it with the Narwhals API.
lazy_frame: FrameT = nw.from_native(sn.DataFrame(read_named_table("example_table", ns)))

lazy_frame = lazy_frame.select(nw.col("id").abs(), new_id=nw.col("id"))

df: sdf.DataFrame = lazy_frame.to_native()
df: sn.DataFrame = lazy_frame.to_native()

print(df.to_substrait(registry))
118 changes: 59 additions & 59 deletions pixi.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ readme = "README.md"
requires-python = ">=3.10,<3.15"
dependencies = [
"protobuf >=5,<7",
"substrait-protobuf==0.86.0",
"substrait-extensions==0.86.0",
"substrait-protobuf==0.96.0",
"substrait-extensions==0.96.0",
]
dynamic = ["version"]

[tool.setuptools_scm]
write_to = "src/substrait/_version.py"

[project.optional-dependencies]
extensions = ["substrait-antlr==0.86.0", "pyyaml"]
extensions = ["substrait-antlr==0.96.0", "pyyaml"]
sql = ["sqloxide", "deepdiff"]

[dependency-groups]
dev = ["pytest >= 7.0.0", "substrait-antlr==0.86.0", "pyyaml", "sqloxide", "deepdiff", "duckdb<=1.2.2; python_version < '3.14'", "datafusion"]
dev = ["pytest >= 7.0.0", "substrait-antlr==0.96.0", "pyyaml", "sqloxide", "deepdiff", "duckdb<=1.2.2; python_version < '3.14'", "datafusion"]

[tool.pytest.ini_options]
pythonpath = "src"
Expand Down
Loading
Loading