Skip to content

Commit 604c212

Browse files
committed
docs: update README to reflect SQL frontend, add SQL docs page
- Replace "Planned: SQL" with working SQL section and examples - Remove "No SQL mode" from "What doesn't exist yet" - Add SQL docs page with full syntax reference - Add SQL to sidebar
1 parent 6a77d14 commit 604c212

File tree

3 files changed

+153
-4
lines changed

3 files changed

+153
-4
lines changed

README.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,26 @@ const rows = await drainPipeline(sorted)
193193
await spill.cleanup()
194194
```
195195

196-
### Planned: SQL → AST → operators
196+
### SQL frontend
197197

198-
For users who prefer SQL syntax, a future layer can parse SQL into an AST and compile it to operator composition — same zero-copy pipeline underneath, SQL is just another frontend.
198+
SQL is another way in — same operator pipeline underneath:
199+
200+
```typescript
201+
const qm = QueryMode.local()
202+
const results = await qm
203+
.sql("SELECT region, SUM(amount) AS total FROM orders WHERE status = 'active' GROUP BY region ORDER BY total DESC LIMIT 10")
204+
.collect()
205+
206+
// SQL and DataFrame compose — chain further operations after SQL
207+
const filtered = await qm
208+
.sql("SELECT * FROM events WHERE created_at > '2026-01-01'")
209+
.filter("country", "eq", "US")
210+
.sort("amount", "desc")
211+
.limit(50)
212+
.collect()
213+
```
214+
215+
Supports: SELECT, WHERE (AND/OR/NOT, LIKE, IN, NOT IN, BETWEEN, IS NULL), GROUP BY, HAVING, ORDER BY (multi-column), LIMIT/OFFSET, DISTINCT, CASE/CAST, arithmetic expressions, JOINs.
199216

200217
### Why this matters
201218

@@ -226,7 +243,7 @@ npx tsx examples/nextjs-api-route.ts
226243

227244
- **TypeScript orchestration** — Durable Object lifecycle, R2 range reads, footer caching, request routing
228245
- **Zig WASM engine** (`wasm/`) — column decoding, SIMD ops, SQL execution, vector search, fragment writing, compiles to `querymode.wasm`
229-
- **Code-first query API**`.table().filter().select().sort().limit().exec()`, no SQL
246+
- **Code-first query API**`.table().filter().select().sort().limit().exec()` or `.sql("SELECT ...")`
230247
- **Write path**`TableQuery.append(rows)` with CAS-based manifest coordination via Master DO
231248
- **Master/Query DO split** — single-writer Master broadcasts footer invalidations to per-region Query DOs
232249
- **Footer caching** — table footers (~4KB each) cached in DO memory with VIP eviction (hot tables protected from eviction)
@@ -243,7 +260,6 @@ npx tsx examples/nextjs-api-route.ts
243260
- No deployed instance
244261
- No browser mode
245262
- No npm package published (install from source via git clone)
246-
- No SQL mode (planned — SQL frontend compiling to operator pipeline)
247263

248264
## Architecture
249265
![querymode-architecture](docs/architecture/querymode-architecture.svg)

docs/astro.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export default defineConfig({
1919
{ label: "Why QueryMode", slug: "why-querymode" },
2020
{ label: "Getting Started", slug: "getting-started" },
2121
{ label: "DataFrame API", slug: "dataframe-api" },
22+
{ label: "SQL", slug: "sql" },
2223
{ label: "Operators", slug: "operators" },
2324
{ label: "Formats", slug: "formats" },
2425
{ label: "Architecture", slug: "architecture" },

docs/src/content/docs/sql.mdx

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
---
2+
title: SQL
3+
description: SQL frontend — same operator pipeline, SQL syntax.
4+
---
5+
6+
SQL is another way into the same operator pipeline. The parser compiles SQL to a `QueryDescriptor`, which builds the same pull-based operator chain as the DataFrame API.
7+
8+
## Usage
9+
10+
```typescript
11+
import { QueryMode } from "querymode/local"
12+
13+
const qm = QueryMode.local()
14+
15+
const results = await qm
16+
.sql("SELECT region, SUM(amount) AS total FROM orders GROUP BY region ORDER BY total DESC")
17+
.collect()
18+
```
19+
20+
## SQL + DataFrame compose
21+
22+
`.sql()` returns a `DataFrame`, so you can chain DataFrame methods after it:
23+
24+
```typescript
25+
const results = await qm
26+
.sql("SELECT * FROM events WHERE created_at > '2026-01-01'")
27+
.filter("country", "eq", "US")
28+
.sort("amount", "desc")
29+
.limit(50)
30+
.collect()
31+
```
32+
33+
## Supported syntax
34+
35+
### SELECT
36+
37+
```sql
38+
SELECT *
39+
SELECT col1, col2
40+
SELECT col1 AS alias
41+
SELECT DISTINCT col1, col2
42+
SELECT COUNT(*), SUM(amount), AVG(score)
43+
```
44+
45+
### WHERE
46+
47+
```sql
48+
WHERE age > 25
49+
WHERE status = 'active' AND amount >= 100
50+
WHERE dept = 'eng' OR age > 30
51+
WHERE name LIKE '%alice%'
52+
WHERE id IN (1, 2, 3)
53+
WHERE id NOT IN (4, 5)
54+
WHERE amount BETWEEN 100 AND 500
55+
WHERE email IS NULL
56+
WHERE email IS NOT NULL
57+
WHERE NOT (status = 'deleted')
58+
```
59+
60+
### GROUP BY / HAVING
61+
62+
```sql
63+
GROUP BY region
64+
GROUP BY region, category
65+
HAVING SUM(amount) > 1000
66+
```
67+
68+
### ORDER BY
69+
70+
```sql
71+
ORDER BY amount DESC
72+
ORDER BY region ASC, amount DESC
73+
```
74+
75+
### LIMIT / OFFSET
76+
77+
```sql
78+
LIMIT 100
79+
LIMIT 100 OFFSET 50
80+
```
81+
82+
### Expressions
83+
84+
```sql
85+
SELECT salary / 1000 AS salary_k
86+
SELECT CASE WHEN age > 30 THEN 'senior' ELSE 'junior' END AS level
87+
SELECT CAST(age AS text) AS age_str
88+
```
89+
90+
### Aggregate functions
91+
92+
`COUNT`, `SUM`, `AVG`, `MIN`, `MAX`, `COUNT(DISTINCT col)`.
93+
94+
### JOINs
95+
96+
```sql
97+
SELECT * FROM orders JOIN users ON orders.user_id = users.id
98+
SELECT * FROM orders LEFT JOIN users ON orders.user_id = users.id
99+
```
100+
101+
## How it works
102+
103+
```
104+
SQL string → lexer → parser → AST → compiler → QueryDescriptor → operator pipeline
105+
```
106+
107+
The SQL frontend is a recursive descent parser written in TypeScript. It produces an AST that the compiler maps to the same `QueryDescriptor` the DataFrame API uses. Features that can be expressed as `FilterOp[]` (simple AND conditions with eq/gt/lt/etc) are pushed down to the inner executor. Features that can't (OR, LIKE, NOT IN, HAVING, multi-column ORDER BY, CASE/CAST/arithmetic) are handled by `SqlWrappingExecutor`, which applies them on the result rows.
108+
109+
## Programmatic access
110+
111+
If you need the parsed AST or compiled descriptor directly:
112+
113+
```typescript
114+
import { parse, compile, compileFull, sqlToDescriptor } from "querymode/sql"
115+
116+
// Parse SQL to AST
117+
const ast = parse("SELECT * FROM users WHERE age > 25")
118+
119+
// Compile AST to QueryDescriptor
120+
const descriptor = compile(ast)
121+
122+
// Or do both in one step
123+
const descriptor = sqlToDescriptor("SELECT * FROM users WHERE age > 25")
124+
125+
// compileFull returns additional metadata for the wrapping executor
126+
const result = compileFull(ast)
127+
// result.descriptor — QueryDescriptor
128+
// result.whereExpr — full WHERE expression (when OR/LIKE/NOT IN present)
129+
// result.havingExpr — HAVING expression
130+
// result.allOrderBy — multi-column ORDER BY
131+
// result.computedExprs — CASE/CAST/arithmetic in SELECT
132+
```

0 commit comments

Comments
 (0)