1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// SPDX-FileCopyrightText: 2026 Alexander Merose <al@merose.com> & ddx Authors
//
// SPDX-License-Identifier: Apache-2.0
//! The SQL source-to-source rewrite.
//!
//! Rewrite every `grad`/`jvp` marker in the SQL *text* before it reaches the
//! engine, then hand plain SQL to a stock [`SessionContext`]. This is the
//! universal path: it runs before planning, so it works for every query shape
//! the parser accepts — recursive CTEs, DML, subqueries — which is what lets a
//! whole training loop live in one query. Path B (in-engine, [`crate::analyzer`])
//! is the more ergonomic one but is bounded by what a `LogicalPlan` can carry.
use Result;
use ;
use GenericDialect;
use Ddx;
use crateto_df_err;
/// Rewrite `grad`/`jvp` markers in `sql` and run the result on `ctx` — the
/// one-liner form of the text rewrite.
///
/// The context needs no ddx setup at all: no marker UDFs, no analyzer rule. By
/// the time the engine sees the statement the markers are gone, replaced by
/// ordinary derivative SQL.
///
/// A statement containing no marker is passed through byte-identical and is
/// never even parsed by ddx, so wrapping every query in
/// `ddx_sql` costs essentially nothing.
///
/// ```
/// # use datafusion::prelude::SessionContext;
/// # use ddx_datafusion::ddx_sql;
/// # #[tokio::main]
/// # async fn main() -> datafusion::error::Result<()> {
/// let ctx = SessionContext::new();
/// ctx.sql("CREATE TABLE t AS VALUES (1.0), (2.0), (3.0)").await?.collect().await?;
///
/// // d(x*x)/dx = 2x, computed by the engine as an ordinary column.
/// let df = ddx_sql(&ctx, "SELECT grad(column1 * column1, column1) AS d FROM t").await?;
/// let batches = df.collect().await?;
/// assert_eq!(batches[0].num_rows(), 3);
/// # Ok(())
/// # }
/// ```
pub async
/// [`ddx_sql`] driven by a caller-supplied engine — use this when you have
/// registered custom differentiation rules via [`Ddx::register`].
pub async
/// Rewrite the markers in `sql` and return the derivative SQL as text, without
/// running it.
///
/// Useful for logging what will execute, for feeding another tool, or for the
/// `ddxdb` Python shim, which does exactly this and then calls a stock
/// `Context.sql()`.
/// [`rewrite_sql`] driven by a caller-supplied engine.
///
/// `GenericDialect` is the parser DataFusion itself uses for SQL, and
/// [`Ddx::for_datafusion`] supplies the matching identifier-folding policy
/// (unquoted folds, quoted keeps case) — the two must agree or column matching
/// silently diverges from the engine's own.