From c3320c417261371f9480853e0700967971c7d158 Mon Sep 17 00:00:00 2001 From: Taylor Bantle Date: Tue, 7 Jul 2026 15:00:33 -0700 Subject: [PATCH 1/8] docs: regenerate v2 API docs for the new SQL write endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pulls v2.yaml from ld's taylor/api-v2-sql-write branch, which turns POST /api/v2/databases/{owner}/{database}/sql into a dual-purpose endpoint: a SqlReadRequest body behaves as before, a SqlWriteRequest body ({from_branch, to_branch, query}) kicks off an async write. The generator didn't support oneOf request-body schemas — it silently dropped the request-body table and example payload for any endpoint shaped that way. Fixed generate-api-v2.mjs to render one table/example per oneOf variant, labeled by schema title, and regenerated database.md and models.md. Also fixed a stale hand-written link in README.md's index (old operationId/title for this endpoint). Co-Authored-By: Claude Sonnet 5 --- scripts/generate-api-v2.mjs | 104 ++++++--- .../content/products/dolthub/api/v2/README.md | 2 +- .../products/dolthub/api/v2/database.md | 29 ++- .../content/products/dolthub/api/v2/models.md | 23 ++ specs/dolthub-v2.yaml | 212 +++++++++++++----- 5 files changed, 274 insertions(+), 96 deletions(-) diff --git a/scripts/generate-api-v2.mjs b/scripts/generate-api-v2.mjs index 4462563..26c9e69 100644 --- a/scripts/generate-api-v2.mjs +++ b/scripts/generate-api-v2.mjs @@ -82,39 +82,64 @@ function methodBadge(method) { return `![${label}](https://img.shields.io/badge/${label}-${color}?style=flat-square)`; } -function curlExample(method, path, operation) { +// A request body is normally a single schema. When it's a `oneOf` (e.g. an +// endpoint that's dual-purpose depending on the payload shape), treat each +// branch as its own variant — each gets its own request-body table and +// example below, labeled with the branch's schema title. +function requestBodySchemaVariants(requestBody) { + const schema = requestBody?.content?.["application/json"]?.schema; + if (!schema) return []; + const resolved = deref(schema); + if (Array.isArray(resolved.oneOf) && resolved.oneOf.length) { + return resolved.oneOf.map((s) => deref(s)); + } + return [resolved]; +} + +function examplePayload(resolved) { + const required = resolved.required ?? []; + const props = resolved.properties ?? {}; + const example = Object.fromEntries( + Object.entries(props) + .filter(([k]) => required.includes(k)) + .slice(0, 4) + .map(([k, v]) => { + const t = v.type ?? (v.enum ? "enum" : "object"); + const ex = + v.examples?.[0] ?? + v.example ?? + (t === "string" ? `"example_${k}"` : t === "boolean" ? false : 0); + return [k, ex]; + }) + ); + return Object.keys(example).length > 0 ? example : null; +} + +function curlLines(method, path, payload) { const lines = [ `curl -X ${METHOD_LABELS[method] ?? method.toUpperCase()} 'https://www.dolthub.com${path}'`, ` -H 'Authorization: Bearer YOUR_TOKEN'`, ]; if (method !== "get" && method !== "delete") { lines.push(` -H 'Content-Type: application/json'`); - const reqBody = operation.requestBody?.content?.["application/json"]?.schema; - if (reqBody) { - const resolved = deref(reqBody); - const required = resolved.required ?? []; - const props = resolved.properties ?? {}; - const example = Object.fromEntries( - Object.entries(props) - .filter(([k]) => required.includes(k)) - .slice(0, 4) - .map(([k, v]) => { - const t = v.type ?? (v.enum ? "enum" : "object"); - const ex = - v.examples?.[0] ?? - v.example ?? - (t === "string" ? `"example_${k}"` : t === "boolean" ? false : 0); - return [k, ex]; - }) - ); - if (Object.keys(example).length > 0) { - lines.push(` -d '${JSON.stringify(example)}'`); - } - } + if (payload) lines.push(` -d '${JSON.stringify(payload)}'`); } return lines.join(" \\\n"); } +// Returns one example per request-body variant: [{ label, curl }]. `label` +// is null for the common single-schema case (no "Example request" suffix). +function curlExamples(method, path, operation) { + const variants = requestBodySchemaVariants(operation.requestBody); + if (variants.length <= 1) { + return [{ label: null, curl: curlLines(method, path, variants[0] && examplePayload(variants[0])) }]; + } + return variants.map((v) => ({ + label: v.title ?? null, + curl: curlLines(method, path, examplePayload(v)), + })); +} + function parametersSection(params) { if (!params?.length) return ""; const rows = params @@ -131,11 +156,7 @@ function parametersSection(params) { return `\n**Parameters**\n\n| Name | In | Type | Required | Description |\n|------|----|------|----------|-------------|\n${rows}\n`; } -function requestBodySection(requestBody) { - if (!requestBody) return ""; - const schema = requestBody.content?.["application/json"]?.schema; - if (!schema) return ""; - const resolved = deref(schema); +function requestBodyFieldsTable(resolved) { const required = resolved.required ?? []; const props = resolved.properties ?? {}; if (!Object.keys(props).length) return ""; @@ -147,7 +168,25 @@ function requestBodySection(requestBody) { return `| \`${k}\` | ${type} | ${req} | ${desc} |`; }) .join("\n"); - return `\n**Request body**\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n${rows}\n`; + return `| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n${rows}\n`; +} + +function requestBodySection(requestBody) { + const variants = requestBodySchemaVariants(requestBody); + if (!variants.length) return ""; + if (variants.length === 1) { + const table = requestBodyFieldsTable(variants[0]); + return table ? `\n**Request body**\n\n${table}` : ""; + } + return variants + .map((v) => { + const table = requestBodyFieldsTable(v); + if (!table) return ""; + const label = v.title ? ` — \`${v.title}\`` : ""; + return `\n**Request body${label}**\n\n${table}`; + }) + .filter(Boolean) + .join(""); } function responseSchemaName(schema) { @@ -264,7 +303,12 @@ function endpointBlock(method, path, operation, headingLevel = "###") { const body = requestBodySection(deref(operation.requestBody ?? {})); const responses = responsesSection(operation.responses); const successExample = successExampleBlock(operation.responses); - const curl = `\n**Example request**\n\n\`\`\`sh\n${curlExample(method, path, operation)}\n\`\`\`\n`; + const curl = curlExamples(method, path, operation) + .map(({ label, curl }) => { + const heading = label ? `\n**Example request — \`${label}\`**\n\n` : `\n**Example request**\n\n`; + return `${heading}\`\`\`sh\n${curl}\n\`\`\`\n`; + }) + .join(""); return [heading, methodPath, description, params, body, curl, responses, successExample] .filter(Boolean) .join(""); diff --git a/site/dolt/src/content/products/dolthub/api/v2/README.md b/site/dolt/src/content/products/dolthub/api/v2/README.md index 6c58579..b8a79b7 100644 --- a/site/dolt/src/content/products/dolthub/api/v2/README.md +++ b/site/dolt/src/content/products/dolthub/api/v2/README.md @@ -40,7 +40,7 @@ Compared to `v1alpha1`, v2 commits to: | **GET** | `/api/v2/databases/{owner}/{database}/releases` | [List releases](database#listReleases) | | **POST** | `/api/v2/databases/{owner}/{database}/releases` | [Create a release](database#createRelease) | | **GET** | `/api/v2/databases/{owner}/{database}/sql` | [Run a read-only SQL query](database#runSqlReadQuery) | -| **POST** | `/api/v2/databases/{owner}/{database}/sql` | [Run a read-only SQL query (large-query variant)](database#runSqlReadQueryPost) | +| **POST** | `/api/v2/databases/{owner}/{database}/sql` | [Run a SQL query (read or write)](database#runSqlPost) | | **GET** | `/api/v2/databases/{owner}/{database}/pulls` | [List pull requests](database#listPulls) | | **POST** | `/api/v2/databases/{owner}/{database}/pulls` | [Create a pull request](database#createPull) | | **GET** | `/api/v2/databases/{owner}/{database}/pulls/{pull_number}` | [Get a pull request](database#getPull) | diff --git a/site/dolt/src/content/products/dolthub/api/v2/database.md b/site/dolt/src/content/products/dolthub/api/v2/database.md index 5503fb3..f313302 100644 --- a/site/dolt/src/content/products/dolthub/api/v2/database.md +++ b/site/dolt/src/content/products/dolthub/api/v2/database.md @@ -193,10 +193,10 @@ curl -X GET 'https://www.dolthub.com/api/v2/databases/{owner}/{database}/sql' \ --- -### Run a read-only SQL query (large-query variant) {#runSqlReadQueryPost} +### Run a SQL query (read or write) {#runSqlPost} `POST /api/v2/databases/{owner}/{database}/sql` -Identical to `GET /sql` but accepts the query in the request body to avoid the browser/proxy URL-length limit (~4–8 KB) that `?q=` hits for large SQL statements. Use this when the query is too long for a query string; prefer `GET` for short queries (simpler to cache and log). The response shape is identical. +Dual-purpose `/sql` POST: discriminated by the request body shape. A `SqlReadRequest` body (`{ ref, q }`) runs a read identical to `GET /sql` — same response shape — but carries the query in the body to dodge the browser/proxy URL-length limit (~4–8 KB) that `?q=` hits for large SQL statements. A `SqlWriteRequest` body (`{ from_branch, to_branch, query }`) kicks off an asynchronous SQL write — running the write on `to_branch` (creating it from `from_branch` if it doesn't exist), then merging `from_branch` into `to_branch` — and returns `202` with an `OperationRef`; poll `GET /api/v2/operations/{id}` for completion. Authentication is required for writes; reads follow the database-level convention (public DBs readable without a token, private DBs require one). **Parameters** @@ -206,7 +206,7 @@ Identical to `GET /sql` but accepts the query in the request body to avoid the b | `owner` | path | string | yes | The user or organization that owns the database. | | `database` | path | string | yes | The database name, unique within the owner. | -**Request body** +**Request body — `SqlReadRequest`** | Field | Type | Required | Description | |-------|------|----------|-------------| @@ -215,7 +215,15 @@ Identical to `GET /sql` but accepts the query in the request body to avoid the b | `limit` | integer | no | Maximum number of rows to return (default `1000`). | | `timeout_ms` | integer | no | Per-query execution timeout in milliseconds (default `30000`, cap `60000`). | -**Example request** +**Request body — `SqlWriteRequest`** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `from_branch` | string | yes | The branch to merge from after the write completes. | +| `to_branch` | string | yes | The branch the write runs on. Created from `from_branch` when it doesn't already exist. | +| `query` | string | yes | The SQL write statement to execute. | + +**Example request — `SqlReadRequest`** ```sh curl -X POST 'https://www.dolthub.com/api/v2/databases/{owner}/{database}/sql' \ @@ -224,13 +232,24 @@ curl -X POST 'https://www.dolthub.com/api/v2/databases/{owner}/{database}/sql' \ -d '{"ref":"main","q":"SELECT name, year FROM jails WHERE state = 'California' LIMIT 10"}' ``` +**Example request — `SqlWriteRequest`** + +```sh +curl -X POST 'https://www.dolthub.com/api/v2/databases/{owner}/{database}/sql' \ + -H 'Authorization: Bearer YOUR_TOKEN' \ + -H 'Content-Type: application/json' \ + -d '{"from_branch":"main","to_branch":"feature/new-states","query":"INSERT INTO states (name, abbr) VALUES ('Puerto Rico', 'PR')"}' +``` + **Responses** | Status | Description | Schema | |--------|-------------|--------| -| `200` | The query result. SQL-level errors live in `status` + `message`. | [`QueryResult`](models#model-queryresult) | +| `200` | The read query result. SQL-level errors live in `status` + `message`. | [`QueryResult`](models#model-queryresult) | +| `202` | The write operation has been accepted and is queued. | [`OperationRef`](models#model-operationref) | | `400` | The request was malformed or failed input validation. | [`Problem`](models#model-problem) | | `401` | Authentication credentials were missing or invalid. | [`Problem`](models#model-problem) | +| `403` | Authenticated, but not permitted to perform this action. | [`Problem`](models#model-problem) | | `404` | The requested resource does not exist. | [`Problem`](models#model-problem) | | `405` | The HTTP method is not supported for this resource. | [`Problem`](models#model-problem) | | `500` | An unexpected server error occurred. | [`Problem`](models#model-problem) | diff --git a/site/dolt/src/content/products/dolthub/api/v2/models.md b/site/dolt/src/content/products/dolthub/api/v2/models.md index 06b0479..8c691a6 100644 --- a/site/dolt/src/content/products/dolthub/api/v2/models.md +++ b/site/dolt/src/content/products/dolthub/api/v2/models.md @@ -449,6 +449,29 @@ One column in a query result's schema. --- +## SqlReadRequest {#model-sqlreadrequest} +Body for the read variant of `POST /api/v2/databases/{owner}/{database}/sql`. Identical semantics to the `GET /sql` query string — pass the query in the body when it's too long for a URL. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `ref` | `string` | yes | The branch, tag, or commit hash to query against. A 32-character value using only the characters `0-9a-v` (Dolt's base32 alphabet) is treated as a commit hash; everything else resolves as a branch, tag, or other ref. | +| `q` | `string` | yes | The SQL query to execute. Read-only — mutations are rejected. | +| `limit` | `integer` | no | Maximum number of rows to return (default `1000`). | +| `timeout_ms` | `integer` | no | Per-query execution timeout in milliseconds (default `30000`, cap `60000`). | + +--- + +## SqlWriteRequest {#model-sqlwriterequest} +Body for the write variant of `POST /api/v2/databases/{owner}/{database}/sql`. Runs `query` on `to_branch` (creating it from `from_branch` if it doesn't exist) then merges `from_branch` into `to_branch`. Returns `202` + `OperationRef`. `from_branch` and `to_branch` are bare branch names — both must live in the URL's `{owner}/{database}`. + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `from_branch` | `string` | yes | The branch to merge from after the write completes. | +| `to_branch` | `string` | yes | The branch the write runs on. Created from `from_branch` when it doesn't already exist. | +| `query` | `string` | yes | The SQL write statement to execute. | + +--- + ## QueryResult {#model-queryresult} The result of a `runSqlReadQuery` call. SQL-level conditions (success, error, timeout, row-limit) live in `status` and `message` — they are query-level, not transport-level. diff --git a/specs/dolthub-v2.yaml b/specs/dolthub-v2.yaml index cfd4ecb..4af8de3 100644 --- a/specs/dolthub-v2.yaml +++ b/specs/dolthub-v2.yaml @@ -42,7 +42,7 @@ servers: # operations may override this (e.g. public reads) once paths are added. security: - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] # Operation tags are registered here so renderers (Scalar) group operations consistently. # New tags are added as resources land in subsequent phases. @@ -62,6 +62,9 @@ paths: description: Returns the profile of the user identified by the request's credentials. tags: - User + security: + - personalAccessToken: [] + - oauth2: [api_read_write] responses: "200": description: The authenticated user's profile. @@ -106,7 +109,7 @@ paths: - Database security: - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] requestBody: required: true content: @@ -164,7 +167,7 @@ paths: security: - {} - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -233,7 +236,7 @@ paths: security: - {} - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -298,7 +301,7 @@ paths: - Database security: - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -377,7 +380,7 @@ paths: security: - {} - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -445,7 +448,7 @@ paths: - Database security: - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -527,7 +530,7 @@ paths: security: - {} - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -595,7 +598,7 @@ paths: - Database security: - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -672,7 +675,7 @@ paths: security: - {} - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -741,7 +744,7 @@ paths: - Database security: - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -827,7 +830,7 @@ paths: security: - {} - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -919,19 +922,25 @@ paths: "500": $ref: "#/components/responses/InternalServerError" post: - operationId: runSqlReadQueryPost - summary: Run a read-only SQL query (large-query variant). + operationId: runSqlPost + summary: Run a SQL query (read or write). description: >- - Identical to `GET /sql` but accepts the query in the request body to avoid the - browser/proxy URL-length limit (~4–8 KB) that `?q=` hits for large SQL statements. - Use this when the query is too long for a query string; prefer `GET` for short queries - (simpler to cache and log). The response shape is identical. + Dual-purpose `/sql` POST: discriminated by the request body shape. A `SqlReadRequest` + body (`{ ref, q }`) runs a read identical to `GET /sql` — same response shape — but + carries the query in the body to dodge the browser/proxy URL-length limit (~4–8 KB) + that `?q=` hits for large SQL statements. A `SqlWriteRequest` body + (`{ from_branch, to_branch, query }`) kicks off an asynchronous SQL write — running + the write on `to_branch` (creating it from `from_branch` if it doesn't exist), then + merging `from_branch` into `to_branch` — and returns `202` with an `OperationRef`; + poll `GET /api/v2/operations/{id}` for completion. Authentication is required for + writes; reads follow the database-level convention (public DBs readable without a + token, private DBs require one). tags: - Database security: - {} - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -954,37 +963,12 @@ paths: content: application/json: schema: - type: object - required: - - ref - - q - properties: - ref: - type: string - minLength: 1 - description: >- - The branch, tag, or commit hash to query against. A 32-character value using - only the characters `0-9a-v` (Dolt's base32 alphabet) is treated as a commit - hash; everything else resolves as a branch, tag, or other ref. - example: main - q: - type: string - minLength: 1 - description: The SQL query to execute. Read-only — mutations are rejected. - example: "SELECT name, year FROM jails WHERE state = 'California' LIMIT 10" - limit: - type: integer - minimum: 1 - description: Maximum number of rows to return (default `1000`). - example: 100 - timeout_ms: - type: integer - minimum: 1 - description: Per-query execution timeout in milliseconds (default `30000`, cap `60000`). - example: 10000 + oneOf: + - $ref: "#/components/schemas/SqlReadRequest" + - $ref: "#/components/schemas/SqlWriteRequest" responses: "200": - description: The query result. SQL-level errors live in `status` + `message`. + description: The read query result. SQL-level errors live in `status` + `message`. headers: x-request-id: description: >- @@ -1006,10 +990,35 @@ paths: properties: data: $ref: "#/components/schemas/QueryResult" + "202": + description: The write operation has been accepted and is queued. + headers: + x-request-id: + description: >- + The request identifier — echoed on every response. Reuse the inbound + `x-request-id` from the caller / edge proxy when present; otherwise the + server mints a `req_` value. + schema: + type: string + examples: + - req_2f1c9e7a-3b6d-4c5e-8a9f-0d1e2f3a4b5c + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/Envelope" + - type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/OperationRef" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" + "403": + $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "405": @@ -1031,7 +1040,7 @@ paths: security: - {} - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -1099,7 +1108,7 @@ paths: - Database security: - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -1177,7 +1186,7 @@ paths: security: - {} - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -1251,7 +1260,7 @@ paths: - Database security: - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -1337,7 +1346,7 @@ paths: security: - {} - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -1412,7 +1421,7 @@ paths: - Database security: - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -1494,7 +1503,7 @@ paths: - Database security: - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -1569,7 +1578,7 @@ paths: - Database security: - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -1644,7 +1653,7 @@ paths: - Database security: - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -1732,7 +1741,7 @@ paths: security: - {} - personalAccessToken: [] - - oauth2: [] + - oauth2: [api_read_write] parameters: - name: owner in: path @@ -1798,6 +1807,9 @@ paths: name returned by the backend. tags: - Operations + security: + - personalAccessToken: [] + - oauth2: [api_read_write] parameters: - name: operation_id in: path @@ -1848,12 +1860,19 @@ components: description: "A DoltHub personal access token, sent as `Authorization: Bearer `." oauth2: type: oauth2 - description: "A DoltHub OAuth 2.0 access token, sent as `Authorization: Bearer `." + description: >- + A DoltHub OAuth 2.0 access token, sent as `Authorization: Bearer `. v2 ships + with a single scope, `api_read_write`, which grants both reads and writes — there is + no read-only scope today. Every endpoint that accepts `oauth2` lists `api_read_write` + as the required scope so the rendered docs and any client-config generator surface + the value callers must actually request; future per-endpoint narrowing (e.g. + `api_read`) will be additive under the v2 stability policy (§5.1). flows: authorizationCode: authorizationUrl: https://www.dolthub.com/oauth/authorize tokenUrl: https://www.dolthub.com/oauth/token - scopes: {} + scopes: + api_read_write: Full read and write access to the v2 API. schemas: # --- Error model (1.c, §5.3) --------------------------------------------------------- @@ -3186,6 +3205,79 @@ components: (e.g. `COUNT(*)`). examples: - jails + SqlReadRequest: + type: object + title: SqlReadRequest + description: >- + Body for the read variant of `POST /api/v2/databases/{owner}/{database}/sql`. Identical + semantics to the `GET /sql` query string — pass the query in the body when it's too + long for a URL. + required: + - ref + - q + properties: + ref: + type: string + minLength: 1 + description: >- + The branch, tag, or commit hash to query against. A 32-character value using + only the characters `0-9a-v` (Dolt's base32 alphabet) is treated as a commit + hash; everything else resolves as a branch, tag, or other ref. + examples: + - main + q: + type: string + minLength: 1 + description: The SQL query to execute. Read-only — mutations are rejected. + examples: + - "SELECT name, year FROM jails WHERE state = 'California' LIMIT 10" + limit: + type: integer + minimum: 1 + description: Maximum number of rows to return (default `1000`). + examples: + - 100 + timeout_ms: + type: integer + minimum: 1 + description: Per-query execution timeout in milliseconds (default `30000`, cap `60000`). + examples: + - 10000 + additionalProperties: false + SqlWriteRequest: + type: object + title: SqlWriteRequest + description: >- + Body for the write variant of `POST /api/v2/databases/{owner}/{database}/sql`. Runs + `query` on `to_branch` (creating it from `from_branch` if it doesn't exist) then merges + `from_branch` into `to_branch`. Returns `202` + `OperationRef`. `from_branch` and + `to_branch` are bare branch names — both must live in the URL's `{owner}/{database}`. + required: + - from_branch + - to_branch + - query + properties: + from_branch: + type: string + minLength: 1 + description: The branch to merge from after the write completes. + examples: + - main + to_branch: + type: string + minLength: 1 + description: >- + The branch the write runs on. Created from `from_branch` when it doesn't + already exist. + examples: + - feature/new-states + query: + type: string + minLength: 1 + description: The SQL write statement to execute. + examples: + - "INSERT INTO states (name, abbr) VALUES ('Puerto Rico', 'PR')" + additionalProperties: false QueryResult: type: object title: QueryResult From c2b21f8b9570adea5e312e1e4c677f88cc29c036 Mon Sep 17 00:00:00 2001 From: Taylor Bantle Date: Wed, 8 Jul 2026 15:11:06 -0700 Subject: [PATCH 2/8] docs: regenerate v2 API docs after main merge (comment cleanup only) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merged origin/main into ld's taylor/api-v2-sql-write locally to pick up its "drop stale plan-phase reference" cleanup. No schema or endpoint changes — just four description strings in models.md losing their internal (§5.x) plan-doc references. Note: that merge on the ld side has an unresolved conflict in sql.ts (application code, not the spec) — left for manual resolution there, not touched from this repo. Co-Authored-By: Claude Sonnet 5 --- .../content/products/dolthub/api/v2/models.md | 12 ++--- specs/dolthub-v2.yaml | 52 ++++++++----------- 2 files changed, 27 insertions(+), 37 deletions(-) diff --git a/site/dolt/src/content/products/dolthub/api/v2/models.md b/site/dolt/src/content/products/dolthub/api/v2/models.md index 8c691a6..e034613 100644 --- a/site/dolt/src/content/products/dolthub/api/v2/models.md +++ b/site/dolt/src/content/products/dolthub/api/v2/models.md @@ -8,7 +8,7 @@ description: Request and response schemas for the DoltHub v2 API. Shared request and response types used across the v2 API. See the [error model](#model-problem) for how failures are reported. ## ErrorCode {#model-errorcode} -A stable, machine-readable error code in SCREAMING_SNAKE_CASE. Clients branch on this value, never on the human-readable `title`/`detail` prose. The baseline codes below cover the standard HTTP failure categories; endpoint-specific codes (e.g. `BRANCH_NOT_FOUND`) are appended to this enum alongside the endpoints that emit them, which is an additive, non-breaking change under the v2 stability policy (§5.1). +A stable, machine-readable error code in SCREAMING_SNAKE_CASE. Clients branch on this value, never on the human-readable `title`/`detail` prose. The baseline codes below cover the standard HTTP failure categories; endpoint-specific codes (e.g. `BRANCH_NOT_FOUND`) are appended to this enum alongside the endpoints that emit them, which is an additive, non-breaking change under the v2 stability policy. **Enum values** @@ -38,13 +38,13 @@ A structured error body returned for every non-2xx response, following RFC 9457 | `status` | `integer` | yes | The HTTP status code, repeated in the body for convenience. | | `detail` | `string` | no | A human-readable explanation specific to this occurrence of the problem. | | `instance` | `string` | no | A URI reference identifying the specific occurrence (typically the request path). | -| `code` | `string` | yes | A stable, machine-readable error code in SCREAMING_SNAKE_CASE. Clients branch on this value, never on the human-readable `title`/`detail` prose. The baseline codes below cover the standard HTTP failure categories; endpoint-specific codes (e.g. `BRANCH_NOT_FOUND`) are appended to this enum alongside the endpoints that emit them, which is an additive, non-breaking change under the v2 stability policy (§5.1). | +| `code` | `string` | yes | A stable, machine-readable error code in SCREAMING_SNAKE_CASE. Clients branch on this value, never on the human-readable `title`/`detail` prose. The baseline codes below cover the standard HTTP failure categories; endpoint-specific codes (e.g. `BRANCH_NOT_FOUND`) are appended to this enum alongside the endpoints that emit them, which is an additive, non-breaking change under the v2 stability policy. | | `request_id` | `string` | yes | The request identifier, echoed on every response. Include it when contacting support so a request can be traced end-to-end. | --- ## Meta {#model-meta} -Response metadata carried alongside the primary `data` payload. All fields are optional; list endpoints populate `next_page_token` for cursor pagination (§5.6). +Response metadata carried alongside the primary `data` payload. All fields are optional; list endpoints populate `next_page_token` for cursor pagination. | Field | Type | Required | Description | |-------|------|----------|-------------| @@ -53,12 +53,12 @@ Response metadata carried alongside the primary `data` payload. All fields are o --- ## Envelope {#model-envelope} -The success envelope wrapping every 2xx response body (§5.4): the resource or list of resources under `data`, with optional `meta`. This is the single success shape for the API — there are no unenveloped success bodies. Endpoints narrow `data` to a concrete resource via `allOf` (see the section comment above); the base leaves `data` unconstrained so that composition works. +The success envelope wrapping every 2xx response body: the resource or list of resources under `data`, with optional `meta`. This is the single success shape for the API — there are no unenveloped success bodies. Endpoints narrow `data` to a concrete resource via `allOf` (see the section comment above); the base leaves `data` unconstrained so that composition works. | Field | Type | Required | Description | |-------|------|----------|-------------| | `data` | `object,array` | yes | The primary response payload — a resource, or an array of resources for list endpoints. | -| `meta` | `object` | no | Response metadata carried alongside the primary `data` payload. All fields are optional; list endpoints populate `next_page_token` for cursor pagination (§5.6). | +| `meta` | `object` | no | Response metadata carried alongside the primary `data` payload. All fields are optional; list endpoints populate `next_page_token` for cursor pagination. | --- @@ -298,7 +298,7 @@ Error details recorded when an operation reaches the `failed` status. | Field | Type | Required | Description | |-------|------|----------|-------------| | `status` | `integer` | yes | HTTP-equivalent status code for the failure. | -| `code` | `string` | yes | A stable, machine-readable error code in SCREAMING_SNAKE_CASE. Clients branch on this value, never on the human-readable `title`/`detail` prose. The baseline codes below cover the standard HTTP failure categories; endpoint-specific codes (e.g. `BRANCH_NOT_FOUND`) are appended to this enum alongside the endpoints that emit them, which is an additive, non-breaking change under the v2 stability policy (§5.1). | +| `code` | `string` | yes | A stable, machine-readable error code in SCREAMING_SNAKE_CASE. Clients branch on this value, never on the human-readable `title`/`detail` prose. The baseline codes below cover the standard HTTP failure categories; endpoint-specific codes (e.g. `BRANCH_NOT_FOUND`) are appended to this enum alongside the endpoints that emit them, which is an additive, non-breaking change under the v2 stability policy. | | `title` | `string` | yes | A short, human-readable summary of the failure. | | `detail` | `string` | no | A human-readable explanation of the failure, sourced from the underlying operation error message when available. | diff --git a/specs/dolthub-v2.yaml b/specs/dolthub-v2.yaml index 4af8de3..643a8af 100644 --- a/specs/dolthub-v2.yaml +++ b/specs/dolthub-v2.yaml @@ -1,15 +1,8 @@ # DoltHub Public API v2 — OpenAPI contract. # -# This is the single source of truth for the v2 API (see dolthub-api-v2-plan.md §5.7). -# TypeScript DTO types, runtime/contract-test JSON Schemas, and the public docs are all -# generated from this file — never hand-edited downstream. -# -# Shared components are built up across phases: -# - components.schemas.Problem / ErrorCode -> 1.c (error model) [done] -# - components.schemas.Envelope / Meta -> 1.d (success envelope) [done] -# - components.schemas.Operation -> 4.a (async operation resource) [done] -# The first real path is GET /api/v2/user (1.t), which also adds the User / -# EmailAddress resources. +# This is the single source of truth for the v2 API. TypeScript DTO types, +# runtime/contract-test JSON Schemas, and the public docs are all generated +# from this file — never hand-edited downstream. openapi: 3.1.0 info: @@ -45,7 +38,6 @@ security: - oauth2: [api_read_write] # Operation tags are registered here so renderers (Scalar) group operations consistently. -# New tags are added as resources land in subsequent phases. tags: - name: Database description: DoltHub databases and their metadata. @@ -1850,7 +1842,7 @@ paths: $ref: "#/components/responses/InternalServerError" components: - # securitySchemes model the credential types the v2 auth layer accepts (§5.5). Personal + # securitySchemes model the credential types the v2 auth layer accepts. Personal # access tokens and OAuth access tokens are both presented in the `Authorization` header. # The public API does not support browser session cookies. securitySchemes: @@ -1875,9 +1867,8 @@ components: api_read_write: Full read and write access to the v2 API. schemas: - # --- Error model (1.c, §5.3) --------------------------------------------------------- - # Every non-2xx response uses Problem (RFC 9457). The Operation resource (1.e) is added - # to this map by its PR. + # --- Error model --------------------------------------------------------------------- + # Every non-2xx response uses Problem (RFC 9457). ErrorCode: type: string title: ErrorCode @@ -1886,7 +1877,7 @@ components: this value, never on the human-readable `title`/`detail` prose. The baseline codes below cover the standard HTTP failure categories; endpoint-specific codes (e.g. `BRANCH_NOT_FOUND`) are appended to this enum alongside the endpoints that emit them, - which is an additive, non-breaking change under the v2 stability policy (§5.1). + which is an additive, non-breaking change under the v2 stability policy. enum: - VALIDATION_FAILED # 400 — malformed request or failed input validation - UNAUTHENTICATED # 401 — missing or invalid credentials @@ -1922,7 +1913,7 @@ components: A URI identifying the problem type; when dereferenced it points at human-readable documentation for the error. examples: - - https://dolthub.com/docs/api/errors/not-found + - https://dolthub.com/docs/products/dolthub/api/v2/models/#model-errorcode title: type: string description: A short, human-readable summary of the problem type. @@ -1958,7 +1949,7 @@ components: - req_01HZX9P7Q5N2M8 additionalProperties: true examples: - - type: https://dolthub.com/docs/api/errors/not-found + - type: https://dolthub.com/docs/products/dolthub/api/v2/models/#model-errorcode title: Not found status: 404 detail: "Branch 'main' does not exist in dolthub/us-jails" @@ -1966,7 +1957,7 @@ components: code: NOT_FOUND request_id: req_01HZX9P7Q5N2M8 - # --- Success envelope (1.d, §5.4) ---------------------------------------------------- + # --- Success envelope ---------------------------------------------------------------- # Every 2xx response body is enveloped: the resource (or list) under `data`, with # optional response metadata under `meta`. Concrete endpoint responses specialize the # envelope by narrowing `data` to a specific resource via `allOf`, e.g.: @@ -1984,13 +1975,13 @@ components: # $ref: "#/components/schemas/User" # # The base types `data` only loosely (object or array) so this allOf narrowing composes - # cleanly ((object|array) ∩ User = User). The first real use is GET /api/v2/user in 1.t. + # cleanly ((object|array) ∩ User = User). Meta: type: object title: Meta description: >- Response metadata carried alongside the primary `data` payload. All fields are - optional; list endpoints populate `next_page_token` for cursor pagination (§5.6). + optional; list endpoints populate `next_page_token` for cursor pagination. properties: next_page_token: type: string @@ -2007,7 +1998,7 @@ components: type: object title: Envelope description: >- - The success envelope wrapping every 2xx response body (§5.4): the resource or list of + The success envelope wrapping every 2xx response body: the resource or list of resources under `data`, with optional `meta`. This is the single success shape for the API — there are no unenveloped success bodies. Endpoints narrow `data` to a concrete resource via `allOf` (see the section comment above); the base leaves `data` @@ -2026,10 +2017,10 @@ components: meta: next_page_token: eyJvZmZzZXQiOjI1fQ - # --- Resources (1.t onward) ---------------------------------------------------------- + # --- Resources ---------------------------------------------------------------------- # Resource schemas are the public DTO surface — what the v2 spec contract guarantees to - # integrators. Field names are snake_case (§5.2). Fields are added additively under the - # v2 stability policy (§5.1); renames or removals require v3. + # integrators. Field names are snake_case. Fields are added additively under the v2 + # stability policy; renames or removals require v3. DatabaseRef: type: object title: DatabaseRef @@ -3424,8 +3415,8 @@ components: is_verified: true # Reusable parameters — pulled out as components.parameters so every list endpoint $refs - # the same shape rather than redeclaring it. PageToken is the v2 pagination convention - # (§5.6, established by 2.b); the response side lives in components.schemas.Meta.next_page_token. + # the same shape rather than redeclaring it. PageToken is the v2 pagination convention; + # the response side lives in components.schemas.Meta.next_page_token. parameters: PageToken: name: page_token @@ -3439,10 +3430,9 @@ components: type: string example: eyJvZmZzZXQiOjI1fQ - # Reusable error responses — the status-code conventions for v2 (§5.3). Operations - # reference these so every endpoint returns the right code with one consistent body - # instead of the v1 "everything is 400" behavior. They are wired into operations as paths - # are added (first in 1.t). + # Reusable error responses — the status-code conventions for v2. Operations reference + # these so every endpoint returns the right code with one consistent body instead of the + # v1 "everything is 400" behavior. responses: BadRequest: description: The request was malformed or failed input validation. From 219ba6488538705afa6091befe01660af10b1f8a Mon Sep 17 00:00:00 2001 From: Taylor Bantle Date: Wed, 8 Jul 2026 15:44:16 -0700 Subject: [PATCH 3/8] docs: add v1alpha1 SQL write endpoints to the v2 migration table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The endpoint-mapping table was missing the async write flow entirely (POST .../write/{from_branch}/{to_branch} and its GET .../write poll endpoint), left out before v2 had a write-capable SQL endpoint. Map them to the new dual-purpose POST .../sql (runSqlPost) and the standard operations-polling pattern used elsewhere in this table. The v2 index (README.md) already covers this endpoint correctly from an earlier commit — no change needed there. Co-Authored-By: Claude Sonnet 5 --- site/dolt/src/content/products/dolthub/api/v2/migration.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/site/dolt/src/content/products/dolthub/api/v2/migration.md b/site/dolt/src/content/products/dolthub/api/v2/migration.md index bb84ac9..f9e63dc 100644 --- a/site/dolt/src/content/products/dolthub/api/v2/migration.md +++ b/site/dolt/src/content/products/dolthub/api/v2/migration.md @@ -121,6 +121,8 @@ curl 'https://www.dolthub.com/api/v2/operations/owners/dolthub/repos/us-jails/jo | `POST /database` | [`POST /api/v2/databases`](database#createDatabase) | | `GET /{owner}/{database}?q=` | [`GET /api/v2/databases/{owner}/{database}/sql?q=`](database#runSqlReadQuery) | | `GET /{owner}/{database}/{ref}?q=` | [`GET /api/v2/databases/{owner}/{database}/sql?q=&ref=`](database#runSqlReadQuery) | +| `POST /{owner}/{database}/write/{from_branch}/{to_branch}` | [`POST /api/v2/databases/{owner}/{database}/sql`](database#runSqlPost) | +| `GET /{owner}/{database}/write` | Poll [`GET /api/v2/operations/{id}`](operations#getOperation) | | `GET /{owner}/{database}/forks` | [`GET /api/v2/databases/{owner}/{database}/forks`](database#listForks) | | `GET /{owner}/{database}/branches` | [`GET /api/v2/databases/{owner}/{database}/branches`](database#listBranches) | | `POST /{owner}/{database}/branches` | [`POST /api/v2/databases/{owner}/{database}/branches`](database#createBranch) | From d50182b052c20c1cea76dc91292d3ccb67c4de5d Mon Sep 17 00:00:00 2001 From: Taylor Bantle Date: Thu, 9 Jul 2026 12:15:52 -0700 Subject: [PATCH 4/8] =?UTF-8?q?docs:=20regenerate=20v2=20API=20docs=20?= =?UTF-8?q?=E2=80=94=20SQL=20write=20split=20into=20POST=20/sql-writes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ld's api-v2-sql-write branch merged to main (#23820) and was then redesigned in a follow-up (#23932, "split POST /sql into read-only + new /sql-writes"): the dual-purpose oneOf body on POST /sql is gone. POST /sql is read-only again (runSqlReadQueryPost); the async write moved to its own POST /sql-writes (runSqlWriteQuery). Pulled the latest v2.yaml from ld-clone's main and regenerated. Updated README.md's index and migration.md's endpoint-mapping table to point at the new operation. Also fixes a generator bug: the sub-resource grouping in database.md didn't know about the new "sql-writes" path segment, so it fell back to a raw "## sql-writes" heading instead of folding into the existing "## SQL" section. The spec itself also picked up its own cleanup upstream — the stale "phase-4" internal-jargon wording I flagged earlier is gone, replaced with concrete endpoint references. Co-Authored-By: Claude Sonnet 5 --- scripts/generate-api-v2.mjs | 4 +- .../content/products/dolthub/api/v2/README.md | 3 +- .../products/dolthub/api/v2/database.md | 79 +++++++++++------ .../products/dolthub/api/v2/migration.md | 2 +- .../content/products/dolthub/api/v2/models.md | 2 +- specs/dolthub-v2.yaml | 87 ++++++++++++++----- 6 files changed, 124 insertions(+), 53 deletions(-) diff --git a/scripts/generate-api-v2.mjs b/scripts/generate-api-v2.mjs index 26c9e69..9b144b7 100644 --- a/scripts/generate-api-v2.mjs +++ b/scripts/generate-api-v2.mjs @@ -273,7 +273,9 @@ function subResource(path) { case "tags": return "Tags"; case "forks": return "Forks"; case "releases": return "Releases"; - case "sql": return "SQL"; + case "sql": + case "sql-writes": + return "SQL"; case "pulls": return "Pull Requests"; case "imports": return "Imports"; default: return m[1]; diff --git a/site/dolt/src/content/products/dolthub/api/v2/README.md b/site/dolt/src/content/products/dolthub/api/v2/README.md index b8a79b7..6893e2b 100644 --- a/site/dolt/src/content/products/dolthub/api/v2/README.md +++ b/site/dolt/src/content/products/dolthub/api/v2/README.md @@ -40,7 +40,8 @@ Compared to `v1alpha1`, v2 commits to: | **GET** | `/api/v2/databases/{owner}/{database}/releases` | [List releases](database#listReleases) | | **POST** | `/api/v2/databases/{owner}/{database}/releases` | [Create a release](database#createRelease) | | **GET** | `/api/v2/databases/{owner}/{database}/sql` | [Run a read-only SQL query](database#runSqlReadQuery) | -| **POST** | `/api/v2/databases/{owner}/{database}/sql` | [Run a SQL query (read or write)](database#runSqlPost) | +| **POST** | `/api/v2/databases/{owner}/{database}/sql` | [Run a SQL read query (body-encoded)](database#runSqlReadQueryPost) | +| **POST** | `/api/v2/databases/{owner}/{database}/sql-writes` | [Run an asynchronous SQL write](database#runSqlWriteQuery) | | **GET** | `/api/v2/databases/{owner}/{database}/pulls` | [List pull requests](database#listPulls) | | **POST** | `/api/v2/databases/{owner}/{database}/pulls` | [Create a pull request](database#createPull) | | **GET** | `/api/v2/databases/{owner}/{database}/pulls/{pull_number}` | [Get a pull request](database#getPull) | diff --git a/site/dolt/src/content/products/dolthub/api/v2/database.md b/site/dolt/src/content/products/dolthub/api/v2/database.md index f313302..c77bb15 100644 --- a/site/dolt/src/content/products/dolthub/api/v2/database.md +++ b/site/dolt/src/content/products/dolthub/api/v2/database.md @@ -118,7 +118,7 @@ curl -X GET 'https://www.dolthub.com/api/v2/databases/{owner}/{database}' \ ### Run a read-only SQL query {#runSqlReadQuery} `GET /api/v2/databases/{owner}/{database}/sql` -Executes a read-only SQL query against the database at the given revision and returns the result rows, the column schema, and the query-execution status. Read queries are not paginated — the full row set comes back in one response, bounded by `limit` (default 1000) and the server-enforced row cap. For larger result sets, refine the query (`LIMIT`/`WHERE`/`SELECT` fewer columns) or use the phase-4 async SQL job. +Executes a read-only SQL query against the database at the given revision and returns the result rows, the column schema, and the query-execution status. Read queries are not paginated — the full row set comes back in one response, bounded by `limit` (default 1000) and the server-enforced row cap. For larger result sets, refine the query (`LIMIT`/`WHERE`/`SELECT` fewer columns). SQL-level errors (syntax errors, missing tables, timeouts, row-limit exceeded) come back as `200` with `status: "error" | "timeout" | "row_limit" | "not_workspace"` and a human-readable `message` — they are query-level conditions, not transport-level failures. HTTP `4xx` / `5xx` are reserved for transport-level problems (auth, the database doesn't exist, malformed request). Public databases are readable without authentication; private databases require a credential with access (and return `404` otherwise, so their existence isn't leaked). @@ -130,8 +130,8 @@ Public databases are readable without authentication; private databases require | `owner` | path | string | yes | The user or organization that owns the database. | | `database` | path | string | yes | The database name, unique within the owner. | | `ref` | query | string | yes | The branch, tag, or commit hash to query against. A 32-character value using only the characters `0-9a-v` (Dolt's base32 alphabet) is treated as a commit hash; everything else resolves as a branch, tag, or other ref. | -| `q` | query | string | yes | The SQL query to execute. Read-only — use the phase-4 SQL write endpoint for mutations. | -| `limit` | query | string | no | Maximum number of rows to return (default `1000`). The server enforces an upper cap; refine the query or use the async SQL job for larger result sets. | +| `q` | query | string | yes | The SQL query to execute. Read-only — use `POST /sql-writes` for mutations. | +| `limit` | query | string | no | Maximum number of rows to return (default `1000`). The server enforces an upper cap; refine the query for larger result sets. | | `timeout_ms` | query | string | no | Per-query execution timeout in milliseconds (default `30000`, server-enforced cap `60000`). A query that hits the cap returns `status: "timeout"`. | **Example request** @@ -193,10 +193,10 @@ curl -X GET 'https://www.dolthub.com/api/v2/databases/{owner}/{database}/sql' \ --- -### Run a SQL query (read or write) {#runSqlPost} +### Run a SQL read query (body-encoded) {#runSqlReadQueryPost} `POST /api/v2/databases/{owner}/{database}/sql` -Dual-purpose `/sql` POST: discriminated by the request body shape. A `SqlReadRequest` body (`{ ref, q }`) runs a read identical to `GET /sql` — same response shape — but carries the query in the body to dodge the browser/proxy URL-length limit (~4–8 KB) that `?q=` hits for large SQL statements. A `SqlWriteRequest` body (`{ from_branch, to_branch, query }`) kicks off an asynchronous SQL write — running the write on `to_branch` (creating it from `from_branch` if it doesn't exist), then merging `from_branch` into `to_branch` — and returns `202` with an `OperationRef`; poll `GET /api/v2/operations/{id}` for completion. Authentication is required for writes; reads follow the database-level convention (public DBs readable without a token, private DBs require one). +Body-encoded read variant of `GET /sql` — identical semantics and response shape, but the query is carried in the body to dodge the browser/proxy URL-length limit (~4–8 KB) that `?q=` hits for large SQL statements. Reads follow the database-level convention: public databases are readable without a token, private databases require one. Writes go to `POST /sql-writes`, not here. **Parameters** @@ -206,7 +206,7 @@ Dual-purpose `/sql` POST: discriminated by the request body shape. A `SqlReadReq | `owner` | path | string | yes | The user or organization that owns the database. | | `database` | path | string | yes | The database name, unique within the owner. | -**Request body — `SqlReadRequest`** +**Request body** | Field | Type | Required | Description | |-------|------|----------|-------------| @@ -215,15 +215,7 @@ Dual-purpose `/sql` POST: discriminated by the request body shape. A `SqlReadReq | `limit` | integer | no | Maximum number of rows to return (default `1000`). | | `timeout_ms` | integer | no | Per-query execution timeout in milliseconds (default `30000`, cap `60000`). | -**Request body — `SqlWriteRequest`** - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `from_branch` | string | yes | The branch to merge from after the write completes. | -| `to_branch` | string | yes | The branch the write runs on. Created from `from_branch` when it doesn't already exist. | -| `query` | string | yes | The SQL write statement to execute. | - -**Example request — `SqlReadRequest`** +**Example request** ```sh curl -X POST 'https://www.dolthub.com/api/v2/databases/{owner}/{database}/sql' \ @@ -232,24 +224,13 @@ curl -X POST 'https://www.dolthub.com/api/v2/databases/{owner}/{database}/sql' \ -d '{"ref":"main","q":"SELECT name, year FROM jails WHERE state = 'California' LIMIT 10"}' ``` -**Example request — `SqlWriteRequest`** - -```sh -curl -X POST 'https://www.dolthub.com/api/v2/databases/{owner}/{database}/sql' \ - -H 'Authorization: Bearer YOUR_TOKEN' \ - -H 'Content-Type: application/json' \ - -d '{"from_branch":"main","to_branch":"feature/new-states","query":"INSERT INTO states (name, abbr) VALUES ('Puerto Rico', 'PR')"}' -``` - **Responses** | Status | Description | Schema | |--------|-------------|--------| | `200` | The read query result. SQL-level errors live in `status` + `message`. | [`QueryResult`](models#model-queryresult) | -| `202` | The write operation has been accepted and is queued. | [`OperationRef`](models#model-operationref) | | `400` | The request was malformed or failed input validation. | [`Problem`](models#model-problem) | | `401` | Authentication credentials were missing or invalid. | [`Problem`](models#model-problem) | -| `403` | Authenticated, but not permitted to perform this action. | [`Problem`](models#model-problem) | | `404` | The requested resource does not exist. | [`Problem`](models#model-problem) | | `405` | The HTTP method is not supported for this resource. | [`Problem`](models#model-problem) | | `500` | An unexpected server error occurred. | [`Problem`](models#model-problem) | @@ -293,6 +274,50 @@ curl -X POST 'https://www.dolthub.com/api/v2/databases/{owner}/{database}/sql' \ } ``` +--- + +### Run an asynchronous SQL write {#runSqlWriteQuery} +`POST /api/v2/databases/{owner}/{database}/sql-writes` + +Kicks off an asynchronous SQL write. Runs `query` on `to_branch` (creating it from `from_branch` if it doesn't exist), then merges `from_branch` into `to_branch`. Returns `202` with an `OperationRef`; follow `OperationRef.href` to `GET /api/v2/operations/{id}` and poll until `status` is `succeeded` or `failed`. Authentication is required. + + +**Parameters** + +| Name | In | Type | Required | Description | +|------|----|------|----------|-------------| +| `owner` | path | string | yes | The user or organization that owns the database. | +| `database` | path | string | yes | The database name, unique within the owner. | + +**Request body** + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `from_branch` | string | yes | The branch to merge from after the write completes. | +| `to_branch` | string | yes | The branch the write runs on. Created from `from_branch` when it doesn't already exist. | +| `query` | string | yes | The SQL write statement to execute. | + +**Example request** + +```sh +curl -X POST 'https://www.dolthub.com/api/v2/databases/{owner}/{database}/sql-writes' \ + -H 'Authorization: Bearer YOUR_TOKEN' \ + -H 'Content-Type: application/json' \ + -d '{"from_branch":"main","to_branch":"feature/new-states","query":"INSERT INTO states (name, abbr) VALUES ('Puerto Rico', 'PR')"}' +``` + +**Responses** + +| Status | Description | Schema | +|--------|-------------|--------| +| `202` | The write operation has been accepted and is queued. | [`OperationRef`](models#model-operationref) | +| `400` | The request was malformed or failed input validation. | [`Problem`](models#model-problem) | +| `401` | Authentication credentials were missing or invalid. | [`Problem`](models#model-problem) | +| `403` | Authenticated, but not permitted to perform this action. | [`Problem`](models#model-problem) | +| `404` | The requested resource does not exist. | [`Problem`](models#model-problem) | +| `405` | The HTTP method is not supported for this resource. | [`Problem`](models#model-problem) | +| `500` | An unexpected server error occurred. | [`Problem`](models#model-problem) | + ## Branches @@ -920,7 +945,7 @@ curl -X GET 'https://www.dolthub.com/api/v2/databases/{owner}/{database}/pulls/{ ### Update a pull request {#updatePull} `PATCH /api/v2/databases/{owner}/{database}/pulls/{pull_number}` -Partially updates the pull request `{pull_number}` in `{owner}/{database}`. The body carries only the fields the caller is changing — any subset of `title`, `description`, `state`; at least one must be present. `state` is restricted to the writable transitions (`open` ↔ `closed`); merging happens via the dedicated merge operation (phase 4), not by writing state here. Returns the canonical `Pull` resource on `200`. Authentication required. +Partially updates the pull request `{pull_number}` in `{owner}/{database}`. The body carries only the fields the caller is changing — any subset of `title`, `description`, `state`; at least one must be present. `state` is restricted to the writable transitions (`open` ↔ `closed`); merging happens via the dedicated merge endpoint (`POST /pulls/{pull_number}/merge`), not by writing state here. Returns the canonical `Pull` resource on `200`. Authentication required. **Parameters** diff --git a/site/dolt/src/content/products/dolthub/api/v2/migration.md b/site/dolt/src/content/products/dolthub/api/v2/migration.md index f9e63dc..700c70a 100644 --- a/site/dolt/src/content/products/dolthub/api/v2/migration.md +++ b/site/dolt/src/content/products/dolthub/api/v2/migration.md @@ -121,7 +121,7 @@ curl 'https://www.dolthub.com/api/v2/operations/owners/dolthub/repos/us-jails/jo | `POST /database` | [`POST /api/v2/databases`](database#createDatabase) | | `GET /{owner}/{database}?q=` | [`GET /api/v2/databases/{owner}/{database}/sql?q=`](database#runSqlReadQuery) | | `GET /{owner}/{database}/{ref}?q=` | [`GET /api/v2/databases/{owner}/{database}/sql?q=&ref=`](database#runSqlReadQuery) | -| `POST /{owner}/{database}/write/{from_branch}/{to_branch}` | [`POST /api/v2/databases/{owner}/{database}/sql`](database#runSqlPost) | +| `POST /{owner}/{database}/write/{from_branch}/{to_branch}` | [`POST /api/v2/databases/{owner}/{database}/sql-writes`](database#runSqlWriteQuery) | | `GET /{owner}/{database}/write` | Poll [`GET /api/v2/operations/{id}`](operations#getOperation) | | `GET /{owner}/{database}/forks` | [`GET /api/v2/databases/{owner}/{database}/forks`](database#listForks) | | `GET /{owner}/{database}/branches` | [`GET /api/v2/databases/{owner}/{database}/branches`](database#listBranches) | diff --git a/site/dolt/src/content/products/dolthub/api/v2/models.md b/site/dolt/src/content/products/dolthub/api/v2/models.md index e034613..bbbc5b5 100644 --- a/site/dolt/src/content/products/dolthub/api/v2/models.md +++ b/site/dolt/src/content/products/dolthub/api/v2/models.md @@ -462,7 +462,7 @@ Body for the read variant of `POST /api/v2/databases/{owner}/{database}/sql`. Id --- ## SqlWriteRequest {#model-sqlwriterequest} -Body for the write variant of `POST /api/v2/databases/{owner}/{database}/sql`. Runs `query` on `to_branch` (creating it from `from_branch` if it doesn't exist) then merges `from_branch` into `to_branch`. Returns `202` + `OperationRef`. `from_branch` and `to_branch` are bare branch names — both must live in the URL's `{owner}/{database}`. +Body for `POST /api/v2/databases/{owner}/{database}/sql-writes`. Runs `query` on `to_branch` (creating it from `from_branch` if it doesn't exist) then merges `from_branch` into `to_branch`. Returns `202` + `OperationRef`. `from_branch` and `to_branch` are bare branch names — both must live in the URL's `{owner}/{database}`. | Field | Type | Required | Description | |-------|------|----------|-------------| diff --git a/specs/dolthub-v2.yaml b/specs/dolthub-v2.yaml index 643a8af..231a8a8 100644 --- a/specs/dolthub-v2.yaml +++ b/specs/dolthub-v2.yaml @@ -807,7 +807,7 @@ paths: the result rows, the column schema, and the query-execution status. Read queries are not paginated — the full row set comes back in one response, bounded by `limit` (default 1000) and the server-enforced row cap. For larger result sets, refine the - query (`LIMIT`/`WHERE`/`SELECT` fewer columns) or use the phase-4 async SQL job. + query (`LIMIT`/`WHERE`/`SELECT` fewer columns). SQL-level errors (syntax errors, missing tables, timeouts, row-limit exceeded) come back as `200` with `status: "error" | "timeout" | "row_limit" | "not_workspace"` and a @@ -854,7 +854,7 @@ paths: - name: q in: query required: true - description: The SQL query to execute. Read-only — use the phase-4 SQL write endpoint for mutations. + description: The SQL query to execute. Read-only — use `POST /sql-writes` for mutations. schema: type: string minLength: 1 @@ -864,7 +864,7 @@ paths: required: false description: >- Maximum number of rows to return (default `1000`). The server enforces an upper - cap; refine the query or use the async SQL job for larger result sets. + cap; refine the query for larger result sets. schema: type: string pattern: "^[1-9][0-9]*$" @@ -914,19 +914,14 @@ paths: "500": $ref: "#/components/responses/InternalServerError" post: - operationId: runSqlPost - summary: Run a SQL query (read or write). + operationId: runSqlReadQueryPost + summary: Run a SQL read query (body-encoded). description: >- - Dual-purpose `/sql` POST: discriminated by the request body shape. A `SqlReadRequest` - body (`{ ref, q }`) runs a read identical to `GET /sql` — same response shape — but - carries the query in the body to dodge the browser/proxy URL-length limit (~4–8 KB) - that `?q=` hits for large SQL statements. A `SqlWriteRequest` body - (`{ from_branch, to_branch, query }`) kicks off an asynchronous SQL write — running - the write on `to_branch` (creating it from `from_branch` if it doesn't exist), then - merging `from_branch` into `to_branch` — and returns `202` with an `OperationRef`; - poll `GET /api/v2/operations/{id}` for completion. Authentication is required for - writes; reads follow the database-level convention (public DBs readable without a - token, private DBs require one). + Body-encoded read variant of `GET /sql` — identical semantics and response shape, + but the query is carried in the body to dodge the browser/proxy URL-length limit + (~4–8 KB) that `?q=` hits for large SQL statements. Reads follow the + database-level convention: public databases are readable without a token, private + databases require one. Writes go to `POST /sql-writes`, not here. tags: - Database security: @@ -955,9 +950,7 @@ paths: content: application/json: schema: - oneOf: - - $ref: "#/components/schemas/SqlReadRequest" - - $ref: "#/components/schemas/SqlWriteRequest" + $ref: "#/components/schemas/SqlReadRequest" responses: "200": description: The read query result. SQL-level errors live in `status` + `message`. @@ -982,6 +975,55 @@ paths: properties: data: $ref: "#/components/schemas/QueryResult" + "400": + $ref: "#/components/responses/BadRequest" + "401": + $ref: "#/components/responses/Unauthorized" + "404": + $ref: "#/components/responses/NotFound" + "405": + $ref: "#/components/responses/MethodNotAllowed" + "500": + $ref: "#/components/responses/InternalServerError" + /api/v2/databases/{owner}/{database}/sql-writes: + post: + operationId: runSqlWriteQuery + summary: Run an asynchronous SQL write. + description: >- + Kicks off an asynchronous SQL write. Runs `query` on `to_branch` (creating it from + `from_branch` if it doesn't exist), then merges `from_branch` into `to_branch`. + Returns `202` with an `OperationRef`; follow `OperationRef.href` to + `GET /api/v2/operations/{id}` and poll until `status` is `succeeded` or `failed`. + Authentication is required. + tags: + - Database + security: + - personalAccessToken: [] + - oauth2: [api_read_write] + parameters: + - name: owner + in: path + required: true + description: The user or organization that owns the database. + schema: + type: string + minLength: 1 + example: dolthub + - name: database + in: path + required: true + description: The database name, unique within the owner. + schema: + type: string + minLength: 1 + example: us-jails + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/SqlWriteRequest" + responses: "202": description: The write operation has been accepted and is queued. headers: @@ -1245,8 +1287,9 @@ paths: Partially updates the pull request `{pull_number}` in `{owner}/{database}`. The body carries only the fields the caller is changing — any subset of `title`, `description`, `state`; at least one must be present. `state` is restricted to the writable - transitions (`open` ↔ `closed`); merging happens via the dedicated merge operation - (phase 4), not by writing state here. Returns the canonical `Pull` resource on `200`. + transitions (`open` ↔ `closed`); merging happens via the dedicated merge endpoint + (`POST /pulls/{pull_number}/merge`), not by writing state here. Returns the canonical + `Pull` resource on `200`. Authentication required. tags: - Database @@ -3239,8 +3282,8 @@ components: type: object title: SqlWriteRequest description: >- - Body for the write variant of `POST /api/v2/databases/{owner}/{database}/sql`. Runs - `query` on `to_branch` (creating it from `from_branch` if it doesn't exist) then merges + Body for `POST /api/v2/databases/{owner}/{database}/sql-writes`. Runs `query` on + `to_branch` (creating it from `from_branch` if it doesn't exist) then merges `from_branch` into `to_branch`. Returns `202` + `OperationRef`. `from_branch` and `to_branch` are bare branch names — both must live in the URL's `{owner}/{database}`. required: From 99602b0c08c41b8feaeb92e4382a44ebb2802967 Mon Sep 17 00:00:00 2001 From: Taylor Bantle Date: Thu, 9 Jul 2026 12:18:40 -0700 Subject: [PATCH 5/8] docs: drop oneOf request-body support from the API generator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The redesign in api-v2-sql-split replaced the one endpoint that needed this (dual-purpose POST /sql) with two separate single-schema endpoints. The only oneOf left in the spec is nested inside a property (CreateBranchRequest.from), which this code never handled anyway. Reverting to the simpler single-schema requestBodySection/curlExample — confirmed the regenerated output is byte-identical to before this revert. Co-Authored-By: Claude Sonnet 5 --- scripts/generate-api-v2.mjs | 104 +++++++++++------------------------- 1 file changed, 30 insertions(+), 74 deletions(-) diff --git a/scripts/generate-api-v2.mjs b/scripts/generate-api-v2.mjs index 9b144b7..9fac33b 100644 --- a/scripts/generate-api-v2.mjs +++ b/scripts/generate-api-v2.mjs @@ -82,64 +82,39 @@ function methodBadge(method) { return `![${label}](https://img.shields.io/badge/${label}-${color}?style=flat-square)`; } -// A request body is normally a single schema. When it's a `oneOf` (e.g. an -// endpoint that's dual-purpose depending on the payload shape), treat each -// branch as its own variant — each gets its own request-body table and -// example below, labeled with the branch's schema title. -function requestBodySchemaVariants(requestBody) { - const schema = requestBody?.content?.["application/json"]?.schema; - if (!schema) return []; - const resolved = deref(schema); - if (Array.isArray(resolved.oneOf) && resolved.oneOf.length) { - return resolved.oneOf.map((s) => deref(s)); - } - return [resolved]; -} - -function examplePayload(resolved) { - const required = resolved.required ?? []; - const props = resolved.properties ?? {}; - const example = Object.fromEntries( - Object.entries(props) - .filter(([k]) => required.includes(k)) - .slice(0, 4) - .map(([k, v]) => { - const t = v.type ?? (v.enum ? "enum" : "object"); - const ex = - v.examples?.[0] ?? - v.example ?? - (t === "string" ? `"example_${k}"` : t === "boolean" ? false : 0); - return [k, ex]; - }) - ); - return Object.keys(example).length > 0 ? example : null; -} - -function curlLines(method, path, payload) { +function curlExample(method, path, operation) { const lines = [ `curl -X ${METHOD_LABELS[method] ?? method.toUpperCase()} 'https://www.dolthub.com${path}'`, ` -H 'Authorization: Bearer YOUR_TOKEN'`, ]; if (method !== "get" && method !== "delete") { lines.push(` -H 'Content-Type: application/json'`); - if (payload) lines.push(` -d '${JSON.stringify(payload)}'`); + const reqBody = operation.requestBody?.content?.["application/json"]?.schema; + if (reqBody) { + const resolved = deref(reqBody); + const required = resolved.required ?? []; + const props = resolved.properties ?? {}; + const example = Object.fromEntries( + Object.entries(props) + .filter(([k]) => required.includes(k)) + .slice(0, 4) + .map(([k, v]) => { + const t = v.type ?? (v.enum ? "enum" : "object"); + const ex = + v.examples?.[0] ?? + v.example ?? + (t === "string" ? `"example_${k}"` : t === "boolean" ? false : 0); + return [k, ex]; + }) + ); + if (Object.keys(example).length > 0) { + lines.push(` -d '${JSON.stringify(example)}'`); + } + } } return lines.join(" \\\n"); } -// Returns one example per request-body variant: [{ label, curl }]. `label` -// is null for the common single-schema case (no "Example request" suffix). -function curlExamples(method, path, operation) { - const variants = requestBodySchemaVariants(operation.requestBody); - if (variants.length <= 1) { - return [{ label: null, curl: curlLines(method, path, variants[0] && examplePayload(variants[0])) }]; - } - return variants.map((v) => ({ - label: v.title ?? null, - curl: curlLines(method, path, examplePayload(v)), - })); -} - function parametersSection(params) { if (!params?.length) return ""; const rows = params @@ -156,7 +131,11 @@ function parametersSection(params) { return `\n**Parameters**\n\n| Name | In | Type | Required | Description |\n|------|----|------|----------|-------------|\n${rows}\n`; } -function requestBodyFieldsTable(resolved) { +function requestBodySection(requestBody) { + if (!requestBody) return ""; + const schema = requestBody.content?.["application/json"]?.schema; + if (!schema) return ""; + const resolved = deref(schema); const required = resolved.required ?? []; const props = resolved.properties ?? {}; if (!Object.keys(props).length) return ""; @@ -168,25 +147,7 @@ function requestBodyFieldsTable(resolved) { return `| \`${k}\` | ${type} | ${req} | ${desc} |`; }) .join("\n"); - return `| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n${rows}\n`; -} - -function requestBodySection(requestBody) { - const variants = requestBodySchemaVariants(requestBody); - if (!variants.length) return ""; - if (variants.length === 1) { - const table = requestBodyFieldsTable(variants[0]); - return table ? `\n**Request body**\n\n${table}` : ""; - } - return variants - .map((v) => { - const table = requestBodyFieldsTable(v); - if (!table) return ""; - const label = v.title ? ` — \`${v.title}\`` : ""; - return `\n**Request body${label}**\n\n${table}`; - }) - .filter(Boolean) - .join(""); + return `\n**Request body**\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n${rows}\n`; } function responseSchemaName(schema) { @@ -305,12 +266,7 @@ function endpointBlock(method, path, operation, headingLevel = "###") { const body = requestBodySection(deref(operation.requestBody ?? {})); const responses = responsesSection(operation.responses); const successExample = successExampleBlock(operation.responses); - const curl = curlExamples(method, path, operation) - .map(({ label, curl }) => { - const heading = label ? `\n**Example request — \`${label}\`**\n\n` : `\n**Example request**\n\n`; - return `${heading}\`\`\`sh\n${curl}\n\`\`\`\n`; - }) - .join(""); + const curl = `\n**Example request**\n\n\`\`\`sh\n${curlExample(method, path, operation)}\n\`\`\`\n`; return [heading, methodPath, description, params, body, curl, responses, successExample] .filter(Boolean) .join(""); From beb1db549bab3d35e515657a74677c9bfb0e5b5c Mon Sep 17 00:00:00 2001 From: Taylor Bantle Date: Thu, 9 Jul 2026 13:41:53 -0700 Subject: [PATCH 6/8] =?UTF-8?q?docs:=20regenerate=20v2=20API=20docs=20?= =?UTF-8?q?=E2=80=94=20SqlWriteRequest.query=20renamed=20to=20q?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pulled the latest v2.yaml from ld-clone's main (#23937, api-v2-sql-read-body-query). SqlWriteRequest's SQL-statement field is now `q`, matching SqlReadRequest, instead of `query`. Co-Authored-By: Claude Sonnet 5 --- site/dolt/src/content/products/dolthub/api/v2/database.md | 4 ++-- site/dolt/src/content/products/dolthub/api/v2/models.md | 4 ++-- specs/dolthub-v2.yaml | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/site/dolt/src/content/products/dolthub/api/v2/database.md b/site/dolt/src/content/products/dolthub/api/v2/database.md index c77bb15..310f56d 100644 --- a/site/dolt/src/content/products/dolthub/api/v2/database.md +++ b/site/dolt/src/content/products/dolthub/api/v2/database.md @@ -295,7 +295,7 @@ Kicks off an asynchronous SQL write. Runs `query` on `to_branch` (creating it fr |-------|------|----------|-------------| | `from_branch` | string | yes | The branch to merge from after the write completes. | | `to_branch` | string | yes | The branch the write runs on. Created from `from_branch` when it doesn't already exist. | -| `query` | string | yes | The SQL write statement to execute. | +| `q` | string | yes | The SQL write statement to execute. | **Example request** @@ -303,7 +303,7 @@ Kicks off an asynchronous SQL write. Runs `query` on `to_branch` (creating it fr curl -X POST 'https://www.dolthub.com/api/v2/databases/{owner}/{database}/sql-writes' \ -H 'Authorization: Bearer YOUR_TOKEN' \ -H 'Content-Type: application/json' \ - -d '{"from_branch":"main","to_branch":"feature/new-states","query":"INSERT INTO states (name, abbr) VALUES ('Puerto Rico', 'PR')"}' + -d '{"from_branch":"main","to_branch":"feature/new-states","q":"INSERT INTO states (name, abbr) VALUES ('Puerto Rico', 'PR')"}' ``` **Responses** diff --git a/site/dolt/src/content/products/dolthub/api/v2/models.md b/site/dolt/src/content/products/dolthub/api/v2/models.md index bbbc5b5..aa9159d 100644 --- a/site/dolt/src/content/products/dolthub/api/v2/models.md +++ b/site/dolt/src/content/products/dolthub/api/v2/models.md @@ -462,13 +462,13 @@ Body for the read variant of `POST /api/v2/databases/{owner}/{database}/sql`. Id --- ## SqlWriteRequest {#model-sqlwriterequest} -Body for `POST /api/v2/databases/{owner}/{database}/sql-writes`. Runs `query` on `to_branch` (creating it from `from_branch` if it doesn't exist) then merges `from_branch` into `to_branch`. Returns `202` + `OperationRef`. `from_branch` and `to_branch` are bare branch names — both must live in the URL's `{owner}/{database}`. +Body for `POST /api/v2/databases/{owner}/{database}/sql-writes`. Runs `q` on `to_branch` (creating it from `from_branch` if it doesn't exist) then merges `from_branch` into `to_branch`. Returns `202` + `OperationRef`. `from_branch` and `to_branch` are bare branch names — both must live in the URL's `{owner}/{database}`. | Field | Type | Required | Description | |-------|------|----------|-------------| | `from_branch` | `string` | yes | The branch to merge from after the write completes. | | `to_branch` | `string` | yes | The branch the write runs on. Created from `from_branch` when it doesn't already exist. | -| `query` | `string` | yes | The SQL write statement to execute. | +| `q` | `string` | yes | The SQL write statement to execute. | --- diff --git a/specs/dolthub-v2.yaml b/specs/dolthub-v2.yaml index 231a8a8..571e028 100644 --- a/specs/dolthub-v2.yaml +++ b/specs/dolthub-v2.yaml @@ -3282,14 +3282,14 @@ components: type: object title: SqlWriteRequest description: >- - Body for `POST /api/v2/databases/{owner}/{database}/sql-writes`. Runs `query` on + Body for `POST /api/v2/databases/{owner}/{database}/sql-writes`. Runs `q` on `to_branch` (creating it from `from_branch` if it doesn't exist) then merges `from_branch` into `to_branch`. Returns `202` + `OperationRef`. `from_branch` and `to_branch` are bare branch names — both must live in the URL's `{owner}/{database}`. required: - from_branch - to_branch - - query + - q properties: from_branch: type: string @@ -3305,7 +3305,7 @@ components: already exist. examples: - feature/new-states - query: + q: type: string minLength: 1 description: The SQL write statement to execute. From 2344a011962f5840bc92701dc471a0969814cd48 Mon Sep 17 00:00:00 2001 From: Taylor Bantle Date: Thu, 9 Jul 2026 13:48:15 -0700 Subject: [PATCH 7/8] fix: wrap tables in a scrollable container so wide rows don't clip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tables with long unbroken cells (e.g. the v2 migration guide's endpoint-mapping table) overflowed the content column with no way to see the clipped text — table had no horizontal scroll affordance, unlike
 blocks which already use overflow-x-auto.

Add a rehype plugin that wraps every rendered  in a
.table-scroll div, and give that wrapper overflow-x-auto (moved the
mb-4 spacing there too, off the table itself).

Co-Authored-By: Claude Sonnet 5 
---
 site/shared/config/astro.mjs              |  2 ++
 site/shared/config/rehype-wrap-tables.mjs | 27 +++++++++++++++++++++++
 site/shared/layouts/DocsLayout.astro      |  6 ++++-
 3 files changed, 34 insertions(+), 1 deletion(-)
 create mode 100644 site/shared/config/rehype-wrap-tables.mjs

diff --git a/site/shared/config/astro.mjs b/site/shared/config/astro.mjs
index ffb3317..fe5ab9b 100644
--- a/site/shared/config/astro.mjs
+++ b/site/shared/config/astro.mjs
@@ -8,6 +8,7 @@ import { unified } from "@astrojs/markdown-remark";
 import rehypeSlug from "rehype-slug";
 import rehypeAutolinkHeadings from "rehype-autolink-headings";
 import rehypeBasePath from "./rehype-base.mjs";
+import rehypeWrapTables from "./rehype-wrap-tables.mjs";
 import remarkInjectTitleH1 from "./remark-inject-title-h1.mjs";
 import remarkHeadingId from "remark-heading-id";
 
@@ -60,6 +61,7 @@ export function buildAstroConfig(site, siteDir) {
           [rehypeBasePath, { base }],
           rehypeSlug,
           [rehypeAutolinkHeadings, autolinkHeadingsOptions],
+          rehypeWrapTables,
         ],
       }),
     },
diff --git a/site/shared/config/rehype-wrap-tables.mjs b/site/shared/config/rehype-wrap-tables.mjs
new file mode 100644
index 0000000..80c898f
--- /dev/null
+++ b/site/shared/config/rehype-wrap-tables.mjs
@@ -0,0 +1,27 @@
+// Rehype plugin: wrap every rendered `
` in a scrollable `
`. Tables +// with long unbroken `` cells (endpoint paths, etc.) can overflow the +// content column; without a wrapper the overflow just clips at the viewport +// edge instead of scrolling. +export default function rehypeWrapTables() { + function wrap(node) { + if (node.type === "element" && node.tagName === "table") { + return { + type: "element", + tagName: "div", + properties: { className: ["table-scroll"] }, + children: [node], + }; + } + return node; + } + + function walk(node) { + if (!Array.isArray(node.children)) return; + node.children = node.children.map(child => { + walk(child); + return wrap(child); + }); + } + + return tree => walk(tree); +} diff --git a/site/shared/layouts/DocsLayout.astro b/site/shared/layouts/DocsLayout.astro index 94f7574..c10a270 100644 --- a/site/shared/layouts/DocsLayout.astro +++ b/site/shared/layouts/DocsLayout.astro @@ -279,8 +279,12 @@ const markdownAlternate = @apply border-l-4 border-sky-300 pl-4 italic my-4; } + .docs-main :global(.table-scroll) { + @apply mb-4 overflow-x-auto; + } + .docs-main :global(table) { - @apply w-full mb-4 text-sm; + @apply w-full text-sm; border-collapse: collapse; } From b1125d989b45ec23332432e12851b916eaee6117 Mon Sep 17 00:00:00 2001 From: Taylor Bantle Date: Thu, 9 Jul 2026 13:53:15 -0700 Subject: [PATCH 8/8] fix: wrap long table cells instead of scrolling; shorten v2 migration paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Swap the scroll-wrapper approach for wrapping: drop the rehype table-wrap plugin and instead let cell content break (overflow-wrap: anywhere) with table-layout: fixed so columns share the container width predictably. Also drop the redundant /api/v2 prefix from the v2 column of the migration guide's endpoint-mapping table, matching the v1alpha1 column's style (which never had /api/v1alpha1 in it) — shorter, more symmetric, and less prone to overflow in the first place. Co-Authored-By: Claude Sonnet 5 --- .../products/dolthub/api/v2/migration.md | 50 +++++++++---------- site/shared/config/astro.mjs | 2 - site/shared/config/rehype-wrap-tables.mjs | 27 ---------- site/shared/layouts/DocsLayout.astro | 9 ++-- 4 files changed, 29 insertions(+), 59 deletions(-) delete mode 100644 site/shared/config/rehype-wrap-tables.mjs diff --git a/site/dolt/src/content/products/dolthub/api/v2/migration.md b/site/dolt/src/content/products/dolthub/api/v2/migration.md index 700c70a..a21f328 100644 --- a/site/dolt/src/content/products/dolthub/api/v2/migration.md +++ b/site/dolt/src/content/products/dolthub/api/v2/migration.md @@ -117,29 +117,29 @@ curl 'https://www.dolthub.com/api/v2/operations/owners/dolthub/repos/us-jails/jo | v1alpha1 | v2 | |---|---| -| `GET /user` | [`GET /api/v2/user`](user#getCurrentUser) | -| `POST /database` | [`POST /api/v2/databases`](database#createDatabase) | -| `GET /{owner}/{database}?q=` | [`GET /api/v2/databases/{owner}/{database}/sql?q=`](database#runSqlReadQuery) | -| `GET /{owner}/{database}/{ref}?q=` | [`GET /api/v2/databases/{owner}/{database}/sql?q=&ref=`](database#runSqlReadQuery) | -| `POST /{owner}/{database}/write/{from_branch}/{to_branch}` | [`POST /api/v2/databases/{owner}/{database}/sql-writes`](database#runSqlWriteQuery) | -| `GET /{owner}/{database}/write` | Poll [`GET /api/v2/operations/{id}`](operations#getOperation) | -| `GET /{owner}/{database}/forks` | [`GET /api/v2/databases/{owner}/{database}/forks`](database#listForks) | -| `GET /{owner}/{database}/branches` | [`GET /api/v2/databases/{owner}/{database}/branches`](database#listBranches) | -| `POST /{owner}/{database}/branches` | [`POST /api/v2/databases/{owner}/{database}/branches`](database#createBranch) | -| `GET /{owner}/{database}/pulls` | [`GET /api/v2/databases/{owner}/{database}/pulls`](database#listPulls) | -| `POST /{owner}/{database}/pulls` | [`POST /api/v2/databases/{owner}/{database}/pulls`](database#createPull) | -| `GET /{owner}/{database}/pulls/{id}` | [`GET /api/v2/databases/{owner}/{database}/pulls/{pull_number}`](database#getPull) | -| `PATCH /{owner}/{database}/pulls/{id}` | [`PATCH /api/v2/databases/{owner}/{database}/pulls/{pull_number}`](database#updatePull) | -| `POST /{owner}/{database}/pulls/{id}/comments` | [`POST /api/v2/databases/{owner}/{database}/pulls/{pull_number}/comments`](database#createPullComment) | -| `POST /{owner}/{database}/pulls/{id}/merge` | [`POST /api/v2/databases/{owner}/{database}/pulls/{pull_number}/merge`](database#mergePull) | -| `GET /{owner}/{database}/pulls/{id}/merge` | Poll [`GET /api/v2/operations/{id}`](operations#getOperation) | -| `GET /{owner}/{database}/releases` | [`GET /api/v2/databases/{owner}/{database}/releases`](database#listReleases) | -| `POST /{owner}/{database}/releases` | [`POST /api/v2/databases/{owner}/{database}/releases`](database#createRelease) | -| `GET /{owner}/{database}/tags` | [`GET /api/v2/databases/{owner}/{database}/tags`](database#listTags) | -| `POST /{owner}/{database}/tags` | [`POST /api/v2/databases/{owner}/{database}/tags`](database#createTag) | +| `GET /user` | [`GET /user`](user#getCurrentUser) | +| `POST /database` | [`POST /databases`](database#createDatabase) | +| `GET /{owner}/{database}?q=` | [`GET /databases/{owner}/{database}/sql?q=`](database#runSqlReadQuery) | +| `GET /{owner}/{database}/{ref}?q=` | [`GET /databases/{owner}/{database}/sql?q=&ref=`](database#runSqlReadQuery) | +| `POST /{owner}/{database}/write/{from_branch}/{to_branch}` | [`POST /databases/{owner}/{database}/sql-writes`](database#runSqlWriteQuery) | +| `GET /{owner}/{database}/write` | Poll [`GET /operations/{id}`](operations#getOperation) | +| `GET /{owner}/{database}/forks` | [`GET /databases/{owner}/{database}/forks`](database#listForks) | +| `GET /{owner}/{database}/branches` | [`GET /databases/{owner}/{database}/branches`](database#listBranches) | +| `POST /{owner}/{database}/branches` | [`POST /databases/{owner}/{database}/branches`](database#createBranch) | +| `GET /{owner}/{database}/pulls` | [`GET /databases/{owner}/{database}/pulls`](database#listPulls) | +| `POST /{owner}/{database}/pulls` | [`POST /databases/{owner}/{database}/pulls`](database#createPull) | +| `GET /{owner}/{database}/pulls/{id}` | [`GET /databases/{owner}/{database}/pulls/{pull_number}`](database#getPull) | +| `PATCH /{owner}/{database}/pulls/{id}` | [`PATCH /databases/{owner}/{database}/pulls/{pull_number}`](database#updatePull) | +| `POST /{owner}/{database}/pulls/{id}/comments` | [`POST /databases/{owner}/{database}/pulls/{pull_number}/comments`](database#createPullComment) | +| `POST /{owner}/{database}/pulls/{id}/merge` | [`POST /databases/{owner}/{database}/pulls/{pull_number}/merge`](database#mergePull) | +| `GET /{owner}/{database}/pulls/{id}/merge` | Poll [`GET /operations/{id}`](operations#getOperation) | +| `GET /{owner}/{database}/releases` | [`GET /databases/{owner}/{database}/releases`](database#listReleases) | +| `POST /{owner}/{database}/releases` | [`POST /databases/{owner}/{database}/releases`](database#createRelease) | +| `GET /{owner}/{database}/tags` | [`GET /databases/{owner}/{database}/tags`](database#listTags) | +| `POST /{owner}/{database}/tags` | [`POST /databases/{owner}/{database}/tags`](database#createTag) | | `POST /{owner}/{database}/upload` | [`POST .../imports/uploads`](database#createImportUpload) then [`POST .../imports`](database#createImport) | -| `GET /{owner}/{database}/upload` | Poll [`GET /api/v2/operations/{id}`](operations#getOperation) | -| `POST /fork` | [`POST /api/v2/databases/{owner}/{database}/forks`](database#createFork) | -| `GET /fork` | Poll [`GET /api/v2/operations/{id}`](operations#getOperation) | -| `GET /{owner}/{database}/jobs` | [`GET /api/v2/databases/{owner}/{database}/operations`](operations#listOperations) | -| `GET /users/{username}/operations` | [`GET /api/v2/databases/{owner}/{database}/operations`](operations#listOperations) | +| `GET /{owner}/{database}/upload` | Poll [`GET /operations/{id}`](operations#getOperation) | +| `POST /fork` | [`POST /databases/{owner}/{database}/forks`](database#createFork) | +| `GET /fork` | Poll [`GET /operations/{id}`](operations#getOperation) | +| `GET /{owner}/{database}/jobs` | [`GET /databases/{owner}/{database}/operations`](operations#listOperations) | +| `GET /users/{username}/operations` | [`GET /databases/{owner}/{database}/operations`](operations#listOperations) | diff --git a/site/shared/config/astro.mjs b/site/shared/config/astro.mjs index fe5ab9b..ffb3317 100644 --- a/site/shared/config/astro.mjs +++ b/site/shared/config/astro.mjs @@ -8,7 +8,6 @@ import { unified } from "@astrojs/markdown-remark"; import rehypeSlug from "rehype-slug"; import rehypeAutolinkHeadings from "rehype-autolink-headings"; import rehypeBasePath from "./rehype-base.mjs"; -import rehypeWrapTables from "./rehype-wrap-tables.mjs"; import remarkInjectTitleH1 from "./remark-inject-title-h1.mjs"; import remarkHeadingId from "remark-heading-id"; @@ -61,7 +60,6 @@ export function buildAstroConfig(site, siteDir) { [rehypeBasePath, { base }], rehypeSlug, [rehypeAutolinkHeadings, autolinkHeadingsOptions], - rehypeWrapTables, ], }), }, diff --git a/site/shared/config/rehype-wrap-tables.mjs b/site/shared/config/rehype-wrap-tables.mjs deleted file mode 100644 index 80c898f..0000000 --- a/site/shared/config/rehype-wrap-tables.mjs +++ /dev/null @@ -1,27 +0,0 @@ -// Rehype plugin: wrap every rendered `
` in a scrollable `
`. Tables -// with long unbroken `` cells (endpoint paths, etc.) can overflow the -// content column; without a wrapper the overflow just clips at the viewport -// edge instead of scrolling. -export default function rehypeWrapTables() { - function wrap(node) { - if (node.type === "element" && node.tagName === "table") { - return { - type: "element", - tagName: "div", - properties: { className: ["table-scroll"] }, - children: [node], - }; - } - return node; - } - - function walk(node) { - if (!Array.isArray(node.children)) return; - node.children = node.children.map(child => { - walk(child); - return wrap(child); - }); - } - - return tree => walk(tree); -} diff --git a/site/shared/layouts/DocsLayout.astro b/site/shared/layouts/DocsLayout.astro index c10a270..4a5cfd8 100644 --- a/site/shared/layouts/DocsLayout.astro +++ b/site/shared/layouts/DocsLayout.astro @@ -279,13 +279,10 @@ const markdownAlternate = @apply border-l-4 border-sky-300 pl-4 italic my-4; } - .docs-main :global(.table-scroll) { - @apply mb-4 overflow-x-auto; - } - .docs-main :global(table) { - @apply w-full text-sm; + @apply w-full mb-4 text-sm; border-collapse: collapse; + table-layout: fixed; } .docs-main :global(thead tr) { @@ -296,6 +293,7 @@ const markdownAlternate = @apply font-semibold text-left; padding: 0.5rem 0.75rem; @apply border-b; + overflow-wrap: anywhere; } .docs-main :global(th + th) { @@ -305,6 +303,7 @@ const markdownAlternate = .docs-main :global(td) { padding: 0.5rem 0.75rem; @apply border-b; + overflow-wrap: anywhere; } .docs-main :global(td + td) {