diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bc4506..e36e788 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- chore(query): clarify write statement and dialect documentation - chore: add 503 service unavailable response to load endpoints ## [0.12.0] - 2026-10-15 diff --git a/docs/QueryApi.md b/docs/QueryApi.md index cb351c8..0d1de21 100644 --- a/docs/QueryApi.md +++ b/docs/QueryApi.md @@ -18,6 +18,8 @@ Set `async: true` to execute asynchronously — returns a query run ID for polli To receive the rows as an Arrow IPC stream instead of JSON, send `Accept: application/vnd.apache.arrow.stream` (without also listing `application/json`). A query that completes within the request then returns 200 with an Arrow IPC stream body (schema, record batches, end-of-stream), and the values the JSON body would carry are sent as response headers: `X-Query-Run-Id`, `X-Result-Id` (when the result was saved), `X-Total-Row-Count`, `X-Execution-Time-Ms`, and `X-Query-Warning` (when there is a warning). Arrow keeps `±Inf` and `NaN` float values as they are. An Arrow response always holds the whole result: if the result is larger than an inline response can hold, the request returns 202 with the query run ticket and an `X-Result-Id` header instead, and the full result is read from `GET /v1/results/{id}?format=arrow` once it is ready. A query that goes async, and every error, return the same JSON bodies as without the header. +The endpoint also runs write statements: `INSERT INTO … VALUES … / SELECT …` (append only), `CREATE TABLE`, `CREATE TABLE … AS SELECT …` and `DROP TABLE`. A write runs once, inline, and answers with a one-row result holding a single `rows_affected` column. Writes need an API key holding both the `read` and `write` permissions, may only target the database's own `default` catalog (attached catalogs stay read-only), and are never run from a saved query. `UPDATE` and `DELETE` are not supported yet; use the load endpoint's keyed `update` and `delete` modes. Every other mutation (`ALTER`, views, `COPY`, transactions, `EXPLAIN` of a write) is refused. + ### Example * Api Key Authentication (WorkspaceId): diff --git a/docs/QueryRequest.md b/docs/QueryRequest.md index 97f5c6a..c26fb28 100644 --- a/docs/QueryRequest.md +++ b/docs/QueryRequest.md @@ -11,7 +11,7 @@ Name | Type | Description | Notes **database_id** | **str** | Database to scope the query to (its id). Alternative to the `X-Database-Id` header — exactly one source must be provided. If both this field and the header are set and they disagree, the request is rejected with a 400. | [optional] **default_catalog** | **str** | Catalog that unqualified table references resolve against within the query's database scope. Must name a catalog visible in the database (`default`, an attached catalog alias, or a system catalog). Defaults to `default` when omitted. | [optional] **default_schema** | **str** | Schema that unqualified table references resolve against within the query's database scope. Omit it to use the database's own default schema — the `default_schema` chosen when the database was created, or the single schema it declares, and only `main` when it has neither. The database's `default_schema` field (see `GET /v1/databases/{id}`) reports the value in force. Setting this field overrides that per query, so sending `main` on a database whose data lives in another schema turns a query that works without the field into a \"table not found\" error. Existence is not validated up front — an unknown schema surfaces as a \"table not found\" error at planning time. Fully-qualified references (`<catalog>.<schema>.<table>`) are unaffected. | [optional] -**dialect** | **str** | SQL dialect the `sql` field is written in. One of `hotsql` (the default), `duckdb`, `postgres`, or `snowflake`. When set to anything other than `hotsql`, the query is translated to HotSQL before it runs, so you can use idioms from that dialect (for example Snowflake `IFF(...)` or Postgres `MOD(a, b)`). Write statements are accepted on the same terms as in HotSQL: refused unless the instance accepts SQL writes. An unrecognized value is rejected with a 400. | [optional] +**dialect** | **str** | SQL dialect the `sql` field is written in. One of `hotsql` (the default), `duckdb`, `postgres`, or `snowflake`. When set to anything other than `hotsql`, the query is translated to HotSQL before it runs, so you can use idioms from that dialect (for example Snowflake `IFF(...)` or Postgres `MOD(a, b)`). Write statements are accepted on the same terms as in HotSQL. An unrecognized value is rejected with a 400. | [optional] **sql** | **str** | | ## Example diff --git a/hotdata/api/query_api.py b/hotdata/api/query_api.py index 7632993..604725c 100644 --- a/hotdata/api/query_api.py +++ b/hotdata/api/query_api.py @@ -60,7 +60,7 @@ def query( ) -> QueryResponse: """Execute SQL query - Execute a SQL query scoped to a database. A database is the only window into catalogs: the query sees only that database's auto `default` catalog plus any catalogs explicitly attached to it. Select the database with EITHER the `X-Database-Id` header OR the `database_id` body field (exactly one must be given; if both are sent and disagree, that's a 400). Use standard Postgres-compatible SQL; reference the default catalog as `default..` (or just `.
` / `
`) and attached catalogs by their alias. Results are returned inline and a `result_id` is provided for later retrieval via the Results API. Set `async: true` to execute asynchronously — returns a query run ID for polling. Optionally set `async_after_ms` to attempt synchronous execution first, falling back to async if the query exceeds the timeout. To receive the rows as an Arrow IPC stream instead of JSON, send `Accept: application/vnd.apache.arrow.stream` (without also listing `application/json`). A query that completes within the request then returns 200 with an Arrow IPC stream body (schema, record batches, end-of-stream), and the values the JSON body would carry are sent as response headers: `X-Query-Run-Id`, `X-Result-Id` (when the result was saved), `X-Total-Row-Count`, `X-Execution-Time-Ms`, and `X-Query-Warning` (when there is a warning). Arrow keeps `±Inf` and `NaN` float values as they are. An Arrow response always holds the whole result: if the result is larger than an inline response can hold, the request returns 202 with the query run ticket and an `X-Result-Id` header instead, and the full result is read from `GET /v1/results/{id}?format=arrow` once it is ready. A query that goes async, and every error, return the same JSON bodies as without the header. + Execute a SQL query scoped to a database. A database is the only window into catalogs: the query sees only that database's auto `default` catalog plus any catalogs explicitly attached to it. Select the database with EITHER the `X-Database-Id` header OR the `database_id` body field (exactly one must be given; if both are sent and disagree, that's a 400). Use standard Postgres-compatible SQL; reference the default catalog as `default..
` (or just `.
` / `
`) and attached catalogs by their alias. Results are returned inline and a `result_id` is provided for later retrieval via the Results API. Set `async: true` to execute asynchronously — returns a query run ID for polling. Optionally set `async_after_ms` to attempt synchronous execution first, falling back to async if the query exceeds the timeout. To receive the rows as an Arrow IPC stream instead of JSON, send `Accept: application/vnd.apache.arrow.stream` (without also listing `application/json`). A query that completes within the request then returns 200 with an Arrow IPC stream body (schema, record batches, end-of-stream), and the values the JSON body would carry are sent as response headers: `X-Query-Run-Id`, `X-Result-Id` (when the result was saved), `X-Total-Row-Count`, `X-Execution-Time-Ms`, and `X-Query-Warning` (when there is a warning). Arrow keeps `±Inf` and `NaN` float values as they are. An Arrow response always holds the whole result: if the result is larger than an inline response can hold, the request returns 202 with the query run ticket and an `X-Result-Id` header instead, and the full result is read from `GET /v1/results/{id}?format=arrow` once it is ready. A query that goes async, and every error, return the same JSON bodies as without the header. The endpoint also runs write statements: `INSERT INTO … VALUES … / SELECT …` (append only), `CREATE TABLE`, `CREATE TABLE … AS SELECT …` and `DROP TABLE`. A write runs once, inline, and answers with a one-row result holding a single `rows_affected` column. Writes need an API key holding both the `read` and `write` permissions, may only target the database's own `default` catalog (attached catalogs stay read-only), and are never run from a saved query. `UPDATE` and `DELETE` are not supported yet; use the load endpoint's keyed `update` and `delete` modes. Every other mutation (`ALTER`, views, `COPY`, transactions, `EXPLAIN` of a write) is refused. :param query_request: (required) :type query_request: QueryRequest @@ -137,7 +137,7 @@ def query_with_http_info( ) -> ApiResponse[QueryResponse]: """Execute SQL query - Execute a SQL query scoped to a database. A database is the only window into catalogs: the query sees only that database's auto `default` catalog plus any catalogs explicitly attached to it. Select the database with EITHER the `X-Database-Id` header OR the `database_id` body field (exactly one must be given; if both are sent and disagree, that's a 400). Use standard Postgres-compatible SQL; reference the default catalog as `default..
` (or just `.
` / `
`) and attached catalogs by their alias. Results are returned inline and a `result_id` is provided for later retrieval via the Results API. Set `async: true` to execute asynchronously — returns a query run ID for polling. Optionally set `async_after_ms` to attempt synchronous execution first, falling back to async if the query exceeds the timeout. To receive the rows as an Arrow IPC stream instead of JSON, send `Accept: application/vnd.apache.arrow.stream` (without also listing `application/json`). A query that completes within the request then returns 200 with an Arrow IPC stream body (schema, record batches, end-of-stream), and the values the JSON body would carry are sent as response headers: `X-Query-Run-Id`, `X-Result-Id` (when the result was saved), `X-Total-Row-Count`, `X-Execution-Time-Ms`, and `X-Query-Warning` (when there is a warning). Arrow keeps `±Inf` and `NaN` float values as they are. An Arrow response always holds the whole result: if the result is larger than an inline response can hold, the request returns 202 with the query run ticket and an `X-Result-Id` header instead, and the full result is read from `GET /v1/results/{id}?format=arrow` once it is ready. A query that goes async, and every error, return the same JSON bodies as without the header. + Execute a SQL query scoped to a database. A database is the only window into catalogs: the query sees only that database's auto `default` catalog plus any catalogs explicitly attached to it. Select the database with EITHER the `X-Database-Id` header OR the `database_id` body field (exactly one must be given; if both are sent and disagree, that's a 400). Use standard Postgres-compatible SQL; reference the default catalog as `default..
` (or just `.
` / `
`) and attached catalogs by their alias. Results are returned inline and a `result_id` is provided for later retrieval via the Results API. Set `async: true` to execute asynchronously — returns a query run ID for polling. Optionally set `async_after_ms` to attempt synchronous execution first, falling back to async if the query exceeds the timeout. To receive the rows as an Arrow IPC stream instead of JSON, send `Accept: application/vnd.apache.arrow.stream` (without also listing `application/json`). A query that completes within the request then returns 200 with an Arrow IPC stream body (schema, record batches, end-of-stream), and the values the JSON body would carry are sent as response headers: `X-Query-Run-Id`, `X-Result-Id` (when the result was saved), `X-Total-Row-Count`, `X-Execution-Time-Ms`, and `X-Query-Warning` (when there is a warning). Arrow keeps `±Inf` and `NaN` float values as they are. An Arrow response always holds the whole result: if the result is larger than an inline response can hold, the request returns 202 with the query run ticket and an `X-Result-Id` header instead, and the full result is read from `GET /v1/results/{id}?format=arrow` once it is ready. A query that goes async, and every error, return the same JSON bodies as without the header. The endpoint also runs write statements: `INSERT INTO … VALUES … / SELECT …` (append only), `CREATE TABLE`, `CREATE TABLE … AS SELECT …` and `DROP TABLE`. A write runs once, inline, and answers with a one-row result holding a single `rows_affected` column. Writes need an API key holding both the `read` and `write` permissions, may only target the database's own `default` catalog (attached catalogs stay read-only), and are never run from a saved query. `UPDATE` and `DELETE` are not supported yet; use the load endpoint's keyed `update` and `delete` modes. Every other mutation (`ALTER`, views, `COPY`, transactions, `EXPLAIN` of a write) is refused. :param query_request: (required) :type query_request: QueryRequest @@ -214,7 +214,7 @@ def query_without_preload_content( ) -> RESTResponseType: """Execute SQL query - Execute a SQL query scoped to a database. A database is the only window into catalogs: the query sees only that database's auto `default` catalog plus any catalogs explicitly attached to it. Select the database with EITHER the `X-Database-Id` header OR the `database_id` body field (exactly one must be given; if both are sent and disagree, that's a 400). Use standard Postgres-compatible SQL; reference the default catalog as `default..
` (or just `.
` / `
`) and attached catalogs by their alias. Results are returned inline and a `result_id` is provided for later retrieval via the Results API. Set `async: true` to execute asynchronously — returns a query run ID for polling. Optionally set `async_after_ms` to attempt synchronous execution first, falling back to async if the query exceeds the timeout. To receive the rows as an Arrow IPC stream instead of JSON, send `Accept: application/vnd.apache.arrow.stream` (without also listing `application/json`). A query that completes within the request then returns 200 with an Arrow IPC stream body (schema, record batches, end-of-stream), and the values the JSON body would carry are sent as response headers: `X-Query-Run-Id`, `X-Result-Id` (when the result was saved), `X-Total-Row-Count`, `X-Execution-Time-Ms`, and `X-Query-Warning` (when there is a warning). Arrow keeps `±Inf` and `NaN` float values as they are. An Arrow response always holds the whole result: if the result is larger than an inline response can hold, the request returns 202 with the query run ticket and an `X-Result-Id` header instead, and the full result is read from `GET /v1/results/{id}?format=arrow` once it is ready. A query that goes async, and every error, return the same JSON bodies as without the header. + Execute a SQL query scoped to a database. A database is the only window into catalogs: the query sees only that database's auto `default` catalog plus any catalogs explicitly attached to it. Select the database with EITHER the `X-Database-Id` header OR the `database_id` body field (exactly one must be given; if both are sent and disagree, that's a 400). Use standard Postgres-compatible SQL; reference the default catalog as `default..
` (or just `.
` / `
`) and attached catalogs by their alias. Results are returned inline and a `result_id` is provided for later retrieval via the Results API. Set `async: true` to execute asynchronously — returns a query run ID for polling. Optionally set `async_after_ms` to attempt synchronous execution first, falling back to async if the query exceeds the timeout. To receive the rows as an Arrow IPC stream instead of JSON, send `Accept: application/vnd.apache.arrow.stream` (without also listing `application/json`). A query that completes within the request then returns 200 with an Arrow IPC stream body (schema, record batches, end-of-stream), and the values the JSON body would carry are sent as response headers: `X-Query-Run-Id`, `X-Result-Id` (when the result was saved), `X-Total-Row-Count`, `X-Execution-Time-Ms`, and `X-Query-Warning` (when there is a warning). Arrow keeps `±Inf` and `NaN` float values as they are. An Arrow response always holds the whole result: if the result is larger than an inline response can hold, the request returns 202 with the query run ticket and an `X-Result-Id` header instead, and the full result is read from `GET /v1/results/{id}?format=arrow` once it is ready. A query that goes async, and every error, return the same JSON bodies as without the header. The endpoint also runs write statements: `INSERT INTO … VALUES … / SELECT …` (append only), `CREATE TABLE`, `CREATE TABLE … AS SELECT …` and `DROP TABLE`. A write runs once, inline, and answers with a one-row result holding a single `rows_affected` column. Writes need an API key holding both the `read` and `write` permissions, may only target the database's own `default` catalog (attached catalogs stay read-only), and are never run from a saved query. `UPDATE` and `DELETE` are not supported yet; use the load endpoint's keyed `update` and `delete` modes. Every other mutation (`ALTER`, views, `COPY`, transactions, `EXPLAIN` of a write) is refused. :param query_request: (required) :type query_request: QueryRequest diff --git a/hotdata/models/query_request.py b/hotdata/models/query_request.py index 509e91a..21ca9ad 100644 --- a/hotdata/models/query_request.py +++ b/hotdata/models/query_request.py @@ -33,7 +33,7 @@ class QueryRequest(BaseModel): database_id: Optional[StrictStr] = Field(default=None, description="Database to scope the query to (its id). Alternative to the `X-Database-Id` header — exactly one source must be provided. If both this field and the header are set and they disagree, the request is rejected with a 400.") default_catalog: Optional[StrictStr] = Field(default=None, description="Catalog that unqualified table references resolve against within the query's database scope. Must name a catalog visible in the database (`default`, an attached catalog alias, or a system catalog). Defaults to `default` when omitted.") default_schema: Optional[StrictStr] = Field(default=None, description="Schema that unqualified table references resolve against within the query's database scope. Omit it to use the database's own default schema — the `default_schema` chosen when the database was created, or the single schema it declares, and only `main` when it has neither. The database's `default_schema` field (see `GET /v1/databases/{id}`) reports the value in force. Setting this field overrides that per query, so sending `main` on a database whose data lives in another schema turns a query that works without the field into a \"table not found\" error. Existence is not validated up front — an unknown schema surfaces as a \"table not found\" error at planning time. Fully-qualified references (`..
`) are unaffected.") - dialect: Optional[StrictStr] = Field(default=None, description="SQL dialect the `sql` field is written in. One of `hotsql` (the default), `duckdb`, `postgres`, or `snowflake`. When set to anything other than `hotsql`, the query is translated to HotSQL before it runs, so you can use idioms from that dialect (for example Snowflake `IFF(...)` or Postgres `MOD(a, b)`). Write statements are accepted on the same terms as in HotSQL: refused unless the instance accepts SQL writes. An unrecognized value is rejected with a 400.") + dialect: Optional[StrictStr] = Field(default=None, description="SQL dialect the `sql` field is written in. One of `hotsql` (the default), `duckdb`, `postgres`, or `snowflake`. When set to anything other than `hotsql`, the query is translated to HotSQL before it runs, so you can use idioms from that dialect (for example Snowflake `IFF(...)` or Postgres `MOD(a, b)`). Write statements are accepted on the same terms as in HotSQL. An unrecognized value is rejected with a 400.") sql: StrictStr __properties: ClassVar[List[str]] = ["async", "async_after_ms", "database_id", "default_catalog", "default_schema", "dialect", "sql"]