ddxdb
Write calculus directly in SQL and let the database evaluate the derivative, row by row, alongside everything else:
SELECT i, grad(x * y, x) AS dfdx, grad(x * y, y) AS dfdy FROM g
grad and jvp are markers, not row functions. They are rewritten away into
ordinary derivative SQL before the engine sees them, so what runs is a plain
expression — the relational equivalent of jax.vmap(jax.grad(f)), with the rows
as the batch dimension.
This is the Python distribution of ddx, a
thin wrapper over the ddx-core engine.
Install
pip install ddxdb # everything below except Context
pip install "ddxdb[datafusion]" # + the DataFusion Context
rewrite_sql is the whole library
Text in, text out — so it works with any engine that accepts SQL. Pass the result wherever you would have passed the original:
import ddxdb
ddxdb.rewrite_sql("SELECT grad(sin(x), x) AS d FROM t")
# 'SELECT (cos(x)) AS d FROM t'
con.sql(ddxdb.rewrite_sql(q, "duckdb")) # DuckDB
session.sql(ddxdb.rewrite_sql(q, "spark")) # Spark
ctx.sql(ddxdb.rewrite_sql(q)) # DataFusion
Accepted dialects: generic, datafusion, postgres, ansi, snowflake,
oracle, duckdb, mysql, sqlite, bigquery, redshift, hive, spark,
databricks, mssql, teradata, clickhouse.
Pick the one that matches the engine you will run on, not just the one that parses your SQL. The dialect also decides which column an identifier names, and engines disagree three ways:
unquoted X means |
so "X" is |
|
|---|---|---|
| Postgres, DataFusion, generic, ansi | "x" |
a different column |
| Snowflake, Oracle | "X" |
the same column |
| DuckDB, Spark, MySQL, SQLite, BigQuery, Redshift, Hive, Databricks, SQL Server, Teradata | any casing | the same column |
| ClickHouse | X exactly |
the same column, and "x" is not |
Getting this wrong does not raise. grad("X" * "X", X) is 2X on Snowflake and
0 on Postgres — both correct, for different engines — so ddx keeps a table
rather than a default, and refuses a dialect whose rule it has not established.
Because the rewrite happens in your process, on your connection, it sees your temp tables, session settings and open transaction — anything the query itself could see. A rewrite performed inside the database, on a connection of its own, would not.
Context, for DataFusion
A real SessionContext subclass whose .sql() rewrites first — every inherited
method, property and constructor argument works unchanged:
ctx = ddxdb.Context()
ctx.sql("SELECT grad(x * x, x) AS d FROM t").collect() # → 2x
It lives in ddxdb.datafusion (a subclass needs its base class at import time,
so it cannot sit beside rewrite_sql without dragging DataFusion in) and is
re-exported as ddxdb.Context, imported on first use. import ddxdb still needs
no engine.
There is sugar for DataFusion and not for other engines because DataFusion is ddx's integration target. Everything else uses the one-liner above, which is why there are no per-engine helpers here to drift out of date.
What you can write
+ - * /; the chain rule for the trig / inverse-trig / exp / log / hyperbolic
set plus abs; power with a constant base or exponent. Higher order falls out
of nesting — grad(grad(f, x), x) just works. Differentiating through an
aggregate is linearity, so the marker goes inside it, which is what makes a
gradient-descent step expressible in SQL:
SELECT theta - 0.01 * AVG(grad(loss, theta)) FROM batch
A marker rewrites in place, so it is legal anywhere a scalar expression is — including inside a recursive CTE, which is how a whole training loop fits in one query.
Two other functions
ddxdb.differentiate_sql("x * y", "x") # 'y' — the derivative as text
The escape hatch, for assembling SQL where a marker cannot reach — inside a
recursive term you are building programmatically, or a query some other tool
emits. Everything else should use rewrite_sql.
ddxdb.supported_functions() # ['abs', 'acos', 'asin', ...]
The unary functions ddx has a rule for, read from the engine rather than restated. Note that a name being present does not by itself make an expression differentiable — the surrounding constructs matter too — so catching the typed error below remains the general answer to "can ddx handle this?".
Errors are typed
An unsupported construct is always an error, never a silently wrong number — this is a numerical-correctness library, and a plausible-looking wrong derivative is the worst thing it could produce. The kind of failure is a class, so you can catch the one you can act on:
try:
ddxdb.rewrite_sql(query)
except ddxdb.UnsupportedExpression:
... # no rule for something in there — fall back
except ddxdb.AmbiguousColumn:
... # the query needs a qualifier — a fix the caller makes
All of them derive from ddxdb.DdxError. The full set is
UnsupportedExpression, InvalidMarker, AmbiguousColumn,
ProjectionBoundary and SqlParseError, plus NotScalar,
UnknownColumn and InvalidColumn from whole-query grad.
Gradients of whole queries: grad(loss, table.column)
grad(expr, column) differentiates one expression. Training a model needs the
gradient of a whole query, and that is grad too, in a FROM clause: the
gradient of the loss a CTE computes, as a relation shaped like the table.
from ddxdb import Context
ctx = Context()
# ... register x(sample, inp, val), w(inp, out, val), y(sample, out, val) ...
ctx.sql("""
WITH h AS (
SELECT x.sample, w.out, tanh(SUM(x.val * w.val)) AS val
FROM x JOIN w ON x.inp = w.inp GROUP BY x.sample, w.out),
loss AS (
SELECT SUM(power(h.val - y.val, 2)) AS l
FROM h JOIN y ON h.sample = y.sample AND h.out = y.out)
SELECT w.inp, w.out, w.val - 0.1 * g.val AS val
FROM w JOIN grad(loss, w.val) g ON w.inp = g.inp AND w.out = g.out
""")
That is one SGD step: params - lr * grad(loss)(params), written as a join.
Nothing in the loss is labelled for ddx; the one function it gives a meaning
to is ddx_stop_gradient(x), JAX's lax.stop_gradient. On a plain
SessionContext, ddxdb.ad.sql(ctx, statement) does the same, and
ddxdb.ad.sql_all runs several statements that take grad of one loss for
the price of one backward pass. ddxdb.ad.grad and ddxdb.ad.vjp give the
underlying program. A query that is not a loss raises NotScalar, a column
the loss does not read raises UnknownColumn, and a column that cannot be
differentiated (not a float, or in a table whose rows do not have unique
dims) raises InvalidColumn. This needs DataFusion.
Another engine needs no DataFusion: ddxdb.grad_plan(plan_bytes, wrt)
differentiates the serialized Substrait plan its producer writes, and
ddxdb.run(backend, program) runs the result on any object with four methods,
select_all, returns_rows, materialize and drop_table (the
ddxdb.Backend protocol). Both are ddx-ad's own Rust, the same code the Rust
adapter runs. Pass namespace="__ddx_mine_" to get the same program from the
same plan every time.
One thing to know
grad does not see through a CTE or a view. Differentiation stops at column
references, so a column computed upstream is a constant to it:
WITH v AS (SELECT x, sin(x) AS s FROM t)
SELECT grad(s * x, x) FROM v -- ds/dx is treated as 0
That is defensible relational semantics and a real trap, so ddx refuses the
worst case rather than quietly dropping the term: referencing a computed CTE
alias as a non-wrt term raises ProjectionBoundary and tells you to
differentiate inside the CTE instead. Differentiating with respect to such an
alias is fine — every occurrence is then the differentiation leaf, and
grad(s * s, s) is exactly 2s.
Development
pip install maturin pytest
maturin develop --uv
python -m pytest tests/
Building compiles protoc from source for the substrait crate (it needs
cmake and a C++ compiler), which takes a few minutes the first time.
License
Licensed under Apache-2.0, the same as the rest of ddx.
Metadata
Release files for ddxdb 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ddxdb-0.2.0.tar.gz | 205.6 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| ddxdb-0.2.0-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details |
| ddxdb-0.2.0-cp310-abi3-manylinux_2_28_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.28+ x86-64 | Details |
| ddxdb-0.2.0-cp310-abi3-manylinux_2_28_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.28+ ARM64 | Details |
| ddxdb-0.2.0-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details |
| ddxdb-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl | CPython 3.10 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 26.1 MB
Release files / ddxdb-0.2.0.tar.gz
| Download URL | ddxdb-0.2.0.tar.gz |
|---|---|
| Size | 205.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6c7dda21eb959e07ed3c521a4bd995c506e5e4d7c29bf848413667be239f5654
|
|
BLAKE2b-256 checksum How to use checksums |
e72ede990ffdcbe914ec911646eb58b53567dc4d73da47f8d056fd205badc246
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency logRelease files / ddxdb-0.2.0-cp310-abi3-win_amd64.whl
| Download URL | ddxdb-0.2.0-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 5.4 MB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
f895835beeca9808759a425495ebb485225a236782ce755648ae73967835584b
|
|
BLAKE2b-256 checksum How to use checksums |
7b2696bb82998c683887622ca5f949ebd73b8220b05885da9e7f77d656733c33
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency logRelease files / ddxdb-0.2.0-cp310-abi3-manylinux_2_28_x86_64.whl
| Download URL | ddxdb-0.2.0-cp310-abi3-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 5.4 MB |
| Tags | CPython 3.10 Linux glibc 2.28+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
f5f1deb0cdaa6d3cd7a6ef18ad87d62ade5f8484bdd9e83d58df11611f96cfed
|
|
BLAKE2b-256 checksum How to use checksums |
e71efa9219b33075ed6a6a1967288132f83582ef2aa51d3972a9d3f5ed146bae
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency logRelease files / ddxdb-0.2.0-cp310-abi3-manylinux_2_28_aarch64.whl
| Download URL | ddxdb-0.2.0-cp310-abi3-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 5.0 MB |
| Tags | CPython 3.10 Linux glibc 2.28+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
22875ad4f043833ad5b008edf11b349b7bc6770d0c0af47752b59b78cc91309b
|
|
BLAKE2b-256 checksum How to use checksums |
28304f9e853c23ba7aed1994326311dc507946b5ee7410ebab5ec251f21ad9a4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency logRelease files / ddxdb-0.2.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | ddxdb-0.2.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 4.9 MB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
aefaed23cc7b507cd96ac45c10296045aa32665f950629ab48f54b8d1bd491cf
|
|
BLAKE2b-256 checksum How to use checksums |
d225344a07f30c6790769ca937dd1976e148a5d5df377c60c11fc6ec522d1f6f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency logRelease files / ddxdb-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | ddxdb-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 5.1 MB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
21c7fee7061a958d6c1b0b7df5f488ebbff6670b5f57af0858df45151fffe72e
|
|
BLAKE2b-256 checksum How to use checksums |
e27ef828016dec761f41c1b9299e25f5f41ed74c7f3955a3275ac83cf378f0c9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency log