diff --git a/.openapi-generator/FILES b/.openapi-generator/FILES index 8569f3e..843254f 100644 --- a/.openapi-generator/FILES +++ b/.openapi-generator/FILES @@ -89,6 +89,7 @@ docs/ListWorkspacesResponse.md docs/LoadManagedTableRequest.md docs/LoadManagedTableResponse.md docs/ManagedSchemaResponse.md +docs/ManagedTableConstantPerKeyResponse.md docs/ManagedTableResponse.md docs/MintUploadPartsRequest.md docs/MintUploadPartsResponse.md @@ -115,6 +116,7 @@ docs/TemporalProfileDetail.md docs/TextProfileDetail.md docs/UpdateEmbeddingProviderRequest.md docs/UpdateEmbeddingProviderResponse.md +docs/UpdateManagedTableRequest.md docs/UpdateSavedQueryRequest.md docs/UploadSessionResponse.md docs/UploadsApi.md @@ -224,6 +226,7 @@ src/models/list_workspaces_response.rs src/models/load_managed_table_request.rs src/models/load_managed_table_response.rs src/models/managed_schema_response.rs +src/models/managed_table_constant_per_key_response.rs src/models/managed_table_response.rs src/models/mint_upload_parts_request.rs src/models/mint_upload_parts_response.rs @@ -247,6 +250,7 @@ src/models/temporal_profile_detail.rs src/models/text_profile_detail.rs src/models/update_embedding_provider_request.rs src/models/update_embedding_provider_response.rs +src/models/update_managed_table_request.rs src/models/update_saved_query_request.rs src/models/upload_session_response.rs src/models/upsert_database_context_request.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index c938efe..98c4970 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- chore(databases): clarify default_connection_id usage and catalog attachment rules ## [0.18.1] - 2026-09-18 diff --git a/docs/AddManagedTableDecl.md b/docs/AddManagedTableDecl.md index 3ea2ed6..18f1044 100644 --- a/docs/AddManagedTableDecl.md +++ b/docs/AddManagedTableDecl.md @@ -4,8 +4,8 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**constant_per_key** | Option<**Vec**> | Columns whose value is determined by this table's `key`: for every uploaded row, every stored row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation (`delete`, `update`, `upsert`) restrict its search for prior versions to the values the upload carries, which prunes far harder than the key alone when the key's own file statistics are weak. Omit (the default) for the unrestricted search. **Correctness-affecting, not a hint.** If the assertion is false, a mutation supersedes one version of a key and appends beside another, silently duplicating it. Declare it only where the invariant is established. | [optional] **key** | Option<**Vec**> | Columns that uniquely identify a row, enabling the key-based load modes (`delete`, `update`, `upsert`) on this table: those loads match rows by these columns' values. Omit (the default) to declare no key; the table can still be loaded with `replace` and `append`, but key-based modes are then rejected. | [optional] -**key_determines** | Option<**Vec**> | Columns whose value is determined by this table's `key`: for every uploaded row, every stored row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation (`delete`, `update`, `upsert`) restrict its search for prior versions to the values the upload carries, which prunes far harder than the key alone when the key's own file statistics are weak. Omit (the default) for the unrestricted search. **Correctness-affecting, not a hint.** If the assertion is false, a mutation supersedes one version of a key and appends beside another, silently duplicating it. Declare it only where the invariant is established. | [optional] **name** | **String** | | **partition_by** | Option<[**Vec**](TablePartitionKey.md)> | Partition keys for this table, applied in order. Omit for no partitioning. Declared when the table is created and fixed thereafter. | [optional] **sorted_by** | Option<[**Vec**](TableSortKey.md)> | Sort keys for this table, applied in order. Omit for no sort order. Declared when the table is created and fixed thereafter. | [optional] diff --git a/docs/AddManagedTableRequest.md b/docs/AddManagedTableRequest.md index ecb2f58..09fbb72 100644 --- a/docs/AddManagedTableRequest.md +++ b/docs/AddManagedTableRequest.md @@ -4,8 +4,8 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**constant_per_key** | Option<**Vec**> | Columns whose value is determined by this table's `key`: for every uploaded row, every stored row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation (`delete`, `update`, `upsert`) restrict its search for prior versions to the values the upload carries, which prunes far harder than the key alone when the key's own file statistics are weak. Omit (the default) for the unrestricted search. **Correctness-affecting, not a hint.** If the assertion is false, a mutation supersedes one version of a key and appends beside another, silently duplicating it. Declare it only where the invariant is established. | [optional] **key** | Option<**Vec**> | Columns that uniquely identify a row, enabling the key-based load modes (`delete`, `update`, `upsert`) on this table: those loads match rows by these columns' values. Omit (the default) to declare no key; the table can still be loaded with `replace` and `append`, but key-based modes are then rejected. | [optional] -**key_determines** | Option<**Vec**> | Columns whose value is determined by this table's `key`: for every uploaded row, every stored row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation (`delete`, `update`, `upsert`) restrict its search for prior versions to the values the upload carries, which prunes far harder than the key alone when the key's own file statistics are weak. Omit (the default) for the unrestricted search. **Correctness-affecting, not a hint.** If the assertion is false, a mutation supersedes one version of a key and appends beside another, silently duplicating it. Declare it only where the invariant is established. | [optional] **name** | **String** | | **partition_by** | Option<[**Vec**](TablePartitionKey.md)> | Partition keys for this table, applied in order. Omit for no partitioning. Declared when the table is created and fixed thereafter. | [optional] **sorted_by** | Option<[**Vec**](TableSortKey.md)> | Sort keys for this table, applied in order. Omit for no sort order. Declared when the table is created and fixed thereafter. | [optional] diff --git a/docs/ConnectionsApi.md b/docs/ConnectionsApi.md index 51ecc13..ada5b39 100644 --- a/docs/ConnectionsApi.md +++ b/docs/ConnectionsApi.md @@ -13,6 +13,7 @@ Method | HTTP request | Description [**get_table_profile**](ConnectionsApi.md#get_table_profile) | **GET** /v1/connections/{connection_id}/tables/{schema}/{table}/profile | Get table profile [**list_connections**](ConnectionsApi.md#list_connections) | **GET** /v1/connections | List connections [**load_managed_table**](ConnectionsApi.md#load_managed_table) | **POST** /v1/connections/{connection_id}/schemas/{schema}/tables/{table}/loads | Load managed table from inline data, upload, or query result +[**set_managed_table_constant_per_key**](ConnectionsApi.md#set_managed_table_constant_per_key) | **PUT** /v1/connections/{connection_id}/schemas/{schema}/tables/{table}/constant-per-key | Declare which columns are constant per key @@ -292,3 +293,36 @@ Name | Type | Description | Required | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## set_managed_table_constant_per_key + +> models::ManagedTableConstantPerKeyResponse set_managed_table_constant_per_key(connection_id, schema, table, update_managed_table_request) +Declare which columns are constant per key + +Replace the columns a table declares constant for a given key: for every row, any other row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation (`delete`, `update`, `upsert`) narrow its search for prior versions to the values the upload carries, which prunes far harder than the key alone when the key's own file statistics do not discriminate. Unlike `partition_by` and `sorted_by`, this is NOT fixed when the table is created. It changes only which files a mutation opens, never how rows are written, so nothing stored becomes wrong when it changes and a populated table can adopt it with no rewrite. It takes effect on the next load. Send an empty array to revoke it, restoring the unrestricted search — this is the way to undo a declaration that turns out to be false. **This is correctness-affecting, not a hint.** If the assertion is false, a keyed mutation supersedes one version of a key and appends beside another, silently duplicating it, and the pruning conceals its own evidence because the file holding the missed row is never opened. Declare it only where the invariant is established. + +### Parameters + + +Name | Type | Description | Required | Notes +------------- | ------------- | ------------- | ------------- | ------------- +**connection_id** | **String** | Connection ID | [required] | +**schema** | **String** | Schema name | [required] | +**table** | **String** | Table name | [required] | +**update_managed_table_request** | [**UpdateManagedTableRequest**](UpdateManagedTableRequest.md) | | [required] | + +### Return type + +[**models::ManagedTableConstantPerKeyResponse**](ManagedTableConstantPerKeyResponse.md) + +### Authorization + +[WorkspaceId](../README.md#WorkspaceId), [BearerAuth](../README.md#BearerAuth) + +### HTTP request headers + +- **Content-Type**: application/json +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/docs/CreateDatabaseResponse.md b/docs/CreateDatabaseResponse.md index ee00064..8a0b4df 100644 --- a/docs/CreateDatabaseResponse.md +++ b/docs/CreateDatabaseResponse.md @@ -6,7 +6,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **created** | Option<**bool**> | Whether this call brought the database into existence. Only `false` when `if_not_exists` found a database already carrying the requested name, in which case nothing was created and the existing one is returned. The response status says the same thing — `201` against `200` — but generated clients often surface only the body, so it is stated here as well. Always sent. It is declared optional so that a client built against a newer version of this API still accepts a response from a deployment that predates the field. Absent therefore means \"this deployment cannot say\", which is not the same as `false` — test for the two values explicitly rather than for truthiness. | [optional] **default_catalog** | **String** | Name the database's default catalog answers to inside its query scope (`default` unless overridden at create time). | -**default_connection_id** | **String** | Internal id of the connection that backs this database's `default` catalog. Workspace-level connection endpoints (list, get, health, delete, cache purge) refuse to act on this id — it is exposed only for the managed-tables load endpoint (`POST /v1/connections/{id}/schemas/{s}/tables/{t}/loads`) so callers can load data into tables declared at database-create time. Addressing it directly in SQL is not the recommended path — use `default` inside an `X-Database-Id` scope instead. | +**default_connection_id** | **String** | Id of the connection that backs this database's `default` catalog. Two uses: pass it as `connection_id` to `POST /v1/databases/{other}/catalogs` to attach this database's catalog into another database, and as the connection in the managed-tables load endpoint (`POST /v1/connections/{id}/schemas/{s}/tables/{t}/loads`) to load tables declared at create time. Other connection endpoints (list, get, health, delete, cache purge) refuse to act on it. In SQL, address the catalog as `default` inside an `X-Database-Id` scope, not by this id. | **default_schema** | **String** | Schema that unqualified table names resolve to inside this database's query scope. `main` unless the database declares a single schema or a `default_schema` was set at create time. | **expires_at** | Option<**String**> | When this database expires. | [optional] **forked_from** | Option<[**models::ForkedFromInfo**](ForkedFromInfo.md)> | | [optional] diff --git a/docs/CreateIndexRequest.md b/docs/CreateIndexRequest.md index 1594356..f3c4925 100644 --- a/docs/CreateIndexRequest.md +++ b/docs/CreateIndexRequest.md @@ -14,6 +14,7 @@ Name | Type | Description | Notes **index_type** | Option<**IndexType**> | Index type. `sorted` supports range queries, `bm25` full-text search, and `vector` similarity search. (enum: sorted, bm25, vector) | [optional][default to Sorted] **metric** | Option<**String**> | Distance metric for vector indexes: \"l2\", \"cosine\", or \"dot\". When omitted, defaults to \"l2\" for float array columns or the provider's preferred metric for text columns with auto-embedding. | [optional] **output_column** | Option<**String**> | Custom name for the generated embedding column. Defaults to `{column}_embedding`. | [optional] +**vector_precision** | Option<**VectorPrecision**> | How precisely a vector index stores each number of a vector. Lower precision shrinks the index so a larger table can be indexed within the same memory, and lets searches run on a smaller instance. Omit this field to store vectors at the same precision as the column, which is the default. The quality figures below come from one benchmark — 1536-dimension text embeddings, cosine distance, default search settings — and are a guide, not a guarantee. Other models, dimensions, distance metrics and data distributions behave differently, so measure on your own data before moving a production index to a lower precision. `float32` — on a `float64` column this halves the index. Widely used embedding models emit 32-bit values, so for those nothing is lost; vectors that genuinely carry more than 32 bits of precision will lose some. `float16` — half the memory of `float32`. In that benchmark its results matched `float32` to within 0.1 percentage points. `float8` — a quarter of the memory of `float32`. In that benchmark it scored about 4 percentage points below `float32`, and raising the search effort did not close the gap, so treat the reduction as permanent for a given index. `float64` — accepted only for a column that already holds double-precision values; it cannot add precision the stored data does not have. Changing this means dropping the index and creating it again. It affects only the index: the table's own values are never altered, and text columns indexed with a generated embedding are not re-embedded. (enum: float64, float32, float16, float8) | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/docs/CreateUploadRequest.md b/docs/CreateUploadRequest.md index 58b350d..467f9a1 100644 --- a/docs/CreateUploadRequest.md +++ b/docs/CreateUploadRequest.md @@ -8,7 +8,7 @@ Name | Type | Description | Notes **checksum_value** | Option<**String**> | Integrity checksum value, paired with `checksum_algo`. Optional. | [optional] **content_encoding** | Option<**String**> | Content encoding to record for the uploaded file (for example `gzip`). Optional. | [optional] **content_type** | Option<**String**> | Content type to record for the uploaded file (for example the Parquet, CSV, or JSON MIME type). Optional. | [optional] -**declared_size_bytes** | Option<**i64**> | The exact size, in bytes, of the file you will upload. Optional. When provided, it is validated at create time against the maximum allowed size, and again at finalize against the bytes actually uploaded — a mismatch fails the finalize. Omit it to create a streaming (unknown-size) upload: the session is always multi-part and returns no part URLs up front; instead you mint part URLs on demand from `POST /v1/uploads/{upload_id}/parts` as you upload, and finalize validates only that the file is non-empty. | [optional] +**declared_size_bytes** | Option<**i64**> | The exact size, in bytes, of the file you will upload. Optional. When provided, it is checked at create time against the maximum upload size (16 GiB by default, the same for every file format), so an oversized file is refused before you transfer any of it; it is checked again at finalize against the bytes actually uploaded — a mismatch fails the finalize. Omit it to create a streaming (unknown-size) upload: the session is always multi-part and returns no part URLs up front; instead you mint part URLs on demand from `POST /v1/uploads/{upload_id}/parts` as you upload, and finalize checks only that the file is non-empty and within the maximum upload size. | [optional] **filename** | Option<**String**> | Original file name, recorded with the upload for your own bookkeeping. Optional and advisory — it does not affect how the file is uploaded or loaded. | [optional] **part_size** | Option<**i64**> | Preferred size, in bytes, of each part for a large (multi-part) upload. Optional hint — the service clamps it to the allowed part-size range and to the maximum number of parts, and ignores it for small files uploaded with a single `PUT`. Omit to let the service choose. | [optional] diff --git a/docs/DatabaseDefaultTableDecl.md b/docs/DatabaseDefaultTableDecl.md index 3df94a8..49089d1 100644 --- a/docs/DatabaseDefaultTableDecl.md +++ b/docs/DatabaseDefaultTableDecl.md @@ -4,8 +4,8 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- +**constant_per_key** | Option<**Vec**> | Columns whose value is determined by this table's `key`: for every uploaded row, every stored row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation (`delete`, `update`, `upsert`) restrict its search for prior versions to the values the upload carries, which prunes far harder than the key alone when the key's own file statistics are weak. Omit (the default) for the unrestricted search. **Correctness-affecting, not a hint.** If the assertion is false, a mutation supersedes one version of a key and appends beside another, silently duplicating it. Declare it only where the invariant is established. | [optional] **key** | Option<**Vec**> | Columns that uniquely identify a row, enabling the key-based load modes (`delete`, `update`, `upsert`) on this table: those loads match rows by these columns' values. Omit (the default) to declare no key; the table can still be loaded with `replace` and `append`, but key-based modes are then rejected. | [optional] -**key_determines** | Option<**Vec**> | Columns whose value is determined by this table's `key`: for every uploaded row, every stored row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation (`delete`, `update`, `upsert`) restrict its search for prior versions to the values the upload carries, which prunes far harder than the key alone when the key's own file statistics are weak. Omit (the default) for the unrestricted search. **Correctness-affecting, not a hint.** If the assertion is false, a mutation supersedes one version of a key and appends beside another, silently duplicating it. Declare it only where the invariant is established. | [optional] **name** | **String** | | **partition_by** | Option<[**Vec**](TablePartitionKey.md)> | Partition keys for this table, applied in order. Omit for no partitioning. Declared when the table is created and fixed thereafter. | [optional] **sorted_by** | Option<[**Vec**](TableSortKey.md)> | Sort keys for this table, applied in order. Omit for no sort order. Declared when the table is created and fixed thereafter. | [optional] diff --git a/docs/DatabaseDetailResponse.md b/docs/DatabaseDetailResponse.md index a3e833a..7bcc7a9 100644 --- a/docs/DatabaseDetailResponse.md +++ b/docs/DatabaseDetailResponse.md @@ -7,7 +7,7 @@ Name | Type | Description | Notes **attachments** | [**Vec**](DatabaseAttachmentInfo.md) | | **created_at** | Option<**String**> | When the database was created. | [optional] **default_catalog** | **String** | Name the database's default catalog answers to inside its query scope (`default` unless overridden at create time). | -**default_connection_id** | **String** | | +**default_connection_id** | **String** | Id of the connection backing this database's `default` catalog. Pass it as `connection_id` to `POST /v1/databases/{other}/catalogs` to attach this database's catalog into another database. In SQL, address the catalog as `default` inside an `X-Database-Id` scope, not by id. | **default_schema** | **String** | Schema that unqualified table names resolve to inside this database's query scope. `main` unless the database declares a single schema or a `default_schema` was set at create time. | **expires_at** | Option<**String**> | When this database expires. | [optional] **forked_from** | Option<[**models::ForkedFromInfo**](ForkedFromInfo.md)> | | [optional] diff --git a/docs/DatabasesApi.md b/docs/DatabasesApi.md index a13ee49..cd3148a 100644 --- a/docs/DatabasesApi.md +++ b/docs/DatabasesApi.md @@ -20,6 +20,7 @@ Method | HTTP request | Description [**list_databases**](DatabasesApi.md#list_databases) | **GET** /v1/databases | List databases [**load_database_table**](DatabasesApi.md#load_database_table) | **POST** /v1/databases/{database_id}/schemas/{schema}/tables/{table}/loads | Load database table from inline data, upload, or query result [**lookup_database_by_name**](DatabasesApi.md#lookup_database_by_name) | **GET** /v1/databases/by-name | Look up a database by name +[**set_database_table_constant_per_key**](DatabasesApi.md#set_database_table_constant_per_key) | **PUT** /v1/databases/{database_id}/schemas/{schema}/tables/{table}/constant-per-key | Declare which columns are constant per key @@ -91,7 +92,7 @@ Name | Type | Description | Required | Notes > attach_database_catalog(database_id, attach_database_catalog_request) Attach catalog to database -Attach an existing connection (catalog) to a database with an optional alias. Inside the database the catalog is reachable as the alias (when set) or its original name. +Attach a catalog to a database so its tables are queryable alongside the database's own. Pass another database's `default_connection_id` as `connection_id` to read across the two in one query. Inside the database the catalog answers to `alias` when set, otherwise to the name it already answers to in its own scope. That name may not be `default`, a reserved name, or this database's own default catalog name — so attaching a database that kept the stock `default` catalog needs an `alias`. Attaching is read-only and copies nothing: loads still target the database's own default catalog, and detaching withdraws visibility rather than deleting data. A database's own default catalog is always attached and cannot be attached again. Attaching is not transitive — a database sees the catalog it attached, not that catalog's own attachments. ### Parameters @@ -152,7 +153,7 @@ Name | Type | Description | Required | Notes > models::DatabaseCountResponse count_databases(search, batch) Count databases -Return the total number of databases in the workspace. This is the whole-workspace total, not a page size: the `count` field on the listing reports how many rows that one page returned, so totalling a workspace from `GET /v1/databases` means walking every page. Pass `search` to count only databases whose name contains that text (case-insensitive), or `batch` with the `batch_id` returned by a bulk-creation call to count only that batch's databases. The filters mean exactly what they mean on the listing, so a count and a listing given the same filters describe the same set. +Return the total number of databases in the workspace. This is the whole-workspace total, not a page size: the `count` field on the listing reports how many rows that one page returned, so totalling a workspace from `GET /v1/databases` means walking every page. Pass `search` to count only databases whose name contains that text, ignoring the case of unaccented Latin letters and digits, or `batch` with the `batch_id` returned by a bulk-creation call to count only that batch's databases. The filters mean exactly what they mean on the listing, so a count and a listing given the same filters describe the same set. ### Parameters @@ -213,7 +214,7 @@ Name | Type | Description | Required | Notes > delete_database(database_id) Delete database -Delete a database and its auto-created default catalog. Attached catalogs are detached (their underlying connections are not deleted). +Delete a database and its auto-created default catalog. Catalogs attached to it are detached (the catalogs themselves are not deleted). Refused while another database attaches this one's catalog — detach it there first — unless this database is past its `expires_at`, in which case it can be deleted regardless and the attaching database loses the catalog. A database that attaches one should watch that date. ### Parameters @@ -302,7 +303,7 @@ Name | Type | Description | Required | Notes > models::CreateDatabaseResponse fork_database(database_id, fork_database_request) Fork database -Create a new database that is an independent fork of an existing one. The fork has its own default catalog and contains the same schemas, tables, and data as the source; the source is left unchanged. External catalogs attached to the source are re-attached to the fork. Optional `name` sets the fork's display label; when omitted, the fork takes the source's label followed by a short suffix derived from the fork's own ID, so the two stay distinguishable. Optional `expires_at` sets when the fork expires — accepts an RFC 3339 timestamp or a relative duration suffixed with `h` (hours), `m` (minutes), or `d` (days), e.g. `24h`, `90m`, `7d`. When omitted, a still-future expiry on the source is carried over; otherwise the fork never expires. Any indexes on the source's tables are not carried over. +Create a new database that is an independent fork of an existing one. The fork has its own default catalog and contains the same schemas, tables, and data as the source; the source is left unchanged. External catalogs attached to the source are re-attached to the fork. Optional `name` sets the fork's display label; when omitted, the fork takes the source's label followed by a short suffix derived from the fork's own ID, so the two stay distinguishable. Optional `expires_at` sets when the fork expires — accepts an RFC 3339 timestamp or a relative duration suffixed with `h` (hours), `m` (minutes), or `d` (days), e.g. `24h`, `90m`, `7d`. When omitted, a still-future expiry on the source is carried over; otherwise the fork never expires. Any indexes on the source's tables are not carried over. A fork adds no stored bytes at first, because it starts out sharing the source's storage. Routine maintenance can later rewrite a shared table into the fork's own storage, and the fork is billed for that copy from then on. Whether and when that happens depends on the table, so a fork that is only read can keep sharing indefinitely. ### Parameters @@ -424,7 +425,7 @@ Name | Type | Description | Required | Notes > models::ListDatabasesResponse list_databases(limit, cursor, search, batch) List databases -List databases in the workspace, newest first, one page at a time. When no `limit` is given a default page size is applied, so a single call returns at most one page rather than every database. If the response's `has_more` is true, pass its `next_cursor` value back as the `cursor` query parameter to fetch the next page. Pass `search` to return only databases whose name *contains* that text (case-insensitive); to fetch the single database whose name matches exactly, use `GET /v1/databases/by-name` instead. Pass `batch` with the `batch_id` returned by a bulk-creation call to list only that batch's databases. +List databases in the workspace, newest first, one page at a time. When no `limit` is given a default page size is applied, so a single call returns at most one page rather than every database. If the response's `has_more` is true, pass its `next_cursor` value back as the `cursor` query parameter to fetch the next page. Pass `search` to return only databases whose name *contains* that text, ignoring the case of unaccented Latin letters and digits; to fetch the single database whose name matches exactly, use `GET /v1/databases/by-name` instead. Pass `batch` with the `batch_id` returned by a bulk-creation call to list only that batch's databases. ### Parameters @@ -490,14 +491,14 @@ Name | Type | Description | Required | Notes > models::DatabaseDetailResponse lookup_database_by_name(name) Look up a database by name -Fetch a single database by its exact name. This is the counterpart to the listing's `search` filter, which matches any database whose name merely contains the text. Matching ignores case for names made of unaccented Latin letters and digits; that much is guaranteed. For names containing other characters — accented letters, or any non-Latin script — whether case is ignored depends on the deployment, so rely on neither: look those up with the capitalisation they were created with. Returns 404 when no database has that name. A name shared by more than one database returns 409 rather than picking one of them; address those by id. +Fetch a single database by its exact name. This is the counterpart to the listing's `search` filter, which matches any database whose name merely contains the text. Matching ignores case for unaccented Latin letters and digits, and only for those. Every other character has to match exactly, so a name containing an accented letter or a non-Latin script must be looked up with the capitalisation it was created with. Returns 404 when no database has that name. A name shared by more than one database returns 409 rather than picking one of them; address those by id. ### Parameters Name | Type | Description | Required | Notes ------------- | ------------- | ------------- | ------------- | ------------- -**name** | **String** | Exact name to look up. Unlike the listing's `search`, which matches any database whose name *contains* the text, this matches the whole name. Case is ignored for unaccented Latin letters and digits. For other characters, whether case is ignored depends on the deployment, so look those names up with the capitalisation they were created with. | [required] | +**name** | **String** | Exact name to look up. Unlike the listing's `search`, which matches any database whose name *contains* the text, this matches the whole name. Case is ignored for unaccented Latin letters and digits, and only for those. Every other character has to match exactly, so look a name holding one up with the capitalisation it was created with. | [required] | ### Return type @@ -514,3 +515,36 @@ Name | Type | Description | Required | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## set_database_table_constant_per_key + +> models::ManagedTableConstantPerKeyResponse set_database_table_constant_per_key(database_id, schema, table, update_managed_table_request) +Declare which columns are constant per key + +Replace the columns a table declares constant for a given key: for every row, any other row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation (`delete`, `update`, `upsert`) narrow its search for prior versions to the values the upload carries. Unlike `partition_by` and `sorted_by`, this is NOT fixed when the table is created — it changes only which files a mutation opens, never how rows are written — so a populated table can adopt it with no rewrite, taking effect on the next load. Send an empty array to revoke it. **Correctness-affecting, not a hint.** If the assertion is false, a keyed mutation supersedes one version of a key and appends beside another, silently duplicating it. Declare it only where the invariant is established. + +### Parameters + + +Name | Type | Description | Required | Notes +------------- | ------------- | ------------- | ------------- | ------------- +**database_id** | **String** | Database ID | [required] | +**schema** | **String** | Schema name | [required] | +**table** | **String** | Table name | [required] | +**update_managed_table_request** | [**UpdateManagedTableRequest**](UpdateManagedTableRequest.md) | | [required] | + +### Return type + +[**models::ManagedTableConstantPerKeyResponse**](ManagedTableConstantPerKeyResponse.md) + +### Authorization + +[WorkspaceId](../README.md#WorkspaceId), [BearerAuth](../README.md#BearerAuth) + +### HTTP request headers + +- **Content-Type**: application/json +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/docs/IndexEntryResponse.md b/docs/IndexEntryResponse.md index fecdde7..f351433 100644 --- a/docs/IndexEntryResponse.md +++ b/docs/IndexEntryResponse.md @@ -12,6 +12,7 @@ Name | Type | Description | Notes **source_column** | Option<**String**> | Source text column for an embedding-backed vector index. A query searches it via `vector_distance(, …)`; the indexed `columns` hold the generated embedding column instead. Absent for BM25, sorted, and direct (existing-column) vector indexes. | [optional] **status** | [**models::IndexStatus**](IndexStatus.md) | | **updated_at** | **String** | | +**vector_precision** | Option<**String**> | How precisely this vector index stores each number of a vector, when it was created with an explicit precision. Absent means it stores at the same precision as the column, which is the default. Also absent for BM25 and sorted indexes. | [optional] **connection_id** | Option<**String**> | | [optional] **schema_name** | **String** | | **table_name** | **String** | | diff --git a/docs/IndexInfoResponse.md b/docs/IndexInfoResponse.md index 70d56f0..fdf5064 100644 --- a/docs/IndexInfoResponse.md +++ b/docs/IndexInfoResponse.md @@ -12,6 +12,7 @@ Name | Type | Description | Notes **source_column** | Option<**String**> | Source text column for an embedding-backed vector index. A query searches it via `vector_distance(, …)`; the indexed `columns` hold the generated embedding column instead. Absent for BM25, sorted, and direct (existing-column) vector indexes. | [optional] **status** | [**models::IndexStatus**](IndexStatus.md) | | **updated_at** | **String** | | +**vector_precision** | Option<**String**> | How precisely this vector index stores each number of a vector, when it was created with an explicit precision. Absent means it stores at the same precision as the column, which is the default. Also absent for BM25 and sorted indexes. | [optional] [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/docs/JobType.md b/docs/JobType.md index 95823b9..37aeb78 100644 --- a/docs/JobType.md +++ b/docs/JobType.md @@ -15,6 +15,7 @@ | ResultRetention | result_retention | | DucklakeCompaction | ducklake_compaction | | DucklakeTableCompaction | ducklake_table_compaction | +| TableConstantsUpdate | table_constants_update | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/docs/ManagedTableConstantPerKeyResponse.md b/docs/ManagedTableConstantPerKeyResponse.md new file mode 100644 index 0000000..c22726b --- /dev/null +++ b/docs/ManagedTableConstantPerKeyResponse.md @@ -0,0 +1,14 @@ +# ManagedTableConstantPerKeyResponse + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**connection_id** | **String** | Connection backing the catalog the table belongs to. For a database default catalog this is the database's `default_connection_id`, so it is the value that addresses the table through the connection-scoped endpoints — not the database id the request may have used. | +**constant_per_key** | **Vec** | The columns now declared constant per key. Empty means no declaration, i.e. the unrestricted search. | +**schema** | **String** | Schema the table belongs to, as stored: lowercased, which may differ from the spelling in the request path. | +**table** | **String** | Table the declaration was written to, as stored: lowercased, which may differ from the spelling in the request path. | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/docs/TableInfo.md b/docs/TableInfo.md index b2082b1..5f80878 100644 --- a/docs/TableInfo.md +++ b/docs/TableInfo.md @@ -6,6 +6,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **columns** | Option<[**Vec**](ColumnInfo.md)> | | [optional] **connection** | **String** | | +**constant_per_key** | **Vec** | Columns the table declares constant for a given key: for every row, any other row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation narrow its search for prior versions to the values the upload carries. Empty when none is declared, which is the unrestricted search. Unlike `partition_by` and `sorted_by` this is NOT fixed at creation — it changes only which files a mutation opens, never how rows are written — so read it here rather than assuming a declaration took effect. | **last_sync** | Option<**String**> | | [optional] **partition_by** | [**Vec**](TablePartitionKey.md) | The table's partition keys, in the order they were declared when the table was created. Empty when the table is not partitioned. A table's storage layout is fixed when the table is created and cannot be changed afterwards, so this is how to confirm a table really was created with the layout that was asked for. The field is always present: an empty array means \"no partitioning declared\", which is not the same as a response that omits the field entirely. Reported for tables in a Hotdata instant database, which are the only ones whose layout is declared here. A table discovered from an external connection always reports an empty array — its layout belongs to the upstream system, so an empty array there means \"not known from here\", not \"confirmed unpartitioned\". | **schema** | **String** | | diff --git a/docs/UpdateManagedTableRequest.md b/docs/UpdateManagedTableRequest.md new file mode 100644 index 0000000..4f5f704 --- /dev/null +++ b/docs/UpdateManagedTableRequest.md @@ -0,0 +1,11 @@ +# UpdateManagedTableRequest + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**constant_per_key** | Option<**Vec**> | Columns whose value is the same for every row sharing this table's key. Send `[]` to revoke the declaration, which restores the unrestricted search on the next load — this is the kill switch if a declaration turns out to be false. **Correctness-affecting, not a hint.** If the assertion is false, a keyed mutation supersedes one version of a key and appends beside another, silently duplicating it. | [optional] + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/docs/UploadsApi.md b/docs/UploadsApi.md index 471be7e..d4f07fb 100644 --- a/docs/UploadsApi.md +++ b/docs/UploadsApi.md @@ -16,7 +16,7 @@ Method | HTTP request | Description > models::UploadSessionResponse create_upload_session_handler(create_upload_request) Create upload session -Create an upload session for a file you will upload directly to the URL the response carries, without sending it through this API. The response is one of three shapes. For a small file (`mode: single`) it contains a short-lived `url` to `PUT` the whole file to. For a large file with a known size (`mode: multipart`) it contains `part_urls` and `part_size`: split the file into `part_size`-byte chunks (the last is the remainder) and `PUT` chunk *i* (1-based) to `part_urls[i - 1]`, keeping each response's `ETag`. Slice by `part_size`, not by an even division across the number of part URLs (which can make a non-final part too small). For a file whose size you do not know up front, omit `declared_size_bytes`: the response is `mode: multipart` with a `part_size` but NO `part_urls`. As you stream, call `POST /v1/uploads/{upload_id}/parts` with a batch of `part_numbers` to mint per-part `PUT` URLs, `PUT` each part and keep its `ETag`, then finalize as for any multipart upload. In all cases the response also includes a one-time `finalize_token`. After uploading, call the finalize endpoint with the token (and, for multipart, the `{part_number, e_tag}` list) to make the upload usable as managed-table contents. The returned upload ID can then be passed to the managed-table load endpoint. You may hint a preferred part size with `part_size`; the service clamps it to the allowed range and ignores it for single-`PUT` uploads. A `501` with error code `PRESIGN_UNSUPPORTED` means this deployment cannot issue upload URLs; send the data inline on the load endpoint instead. +Create an upload session for a file you will upload directly to the URL the response carries, without sending it through this API. The response is one of three shapes. For a small file (`mode: single`) it contains a short-lived `url` to `PUT` the whole file to. For a large file with a known size (`mode: multipart`) it contains `part_urls` and `part_size`: split the file into `part_size`-byte chunks (the last is the remainder) and `PUT` chunk *i* (1-based) to `part_urls[i - 1]`, keeping each response's `ETag`. Slice by `part_size`, not by an even division across the number of part URLs (which can make a non-final part too small). For a file whose size you do not know up front, omit `declared_size_bytes`: the response is `mode: multipart` with a `part_size` but NO `part_urls`. As you stream, call `POST /v1/uploads/{upload_id}/parts` with a batch of `part_numbers` to mint per-part `PUT` URLs, `PUT` each part and keep its `ETag`, then finalize as for any multipart upload. In all cases the response also includes a one-time `finalize_token`. After uploading, call the finalize endpoint with the token (and, for multipart, the `{part_number, e_tag}` list) to make the upload usable as managed-table contents. The returned upload ID can then be passed to the managed-table load endpoint. You may hint a preferred part size with `part_size`; the service clamps it to the allowed range and ignores it for single-`PUT` uploads. One upload may be at most 16 GiB by default, whatever its format. Declare `declared_size_bytes` and an oversized file is refused here, before you transfer any of it; an upload created without a declared size is refused at finalize instead. To load more than that into one table, split the data across several uploads and load each one with `mode: append`. A `501` with error code `PRESIGN_UNSUPPORTED` means this deployment cannot issue upload URLs; send the data inline on the load endpoint instead. ### Parameters @@ -46,7 +46,7 @@ Name | Type | Description | Required | Notes > models::BatchCreateUploadResponse create_upload_sessions_batch_handler(batch_create_upload_request) Create upload sessions in bulk -Create upload sessions for several files in one request. Each file is planned independently and the response returns one session per requested file, in the same order. Each session is finalized separately via the finalize endpoint, so you can upload and finalize files at your own pace. A `501` with error code `PRESIGN_UNSUPPORTED` means this deployment cannot issue upload URLs; send the data inline on the load endpoint instead. +Create upload sessions for several files in one request. Each file is planned independently and the response returns one session per requested file, in the same order. Each session is finalized separately via the finalize endpoint, so you can upload and finalize files at your own pace. The maximum upload size (16 GiB by default, the same for every file format) applies to each file, not to the request as a whole. The batch is all-or-nothing: if any one file is refused, no sessions are created and the ones already planned are discarded, so retry the whole request rather than the rejected file alone. A `501` with error code `PRESIGN_UNSUPPORTED` means this deployment cannot issue upload URLs; send the data inline on the load endpoint instead. ### Parameters @@ -76,7 +76,7 @@ Name | Type | Description | Required | Notes > models::FinalizeUploadResponse finalize_upload_handler(upload_id, x_upload_finalize_token, finalize_upload_request) Finalize upload -Confirm that a file has been uploaded and make it usable as managed-table contents. Supply the `finalize_token` returned when the session was created, in the `X-Upload-Finalize-Token` header. When you declared a size at create time, the uploaded file's size is validated against it and a mismatch is rejected. An upload created without a declared size is finalized from its uploaded parts; it must be non-empty and is rejected if it exceeds the server's maximum upload size. Finalize is exactly-once: a second finalize of the same upload is rejected. +Confirm that a file has been uploaded and make it usable as managed-table contents. Supply the `finalize_token` returned when the session was created, in the `X-Upload-Finalize-Token` header. When you declared a size at create time, the uploaded file's size is validated against it and a mismatch is rejected. An upload created without a declared size is finalized from its uploaded parts; it must be non-empty and is rejected if it exceeds the maximum upload size (16 GiB by default, the same for every file format). Finalize is exactly-once: a second finalize of the same upload is rejected. ### Parameters diff --git a/src/apis/connections_api.rs b/src/apis/connections_api.rs index 73edcfd..9e8d1dd 100644 --- a/src/apis/connections_api.rs +++ b/src/apis/connections_api.rs @@ -94,6 +94,16 @@ pub enum LoadManagedTableError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`set_managed_table_constant_per_key`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SetManagedTableConstantPerKeyError { + Status400(models::ApiErrorResponse), + Status404(models::ApiErrorResponse), + Status409(models::ApiErrorResponse), + UnknownValue(serde_json::Value), +} + /// Declare a new schema (and optionally its tables) on an existing managed catalog after creation. The schema is added to the connection's declaration; declared tables can then be populated via the managed-table load endpoint. Only valid against connections whose source type is `managed`. Identifiers are normalized to lowercase. pub async fn add_managed_schema( configuration: &configuration::Configuration, @@ -694,3 +704,79 @@ pub async fn load_managed_table( })) } } + +/// Replace the columns a table declares constant for a given key: for every row, any other row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation (`delete`, `update`, `upsert`) narrow its search for prior versions to the values the upload carries, which prunes far harder than the key alone when the key's own file statistics do not discriminate. Unlike `partition_by` and `sorted_by`, this is NOT fixed when the table is created. It changes only which files a mutation opens, never how rows are written, so nothing stored becomes wrong when it changes and a populated table can adopt it with no rewrite. It takes effect on the next load. Send an empty array to revoke it, restoring the unrestricted search — this is the way to undo a declaration that turns out to be false. **This is correctness-affecting, not a hint.** If the assertion is false, a keyed mutation supersedes one version of a key and appends beside another, silently duplicating it, and the pruning conceals its own evidence because the file holding the missed row is never opened. Declare it only where the invariant is established. +pub async fn set_managed_table_constant_per_key( + configuration: &configuration::Configuration, + connection_id: &str, + schema: &str, + table: &str, + update_managed_table_request: models::UpdateManagedTableRequest, +) -> Result> { + // add a prefix to parameters to efficiently prevent name collisions + let p_path_connection_id = connection_id; + let p_path_schema = schema; + let p_path_table = table; + let p_body_update_managed_table_request = update_managed_table_request; + + let uri_str = format!( + "{}/v1/connections/{connection_id}/schemas/{schema}/tables/{table}/constant-per-key", + configuration.base_path, + connection_id = crate::apis::urlencode(p_path_connection_id), + schema = crate::apis::urlencode(p_path_schema), + table = crate::apis::urlencode(p_path_table) + ); + let mut req_builder = configuration.client.request(reqwest::Method::PUT, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + if let Some(apikey) = configuration.api_keys.get("X-Workspace-Id") { + let key = apikey.key.clone(); + let value = match apikey.prefix { + Some(ref prefix) => format!("{} {}", prefix, key), + None => key, + }; + req_builder = req_builder.header("X-Workspace-Id", value); + }; + if let Some(token) = configuration.resolve_bearer_token().await { + req_builder = req_builder.bearer_auth(token); + }; + req_builder = req_builder.json(&p_body_update_managed_table_request); + + let req = req_builder.build()?; + crate::http_log::log_request(&req); + // Route through the shared retry helper so HTTP 429 (OVERLOADED admission + // shedding) is retried per `configuration.retry` on every generated op, not + // just the hand-written query path. See crate::http::execute_retrying. + let resp = crate::http::execute_retrying(configuration, req).await?; + + let status = resp.status(); + crate::http_log::log_response_status(status); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + crate::http_log::log_response_body(&content); + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ManagedTableConstantPerKeyResponse`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::ManagedTableConstantPerKeyResponse`")))), + } + } else { + let content = resp.text().await?; + crate::http_log::log_response_body(&content); + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} diff --git a/src/apis/databases_api.rs b/src/apis/databases_api.rs index 51cc9f7..75c8d7b 100644 --- a/src/apis/databases_api.rs +++ b/src/apis/databases_api.rs @@ -74,6 +74,7 @@ pub enum CreateDatabaseError { #[serde(untagged)] pub enum DeleteDatabaseError { Status404(models::ApiErrorResponse), + Status409(models::ApiErrorResponse), UnknownValue(serde_json::Value), } @@ -101,6 +102,7 @@ pub enum DetachDatabaseCatalogError { pub enum ForkDatabaseError { Status400(models::ApiErrorResponse), Status404(models::ApiErrorResponse), + Status409(models::ApiErrorResponse), UnknownValue(serde_json::Value), } @@ -156,6 +158,16 @@ pub enum LookupDatabaseByNameError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`set_database_table_constant_per_key`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SetDatabaseTableConstantPerKeyError { + Status400(models::ApiErrorResponse), + Status404(models::ApiErrorResponse), + Status409(models::ApiErrorResponse), + UnknownValue(serde_json::Value), +} + /// Declare a new schema (and optionally its tables) on the database's auto-created default catalog after creation. The schema becomes reachable inside the database scope (e.g. `default..` and `information_schema.schemata`) without the caller naming the database's default connection. Identifiers are normalized to lowercase. pub async fn add_database_schema( configuration: &configuration::Configuration, @@ -301,7 +313,7 @@ pub async fn add_database_table( } } -/// Attach an existing connection (catalog) to a database with an optional alias. Inside the database the catalog is reachable as the alias (when set) or its original name. +/// Attach a catalog to a database so its tables are queryable alongside the database's own. Pass another database's `default_connection_id` as `connection_id` to read across the two in one query. Inside the database the catalog answers to `alias` when set, otherwise to the name it already answers to in its own scope. That name may not be `default`, a reserved name, or this database's own default catalog name — so attaching a database that kept the stock `default` catalog needs an `alias`. Attaching is read-only and copies nothing: loads still target the database's own default catalog, and detaching withdraws visibility rather than deleting data. A database's own default catalog is always attached and cannot be attached again. Attaching is not transitive — a database sees the catalog it attached, not that catalog's own attachments. pub async fn attach_database_catalog( configuration: &configuration::Configuration, database_id: &str, @@ -425,7 +437,7 @@ pub async fn bulk_create_databases( } } -/// Return the total number of databases in the workspace. This is the whole-workspace total, not a page size: the `count` field on the listing reports how many rows that one page returned, so totalling a workspace from `GET /v1/databases` means walking every page. Pass `search` to count only databases whose name contains that text (case-insensitive), or `batch` with the `batch_id` returned by a bulk-creation call to count only that batch's databases. The filters mean exactly what they mean on the listing, so a count and a listing given the same filters describe the same set. +/// Return the total number of databases in the workspace. This is the whole-workspace total, not a page size: the `count` field on the listing reports how many rows that one page returned, so totalling a workspace from `GET /v1/databases` means walking every page. Pass `search` to count only databases whose name contains that text, ignoring the case of unaccented Latin letters and digits, or `batch` with the `batch_id` returned by a bulk-creation call to count only that batch's databases. The filters mean exactly what they mean on the listing, so a count and a listing given the same filters describe the same set. pub async fn count_databases( configuration: &configuration::Configuration, search: Option<&str>, @@ -560,7 +572,7 @@ pub async fn create_database( } } -/// Delete a database and its auto-created default catalog. Attached catalogs are detached (their underlying connections are not deleted). +/// Delete a database and its auto-created default catalog. Catalogs attached to it are detached (the catalogs themselves are not deleted). Refused while another database attaches this one's catalog — detach it there first — unless this database is past its `expires_at`, in which case it can be deleted regardless and the attaching database loses the catalog. A database that attaches one should watch that date. pub async fn delete_database( configuration: &configuration::Configuration, database_id: &str, @@ -742,7 +754,7 @@ pub async fn detach_database_catalog( } } -/// Create a new database that is an independent fork of an existing one. The fork has its own default catalog and contains the same schemas, tables, and data as the source; the source is left unchanged. External catalogs attached to the source are re-attached to the fork. Optional `name` sets the fork's display label; when omitted, the fork takes the source's label followed by a short suffix derived from the fork's own ID, so the two stay distinguishable. Optional `expires_at` sets when the fork expires — accepts an RFC 3339 timestamp or a relative duration suffixed with `h` (hours), `m` (minutes), or `d` (days), e.g. `24h`, `90m`, `7d`. When omitted, a still-future expiry on the source is carried over; otherwise the fork never expires. Any indexes on the source's tables are not carried over. +/// Create a new database that is an independent fork of an existing one. The fork has its own default catalog and contains the same schemas, tables, and data as the source; the source is left unchanged. External catalogs attached to the source are re-attached to the fork. Optional `name` sets the fork's display label; when omitted, the fork takes the source's label followed by a short suffix derived from the fork's own ID, so the two stay distinguishable. Optional `expires_at` sets when the fork expires — accepts an RFC 3339 timestamp or a relative duration suffixed with `h` (hours), `m` (minutes), or `d` (days), e.g. `24h`, `90m`, `7d`. When omitted, a still-future expiry on the source is carried over; otherwise the fork never expires. Any indexes on the source's tables are not carried over. A fork adds no stored bytes at first, because it starts out sharing the source's storage. Routine maintenance can later rewrite a shared table into the fork's own storage, and the fork is billed for that copy from then on. Whether and when that happens depends on the table, so a fork that is only read can keep sharing indefinitely. pub async fn fork_database( configuration: &configuration::Configuration, database_id: &str, @@ -1016,7 +1028,7 @@ pub async fn get_database_lineage( } } -/// List databases in the workspace, newest first, one page at a time. When no `limit` is given a default page size is applied, so a single call returns at most one page rather than every database. If the response's `has_more` is true, pass its `next_cursor` value back as the `cursor` query parameter to fetch the next page. Pass `search` to return only databases whose name *contains* that text (case-insensitive); to fetch the single database whose name matches exactly, use `GET /v1/databases/by-name` instead. Pass `batch` with the `batch_id` returned by a bulk-creation call to list only that batch's databases. +/// List databases in the workspace, newest first, one page at a time. When no `limit` is given a default page size is applied, so a single call returns at most one page rather than every database. If the response's `has_more` is true, pass its `next_cursor` value back as the `cursor` query parameter to fetch the next page. Pass `search` to return only databases whose name *contains* that text, ignoring the case of unaccented Latin letters and digits; to fetch the single database whose name matches exactly, use `GET /v1/databases/by-name` instead. Pass `batch` with the `batch_id` returned by a bulk-creation call to list only that batch's databases. pub async fn list_databases( configuration: &configuration::Configuration, limit: Option, @@ -1173,7 +1185,7 @@ pub async fn load_database_table( } } -/// Fetch a single database by its exact name. This is the counterpart to the listing's `search` filter, which matches any database whose name merely contains the text. Matching ignores case for names made of unaccented Latin letters and digits; that much is guaranteed. For names containing other characters — accented letters, or any non-Latin script — whether case is ignored depends on the deployment, so rely on neither: look those up with the capitalisation they were created with. Returns 404 when no database has that name. A name shared by more than one database returns 409 rather than picking one of them; address those by id. +/// Fetch a single database by its exact name. This is the counterpart to the listing's `search` filter, which matches any database whose name merely contains the text. Matching ignores case for unaccented Latin letters and digits, and only for those. Every other character has to match exactly, so a name containing an accented letter or a non-Latin script must be looked up with the capitalisation it was created with. Returns 404 when no database has that name. A name shared by more than one database returns 409 rather than picking one of them; address those by id. pub async fn lookup_database_by_name( configuration: &configuration::Configuration, name: &str, @@ -1235,3 +1247,80 @@ pub async fn lookup_database_by_name( })) } } + +/// Replace the columns a table declares constant for a given key: for every row, any other row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation (`delete`, `update`, `upsert`) narrow its search for prior versions to the values the upload carries. Unlike `partition_by` and `sorted_by`, this is NOT fixed when the table is created — it changes only which files a mutation opens, never how rows are written — so a populated table can adopt it with no rewrite, taking effect on the next load. Send an empty array to revoke it. **Correctness-affecting, not a hint.** If the assertion is false, a keyed mutation supersedes one version of a key and appends beside another, silently duplicating it. Declare it only where the invariant is established. +pub async fn set_database_table_constant_per_key( + configuration: &configuration::Configuration, + database_id: &str, + schema: &str, + table: &str, + update_managed_table_request: models::UpdateManagedTableRequest, +) -> Result> +{ + // add a prefix to parameters to efficiently prevent name collisions + let p_path_database_id = database_id; + let p_path_schema = schema; + let p_path_table = table; + let p_body_update_managed_table_request = update_managed_table_request; + + let uri_str = format!( + "{}/v1/databases/{database_id}/schemas/{schema}/tables/{table}/constant-per-key", + configuration.base_path, + database_id = crate::apis::urlencode(p_path_database_id), + schema = crate::apis::urlencode(p_path_schema), + table = crate::apis::urlencode(p_path_table) + ); + let mut req_builder = configuration.client.request(reqwest::Method::PUT, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + if let Some(apikey) = configuration.api_keys.get("X-Workspace-Id") { + let key = apikey.key.clone(); + let value = match apikey.prefix { + Some(ref prefix) => format!("{} {}", prefix, key), + None => key, + }; + req_builder = req_builder.header("X-Workspace-Id", value); + }; + if let Some(token) = configuration.resolve_bearer_token().await { + req_builder = req_builder.bearer_auth(token); + }; + req_builder = req_builder.json(&p_body_update_managed_table_request); + + let req = req_builder.build()?; + crate::http_log::log_request(&req); + // Route through the shared retry helper so HTTP 429 (OVERLOADED admission + // shedding) is retried per `configuration.retry` on every generated op, not + // just the hand-written query path. See crate::http::execute_retrying. + let resp = crate::http::execute_retrying(configuration, req).await?; + + let status = resp.status(); + crate::http_log::log_response_status(status); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + crate::http_log::log_response_body(&content); + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ManagedTableConstantPerKeyResponse`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::ManagedTableConstantPerKeyResponse`")))), + } + } else { + let content = resp.text().await?; + crate::http_log::log_response_body(&content); + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} diff --git a/src/apis/uploads_api.rs b/src/apis/uploads_api.rs index 529fec6..bac7ce0 100644 --- a/src/apis/uploads_api.rs +++ b/src/apis/uploads_api.rs @@ -50,7 +50,7 @@ pub enum MintUploadPartsHandlerError { UnknownValue(serde_json::Value), } -/// Create an upload session for a file you will upload directly to the URL the response carries, without sending it through this API. The response is one of three shapes. For a small file (`mode: single`) it contains a short-lived `url` to `PUT` the whole file to. For a large file with a known size (`mode: multipart`) it contains `part_urls` and `part_size`: split the file into `part_size`-byte chunks (the last is the remainder) and `PUT` chunk *i* (1-based) to `part_urls[i - 1]`, keeping each response's `ETag`. Slice by `part_size`, not by an even division across the number of part URLs (which can make a non-final part too small). For a file whose size you do not know up front, omit `declared_size_bytes`: the response is `mode: multipart` with a `part_size` but NO `part_urls`. As you stream, call `POST /v1/uploads/{upload_id}/parts` with a batch of `part_numbers` to mint per-part `PUT` URLs, `PUT` each part and keep its `ETag`, then finalize as for any multipart upload. In all cases the response also includes a one-time `finalize_token`. After uploading, call the finalize endpoint with the token (and, for multipart, the `{part_number, e_tag}` list) to make the upload usable as managed-table contents. The returned upload ID can then be passed to the managed-table load endpoint. You may hint a preferred part size with `part_size`; the service clamps it to the allowed range and ignores it for single-`PUT` uploads. A `501` with error code `PRESIGN_UNSUPPORTED` means this deployment cannot issue upload URLs; send the data inline on the load endpoint instead. +/// Create an upload session for a file you will upload directly to the URL the response carries, without sending it through this API. The response is one of three shapes. For a small file (`mode: single`) it contains a short-lived `url` to `PUT` the whole file to. For a large file with a known size (`mode: multipart`) it contains `part_urls` and `part_size`: split the file into `part_size`-byte chunks (the last is the remainder) and `PUT` chunk *i* (1-based) to `part_urls[i - 1]`, keeping each response's `ETag`. Slice by `part_size`, not by an even division across the number of part URLs (which can make a non-final part too small). For a file whose size you do not know up front, omit `declared_size_bytes`: the response is `mode: multipart` with a `part_size` but NO `part_urls`. As you stream, call `POST /v1/uploads/{upload_id}/parts` with a batch of `part_numbers` to mint per-part `PUT` URLs, `PUT` each part and keep its `ETag`, then finalize as for any multipart upload. In all cases the response also includes a one-time `finalize_token`. After uploading, call the finalize endpoint with the token (and, for multipart, the `{part_number, e_tag}` list) to make the upload usable as managed-table contents. The returned upload ID can then be passed to the managed-table load endpoint. You may hint a preferred part size with `part_size`; the service clamps it to the allowed range and ignores it for single-`PUT` uploads. One upload may be at most 16 GiB by default, whatever its format. Declare `declared_size_bytes` and an oversized file is refused here, before you transfer any of it; an upload created without a declared size is refused at finalize instead. To load more than that into one table, split the data across several uploads and load each one with `mode: append`. A `501` with error code `PRESIGN_UNSUPPORTED` means this deployment cannot issue upload URLs; send the data inline on the load endpoint instead. pub async fn create_upload_session_handler( configuration: &configuration::Configuration, create_upload_request: models::CreateUploadRequest, @@ -115,7 +115,7 @@ pub async fn create_upload_session_handler( } } -/// Create upload sessions for several files in one request. Each file is planned independently and the response returns one session per requested file, in the same order. Each session is finalized separately via the finalize endpoint, so you can upload and finalize files at your own pace. A `501` with error code `PRESIGN_UNSUPPORTED` means this deployment cannot issue upload URLs; send the data inline on the load endpoint instead. +/// Create upload sessions for several files in one request. Each file is planned independently and the response returns one session per requested file, in the same order. Each session is finalized separately via the finalize endpoint, so you can upload and finalize files at your own pace. The maximum upload size (16 GiB by default, the same for every file format) applies to each file, not to the request as a whole. The batch is all-or-nothing: if any one file is refused, no sessions are created and the ones already planned are discarded, so retry the whole request rather than the rejected file alone. A `501` with error code `PRESIGN_UNSUPPORTED` means this deployment cannot issue upload URLs; send the data inline on the load endpoint instead. pub async fn create_upload_sessions_batch_handler( configuration: &configuration::Configuration, batch_create_upload_request: models::BatchCreateUploadRequest, @@ -181,7 +181,7 @@ pub async fn create_upload_sessions_batch_handler( } } -/// Confirm that a file has been uploaded and make it usable as managed-table contents. Supply the `finalize_token` returned when the session was created, in the `X-Upload-Finalize-Token` header. When you declared a size at create time, the uploaded file's size is validated against it and a mismatch is rejected. An upload created without a declared size is finalized from its uploaded parts; it must be non-empty and is rejected if it exceeds the server's maximum upload size. Finalize is exactly-once: a second finalize of the same upload is rejected. +/// Confirm that a file has been uploaded and make it usable as managed-table contents. Supply the `finalize_token` returned when the session was created, in the `X-Upload-Finalize-Token` header. When you declared a size at create time, the uploaded file's size is validated against it and a mismatch is rejected. An upload created without a declared size is finalized from its uploaded parts; it must be non-empty and is rejected if it exceeds the maximum upload size (16 GiB by default, the same for every file format). Finalize is exactly-once: a second finalize of the same upload is rejected. pub async fn finalize_upload_handler( configuration: &configuration::Configuration, upload_id: &str, diff --git a/src/models/add_managed_table_decl.rs b/src/models/add_managed_table_decl.rs index 62b1f22..6dfd541 100644 --- a/src/models/add_managed_table_decl.rs +++ b/src/models/add_managed_table_decl.rs @@ -14,12 +14,12 @@ use serde::{Deserialize, Serialize}; /// AddManagedTableDecl : One table declaration inside an add-schema request body. #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct AddManagedTableDecl { + /// Columns whose value is determined by this table's `key`: for every uploaded row, every stored row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation (`delete`, `update`, `upsert`) restrict its search for prior versions to the values the upload carries, which prunes far harder than the key alone when the key's own file statistics are weak. Omit (the default) for the unrestricted search. **Correctness-affecting, not a hint.** If the assertion is false, a mutation supersedes one version of a key and appends beside another, silently duplicating it. Declare it only where the invariant is established. + #[serde(rename = "constant_per_key", skip_serializing_if = "Option::is_none")] + pub constant_per_key: Option>, /// Columns that uniquely identify a row, enabling the key-based load modes (`delete`, `update`, `upsert`) on this table: those loads match rows by these columns' values. Omit (the default) to declare no key; the table can still be loaded with `replace` and `append`, but key-based modes are then rejected. #[serde(rename = "key", skip_serializing_if = "Option::is_none")] pub key: Option>, - /// Columns whose value is determined by this table's `key`: for every uploaded row, every stored row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation (`delete`, `update`, `upsert`) restrict its search for prior versions to the values the upload carries, which prunes far harder than the key alone when the key's own file statistics are weak. Omit (the default) for the unrestricted search. **Correctness-affecting, not a hint.** If the assertion is false, a mutation supersedes one version of a key and appends beside another, silently duplicating it. Declare it only where the invariant is established. - #[serde(rename = "key_determines", skip_serializing_if = "Option::is_none")] - pub key_determines: Option>, #[serde(rename = "name")] pub name: String, /// Partition keys for this table, applied in order. Omit for no partitioning. Declared when the table is created and fixed thereafter. @@ -34,8 +34,8 @@ impl AddManagedTableDecl { /// One table declaration inside an add-schema request body. pub fn new(name: String) -> AddManagedTableDecl { AddManagedTableDecl { + constant_per_key: None, key: None, - key_determines: None, name, partition_by: None, sorted_by: None, diff --git a/src/models/add_managed_table_request.rs b/src/models/add_managed_table_request.rs index 966e8c8..7cb497c 100644 --- a/src/models/add_managed_table_request.rs +++ b/src/models/add_managed_table_request.rs @@ -14,12 +14,12 @@ use serde::{Deserialize, Serialize}; /// AddManagedTableRequest : Request body for adding a table to an existing schema: `POST /v1/connections/{id}/schemas/{schema}/tables` and `POST /v1/databases/{id}/schemas/{schema}/tables`. #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct AddManagedTableRequest { + /// Columns whose value is determined by this table's `key`: for every uploaded row, every stored row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation (`delete`, `update`, `upsert`) restrict its search for prior versions to the values the upload carries, which prunes far harder than the key alone when the key's own file statistics are weak. Omit (the default) for the unrestricted search. **Correctness-affecting, not a hint.** If the assertion is false, a mutation supersedes one version of a key and appends beside another, silently duplicating it. Declare it only where the invariant is established. + #[serde(rename = "constant_per_key", skip_serializing_if = "Option::is_none")] + pub constant_per_key: Option>, /// Columns that uniquely identify a row, enabling the key-based load modes (`delete`, `update`, `upsert`) on this table: those loads match rows by these columns' values. Omit (the default) to declare no key; the table can still be loaded with `replace` and `append`, but key-based modes are then rejected. #[serde(rename = "key", skip_serializing_if = "Option::is_none")] pub key: Option>, - /// Columns whose value is determined by this table's `key`: for every uploaded row, every stored row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation (`delete`, `update`, `upsert`) restrict its search for prior versions to the values the upload carries, which prunes far harder than the key alone when the key's own file statistics are weak. Omit (the default) for the unrestricted search. **Correctness-affecting, not a hint.** If the assertion is false, a mutation supersedes one version of a key and appends beside another, silently duplicating it. Declare it only where the invariant is established. - #[serde(rename = "key_determines", skip_serializing_if = "Option::is_none")] - pub key_determines: Option>, #[serde(rename = "name")] pub name: String, /// Partition keys for this table, applied in order. Omit for no partitioning. Declared when the table is created and fixed thereafter. @@ -34,8 +34,8 @@ impl AddManagedTableRequest { /// Request body for adding a table to an existing schema: `POST /v1/connections/{id}/schemas/{schema}/tables` and `POST /v1/databases/{id}/schemas/{schema}/tables`. pub fn new(name: String) -> AddManagedTableRequest { AddManagedTableRequest { + constant_per_key: None, key: None, - key_determines: None, name, partition_by: None, sorted_by: None, diff --git a/src/models/create_database_response.rs b/src/models/create_database_response.rs index 8e34a68..35f472f 100644 --- a/src/models/create_database_response.rs +++ b/src/models/create_database_response.rs @@ -25,7 +25,7 @@ pub struct CreateDatabaseResponse { /// Name the database's default catalog answers to inside its query scope (`default` unless overridden at create time). #[serde(rename = "default_catalog")] pub default_catalog: String, - /// Internal id of the connection that backs this database's `default` catalog. Workspace-level connection endpoints (list, get, health, delete, cache purge) refuse to act on this id — it is exposed only for the managed-tables load endpoint (`POST /v1/connections/{id}/schemas/{s}/tables/{t}/loads`) so callers can load data into tables declared at database-create time. Addressing it directly in SQL is not the recommended path — use `default` inside an `X-Database-Id` scope instead. + /// Id of the connection that backs this database's `default` catalog. Two uses: pass it as `connection_id` to `POST /v1/databases/{other}/catalogs` to attach this database's catalog into another database, and as the connection in the managed-tables load endpoint (`POST /v1/connections/{id}/schemas/{s}/tables/{t}/loads`) to load tables declared at create time. Other connection endpoints (list, get, health, delete, cache purge) refuse to act on it. In SQL, address the catalog as `default` inside an `X-Database-Id` scope, not by this id. #[serde(rename = "default_connection_id")] pub default_connection_id: String, /// Schema that unqualified table names resolve to inside this database's query scope. `main` unless the database declares a single schema or a `default_schema` was set at create time. diff --git a/src/models/create_index_request.rs b/src/models/create_index_request.rs index 471316b..cb1ff0d 100644 --- a/src/models/create_index_request.rs +++ b/src/models/create_index_request.rs @@ -73,6 +73,9 @@ pub struct CreateIndexRequest { skip_serializing_if = "Option::is_none" )] pub output_column: Option>, + /// How precisely a vector index stores each number of a vector. Lower precision shrinks the index so a larger table can be indexed within the same memory, and lets searches run on a smaller instance. Omit this field to store vectors at the same precision as the column, which is the default. The quality figures below come from one benchmark — 1536-dimension text embeddings, cosine distance, default search settings — and are a guide, not a guarantee. Other models, dimensions, distance metrics and data distributions behave differently, so measure on your own data before moving a production index to a lower precision. `float32` — on a `float64` column this halves the index. Widely used embedding models emit 32-bit values, so for those nothing is lost; vectors that genuinely carry more than 32 bits of precision will lose some. `float16` — half the memory of `float32`. In that benchmark its results matched `float32` to within 0.1 percentage points. `float8` — a quarter of the memory of `float32`. In that benchmark it scored about 4 percentage points below `float32`, and raising the search effort did not close the gap, so treat the reduction as permanent for a given index. `float64` — accepted only for a column that already holds double-precision values; it cannot add precision the stored data does not have. Changing this means dropping the index and creating it again. It affects only the index: the table's own values are never altered, and text columns indexed with a generated embedding are not re-embedded. + #[serde(rename = "vector_precision", skip_serializing_if = "Option::is_none")] + pub vector_precision: Option, } impl CreateIndexRequest { @@ -89,6 +92,7 @@ impl CreateIndexRequest { index_type: None, metric: None, output_column: None, + vector_precision: None, } } } @@ -108,3 +112,21 @@ impl Default for IndexType { Self::Sorted } } +/// How precisely a vector index stores each number of a vector. Lower precision shrinks the index so a larger table can be indexed within the same memory, and lets searches run on a smaller instance. Omit this field to store vectors at the same precision as the column, which is the default. The quality figures below come from one benchmark — 1536-dimension text embeddings, cosine distance, default search settings — and are a guide, not a guarantee. Other models, dimensions, distance metrics and data distributions behave differently, so measure on your own data before moving a production index to a lower precision. `float32` — on a `float64` column this halves the index. Widely used embedding models emit 32-bit values, so for those nothing is lost; vectors that genuinely carry more than 32 bits of precision will lose some. `float16` — half the memory of `float32`. In that benchmark its results matched `float32` to within 0.1 percentage points. `float8` — a quarter of the memory of `float32`. In that benchmark it scored about 4 percentage points below `float32`, and raising the search effort did not close the gap, so treat the reduction as permanent for a given index. `float64` — accepted only for a column that already holds double-precision values; it cannot add precision the stored data does not have. Changing this means dropping the index and creating it again. It affects only the index: the table's own values are never altered, and text columns indexed with a generated embedding are not re-embedded. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum VectorPrecision { + #[serde(rename = "float64")] + Float64, + #[serde(rename = "float32")] + Float32, + #[serde(rename = "float16")] + Float16, + #[serde(rename = "float8")] + Float8, +} + +impl Default for VectorPrecision { + fn default() -> VectorPrecision { + Self::Float64 + } +} diff --git a/src/models/create_upload_request.rs b/src/models/create_upload_request.rs index 86aa600..7b432ba 100644 --- a/src/models/create_upload_request.rs +++ b/src/models/create_upload_request.rs @@ -11,7 +11,7 @@ use crate::models; use serde::{Deserialize, Serialize}; -/// CreateUploadRequest : Request body for `POST /v1/uploads` and for each entry of `POST /v1/uploads/batch`. Describes a single file you intend to upload. The response carries a short-lived URL to `PUT` the bytes to, so the file never passes through the API itself. The declared size is validated against the bytes you actually upload when you finalize. +/// CreateUploadRequest : Request body for `POST /v1/uploads` and for each entry of `POST /v1/uploads/batch`. Describes a single file you intend to upload. The response carries a short-lived URL to `PUT` the bytes to, so the file never passes through the API itself. The declared size is validated against the bytes you actually upload when you finalize. One upload may be at most 16 GiB by default, whatever its format. To load more data than that into a single table, split it across several uploads and load each one with `mode: append`. #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct CreateUploadRequest { /// Integrity checksum algorithm you are volunteering for this file. Currently only `sha256` is accepted. Optional; pair with `checksum_value`. @@ -46,7 +46,7 @@ pub struct CreateUploadRequest { skip_serializing_if = "Option::is_none" )] pub content_type: Option>, - /// The exact size, in bytes, of the file you will upload. Optional. When provided, it is validated at create time against the maximum allowed size, and again at finalize against the bytes actually uploaded — a mismatch fails the finalize. Omit it to create a streaming (unknown-size) upload: the session is always multi-part and returns no part URLs up front; instead you mint part URLs on demand from `POST /v1/uploads/{upload_id}/parts` as you upload, and finalize validates only that the file is non-empty. + /// The exact size, in bytes, of the file you will upload. Optional. When provided, it is checked at create time against the maximum upload size (16 GiB by default, the same for every file format), so an oversized file is refused before you transfer any of it; it is checked again at finalize against the bytes actually uploaded — a mismatch fails the finalize. Omit it to create a streaming (unknown-size) upload: the session is always multi-part and returns no part URLs up front; instead you mint part URLs on demand from `POST /v1/uploads/{upload_id}/parts` as you upload, and finalize checks only that the file is non-empty and within the maximum upload size. #[serde( rename = "declared_size_bytes", default, @@ -73,7 +73,7 @@ pub struct CreateUploadRequest { } impl CreateUploadRequest { - /// Request body for `POST /v1/uploads` and for each entry of `POST /v1/uploads/batch`. Describes a single file you intend to upload. The response carries a short-lived URL to `PUT` the bytes to, so the file never passes through the API itself. The declared size is validated against the bytes you actually upload when you finalize. + /// Request body for `POST /v1/uploads` and for each entry of `POST /v1/uploads/batch`. Describes a single file you intend to upload. The response carries a short-lived URL to `PUT` the bytes to, so the file never passes through the API itself. The declared size is validated against the bytes you actually upload when you finalize. One upload may be at most 16 GiB by default, whatever its format. To load more data than that into a single table, split it across several uploads and load each one with `mode: append`. pub fn new() -> CreateUploadRequest { CreateUploadRequest { checksum_algo: None, diff --git a/src/models/database_default_table_decl.rs b/src/models/database_default_table_decl.rs index 5fc284f..0702156 100644 --- a/src/models/database_default_table_decl.rs +++ b/src/models/database_default_table_decl.rs @@ -14,12 +14,12 @@ use serde::{Deserialize, Serialize}; /// DatabaseDefaultTableDecl : One table declaration inside a default-catalog schema, supplied at database-create time. #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct DatabaseDefaultTableDecl { + /// Columns whose value is determined by this table's `key`: for every uploaded row, every stored row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation (`delete`, `update`, `upsert`) restrict its search for prior versions to the values the upload carries, which prunes far harder than the key alone when the key's own file statistics are weak. Omit (the default) for the unrestricted search. **Correctness-affecting, not a hint.** If the assertion is false, a mutation supersedes one version of a key and appends beside another, silently duplicating it. Declare it only where the invariant is established. + #[serde(rename = "constant_per_key", skip_serializing_if = "Option::is_none")] + pub constant_per_key: Option>, /// Columns that uniquely identify a row, enabling the key-based load modes (`delete`, `update`, `upsert`) on this table: those loads match rows by these columns' values. Omit (the default) to declare no key; the table can still be loaded with `replace` and `append`, but key-based modes are then rejected. #[serde(rename = "key", skip_serializing_if = "Option::is_none")] pub key: Option>, - /// Columns whose value is determined by this table's `key`: for every uploaded row, every stored row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation (`delete`, `update`, `upsert`) restrict its search for prior versions to the values the upload carries, which prunes far harder than the key alone when the key's own file statistics are weak. Omit (the default) for the unrestricted search. **Correctness-affecting, not a hint.** If the assertion is false, a mutation supersedes one version of a key and appends beside another, silently duplicating it. Declare it only where the invariant is established. - #[serde(rename = "key_determines", skip_serializing_if = "Option::is_none")] - pub key_determines: Option>, #[serde(rename = "name")] pub name: String, /// Partition keys for this table, applied in order. Omit for no partitioning. Declared when the table is created and fixed thereafter. @@ -34,8 +34,8 @@ impl DatabaseDefaultTableDecl { /// One table declaration inside a default-catalog schema, supplied at database-create time. pub fn new(name: String) -> DatabaseDefaultTableDecl { DatabaseDefaultTableDecl { + constant_per_key: None, key: None, - key_determines: None, name, partition_by: None, sorted_by: None, diff --git a/src/models/database_detail_response.rs b/src/models/database_detail_response.rs index aae8cad..2c240a5 100644 --- a/src/models/database_detail_response.rs +++ b/src/models/database_detail_response.rs @@ -27,6 +27,7 @@ pub struct DatabaseDetailResponse { /// Name the database's default catalog answers to inside its query scope (`default` unless overridden at create time). #[serde(rename = "default_catalog")] pub default_catalog: String, + /// Id of the connection backing this database's `default` catalog. Pass it as `connection_id` to `POST /v1/databases/{other}/catalogs` to attach this database's catalog into another database. In SQL, address the catalog as `default` inside an `X-Database-Id` scope, not by id. #[serde(rename = "default_connection_id")] pub default_connection_id: String, /// Schema that unqualified table names resolve to inside this database's query scope. `main` unless the database declares a single schema or a `default_schema` was set at create time. diff --git a/src/models/index_entry_response.rs b/src/models/index_entry_response.rs index 9dbced2..02b2159 100644 --- a/src/models/index_entry_response.rs +++ b/src/models/index_entry_response.rs @@ -32,6 +32,9 @@ pub struct IndexEntryResponse { pub status: models::IndexStatus, #[serde(rename = "updated_at")] pub updated_at: String, + /// How precisely this vector index stores each number of a vector, when it was created with an explicit precision. Absent means it stores at the same precision as the column, which is the default. Also absent for BM25 and sorted indexes. + #[serde(rename = "vector_precision", skip_serializing_if = "Option::is_none")] + pub vector_precision: Option, #[serde( rename = "connection_id", default, @@ -66,6 +69,7 @@ impl IndexEntryResponse { source_column: None, status, updated_at, + vector_precision: None, connection_id: None, schema_name, table_name, diff --git a/src/models/index_info_response.rs b/src/models/index_info_response.rs index 5150e8e..d5b4464 100644 --- a/src/models/index_info_response.rs +++ b/src/models/index_info_response.rs @@ -42,6 +42,14 @@ pub struct IndexInfoResponse { pub status: models::IndexStatus, #[serde(rename = "updated_at")] pub updated_at: String, + /// How precisely this vector index stores each number of a vector, when it was created with an explicit precision. Absent means it stores at the same precision as the column, which is the default. Also absent for BM25 and sorted indexes. + #[serde( + rename = "vector_precision", + default, + with = "::serde_with::rust::double_option", + skip_serializing_if = "Option::is_none" + )] + pub vector_precision: Option>, } impl IndexInfoResponse { @@ -63,6 +71,7 @@ impl IndexInfoResponse { source_column: None, status, updated_at, + vector_precision: None, } } } diff --git a/src/models/job_type.rs b/src/models/job_type.rs index 03abf1f..e703b0a 100644 --- a/src/models/job_type.rs +++ b/src/models/job_type.rs @@ -37,6 +37,8 @@ pub enum JobType { DucklakeCompaction, #[serde(rename = "ducklake_table_compaction")] DucklakeTableCompaction, + #[serde(rename = "table_constants_update")] + TableConstantsUpdate, } impl std::fmt::Display for JobType { @@ -53,6 +55,7 @@ impl std::fmt::Display for JobType { Self::ResultRetention => write!(f, "result_retention"), Self::DucklakeCompaction => write!(f, "ducklake_compaction"), Self::DucklakeTableCompaction => write!(f, "ducklake_table_compaction"), + Self::TableConstantsUpdate => write!(f, "table_constants_update"), } } } diff --git a/src/models/managed_table_constant_per_key_response.rs b/src/models/managed_table_constant_per_key_response.rs new file mode 100644 index 0000000..6fe4fec --- /dev/null +++ b/src/models/managed_table_constant_per_key_response.rs @@ -0,0 +1,46 @@ +/* + * Hotdata API + * + * Powerful data platform API for instant databases, queries, and analytics. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: developers@hotdata.dev + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +/// ManagedTableConstantPerKeyResponse : The declaration as it now stands after a `constant-per-key` write. Echoed back rather than returning `204` so a caller can confirm what actually took effect instead of assuming its request applied — the same reason `/v1/information_schema` reports the field. +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ManagedTableConstantPerKeyResponse { + /// Connection backing the catalog the table belongs to. For a database default catalog this is the database's `default_connection_id`, so it is the value that addresses the table through the connection-scoped endpoints — not the database id the request may have used. + #[serde(rename = "connection_id")] + pub connection_id: String, + /// The columns now declared constant per key. Empty means no declaration, i.e. the unrestricted search. + #[serde(rename = "constant_per_key")] + pub constant_per_key: Vec, + /// Schema the table belongs to, as stored: lowercased, which may differ from the spelling in the request path. + #[serde(rename = "schema")] + pub schema: String, + /// Table the declaration was written to, as stored: lowercased, which may differ from the spelling in the request path. + #[serde(rename = "table")] + pub table: String, +} + +impl ManagedTableConstantPerKeyResponse { + /// The declaration as it now stands after a `constant-per-key` write. Echoed back rather than returning `204` so a caller can confirm what actually took effect instead of assuming its request applied — the same reason `/v1/information_schema` reports the field. + pub fn new( + connection_id: String, + constant_per_key: Vec, + schema: String, + table: String, + ) -> ManagedTableConstantPerKeyResponse { + ManagedTableConstantPerKeyResponse { + connection_id, + constant_per_key, + schema, + table, + } + } +} diff --git a/src/models/mod.rs b/src/models/mod.rs index 84d6d17..471f416 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -166,6 +166,8 @@ pub mod load_managed_table_response; pub use self::load_managed_table_response::LoadManagedTableResponse; pub mod managed_schema_response; pub use self::managed_schema_response::ManagedSchemaResponse; +pub mod managed_table_constant_per_key_response; +pub use self::managed_table_constant_per_key_response::ManagedTableConstantPerKeyResponse; pub mod managed_table_response; pub use self::managed_table_response::ManagedTableResponse; pub mod mint_upload_parts_request; @@ -210,6 +212,8 @@ pub mod update_embedding_provider_request; pub use self::update_embedding_provider_request::UpdateEmbeddingProviderRequest; pub mod update_embedding_provider_response; pub use self::update_embedding_provider_response::UpdateEmbeddingProviderResponse; +pub mod update_managed_table_request; +pub use self::update_managed_table_request::UpdateManagedTableRequest; pub mod update_saved_query_request; pub use self::update_saved_query_request::UpdateSavedQueryRequest; pub mod upload_session_response; diff --git a/src/models/table_info.rs b/src/models/table_info.rs index b7217c1..ae8c07e 100644 --- a/src/models/table_info.rs +++ b/src/models/table_info.rs @@ -23,6 +23,9 @@ pub struct TableInfo { pub columns: Option>>, #[serde(rename = "connection")] pub connection: String, + /// Columns the table declares constant for a given key: for every row, any other row sharing its key holds the same value of these columns. Declaring this lets a keyed mutation narrow its search for prior versions to the values the upload carries. Empty when none is declared, which is the unrestricted search. Unlike `partition_by` and `sorted_by` this is NOT fixed at creation — it changes only which files a mutation opens, never how rows are written — so read it here rather than assuming a declaration took effect. + #[serde(rename = "constant_per_key")] + pub constant_per_key: Vec, #[serde( rename = "last_sync", default, @@ -48,6 +51,7 @@ impl TableInfo { /// Single table metadata pub fn new( connection: String, + constant_per_key: Vec, partition_by: Vec, schema: String, sorted_by: Vec, @@ -57,6 +61,7 @@ impl TableInfo { TableInfo { columns: None, connection, + constant_per_key, last_sync: None, partition_by, schema, diff --git a/src/models/update_managed_table_request.rs b/src/models/update_managed_table_request.rs new file mode 100644 index 0000000..59c397c --- /dev/null +++ b/src/models/update_managed_table_request.rs @@ -0,0 +1,29 @@ +/* + * Hotdata API + * + * Powerful data platform API for instant databases, queries, and analytics. + * + * The version of the OpenAPI document: 1.0.0 + * Contact: developers@hotdata.dev + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +/// UpdateManagedTableRequest : Request body for setting the columns a table declares constant per key: `PUT /v1/connections/{id}/schemas/{schema}/tables/{table}/constant-per-key` and `PUT /v1/databases/{id}/schemas/{schema}/tables/{table}/constant-per-key`. The body carries the complete new value: send an empty array to remove the declaration. Sending any other field is rejected — `key`, `partition_by` and `sorted_by` are fixed when the table is created. +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct UpdateManagedTableRequest { + /// Columns whose value is the same for every row sharing this table's key. Send `[]` to revoke the declaration, which restores the unrestricted search on the next load — this is the kill switch if a declaration turns out to be false. **Correctness-affecting, not a hint.** If the assertion is false, a keyed mutation supersedes one version of a key and appends beside another, silently duplicating it. + #[serde(rename = "constant_per_key", skip_serializing_if = "Option::is_none")] + pub constant_per_key: Option>, +} + +impl UpdateManagedTableRequest { + /// Request body for setting the columns a table declares constant per key: `PUT /v1/connections/{id}/schemas/{schema}/tables/{table}/constant-per-key` and `PUT /v1/databases/{id}/schemas/{schema}/tables/{table}/constant-per-key`. The body carries the complete new value: send an empty array to remove the declaration. Sending any other field is rejected — `key`, `partition_by` and `sorted_by` are fixed when the table is created. + pub fn new() -> UpdateManagedTableRequest { + UpdateManagedTableRequest { + constant_per_key: None, + } + } +}