Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ An HTTP wire format for SQL.

POST a SQL statement and parameters to an endpoint, get rows back. Stateless, edge-friendly, JSON over HTTP. Nothing more.

**Status:** v0.1 draft. The spec is being dogfooded against working implementations before being proposed as an RFC. Breaking changes possible until v1.0.
**Status:** v0.2 draft. The spec is being dogfooded against working implementations before being proposed as an RFC. Breaking changes possible until v1.0.

## The problem this solves

Expand All @@ -20,7 +20,7 @@ The PostgreSQL wire protocol is a streaming socket protocol — not HTTP, not ed

`http-sql` is that one format.

## Goals (v0.1)
## Goals (v0.2)

- **One canonical request/response shape** that works for SELECT, INSERT, UPDATE, DELETE, DDL, and batched statements.
- **Stateless HTTP**. No sessions, no connection objects, no websocket upgrades. One request, one response.
Expand All @@ -29,9 +29,9 @@ The PostgreSQL wire protocol is a streaming socket protocol — not HTTP, not ed
- **Vendor-neutral**. The spec doesn't mention any specific database or platform.
- **JSON-native parameters and results**. Strings, numbers, booleans, null, with a tagged form for blobs and other extended types.

## Non-goals (v0.1)
## Non-goals (v0.2)

- **Streaming large result sets.** Pagination is the v0.1 answer. SSE / chunked responses can come in a later revision.
- **Streaming large result sets.** Pagination is the v0.2 answer. SSE / chunked responses can come in a later revision.
- **Cross-request transactions.** One request is one autocommit unit. Batch requests can opt into atomicity. Multi-request transactions need session state and break statelessness; out of scope.
- **Schema management primitives.** DDL is allowed as a normal SQL statement; the spec doesn't add `CREATE TABLE` helpers.
- **Authentication scheme.** Use HTTP auth headers. `Bearer` is recommended but the spec doesn't mandate.
Expand All @@ -42,7 +42,7 @@ The PostgreSQL wire protocol is a streaming socket protocol — not HTTP, not ed

- [SPEC.md](./SPEC.md) — the wire format definition
- [examples/](./examples) — curl invocations, reference client and server, and two full Cloudflare implementations (D1-backed and Durable-Object-backed)
- [conformance/](./conformance) — what a server must do to claim http-sql v0.1 conformance
- [conformance/](./conformance) — what a server must do to claim http-sql v0.2 conformance
- [implementations.md](./implementations.md) — known servers and clients

## Prior art
Expand Down
40 changes: 22 additions & 18 deletions SPEC.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# http-sql v0.1
# http-sql v0.2

An HTTP wire format for submitting a SQL statement and receiving a result set.

**Status:** Draft, v0.1.
**Status:** Draft, v0.2.
**Editor:** [@rafters-studio](https://github.com/rafters-studio)
**License:** MIT

Expand Down Expand Up @@ -69,13 +69,12 @@ The placeholder syntax itself is the server's, not this spec's: http-sql does no

Servers MAY reject batches that exceed a server-defined statement count with HTTP status `413` and `error.code` of `payload_too_large`.

> **Non-normative note (not part of the v0.1 contract).** The `atomic` obligation above is
> one-directional, and v0.1 states that asymmetry deliberately rather than by oversight.
> **Non-normative note (not part of the normative contract).** The `atomic` obligation above is
> one-directional, and this spec states that asymmetry deliberately rather than by oversight.
>
> There is no conforming way to decline. The obligation on `atomic: true` is unqualified
> here and in section 10.1, and section 7 registers no code meaning "atomicity unavailable."
> A server that cannot execute batches transactionally is simply non-conforming for batch in
> v0.1. Even if such a server declined with a `vendor:` code, the signal would not travel:
> A server that cannot execute batches transactionally is simply non-conforming for batch. Even if such a server declined with a `vendor:` code, the signal would not travel:
> section 7 directs clients to treat unknown codes as the closest registered code by HTTP
> status family, which collapses the decline into ordinary `bad_request` / `sql_error`
> semantics.
Expand All @@ -90,7 +89,7 @@ Servers MAY reject batches that exceed a server-defined statement count with HTT
> `not_supported` code, an `atomic` echo field in the section 6.2 envelope, and capability
> advertisement — are recorded in
> [issue #13](https://github.com/rafters-studio/http-sql/issues/13) and are deliberately out
> of scope for v0.1.
> of scope for this version.

## 5. Parameter types

Expand All @@ -116,7 +115,7 @@ For values that cannot be represented as a JSON primitive (binary blobs, integer
- `$type` (REQUIRED, string) — one of the registered types listed below, or a vendor-namespaced type (`vendor:<name>`).
- `$value` (REQUIRED) — the encoded value as a JSON value.

Registered types in v0.1:
Registered types in v0.2:

| `$type` | `$value` encoding |
|----------|--------------------------------------------------------|
Expand Down Expand Up @@ -214,7 +213,7 @@ HTTP status: `4xx` or `5xx`.
- `error.message` (REQUIRED, string) — human-readable explanation. Servers SHOULD avoid leaking sensitive details.
- `error.statementIndex` (REQUIRED for non-atomic batch statement failures, otherwise OPTIONAL, integer) — the zero-based index of the statement that failed. For a non-atomic batch failure it is the client's only means of determining which statements persisted (section 6.2.1), so it MUST be present. MUST be omitted for single-statement requests.

Registered error codes in v0.1:
Registered error codes in v0.2:

| `code` | HTTP | Meaning |
|--------------------------|------|-----------------------------------------------------------------|
Expand All @@ -228,37 +227,37 @@ Registered error codes in v0.1:
| `rate_limited` | 429 | Too many requests. |
| `internal_error` | 500 | Server malfunction. |

`unsupported_media_type` is introduced in v0.2. Per section 11, a new registered error code is an additive change that increments the minor version; this spec has no patch level, so the code is not available to a server that advertises `0.1`.
`unsupported_media_type` was introduced in v0.2 (per section 11, a new registered error code is an additive change that increments the minor version). It is not available to a server that advertises `0.1`.

Vendor codes carry the prefix `vendor:` (e.g. `vendor:cf_d1_quota_exceeded`). Clients SHOULD treat unknown `error.code` values as if they were the closest registered code by HTTP status family.

## 8. Pagination

http-sql v0.1 does not define pagination. Servers SHOULD enforce a server-defined maximum result row count and return `payload_too_large` if exceeded, with `error.message` suggesting `LIMIT` / `OFFSET` in the SQL. Cursor-based pagination is being considered for a future revision.
http-sql v0.2 does not define pagination. Servers SHOULD enforce a server-defined maximum result row count and return `payload_too_large` if exceeded, with `error.message` suggesting `LIMIT` / `OFFSET` in the SQL. Cursor-based pagination is being considered for a future revision.

## 9. Version negotiation

Conforming servers MUST include the response header:

```
X-Http-Sql-Version: 0.1
X-Http-Sql-Version: 0.2
```

on every response (including error responses).

Clients MAY send the request header:

```
X-Http-Sql-Accept-Version: 0.1
X-Http-Sql-Accept-Version: 0.2
```

to indicate the maximum spec version they understand. Servers MAY use this for forward-compatible behavior. v0.1 servers ignore the header.
to indicate the maximum spec version they understand. Servers MAY use this for forward-compatible behavior. v0.2 servers ignore the header.

## 10. Conformance

### 10.1 Server conformance

A v0.1 conforming server MUST:
A v0.2 conforming server MUST:

1. Accept POST requests with `Content-Type: application/json` at one or more endpoint URLs, and reject other media types per section 2.
2. Accept both single-statement (section 4.1) and batch (section 4.2) request shapes.
Expand All @@ -270,14 +269,14 @@ A v0.1 conforming server MUST:
8. Accept the registered parameter types in section 5 (`blob`, `bigint`).
9. Emit the `X-Http-Sql-Version` response header.

A v0.1 conforming server MAY:
A v0.2 conforming server MAY:

- Accept additional vendor-namespaced parameter types or error codes.
- Apply tenancy, ACLs, row-level security, query whitelisting, or any other policy. http-sql is transport, not policy.

### 10.2 Client conformance

A v0.1 conforming client MUST:
A v0.2 conforming client MUST:

1. Send `Content-Type: application/json`.
2. Send exactly one of `sql` or `batch` in the request body.
Expand All @@ -286,7 +285,7 @@ A v0.1 conforming client MUST:
5. On a non-atomic batch error, treat the statements preceding `error.statementIndex` as applied (section 6.2.1). A client MUST NOT assume no statements were applied.
6. Not require any vendor-specific request or response fields beyond those defined here.

A v0.1 conforming client SHOULD:
A v0.2 conforming client SHOULD:

- Send the `X-Http-Sql-Accept-Version` header.
- Treat `error.code` values it does not recognize as the closest registered code by HTTP status family.
Expand All @@ -295,6 +294,11 @@ A v0.1 conforming client SHOULD:

This spec uses `<major>.<minor>` versioning. Until `1.0`, the minor version increments on any breaking change. After `1.0`, breaking changes increment the major version. Additive changes (new optional fields, new registered types, new registered error codes) increment the minor version.

### Version history

- **0.2** — batch failure behavior made normative (sequential non-atomic execution, `statementIndex` REQUIRED on non-atomic statement failures, preceding statements persist); response-side tagged-value emission MUSTs; `unsupported_media_type` registered (415); `lastInsertId` narrowed to string-or-null; `atomic` obligation unconditional; dialect-neutral parameter typing; `X-Http-Sql-Version` MUST.
- **0.1** — initial draft.

## 12. Security considerations

http-sql carries arbitrary SQL strings. Servers MUST treat the SQL as untrusted input from the perspective of authorization, even if the network channel is authenticated. Practical implications:
Expand Down
10 changes: 4 additions & 6 deletions conformance/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# http-sql conformance

A conforming http-sql v0.1 server passes the test cases below when probed at its endpoint URL with a valid bearer token.

Cases marked `(v0.2+)` exercise behavior introduced after v0.1 and are not required of a server that advertises `X-Http-Sql-Version: 0.1`.
A conforming http-sql v0.2 server passes the test cases below when probed at its endpoint URL with a valid bearer token.

This directory will contain a runnable TypeScript test suite. The current document defines the test cases that runner must implement, so server implementers can self-check before installing the runner.

Expand Down Expand Up @@ -31,7 +29,7 @@ Conformance is self-asserted. The community can call out failures via issues.
| R-1 | Body contains both `sql` and `batch` | 400, `error.code` = `bad_request` |
| R-2 | Body contains neither `sql` nor `batch` | 400, `error.code` = `bad_request` |
| R-3 | Body is not valid JSON | 400, `error.code` = `bad_request` |
| R-4 | `Content-Type` other than `application/json` (v0.2+) | 415, `error.code` = `unsupported_media_type` |
| R-4 | `Content-Type` other than `application/json` | 415, `error.code` = `unsupported_media_type` |
| R-5 | `Content-Type: application/json; charset=utf-8` | Executes normally -- media-type parameters are ignored |

### Single-statement execution
Expand Down Expand Up @@ -86,8 +84,8 @@ Servers on a non-SQLite backend substitute their dialect's literal syntax for th

| ID | Description | Expected response |
|-------|------------------------------------------------------------------|---------------------------------------------|
| H-1 | Any successful response | Includes `X-Http-Sql-Version: 0.1` |
| H-2 | Any error response | Includes `X-Http-Sql-Version: 0.1` |
| H-1 | Any successful response | Includes `X-Http-Sql-Version: 0.2` |
| H-2 | Any error response | Includes `X-Http-Sql-Version: 0.2` |

## Optional / "nice to have"

Expand Down
6 changes: 3 additions & 3 deletions examples/cloudflare-durable-object/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// http-sql v0.1 over Cloudflare Durable Objects, with Hono.
// http-sql v0.2 over Cloudflare Durable Objects, with Hono.
//
// Each tenant maps to its own DO instance, and each DO holds its own real
// SQLite via ctx.storage.sql. The Worker is just a router: validate the
Expand All @@ -17,7 +17,7 @@ export interface Env {
TENANT_TOKEN_BOB: string;
}

const VERSION = "0.1";
const VERSION = "0.2";

const app = new Hono<{ Bindings: Env }>();

Expand Down Expand Up @@ -63,7 +63,7 @@ function resolveTenant(header: string, env: Env): string | null {

// =============================================================================
// TenantDO: one Durable Object per tenant. Holds a real SQLite database via
// ctx.storage.sql. Receives http-sql v0.1 envelopes from the router and runs
// ctx.storage.sql. Receives http-sql v0.2 envelopes from the router and runs
// them against its own SQLite. All access for a given tenant is serialized
// through this single instance.
// =============================================================================
Expand Down
8 changes: 4 additions & 4 deletions examples/cloudflare-worker-to-d1/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// http-sql v0.1 over Cloudflare D1, with Hono.
// http-sql v0.2 over Cloudflare D1, with Hono.
//
// POST any http-sql v0.1 request to this Worker; it runs the SQL against the
// bound D1 database and returns a v0.1 response. Tagged params (blob, bigint)
// POST any http-sql v0.2 request to this Worker; it runs the SQL against the
// bound D1 database and returns a v0.2 response. Tagged params (blob, bigint)
// are decoded before binding; binary results are re-encoded going out.

import { Hono } from "hono";
Expand All @@ -24,7 +24,7 @@ interface StatementResult {
lastInsertId?: string | null;
}

const VERSION = "0.1";
const VERSION = "0.2";

const app = new Hono<{ Bindings: Env }>();

Expand Down
4 changes: 2 additions & 2 deletions examples/reference-client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Reference http-sql v0.1 client, ~40 lines.
// Reference http-sql v0.2 client, ~40 lines.
//
// Uses the platform `fetch`. No dependencies.

Expand Down Expand Up @@ -37,7 +37,7 @@ export class HttpSqlClient {
headers: {
"content-type": "application/json",
"authorization": `Bearer ${this.token}`,
"x-http-sql-accept-version": "0.1",
"x-http-sql-accept-version": "0.2",
},
body: JSON.stringify(body),
});
Expand Down
4 changes: 2 additions & 2 deletions examples/reference-server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Reference http-sql v0.1 server, ~80 lines.
// Reference http-sql v0.2 server, ~80 lines.
//
// Runs on any platform with `fetch`-style Request/Response (Workers, Deno,
// Bun, Node 20+ with the undici fetch globals). The SQL execution is faked
Expand All @@ -16,7 +16,7 @@ interface Result {
lastInsertId?: string | number | null;
}

const VERSION_HEADER = { "X-Http-Sql-Version": "0.1" };
const VERSION_HEADER = { "X-Http-Sql-Version": "0.2" };
const JSON_HEADERS = { "content-type": "application/json", ...VERSION_HEADER };

export async function handle(req: Request, auth: (req: Request) => boolean): Promise<Response> {
Expand Down
4 changes: 2 additions & 2 deletions implementations.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# http-sql implementations

A directory of known servers and clients speaking the [http-sql v0.1 spec](./SPEC.md). The list is bootstrap-thin; PRs welcome.
A directory of known servers and clients speaking the [http-sql v0.2 spec](./SPEC.md). The list is bootstrap-thin; PRs welcome.

## Servers

Expand All @@ -27,7 +27,7 @@ The combinations above let you enter at whichever end matches what you already h

## How to add yours

1. Implement the [v0.1 spec](./SPEC.md) (or the [conformance contract](./conformance/README.md) for self-check).
1. Implement the [v0.2 spec](./SPEC.md) (or the [conformance contract](./conformance/README.md) for self-check).
2. Open a PR adding a row to the table above.
3. Include: name, form (Worker / Node / library), backend (D1 / Turso / Postgres / DO / etc), and one-line notes.

Expand Down
Loading