hotdata 0.14.0

Powerful data platform API for datasets, queries, and analytics.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]


## [0.14.0] - 2026-08-20

### Changed

- feat(query): add dialect parameter to query request

## [0.13.0] - 2026-08-18

### Added

- The pluggable per-request bearer hook is back, for consumers that own their own
  credential lifecycle. `Configuration::token_provider` takes an
  `Option<Arc<dyn hotdata::auth::BearerTokenProvider>>`, and
  `Configuration::resolve_bearer_token` resolves the credential once per request
  — every generated op plus the hand-written `submit_query`, `upload_file`
  (create-session *and* finalize), and Arrow fetch. A host whose access token is
  short-lived (e.g. the CLI's PKCE browser-login session, which expires in
  minutes) can now refresh mid-command instead of 401ing on a long call — a
  multi-gigabyte upload whose finalize lands after the TTL, a slow query, a large
  parallel batch. The trait's error type is `BearerTokenError`
  (`Transport`/`Status`/`Malformed`, `#[non_exhaustive]`); a provider that returns
  an error is logged via the `log` facade and the request proceeds
  unauthenticated; that warning is the only trace of the cause, and it requires
  a `log` implementation installed in the host binary to be visible.

  "Per request" includes each attempt of a 429 retry chain: the retry helper
  re-resolves rather than replaying the `Authorization` header from the first
  attempt, which could otherwise outlive a short-lived token (the retry deadline
  defaults to 120s and an honored `Retry-After` is uncapped). Presigned storage
  `PUT`s are excluded by construction — they authorize via the signed URL and
  must never carry a bearer.

  **Additive and non-breaking.** `bearer_access_token` keeps working exactly as
  it did in 0.12.0 when no provider is installed, and `ClientBuilder::api_token`
  still installs the API token there — no caller needs to change anything.

  This does **not** reintroduce the API-token to JWT exchange removed in 0.12.0.
  `TokenManager`, the `/v1/auth/jwt` call, `ClientBuilder::client_id`,
  `PersistCallback`, and `HOTDATA_DISABLE_JWT_EXCHANGE` stay removed. A provider
  is a hook, not an exchange: the SDK never trades one credential for another.

## [0.12.0] - 2026-08-18

### Removed

- **Breaking:** the API-token to JWT key exchange is deprecated and removed.
  Clients now authenticate with the API token itself, sent verbatim as
  `Authorization: Bearer <token>`; the SDK no longer calls `/v1/auth/jwt`, mints
  or refreshes short-lived JWTs, or holds a refresh token. This drops the whole
  `hotdata::auth` module (`TokenManager`, `TokenManagerOptions`,
  `BearerTokenProvider`, `TokenExchangeError`, `PersistCallback`, `CLIENT_ID`),
  the `Configuration::token_provider` field, the
  `Configuration::resolve_bearer_token` method, `ClientBuilder::client_id`, and
  the `HOTDATA_DISABLE_JWT_EXCHANGE` opt-out. `ClientBuilder::api_token` now
  installs the token as `Configuration::bearer_access_token`; callers that only
  used the builder need no changes. Code that installed a custom
  `BearerTokenProvider` should set `bearer_access_token` directly instead.

### Changed

- feat(loads): add idempotency_key to load requests
- feat(loads): support inline csv data in table load requests

## [0.11.0] - 2026-08-12

### Removed

- **Breaking:** session scoping is gone from the API. `ClientBuilder::session_id`,
  the `client::SESSION_ID_HEADER` constant, and the `HOTDATA_SESSION_ID`
  environment variable are removed, and no request sends the `X-Session-Id`
  header any more. Drop the `.session_id(..)` builder call; nothing replaces it.

### Changed

- feat(databases): add bulk operations and count endpoint
- **Breaking:** `list_databases` and `Databases::list` take a `batch` parameter,
  narrowing the listing to one bulk-creation batch; existing callers must pass
  an extra `None`.
- `Databases::count` wraps `GET /v1/databases/count` and returns the number of
  databases in the workspace across every page. Note this is not the listing's
  `count` field, which reports how many rows a single page returned — totalling
  a workspace from the listing means walking every page, and pages cap at 100.
- chore: clarify sort and partition semantics in table schemas
- feat(tables): add partition_by and sorted_by configuration
- `QueryRunInfo::user_public_id` now reports the caller's stable account id (the
  access token's subject) instead of a fingerprint of the bearer token, which
  churned every few minutes as short-lived JWTs were reminted. Grouping a
  caller's query history by this field now holds across token refreshes. A
  request that carries no verified subject still records an opaque
  `user_`-prefixed fingerprint — stable for that credential, but not resolvable
  to an account. This is a server-side change; the SDK only carries the field.
- feat(databases): add search parameter to list endpoint

## [0.10.0] - 2026-07-23

### Changed

- **Breaking:** `list_databases` and `Databases::list` now take `limit` and
  `cursor` pagination parameters; existing callers must pass `None, None`.
- Pagination metadata on `ListDatabasesResponse` (`count`, `limit`, `has_more`)
  is now nullable so the client tolerates responses from a server that predates
  these fields (rolling deploy / version skew).

### Removed

- **Breaking:** the `POST /v1/files` and `GET /v1/files` endpoints are gone in
  favor of the presigned uploads flow. This drops the generated `upload_file` /
  `list_uploads` ops, the `UploadResponse` / `UploadInfo` / `ListUploadsResponse`
  models, and the ergonomic `Client::upload_stream`, `uploads().upload`, and
  `uploads().list` wrappers. Use `Client::upload_file` (presigned
  direct-to-storage) instead.

### Added

- `LoadManagedTableRequest` gains an optional `key` field naming the key columns
  for `delete` / `update` / `upsert` loads.

## [0.9.1] - 2026-07-15

### Added

- Ergonomic `Client::databases().fork()` wrapper for the `fork_database` endpoint.

## [0.9.0] - 2026-07-15

### Changed

- **Breaking:** databases now report the schema that unqualified table names
  resolve to, as a required `default_schema` field on `DatabaseSummary`,
  `DatabaseDetailResponse`, and `CreateDatabaseResponse`. Because the field is
  required, each type's `new()` gained a `default_schema` parameter:
  `DatabaseSummary::new(default_catalog, default_schema, id)`,
  `DatabaseDetailResponse::new(attachments, default_catalog,
  default_connection_id, default_schema, id)`, and
  `CreateDatabaseResponse::new(default_catalog, default_connection_id,
  default_schema, id)`. Callers that construct these types (or match on them
  exhaustively) must supply it; callers that only read them are unaffected.
- feat(databases): add fork endpoint
- chore(api): exclude datasets from public OpenAPI spec + docs cleanup

### Added

- `CreateDatabaseRequest` gains an optional `default_schema` field, naming the
  schema unqualified table names resolve to inside the new database's query
  scope. When omitted, a database declaring exactly one schema adopts that
  schema; otherwise unqualified names resolve to `main`.
- `AddManagedTableDecl`, `AddManagedTableRequest`, and
  `DatabaseDefaultTableDecl` gain an optional `key` field naming the columns that
  uniquely identify a row. Declaring a key enables the key-based load modes
  (`delete`, `update`, `upsert`) on that table, which match rows by those
  columns; a table declared without one accepts only `replace` and `append`.

## [0.8.1] - 2026-07-09

### Changed

- **Breaking:** `LoadManagedTableRequest` can now load from a persisted query
  result as well as an upload, so `upload_id` became optional and moved out of
  the constructor: `new(mode, upload_id)` is now `new(mode)`. Set `upload_id` or
  `result_id` on the returned value — exactly one is required. Note this shipped
  in a patch release, which Cargo treats as compatible with 0.8.0 for a `0.x`
  crate, so it could surface on `cargo update` rather than an explicit upgrade.
- feat(tables): support loading from query results
- feat(databases): expose created_at on list and detail endpoints

## [0.8.0] - 2026-07-07

### Changed

- feat: support async table loads and append mode
- **Breaking:** results and query runs are now scoped to a database via the
  required `X-Database-Id` header. The ergonomic wrappers gain a `database_id`
  argument to match: `Client::get_result`, `Client::list_results`,
  `Client::list_query_runs`, `Client::await_result`, `Client::get_result_arrow`,
  `Client::stream_result_arrow`, `Client::query_to_arrow`, and the
  `results()` / `query_runs()` resource handles. `Client::query`'s truncation
  auto-follow now forwards the query's database scope (the `X-Database-Id`
  header, or the request-body `database_id` when no header is set) to the
  follow-up result and query-run fetches.

## [0.7.0] - 2026-06-30

### Added

- Multipart uploads now survive transient per-part failures. A single part
  exhausting its inner transport retries no longer aborts the whole transfer:
  an outer round loop re-sweeps just the failed parts (at decaying concurrency,
  with backoff) while completed parts keep their ETags, so a flaky or slow link
  recovers instead of discarding the work already done. Each part PUT also gets
  a part-size-scaled total timeout (bounded by an operational ceiling), so a
  silently stalled connection fails fast rather than hanging the upload.

### Changed

- Streaming multipart uploads now mint one presigned part URL per part, on
  demand immediately before each PUT, instead of pre-minting in batches. This
  keeps each URL's age minimal so it can't expire mid-transfer on a slow link,
  at the cost of more `POST /v1/uploads/{id}/parts` requests for large uploads.

### Removed

- `uploads::MAX_MINT_BATCH` (`pub const`), obsoleted by per-part minting. No
  longer part of the public API.

## [0.6.0] - 2026-06-29

### Added

- Streaming uploads with just-in-time part minting for large files.
  `Client::upload_file` now opens a streaming multipart session for files past
  the multipart threshold and mints presigned part URLs on demand (via
  `POST /v1/uploads/{id}/parts`) immediately before each chunk uploads, instead
  of pre-minting every URL when the session opens. A part URL that expires
  mid-transfer (storage `403`) is transparently re-minted and the chunk retried,
  so large or slow uploads that outlive a presigned URL's ~30-minute TTL still
  complete within the session's 24-hour window rather than failing partway.
  Small files keep the single-`PUT` fast path. (Fixes #76.)
- Low-level `POST /v1/uploads/{id}/parts` part-minting endpoint with its
  `MintUploadPartsRequest` / `MintUploadPartsResponse` / `MintedUploadPartResponse`
  models, generated from the OpenAPI spec.

### Changed

- Token exchange (`POST /v1/auth/jwt`) now retries transient failures before
  giving up: a momentary `5xx` or a transport error (connection/read failure)
  is retried with bounded exponential backoff + jitter (3 attempts total), so a
  brief server-side blip no longer fails the caller outright. A `4xx`
  (bad/expired credential) is never retried, and the last status/body is
  preserved once the budget is exhausted. Applies to both the initial mint and
  the refresh path.

### Fixed

- The default `User-Agent` is now computed from the crate version at build time
  (`CARGO_PKG_VERSION`) instead of a hardcoded string, so it always reflects the
  published version.

## [0.5.0] - 2026-06-26

### Added

- Ergonomic presigned (direct-to-storage) file uploads: `Client::upload_file`
  (and `client.uploads().upload_file`) open an upload session, `PUT` the bytes
  straight to object storage — a single `PUT` for small files, bounded-
  concurrency multipart `PUT`s sliced by the server's `part_size` for large
  ones — then finalize, returning the `FinalizeUploadResponse`. Configurable via
  `UploadOptions` (content type/encoding, filename, part-size hint, and an
  `UploadProgress` callback). Never falls back to the legacy `POST /v1/files`
  proxy; storage `PUT`s carry no SDK auth/scope headers. Multipart concurrency
  is tunable via `UploadOptions::max_concurrency` (default 10), bounded by a
  256 MiB peak-memory budget derived from the server's actual part size; when no
  `part_size` is given, the SDK auto-scales the hint (8 MiB for normal files,
  larger only past ~72 GiB to keep the part count under S3's 10,000-part limit).
  Finalize is exactly-once (sent with retries disabled so an ambiguous failure
  can't be retried into a spurious "already finalized" error); part `PUT`s stay
  retryable. Storage `PUT`s use a dedicated header-bare reqwest client, so a host
  app's default headers on the SDK's main client never leak to object storage.
  The multipart session shape is validated (`part_urls` count must match the
  file's part count) and pathological sizes (`> i64::MAX`) are rejected rather
  than silently wrapped.
- Low-level file upload endpoints generated from the OpenAPI spec, including the
  presigned upload-session and finalize operations that `upload_file` builds on.

## [0.4.0] - 2026-06-19

### Removed

- **Breaking:** the datasets API and all related types, following their removal
  from the Hotdata OpenAPI spec. This drops the `datasets_api` module and the
  `client.datasets()` resource handle; every dataset model
  (`CreateDatasetRequest`/`CreateDatasetResponse`, `GetDatasetResponse`,
  `UpdateDatasetRequest`/`UpdateDatasetResponse`, `ListDatasetsResponse`,
  `ListDatasetVersionsResponse`, `RefreshDatasetResponse`, `DatasetSummary`,
  `DatasetVersionSummary`, `DatasetSource` and its `*OneOf*` variants,
  `InlineDatasetSource`, `SavedQueryDatasetSource`, `SqlQueryDatasetSource`,
  `UploadDatasetSource`, `UrlDatasetSource`, `InlineData`, `ColumnTypeSpec`);
  and the dataset-related `JobType` variants (`DatasetRefresh`,
  `CreateDatasetIndex`).

### Fixed

- Pre-response connection errors are now retried transparently on **any** method,
  including `POST`. A pooled keep-alive socket that an intermediary closed on its
  idle timeout surfaces, on the next reuse, as a connection reset before the
  request reaches the server; since the server did no work, the retry can't
  double-execute. This covers every generated op (via `execute_retrying`) and the
  hand-written `Client::query` / `Client::submit_query` paths, governed by the
  same `RetryPolicy` budget as 429. Response-phase transport errors are left
  un-retried so a non-idempotent `POST` can't double-execute (#63).

## [0.3.2] - 2026-06-18

### Changed

- feat(indexes): add source_column field to index responses

## [0.3.1] - 2026-06-16

### Fixed

- `Client::submit_query` (the hand-written 202/async query path the CLI drives
  directly) now retries HTTP 429 (`OVERLOADED`) admission shedding per
  `Configuration::retry`, like every generated op and `Client::query`. It was
  the one query path the 0.3.0 migration missed, so under admission shedding a
  submitted query surfaced the 429 as an error with no retry (#688).

## [0.3.0] - 2026-06-16

### Added

- Every generated `apis::*` operation now transparently retries HTTP 429
  (`OVERLOADED`) admission shedding, honoring `Retry-After` with backoff before
  the op returns (#58). The policy is the new `Configuration::retry` field
  (`crate::query::RetryPolicy`, defaulting to `RetryPolicy::default`); set
  `max_retries` to 0 to disable. The enhanced query path (`crate::query`) keeps
  using its own per-call `QueryConfig::retry` instead.

## [0.2.0] - 2026-06-15

### Added

- Enhanced `Client::query`: transparently retries HTTP 429 (`OVERLOADED`)
  admission shedding honoring `Retry-After` under a deadline budget, and
  auto-follows truncated results to materialize the full row set, guarded by
  configurable `max_auto_rows` (default 1M) and `max_auto_bytes` (default
  64 MiB) ceilings (#688). The raw generated op remains reachable via
  `client.queries().execute()` / `hotdata::apis::query_api::query`.
- `hotdata::query` module with `QueryConfig`, `RetryPolicy`, `PollPolicy`, the
  `QueryError` enum, and the `ResultError` family (`Failed` / `Timeout` /
  `TooLarge` / `Incomplete` / `Unavailable`). `ClientBuilder::query_config` sets
  the instance default; `Client::query_with` takes a per-call override.
- `Client::query_in` (scope a query to a database) and `Client::query_preview`
  (return the bounded preview without auto-following), plus `QueryConfig::with_*`
  setters for fluent per-call overrides
  (`client.query_config().clone().with_auto_follow(false)`).

### Changed

- `Client::query` now returns `Result<QueryResponse, hotdata::QueryError>`
  instead of `Result<QueryResponse, Error<QueryError>>` to carry the overload
  and result-lifecycle errors the bounded-memory query contract introduces.
  Migration: where you matched the old error, e.g.
  `Err(Error::ResponseError(rc))`, now match `Err(QueryError::Submit(
  Error::ResponseError(rc)))`; overload and auto-follow failures arrive as the
  new `QueryError::Overloaded` / `QueryError::Result(..)` variants.
- `Client::query` rejects an explicit `async = true` request up front with
  `QueryError::AsyncRequested` (it is the synchronous-results path); use
  `Client::submit_query` for asynchronous submissions.

## [0.1.4] - 2026-06-15

### Changed

- feat(queries): add preview and total row count fields

## [0.1.3] - 2026-06-09

### Added

- `list_indexes_collection` (`GET /v1/indexes`) in `hotdata::apis::indexes_api` — lists indexes across every table in a database, scoped by the `X-Database-Id` header — along with the `IndexEntryResponse` and `ListIndexesPageResponse` models.

### Removed

- Sandbox endpoints and their models (`sandboxes_api`, `Sandbox`, `SandboxResponse`, `CreateSandboxRequest`, `UpdateSandboxRequest`, `ListSandboxesResponse`, `DeleteSandboxResponse`), following their removal from the Hotdata OpenAPI spec.

### Changed

- Regenerated the client from the updated Hotdata OpenAPI spec.

## [0.1.2] - 2026-06-06

### Added

- Database and managed-catalog schema/table management endpoints: `add_database_schema`, `add_database_table`, `add_managed_schema`, and `add_managed_table`, with their request/response models (`AddManagedSchemaRequest`, `AddManagedTableRequest`, `AddManagedTableDecl`, `ManagedSchemaResponse`, `ManagedTableResponse`).

### Changed

- Regenerated the client from the updated Hotdata OpenAPI spec, catching up on spec changes since 0.1.1. Generated with openapi-generator 7.22.0 (`useChrono=false`, so date-time fields remain `String` as before).

## [0.1.1] - 2026-06-05

### Changed

- Populate crate metadata (`repository`, `homepage`, `documentation`, `readme`, `keywords`, `categories`) for the crates.io listing, and link the [Hotdata CLI](https://github.com/hotdata-dev/hotdata-cli) from the README.
- Publishing to crates.io now uses Trusted Publishing (OIDC) instead of a stored API token.

## [0.1.0] - 2026-06-05

### Added

- Ergonomic `Client` and `ClientBuilder` (`hotdata::Client`) wrapping the generated `Configuration`: set an API token and workspace id, with thin async pass-throughs for `query`, `list_query_runs`, `list_results`, `get_result`, and `list_workspaces`.
- Grouped resource handles on `Client` (`client.datasets()`, `client.secrets()`, `client.query_runs()`, … one per API) so callers no longer import `hotdata::apis::*_api` free functions or thread a `&Configuration` through every call.
- Query convenience helpers: `Client::await_result` polls a persisted result to `ready` (configurable via `PollConfig`) instead of a hand-rolled loop, and `Client::query_to_arrow` submits, awaits, and decodes a result as Arrow in one call (`arrow` feature).
- `hotdata::field` helpers (`set` / `clear` / `unchanged`) for the `Option<Option<T>>` nullable-and-optional update fields, so `Some(Some(v))` / `Some(None)` intents read clearly.
- Typed `ResultStatus` / `QueryRunStatus` enums (`hotdata::status`) with `result_status()` / `run_status()` accessor traits on the response types. Each carries an `Other(String)` catch-all so unrecognized server statuses round-trip instead of failing — matching runtimedb's lenient parsing, where a generated closed enum would not.
- Transparent API-token to JWT exchange via a hand-written `TokenManager` (`hotdata::auth`): `hd_` API tokens are exchanged against `/v1/auth/jwt`, cached, refreshed, and re-minted automatically, with a 30s expiry leeway and single-flight concurrency. JWTs (`eyJ…`) are passed through unchanged. Honors `HOTDATA_DISABLE_JWT_EXCHANGE`.
- Pluggable `BearerTokenProvider` hook on `Configuration` so any async token source can drive bearer auth.
- Optional `arrow` feature: `get_result_arrow` / `stream_result_arrow` decode Apache Arrow IPC result streams into `RecordBatch`es, surface the `X-Total-Row-Count` and `rel="next"` Link headers, and map the `202`/`409`/`404`/`400` result states to typed `ArrowError` variants.
- Flat re-export surface (`hotdata::Client`, `hotdata::Configuration`, `hotdata::prelude::*`) alongside the namespaced `hotdata::apis` / `hotdata::models`. The prelude also re-exports the resource handles, `PollConfig`, the `field` helpers, and (with the `arrow` feature) `ArrowError`.
- The SDK's own error enums (`ClientError`, `TokenExchangeError`, `ArrowError`, `AwaitResultError`, `QueryToArrowError`) are `#[non_exhaustive]`, so new variants can be added without a breaking change — match them with a wildcard arm.
- Request/response debug logging (`hotdata::http_log`) covering every HTTP call — generated ops plus the hand-written `submit_query` / `upload_stream` / Arrow fetch / JWT mint. Each emits `log::debug!` records on the `hotdata::http` target (`>>> METHOD url`, headers, body; `<<< status`, body) so a host (e.g. the CLI's `--debug`) can render them with any `log` backend. `Authorization` bearer tokens and sensitive JSON/form fields (`api_token`, `secret`, `password`, …) are masked before logging; the SDK installs no logger and stays silent without a backend. The `api.mustache` template emits the hooks so they survive regeneration, and the regen-safety CI guard verifies they remain.

### Changed

- Regeneration is now safe for the hand-written ergonomic layer: the generator only overwrites generated subtrees (`src/apis`, `src/models`, `docs`) and skips `src/lib.rs`, `src/auth.rs`, `src/http_log.rs`, `src/arrow.rs`, `src/client.rs`, `src/resources.rs`, `src/field.rs`, and `Cargo.toml` via `.openapi-generator-ignore`. The regen-safety CI guard verifies all of these survive and stay wired into `lib.rs`.
- Initial generated client from the Hotdata OpenAPI spec.