Skip to content

Commit d8e8a0e

Browse files
rustyconoverclaude
andcommitted
docs: act on DX review — accuracy, taxonomy, funnel, and clarity fixes
Addresses the senior-DX review findings: - Pattern count: reconcile four-vs-five — function-patterns intro now says five, tutorial says "three more patterns", home table adds the Buffering row. - Home "Why VGI?" table: drop the false "single-threaded" claim; reframe the in-process vs. worker-process axis honestly and add an Arrow-IPC cost note. - Taxonomy: use "pattern" in prose throughout (kept "shape" only for the visual glyphs); "kind" stays an internal CSS/asset name. - Accuracy: Cloudflare DO backend uses httpx (not stdlib) — corrected. Pushdown how-to now uses params.current_pushdown_filters (already a decoded PushdownFilters), not a nonexistent params.pushdown_filters + deserialize. - Funnel: how-to "Next steps" now link sibling how-to/concept before raw reference; reconcile state-storage vs shared-storage and concepts vs lifecycle. - On-ramps: gloss vgi-serve on first use; explain the Python Client vs SQL ATTACH and the injected ctx; link execution_id to state storage. - Add a troubleshooting callout to tutorial step 2; realistic DuckDB result header; how-to index "Reference & tooling" split; contributing-docs carve-out for advanced illustrative pages + tighter Next-steps rule. Strict build green; 67 doc/e2e assertions pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c346413 commit d8e8a0e

10 files changed

Lines changed: 74 additions & 36 deletions

File tree

docs/contributing-docs.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,13 @@ Every tutorial, how-to, and concept page **must** contain, in order:
2828
2. **Prerequisites** — assumed knowledge, prior steps, and required extras (e.g.
2929
`pip install vgi-python[http]`), with links. Use a list or an admonition.
3030
3. **At least one complete, runnable example** — no `...` elisions in the primary example. It must
31-
pass the documentation-example tests (see below).
32-
4. **A "Next steps" section** — links to the logical next page(s). No dead ends.
31+
pass the documentation-example tests (see below). *Exception:* advanced pages whose feature
32+
isn't exercisable from a self-contained snippet (HTTP serving, auth, optimizer pushdown) may
33+
lead with an illustrative `test="skip"` sketch — but label it illustrative and point to a
34+
runnable worker or the reference for the real thing.
35+
4. **A "Next steps" section** that advances the reader along the funnel: prefer a sibling **how-to**
36+
or a **concept** page, then the **reference** for the full contract. Don't jump straight from a
37+
how-to into auto-generated reference. No dead ends.
3338

3439
## Example rules
3540

docs/how-to/catalogs.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,6 @@ constraints, generated columns, column comments, filter requirements — is cove
6060

6161
## Next steps
6262

63-
- **Persist per-group state** → [State storage](../shared-storage.md).
64-
- **Full catalog options** (tables, views, constraints) → [Catalog Interface](../catalog-interface.md).
63+
- **Persist per-group state** → [State storage](state-storage.md).
64+
- **Full catalog options** (tables, views, constraints) → [Catalog Interface reference](../catalog-interface.md).
6565
- **Exact API** → [API Reference: Catalogs](../api/catalogs.md).

docs/how-to/function-patterns.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: "How to write each of the four VGI function patterns: scalar, table
44

55
# Function patterns
66

7-
**What this is:** a recipe showing each of the four VGI function shapes, with a complete,
7+
**What this is:** a recipe showing each of the five VGI function patterns, with a complete,
88
runnable worker for each. **Who it's for:** developers who've finished the
99
[tutorial](../tutorial/index.md) and want to know which pattern fits their problem.
1010

@@ -199,8 +199,9 @@ When a function must see the **whole** input before it can produce *any* output
199199
top-k, or a full reduction — use a buffering function. Unlike table-in-out (which emits per input
200200
batch), it runs in three phases: `process` (the **sink** — stash each batch, return a `state_id`),
201201
`combine` (reduce all the partials once), and `finalize` (the **source** — stream the result out).
202-
Because the phases can run in different worker processes, state lives in `params.storage` (shared
203-
storage scoped by `execution_id`), not in memory.
202+
Because the phases can run in different worker processes, state can't live in memory — it goes in
203+
`params.storage`, a shared store keyed to this call (its `execution_id`). See
204+
[Persist state across workers](state-storage.md) for the backends behind it.
204205

205206
```python
206207
--8<-- "examples/row_count_worker.py"
@@ -219,8 +220,7 @@ SELECT * FROM buffers.row_count((SELECT * FROM big_table));
219220

220221
## Next steps
221222

222-
- **Persist state across invocations**[State storage](../shared-storage.md).
223-
- **Let the optimizer prune work**[Filter pushdown](../filter-pushdown.md) and
224-
[Column statistics](../column-statistics.md).
225-
- **Understand the call lifecycle**[Concepts: lifecycle](../lifecycle.md).
223+
- **Persist state across workers**[State storage](state-storage.md).
224+
- **Let the optimizer prune work**[Integrate with the optimizer](pushdown-and-statistics.md).
225+
- **Understand the call lifecycle**[Concepts](../concepts/index.md).
226226
- **Exact signatures**[API Reference: Functions](../api/functions.md).

docs/how-to/http-auth.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ The same worker that runs over a subprocess also runs over HTTP — add `--http`
2121
vgi-serve my_worker.py --http
2222
```
2323

24-
The client connects with `transport="http"` instead of spawning a subprocess:
24+
DuckDB still attaches the worker over HTTP the usual way (`ATTACH ... (TYPE vgi, LOCATION
25+
'http://...')`). You only need the **Python `Client`** when you're calling the worker *from Python*
26+
rather than from SQL — for tests, scripts, or another service. It connects with `transport="http"`
27+
instead of spawning a subprocess, and exposes the same call methods:
2528

2629
```python test="skip"
2730
from vgi.client import Client
@@ -39,8 +42,10 @@ The quickest setup is static bearer tokens via an environment variable — comma
3942
VGI_BEARER_TOKENS="token1=alice,token2=bob" vgi-serve my_worker.py --http
4043
```
4144

42-
Unauthenticated requests get HTTP 401. Authenticated requests carry the principal in the
43-
`AuthContext` your function can read via the injected `ctx`:
45+
Unauthenticated requests get HTTP 401. Authenticated requests carry the principal in an
46+
`AuthContext`. Your function reads it through an optional `ctx` parameter: declare `ctx` in the
47+
signature and the framework injects a per-call `CallContext` (auth, logging, transport info) — you
48+
don't pass it from SQL.
4449

4550
```python test="skip"
4651
class Secret(ScalarFunction):

docs/how-to/index.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ real. Each guide assumes you can already write and run a basic worker.
2020
with bearer/JWT auth.
2121
- **[Integrate with the optimizer](pushdown-and-statistics.md)** — accept pushed-down filters and
2222
report column statistics.
23-
- **Describe your functions** — metadata for introspection: [Function Metadata](../metadata.md)
24-
- **Use the CLI** — invoke functions and inspect workers from the shell: [CLI](../cli.md)
23+
Each recipe ends with a **Next steps** section that links onward to a concept page and the full
24+
reference for that topic.
2525

26-
Each recipe links to a deeper **reference** page (Function API, Catalog Interface, Shared Storage,
27-
Authentication, Filter Pushdown, Column Statistics) for the full contract.
26+
## Reference & tooling
27+
28+
- **[Function Metadata](../metadata.md)** — describe your functions for introspection.
29+
- **[CLI](../cli.md)** — invoke functions and inspect workers from the shell.
2830

2931
## Next steps
3032

docs/how-to/pushdown-and-statistics.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ pushed-down `WHERE` predicates and reporting column statistics so the planner ca
1616
## Filter pushdown
1717

1818
A table function can receive the `WHERE` predicates DuckDB would otherwise apply *after* the scan,
19-
and apply them at the source. Opt in with `filter_pushdown = True` in the function's `Meta`, then
20-
read the decoded filters in `process` via `params`:
19+
and apply them at the source. Opt in with `filter_pushdown = True` in the function's `Meta`. The
20+
framework deserializes the predicates for you and exposes them on `params.current_pushdown_filters`
21+
as a `PushdownFilters` tree (or `None` when no filter applies), refreshed before each `process`
22+
call:
2123

2224
```python test="skip"
2325
class Events(TableFunctionGenerator[EventsArgs]):
@@ -26,13 +28,14 @@ class Events(TableFunctionGenerator[EventsArgs]):
2628

2729
@classmethod
2830
def process(cls, params, state, out):
29-
filters = params.pushdown_filters # decoded predicate tree (or None)
31+
filters = params.current_pushdown_filters # PushdownFilters tree, or None
3032
# apply `filters` while generating rows, then out.emit(...) / out.finish()
3133
```
3234

33-
Filters arrive as a hybrid JSON + Arrow structure; decode and evaluate them with
34-
`deserialize_filters` (see [API: Filter Pushdown](../api/filters.md)). The wire format and a worked
35-
example are in the [Filter Pushdown reference](../filter-pushdown.md).
35+
`PushdownFilters` is already decoded — you don't call `deserialize_filters` yourself (that helper is
36+
for the raw wire bytes). To have the framework apply the filters to your output automatically,
37+
set `auto_apply_filters = True` in `Meta`. The node types and a worked example are in the
38+
[Filter Pushdown reference](../filter-pushdown.md) and [API: Filter Pushdown](../api/filters.md).
3639

3740
## Column statistics
3841

@@ -56,7 +59,8 @@ Table(
5659
EXPLAIN SELECT * FROM mydb.data.departments WHERE id > 100; -- Physical Plan: EMPTY_RESULT
5760
```
5861

59-
Full details — RPC-based dynamic statistics, TTLs, spatial bounds — are in the
62+
(The snippets above are illustrative — `schema` and the `departments` table stand in for your own
63+
catalog.) Full details — RPC-based dynamic statistics, TTLs, spatial bounds — are in the
6064
[Column Statistics reference](../column-statistics.md).
6165

6266
## Next steps

docs/how-to/state-storage.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ function that coordinates partial results across workers.
1212

1313
- You've built an aggregate or multi-worker function (see
1414
[Function patterns → Aggregate](function-patterns.md#aggregate)).
15-
- For cloud backends: the relevant extra (`vgi-python[azure]`).
15+
- For the Azure backend: `pip install vgi-python[azure]`. SQLite and Cloudflare DO need no extra.
16+
17+
!!! note "`vgi-serve`"
18+
The commands below use `vgi-serve`, the CLI installed with `vgi-python` that runs a worker
19+
module as a long-lived process (the production counterpart to the tutorial's `uv run`). The
20+
`--http` flag serves it over HTTP instead of stdin/stdout.
1621

1722
## Two kinds of "state" — don't confuse them
1823

@@ -51,7 +56,7 @@ VGI_WORKER_SHARED_STORAGE=cloudflare-do vgi-serve my_worker.py --http
5156
|---|---|---|---|
5257
| SQLite | `sqlite` (default) | local / subprocess | none (stdlib) |
5358
| Azure SQL | `azure-sql` | Azure deployments | `vgi-python[azure]` |
54-
| Cloudflare DO | `cloudflare-do` | edge / multi-cloud | none (stdlib) |
59+
| Cloudflare DO | `cloudflare-do` | edge / multi-cloud | none extra — uses `httpx`, which ships with `vgi-python`; needs a Worker endpoint + token |
5560

5661
The per-backend setup (connection strings, credentials, table provisioning) is documented in the
5762
[Shared Storage reference](../shared-storage.md).

docs/index.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,12 @@ Stock `duckdb` works too — `INSTALL vgi FROM community; LOAD vgi;`.
7373
| C/C++ compilation required | Any language with an Apache Arrow library |
7474
| Tied to a DuckDB version | Version independent |
7575
| Complex build/release cycle | Ship a script or executable |
76-
| Runs in-process | Process isolation |
77-
| Single-threaded | Parallel workers |
76+
| Runs in DuckDB's process | Isolated worker process — a crash or a heavy dependency can't take DuckDB down |
77+
| Native code in DuckDB's threads | Your code, optionally fanned out across worker processes |
78+
79+
**The tradeoff:** data crosses a process boundary as Apache Arrow IPC. That's fast and columnar,
80+
but not free — co-locate workers (subprocess transport) for latency-sensitive paths, and reach for
81+
VGI when the productivity and isolation win outweighs the hop.
7882

7983
**Use cases:** call REST APIs from SQL, run ML inference, process data with pandas/numpy, build
8084
custom ETL transforms, expose external data sources as queryable tables and views.
@@ -87,6 +91,7 @@ custom ETL transforms, expose external data sources as queryable tables and view
8791
| **Table** | `TableFunctionGenerator` | `SELECT * FROM func(args)` | Generate data |
8892
| **Table-in-out** | `TableInOutFunction` | `SELECT * FROM func((SELECT ...))` | Streaming transforms, filtering |
8993
| **Aggregate** | `AggregateFunction` | `SELECT func(col) ... GROUP BY` | Grouped accumulation |
94+
| **Buffering** | `TableBufferingFunction` | `SELECT * FROM func((SELECT ...))` | Sees every row first (sort, top-k) |
9095

9196
See the [API Reference](api/index.md) for the full surface, or jump into the guides below.
9297

docs/tutorial/scalar.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,12 @@ What's going on:
6666

6767
```sql
6868
SELECT calc.double(21);
69-
-- ┌──────────────┐
70-
-- │ 42 │
71-
-- └──────────────┘
69+
-- ┌──────────────────┐
70+
-- │ double(21) │
71+
-- │ int64 │
72+
-- ├──────────────────┤
73+
-- │ 42 │
74+
-- └──────────────────┘
7275
```
7376

7477
Over a real column:

docs/tutorial/table.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ The new pieces, compared to step 1:
3232
??? info "Scalar vs. table — when do I use which?"
3333
Use a **scalar** function when output has exactly one row per input row (a transform). Use a
3434
**table** function when you generate rows independent of any input — a sequence, a data source,
35-
an API result set. There are two more shapes (table-in-out and aggregate) covered in the
36-
[how-to guides](../how-to/function-patterns.md).
35+
an API result set. There are three more patterns (table-in-out, aggregate, and buffering)
36+
covered in the [how-to guides](../how-to/function-patterns.md).
3737

3838
??? info "Generating a lot of rows? Stream with state"
3939
`process` is actually called *repeatedly* until you call `out.finish()`. For large results you
@@ -64,10 +64,19 @@ SELECT * FROM calc.series(3);
6464

6565
That's both function patterns running from SQL. 🎉
6666

67+
??? success "It didn't work?"
68+
- **`Binder Error: table function ... does not exist`** — the SQL name is the snake_case of the
69+
class name (`Series``series`) and a table function is called in `FROM`, not `SELECT`:
70+
`SELECT * FROM calc.series(3)`, not `SELECT calc.series(3)`.
71+
- **The query hangs and never returns** — a table generator must call `out.finish()` when it has
72+
no more rows. Without it the framework keeps calling `process` forever.
73+
- **`ATTACH` errors after editing** — if `calc` is already attached from step 1, `DETACH calc;`
74+
first (or attach under a new name).
75+
6776
## Next steps
6877

69-
- **More function shapes**[How-to: function patterns](../how-to/function-patterns.md) covers
70-
table-in-out (streaming transforms), aggregates, and a string-valued scalar.
78+
- **More function patterns**[How-to: function patterns](../how-to/function-patterns.md) covers
79+
table-in-out (streaming transforms), aggregates, buffering, and a string-valued scalar.
7180
- **Understand what just happened**[Concepts: worker lifecycle](../concepts/index.md) explains
7281
bind → init → process → finish and the transports.
7382
- **Look up the exact API** → the [API Reference](../api/index.md) documents every class and

0 commit comments

Comments
 (0)