Skip to content

spec: sqlflow serve, named SQL over HTTP - #275

Merged
turbolytics merged 2 commits into
mainfrom
spec/serve
Sep 13, 2026
Merged

spec: sqlflow serve, named SQL over HTTP#275
turbolytics merged 2 commits into
mainfrom
spec/serve

Conversation

@turbolytics

@turbolytics turbolytics commented Sep 13, 2026

Copy link
Copy Markdown
Owner

What

A design spec for sqlflow serve: a request/response mode that exposes only the datasets a config declares, as JSON over HTTP, from the same config machinery run uses.

docs/superpowers/specs/2026-09-12-serve-design.md

The shape

  • A separate file: commands: plus a serve: block. The commands: block is the source, so ATTACH … (TYPE POSTGRES, READ_ONLY) reaches the data the same way a pipeline does.
  • Datasets are named SQL with typed parameters (string, integer, timestamp) bound through prepared statements. A dataset has one sql or a grains map of them. Nothing from a request becomes SQL text.
  • Bearer tokens are identities, not secrets: they name the caller in logs and can be revoked.
  • A row cap and a query timeout, enforced. A rate_limit policy, global and per dataset, parsed and refused until it is enforced.
  • CORS for browser callers. sqlflow validate picks the schema by top-level key.

What was measured

On DuckDB 1.5.2 the postgres extension pushes filters and projections down but runs GROUP BY itself. A filter against a Postgres view pushes into the view. A 30-day daily rollup: 2.2 s aggregated in DuckDB, 0.12 s through a view. The example config is shaped by that.

Settled by a Go probe

Probed against DuckDB 1.5.2 through arrow-adbc 1.6.0, and folded into the spec in ac97dbc:

  • ADBC binds parameters by position. Its parameter schema names them 0, 1, and a bound record's field names are ignored. The server numbers $name placeholders itself and checks its count against DuckDB's at startup.
  • A timeout cannot cancel a query. The Go driver manager ignores the context and has no cancel call. At the deadline the caller gets 504, and the query holds the connection until it finishes.
  • $from prepares fine. The earlier claim that it is a parse error was wrong: the DuckDB CLI's EXECUTE q(from := …) call syntax rejected it, not the placeholder. No reserved-word rule exists.
  • Serve examples live in dev/config/serve/, because two sweeps decode everything under dev/config/examples/ as a pipeline. --metrics is dropped until serve records instruments.

First audience is the practitioner who already runs a pipeline and wants programmatic access to what it wrote. The first consumer is the bluesky demo's page; that repo's spec codes against this contract.

A pipeline ends at a sink and the rows need an API. This designs a serve
command: a separate config with commands and datasets, bearer tokens as
identities, typed params bound through prepared statements, a row cap and
a timeout, and a reserved rate-limit policy that is refused until enforced.

Verified on DuckDB 1.5.2 that the postgres extension pushes filters and
projections down but not GROUP BY, and that a filter against a Postgres
view pushes into the view. The example config is shaped by that.
The spec left binding and cancellation to the plan and asserted that $from
fails to parse. A probe against DuckDB 1.5.2 and arrow-adbc 1.6.0 settled
all three, and two of the answers were not the ones the spec planned for:

- DuckDB binds by position. The parameter schema names fields 0 and 1, and
  a bound record's field names are ignored. The server now rewrites $name
  to $N itself and checks DuckDB's parameter count against its own.
- The Go driver manager ignores the context and exposes no cancel. A
  timeout returns 504 while the query runs on and holds the lock.
- $from prepares fine. The CLI's EXECUTE call syntax rejected it. The
  reserved-word rule is gone.

Also from the probe: SetSqlQuery binds eagerly, so a missing view fails at
startup; Arrow renders HUGEINT through a float, so decimals encode as exact
strings; results stream, so the row cap stops the query. Serve examples
move out of dev/config/examples, where two sweeps decode every file as a
pipeline. --metrics is dropped until serve records an instrument.
@turbolytics

Copy link
Copy Markdown
Owner Author

Superseded by #278, which carries this spec's commits and the implementation.

@turbolytics
turbolytics merged commit b0db1ce into main Sep 13, 2026
5 checks passed
turbolytics added a commit that referenced this pull request Sep 13, 2026
#275 squash-merged this branch's first two commits, so both sides added
docs/superpowers/specs/2026-09-12-serve-design.md. main's copy is
byte-identical to ac97dbc. This branch's copy is that plus 40ec437 and
00432fe, which record what implementation changed and the 500 body that
no longer carries DuckDB's error, so the conflict resolves to this
branch's copy.

#277's README, example and manager changes merged cleanly. The cli,
config, schema, validate, managers and serve packages pass under -race
after the merge.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant