You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: document query engine as code — operator composability and pipeline API
Add new section showing how every SQL clause maps to a composable
operator class, with examples of direct operator composition,
DataFrame sugar, and memory-bounded R2 spill. Update test/benchmark
counts to reflect conformance suite and CI benchmarks.
Copy file name to clipboardExpand all lines: README.md
+90-2Lines changed: 90 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -48,6 +48,94 @@ Your app code IS the query execution. The WASM engine is a library function your
48
48
- No fixed operator set — your code can do anything between query steps
49
49
- Same binary everywhere — browser, Node/Bun, Cloudflare DO
50
50
51
+
## Query engine as code
52
+
53
+
Every SQL clause is a composable code primitive. They all implement the same pull-based `Operator` interface — `next() → RowBatch | null` — so you chain them however you want, not how a SQL planner decides.
54
+
55
+
```
56
+
SQL clause Operator class What it does
57
+
───────── ────────────── ────────────
58
+
WHERE FilterOperator Predicate pushdown on rows
59
+
SELECT ProjectOperator Column projection
60
+
ORDER BY ExternalSortOperator Disk-spilling merge sort
// Pull results — zero-copy, no serialization between stages
98
+
const rows =awaitdrainPipeline(top10)
99
+
```
100
+
101
+
### Or use the DataFrame API
102
+
103
+
The same operators power the fluent API — `.filter()` becomes `FilterOperator`, `.sort()` becomes `ExternalSortOperator`, etc:
104
+
105
+
```typescript
106
+
const qm =QueryMode.local()
107
+
const results =awaitqm
108
+
.table("orders")
109
+
.filter("amount", "gt", 100)
110
+
.groupBy("region")
111
+
.aggregate("sum", "amount", "total")
112
+
.sort("total", "desc")
113
+
.limit(10)
114
+
.exec()
115
+
```
116
+
117
+
Both paths produce the same pull-based pipeline. The DataFrame API is sugar; the operators are the engine.
118
+
119
+
### Memory-bounded with R2 spill
120
+
121
+
Operators that accumulate state (sort, join, aggregate) accept a memory budget. When exceeded, they spill to R2 via `SpillBackend` — same interface whether running on Cloudflare edge or local disk:
Traditional engines give you SQL or a DataFrame API. You can't put a window function before a join, run custom logic between pipeline stages, or swap the sort implementation. The planner decides.
136
+
137
+
With QueryMode, operators are building blocks. Your code assembles the pipeline, controls the memory budget, decides when to spill. The query engine isn't a service you call — it's a library your code composes.
0 commit comments