v2.3.0

badges/shieldsv2.3.0Sep 9, 2026by bjchambers

Upgrading?

AI Summary

Spice v2.3.0 introduces performance improvements, broader BigQuery federation, and a new GitHub data connector. It also moves Google models to Vertex AI and adds explicit limits to the caching accelerator.

Key Highlights

  • SQL Federation Improvements fix function translation and NULL handling.
  • BigQuery Federation supports temporal expressions, recursive CTEs, and window aggregates.
  • Caching Accelerator Limits bound what a `refresh_mode: caching` accelerator holds.
  • Stale-While-Revalidate retains cached results across acceleration refreshes.
  • GitHub Review Data adds new tables and columns for repository analysis.

Breaking Changes

  • Google Models on Vertex AI require migration from Google AI Studio credentials.
  • SQL Federation Improvements change behavior for functions like `trim` and `concat`.
  • Caching Accelerator Limits introduce new eviction policies that replace the previous unbounded behavior.

New Features

  • BigQuery Federation
  • Caching Accelerator Limits
  • Stale-While-Revalidate Across a Refresh
  • GitHub Review Data
  • Google Models on Vertex AI

Full Release Notes

Spice v2.3.0 brings performance improvements, broader query federation, and expanded data connector capabilities. The release improves cache reuse and reduces memory use for cached SQL results. It also extends BigQuery support and adds GitHub review, release, and repository data for SQL analysis. Google models now use Vertex AI, so deployments with credentials for Google AI Studio require migration.

Highlights in v2.3.0 include:

- [**SQL Federation Improvements**](#sql-federation-improvements) — bug fixes for function translation, NULL handling, and timezone declarations
- [**BigQuery Federation**](#bigquery-federation) — temporal expressions, recursive CTEs, correlated subqueries, and window aggregates now run as one BigQuery job
- [**Caching Accelerator Limits**](#the-caching-accelerator-accepts-explicit-limits) — `caching_max_size` and `caching_max_items` bound what a `refresh_mode: caching` accelerator holds
- [**Stale-While-Revalidate Across a Refresh**](#sql-results-cache-improvements) — configured stale-while-revalidate windows retain cached results across acceleration refreshes
- [**GitHub Review Data**](#github-data-connector-review-release-and-repository-tables) — eight new tables, 17 new `pulls` columns, and a `repo` / `owner` column on every row
- [**Google Models on Vertex AI**](#google-models-move-to-vertex-ai) — a `from: google` model authenticates as a GCP service account

## What's New in v2.3.0

### SQL Federation Improvements

This release improves SQL translation and function handling for accelerated and federated datasets. The following bug fixes cover string functions, NULL handling, correlated subqueries, and timezone declarations.

- A `trim` call failed on DuckDB, SQLite, and MySQL because DataFusion emitted its canonical name, `btrim`. DuckDB now receives `trim`, with an explicit space argument for the one-argument form. The native SQLite and MySQL paths evaluate `btrim` locally.
- DuckDB federation now lower-cases `to_hex` output to match local evaluation.
- DuckDB federation now decodes `sha256` output into a 32-byte digest instead of a hexadecimal string.
- `concat` uses `||` on DuckDB to consistently propagate NULL arguments.
- `inner_product` now returns NULL for an undefined dot product on DuckDB for consistency.
- Operations that DuckDB cannot handle (such as regex with `U` or `R` flags) are executed locally.
- User-defined functions registered after startup now execute locally.
- Catalog connectors now use the same list of local Spice-only functions as the data connectors.
- Two `EXISTS` shapes emitted SQL which evaluated the correlation over the whole relation, so the bound selected nothing. A semi or mark join then reported a match on a row the plan never read. Both shapes now refuse pushdown and run locally.
- DuckDB labels a `TIMESTAMPTZ` column with the connection's own timezone. The connector built its pool and never pinned that setting. A dataset's schema therefore carried the host timezone, and the same query returned different rows on different machines. A connector session is now pinned to UTC.

### BigQuery Federation

[BigQuery](https://spiceai.org/docs/components/data-connectors/adbc) federation runs more query shapes as one remote job.

- Temporal expressions, recursive CTEs, and integer division now keep their results and stay in one federated statement. These shapes previously failed remotely, split into several queries, or produced wrong cohort boundaries.
- Three more statement shapes now run. The first is a grouped query that projects a wrapped form of its grouping expression. The second is a query whose federated tables all sit inside a correlated subquery. The third is any aggregate window function.
- A query that reads BigQuery tables from several datasets of one project now runs as one BigQuery query. It also no longer returns rows from the wrong dataset when two datasets hold a table of the same name.
- A dataset that stores JSON in a `STRING` column now pushes down scalar `json_as_text` expressions and `json_get(...) IS NULL` checks.
- The built-in `date_trunc` is preserved, and the dialect and the federation policy now agree on which aggregate and window calls are eligible.
- `regexp_match` null checks federate safely.
- A distinct union now renders as `UNION DISTINCT`. BigQuery rejects a bare `UNION`, so such a query failed outright. `UNION ALL` is unchanged.
- A numbering function such as `ROW_NUMBER` no longer carries a window frame, which BigQuery rejects. An aggregate window function keeps its frame.
- Percentile functions and grouping keys now render in the form BigQuery accepts. A reported 29-statement workload that failed on these shapes now runs in full.
- `array_element` translates a non-negative integer literal index that fits in `Int64` as `SAFE_ORDINAL`. Other indexes evaluate locally. This preserves DataFusion's end-relative semantics for negative indexes.

**Cancellation**: A BigQuery query whose client goes away now stops. The BigQuery job ends as cancelled, and the pooled connection returns immediately. Before this release the query ran to completion, and the connection stayed busy for its whole duration. Enough cancellations left an application unable to query at all.

### The Caching Accelerator Accepts Explicit Limits

A `refresh_mode: caching` accelerator had nothing bounding what it held. Retention was derived from `caching_ttl` plus `caching_stale_while_revalidate_ttl`, and only when `caching_stale_if_error` was disabled. A dataset that set `caching_stale_if_error` therefore got no policy at all, and nothing was ever evicted. Nothing capped the acceleration by size or by count either.

Two settings now bound the acceleration. Each one refuses an unparseable value rather than falling back to a default:

- `caching_max_size` — a byte budget, such as `512MiB`.
- `caching_max_items` — a row budget.

`caching_ttl` is also accepted as `caching_item_ttl`, which is the spelling the SQL results, search results, and embeddings caches use. Eviction is entry-granular. A cached response can span several rows, so the runtime ranks entries by their oldest page and removes all of an entry's rows together. The storage schema is unchanged, and no existing acceleration needs a rebuild.

```yaml
datasets:
  - from: https://api.example.com/v1/items
    name: items
    acceleration:
      enabled: true
      engine: duckdb
      refresh_mode: caching
      primary_key: '(request_query, request_path)'
      params:
        caching_ttl: 5m
        caching_max_size: 512MiB
        caching_max_items: 50000
```

`caching_stale_if_error` now fires on the failure it exists for. It keyed off a fetch that returned an error. The HTTP connector reports a failing origin as a successful fetch whose rows carry a 429 or 5xx status once it exhausts `max_retries`. An operator who enabled the setting received the origin's error body instead of the cached response.

A caching accelerator that has nothing bounding it now says so at startup.

### SQL Results Cache Improvements

**Stale results remain available across a refresh when configured.** An acceleration refresh evicted every dependent [SQL results cache](https://spiceai.org/docs/features/caching) entry, and any successful refresh counted as a change. A `refresh_mode: full` update still flushed the whole per-table cache. For a workload with consistently high QPS, each refresh turned a population of cached results into simultaneous synchronous misses.

When `stale_while_revalidate_ttl` is configured, an invalidation now marks dependent entries stale as of the refresh instead of evicting them. Inside the stale window the runtime serves the previous result with `Results-Cache-Status: STALE` and starts one background revalidation per key. Past the stale window the request is a miss, exactly as before. With no stale window configured, invalidation stays hard.

**Cache accounting covers more retained memory.** A cache with a million empty results reported 0.09 GiB against 1.85 GiB of retained memory. Its `max_size` accounting omitted parts of each entry. The cache now shares schemas and input-table sets, copies foreign buffers, and accounts for per-buffer allocation overhead.

In the [reported benchmark](https://github.com/spiceai/spiceai/pull/13908), each entry retained 1%–80% less memory across 20 combinations of result shape and source. The benchmark ran on macOS/arm64 with snmalloc. Reported size ranged from 0.65x to 1.73x of retained memory after the change. The lowest ratio before the change was 0.31x. These figures compare the fix with its merge base, not v2.2.1, and are not guarantees for every workload.

This release also corrects memory accounting for the search results and embeddings caches.

**Pingora cache engine**: Table invalidation read every entry with a destructive `get`, so an invalidation promoted every key in the cache. Scan order replaced recency, and each visited key became a momentary miss to concurrent readers. A read also served a hit destructively, so a second reader reported a miss for a key the cache holds. Both reads are now non-destructive.

### GitHub Data Connector: Review, Release, and Repository Tables

The [GitHub Data Connector](https://spiceai.org/docs/components/data-connectors/github) adds eight tables, 17 columns on `pulls`, and repository identity on every row. An application can now answer a code-review question in SQL. Before this release, `pulls.reviews_count` was a bare integer with no state and no reviewer, and `pulls.review_comments` recorded only inline comments.

| Path                                       | Rows                                                                                    |
| ------------------------------------------ | --------------------------------------------------------------------------------------- |
| `github.com/{owner}/{repo}/reviews`        | One per pull request review, with `state`, `author`, `submitted_at`, and `commit_sha`   |
| `github.com/{owner}/{repo}/review_threads` | One per resolvable thread, with `is_resolved`, `is_outdated`, `path`, and `resolved_by` |
| `github.com/{owner}/{repo}/releases`       | One per release, with `total_download_count` and `assets_count`                         |
| `github.com/{owner}/{repo}/release_assets` | One per asset, with `download_count`, `size`, and `content_type`                        |
| `github.com/{owner}/{repo}/milestones`     | One per milestone, with `due_on` and `progress_percentage`                              |
| `github.com/{owner}/{repo}/repo`           | One row of repository metadata                                                          |
| `github.com/{owner}/repos`                 | Every repository an owner has                                                           |
| `github.com/{login}/user`                  | The public profile of one login                                                         |

`pulls` adds `is_draft`, `mergeable`, `merge_state_status`, `review_decision`, `status_check_rollup`, `merge_queue_state`, `merge_queue_position`, `merged_by`, `closed_by`, `base_ref`, `head_ref`, `head_sha`, `milestone_id`, `milestone_title`, `closing_issues_references`, `closing_issues_count`, and `reactions_count`. `issues` adds `state_reason`, `closed_by`, `reactions_count`, `type`, and `type_color`. Every table returns a `repo` and an `owner` column, so a multi-repository `UNION ALL` keeps its rows apart.

The connector also asks GitHub for a narrower pull request page. A 100-node page now exceeds GitHub's per-request compute budget on a large repository. GitHub rejects that page with `Resource limits for this query exceeded` and returns every node as null, so the dataset never loaded.

### Google Models Move to Vertex AI

A `from: google` chat or embedding model now authenticates as a GCP service account against [Vertex AI](https://cloud.google.com/vertex-ai). Spice no longer accepts a Google AI Studio API key. See [Breaking Changes](#breaking-changes) for the migration.

```yaml
models:
  - from: google:gemini-2.5-pro
    name: gemini
    params:
      google_project: my-project
      google_location: us-central1
      google_service_account_path: /etc/spice/gcp-sa.json
```

Other AI model fixes in this release:

- An [Anthropic](https://spiceai.org/docs/components/models/anthropic) model configured without an explicit model id now resolves. The default named `claude-3-5-sonnet-latest`, which Anthropic has retired, so every such request failed.
- An Anthropic model now refuses an OpenAI `top_logprobs` request instead of translating it to `top_k`. The two fields are unrelated. `top_logprobs` reports log probabilities and `top_k` narrows sampling, so the translation silently changed the model's output.
- Anthropic streaming failures and provider refusals from `openai`, `xai`, and `spiceai` are now classified from the provider's typed error fields. Each path searched the rendered error text for `401`, `429`, or `rate`, and then replaced the provider's own detail with a fixed string.
- A `from: huggingface:` chat model reads `hf_token` again. The parameter moved to the prefix `huggingface`, so `hf_token` was warned about as unknown and a gated repository was downloaded anonymously.
- An [Amazon Bedrock](https://spiceai.org/docs/components/models/bedrock) model now names the credential AWS rejected instead of reporting `unhandled error`.
- The [text-embeddings-inference](https://spiceai.org/docs/components/embeddings/huggingface) model-load path runs its filesystem and tokenizer work on a blocking thread. That work ran on a Tokio worker thread during model registration, so it could starve `/health`.

### Search Improvements and Bug Fixes

This release fixes bugs in [search](https://spiceai.org/docs/features/search) result limits, index updates, and deletes.

- **A result set larger than the requested limit**: `vector_search(tbl, 'query', 10)` against an Elasticsearch-backed index now respects the requested limit.
- **Index writes and deletes kept in sync with the table**: Previously, writes for rows with repeated primary keys, no chunks, or non-embeddable chunks could leave the previous index entry. These outdated index entries are now deleted.
- **A chunked Elasticsearch delete**: The delete filtered on the key columns, and a string key was left to Elasticsearch dynamic mapping as an analyzed `text` field. The delete now filters on a field that can match the key exactly.
- **A partial Elasticsearch delete**: `_delete_by_query` returns 2xx when the request ran, and it reports per-document failures and version conflicts in the body. Spice discarded that body, so a delete could leave documents behind and still report success.
- **Full-text index encoding**: A full-text upsert is a delete followed by an insert, so both halves must encode the primary key the same way. They disagreed for `Float32`, `Float16`, and `Binary` keys, and both the old and the new row stayed in the index.

### Acceleration and Refresh

- **`LIMIT` on a partitioned scan**: `PartitionTableProvider::scan` passed the scan limit as the skip argument rather than the fetch argument. `LIMIT 10` over a three-row partitioned dataset returned zero rows.
- **`acceleration.enabled: false`**: A dataset or a view can set `enabled: false` and leave the rest of the block in place. The runtime read every other setting, accepted it, and then ignored it. The component reported healthy and served federated queries. The runtime now names the settings it discards.
- **`ready_state` on a view**: A view's `acceleration.ready_state` was accepted by the schema and by the parser, and then never applied. The identical key on a dataset was honoured. A view now resolves the key the way a dataset does.
- **A retention policy that cannot start**: A dataset that set `retention_check_enabled: true`, a `retention_period`, and a `time_column` but no `retention_check_interval` got no retention task and no diagnostic. The builder now reports the refusal.
- **A refresh completion that arrives early**: A completion published before a caller registered its wait was dropped with no record, and the caller waited for a refresh that had already happened. The signal is now level-triggered.
- **A refresh completion from the wrong refresh**: A waiter was satisfied by the next completion recorded on the table, whichever refresh produced it. A refresh that was already running could therefore release a caller. Completions are now correlated with the refresh that a caller triggered.
- **A table replaced during a refresh**: Two callers acted on a completion for a table that had since been removed or rebuilt. The runtime now re-resolves the table after the refresh lands and before it acts on the completion.
- **A schema repair on a checkpoint**: Writing a checkpoint's schema also wrote its refresh timestamp, so a schema repair told the scheduler the data was fresh. An overdue dataset then waited a full `refresh_check_interval`. A schema repair now leaves the freshness clock alone.
- **A recorded snapshot schema**: A snapshot's recorded schema is a foreign declaration, and a `Map` that declares its `entries` field nullable is a declaration no accelerator can hold. The restore path now conforms that declaration to the Arrow map layout.
- **A source row durable write-back could not confirm**: The delivery worker read a missing point-scan row as a deletion. A short visibility gap in the accelerator therefore deleted that row from the source of record. The worker now withholds a key it cannot read and retries it on a later pass. A delivery cursor advances only after the pass succeeds. Write-back also refuses a configuration it cannot uphold, which is a behavior change. See [Breaking Changes](#breaking-changes).

### Arrow and Storage Bug Fixes

- **Decimal128 on write paths**: Three conversions could produce a plausible wrong number instead of an error. The sum of two in-range halves wrapped to a large negative decimal at scale 38. A float-to-int cast saturated to `i128::MAX`, and `NaN` became `0`. The declared precision was never checked, so a value needing more digits than the column declares was appended anyway. All three were reachable from Debezium decimal ingestion, where the input is source-controlled. The runtime now validates the destination precision once, where every input form converges.
- **An Iceberg `DELETE` an equality key cannot express**: An Iceberg delete writes an equality delete file, which removes rows whose key columns equal the given values. That statement matches the user's `WHERE` only when the condition reads key columns alone. A condition on a float or a nested column removed rows that did not match. Spice now refuses the statement and names the offending column.
- **A Parquet object overwritten mid-scan**: A listing-table Parquet scan decoded two object generations as one file. The scan now pins one generation through a version id or an `If-Match` header. `dataset_acceleration_refresh_errors` carries `reason=object_generation_changed|parquet_decode|other`, so an expected overwrite is distinguishable from corruption.
- **An Arrow relabel**: `relabel_array_data` carries an array's values across a type change untouched, and only field names and nested nullability flags may differ. Nothing enforced that contract. It now refuses three kinds of target:
  - a target that changes what the buffers mean
  - a target that declares away nulls the array still holds
  - a target whose same-typed sibling fields are reordered

  The third kind produced silent column-value transposition on the Delta Lake column-mapping path.
- **Arrow `MAP` columns**: The Arrow map layout forbids a nullable `entries` field, and `MapArray::try_new` refuses one. Nothing enforced it at decode. A producer that declared it that way handed over a column that decoded cleanly and then failed in the first kernel that rebuilt it. The runtime now normalizes `entries` nullability at every Arrow decode point. A Databricks SQL Warehouse `MAP` column, which declares `entries` nullable, no longer panics the runtime. A Cayenne accelerator that has already persisted the non-conforming declaration is now repaired.
- **A nested nullability difference**: `try_cast_to` decided its fast paths with `Schema::contains`, which permits a nested field's nullability to differ. `RecordBatch` requires the two types to be identical. The shared entry point now aligns the difference instead of publishing it.
- **A retired Vortex file**: Retiring a Cayenne file released its Vortex segments and left its footer in DataFusion's file-metadata cache, which has no TTL. A file opened during retirement could also repopulate the path that retirement had just cleared. The retirement drain had no ceiling. A stalled put therefore held every caller of the invalidation, and the delete sink is one of them. All three faults are fixed.
- **A Cayenne teardown that deleted a shared metastore**: Recreation of a Cayenne dataset could delete a shared metastore and leave other datasets unavailable after restart. The guard checked only the metastore named by that dataset's settings, not catalogs inside its data directory. Open file handles hid the loss until restart. The runtime now scans that directory before catalog changes and again before deletion. It refuses recreation if it finds a Cayenne metastore or cannot safely resolve the configured paths.

### Observability and Operations

- **OpenTelemetry resource attributes**: The [OTLP ingest](https://spiceai.org/docs/features/observability) path parsed resource attributes such as `service.name` and `service.instance.id` and then dropped them. Data points from two processes were therefore indistinguishable once written. Resource attributes now reach the metric data points. The same change closes four ingest races that dropped data with no error. One of those races let a write publish through a table provider that a schema evolution had already replaced.
- **A panicking query**: A query whose execution panicked was sometimes returned as an empty HTTP 200 success, which no client can tell apart from "no rows matched". This happened in 20 of 60 identical runs on `trunk`. A panicking query is now always an error.
- **`runtime.cpu.cores` above the container's ceiling**: `runtime.cpu.cores` was the one CPU entitlement setting taken raw rather than clamped. A pod configured with `runtime.cpu.cores: 6` under `resources.limits.cpu: 2` sized every derived pool for six cores and was then throttled. The runtime now warns and names both readings. It does not clamp, because an operator may size the runtime for a node the pod has not reached yet.
- **HTTP latency**: The HTTP server sets `TCP_NODELAY`, which lowers the latency of a small response body. The Flight SQL server already set it.
- **MCP tools in `runtime.task_history`**: A proxied MCP tool call was recorded under two different `task` values depending on the entry point, so one logical tool split across two rows. Grouping by `task` gave wrong per-tool counts. Both entry points now use the encoded name.
- **A discarded Flight batch**: The runtime reported a `data_loss` count that counted a message by its body length. A batch whose body is empty still carries rows, so the count was wrong. The runtime now reads the IPC header.
- **A hot reload that changes functions or catalogs**: A cached logical plan embeds the `ScalarUDF` and the `TableSource` it was planned against. A hot reload that redefined a SQL function or replaced a catalog left those plans in place. The same SQL then kept answering from the replaced component. The plan cache is installed unconditionally with a one-hour TTL, so no caching configuration was needed to hit this. Both handlers now discard the affected plans.
- **Cloud Connect metrics cadence**: A [Cloud Connect](https://spiceai.org/docs/deployment/cloud/cloud-connect) instance exports metrics every 10 seconds rather than every 30, so a chart drawn from the control stream resolves at 10 seconds. The payload is a snapshot of cumulative totals, so this changes chart resolution and not what is recorded.

### Other Improvements and Bug Fixes

- **Glue catalog**: A discovered Glue table that Spice cannot read, such as an ORC or Avro table, was absent from the catalog with nothing said about it. The catalog connector now reports each such table and the reason.
- **Databricks**: The connector accepts Unity Catalog streaming tables and views. It also forwards the runtime's `spark` feature.
- **DuckDB index materialization**: The DuckDB intermediate index materialization rule reads a table's index list before it rewrites a scan into a materialized CTE. That list had been empty, so the rule never fired and an indexed column never narrowed a scan.
- **Vortex `list_length` pushdown**: DataFusion `array_length(expr)` and `array_length(expr, 1)` now convert to Vortex `list_length` and push into the scan. A list length is computed from offsets, and element values are not materialized.
- **ScyllaDB**: The [ScyllaDB Data Connector](https://spiceai.org/docs/components/data-connectors/scylladb) is out of the default build, alongside ODBC. `make install-scylladb` or `--features scylladb` builds it. A Spicepod that names `scylladb:` on a build without it now says the build lacks the connector rather than offering the closest registered name. The connector also declines a physical sort that CQL cannot serve.
- **Turso**: The accelerator refuses a stored list whose encoding predates the version marker rather than reading it under the current encoding.
- **CLI**: `spice run` resolves `spiced` beside the CLI before it reaches for the managed install, and never from `PATH`. A Spice Cloud project listing is attributed to the organization it was requested for. `spice query` and `spice nsql analyze` keep the API key on its origin across a redirect. A Cloud Connect managed instance no longer warns about the default pods watcher on every `spice run`.
- **MCP tools**: A renamed tool forwards `strict()` and `as_mcp_proxy()` to the tool it wraps. `runtime-tools` declares what its `mcp` feature needs.
- **Connector registries on shutdown**: The runtime no longer clears stateless connector registries on shutdown.

### Dependency Updates

Compared with v2.2.1, this release changes the following versions:

| Dependency / Component | v2.2.1  | v2.3.0  |
| ---------------------- | ------- | ------- |
| **iceberg-rust**       | v0.10.0 | v0.10.1 |
| **Rust toolchain**     | v1.96.1 | v1.97.1 |

DataFusion remains at v54.1.0 and Arrow remains at v58.3.0. Spice updates their fork revisions for the federation, Parquet scan, and cache fixes described above.

## Contributors

- [@bjchambers](https://github.com/bjchambers)
- [@Jeadie](https://github.com/Jeadie)
- [@krinart](https://github.com/krinart)
- [@lukekim](https://github.com/lukekim)
- [@peasee](https://github.com/peasee)
- [@phillipleblanc](https://github.com/phillipleblanc)
- [@sgrebnov](https://github.com/sgrebnov)
- [@vatsalp2008](https://github.com/vatsalp2008)

## Breaking Changes

**Google models authenticate against Vertex AI.** A `from: google` chat or embedding model no longer accepts `google_api_key`. Every such model now authenticates as a GCP service account.

Update each `from: google` model and embedding to set `google_project`, `google_location`, and exactly one credential setting.

Before:

```yaml
models:
  - from: google:gemini-2.5-pro
    name: gemini
    params:
      google_api_key: ${secrets:google_api_key}
```

After:

```yaml
models:
  - from: google:gemini-2.5-pro
    name: gemini
    params:
      google_project: my-project
      google_location: us-central1
      google_service_account_path: /etc/spice/gcp-sa.json
```

| Setting                                  | Description                                                                       |
| ---------------------------------------- | --------------------------------------------------------------------------------- |
| `google_project`                         | The GCP project id. Required.                                                     |
| `google_location`                        | The GCP region, such as `us-central1`, or `global`. Required.                     |
| `google_service_account_path`            | The path to a GCP service account JSON key file.                                  |
| `google_service_account_key`             | A GCP service account JSON key as a string.                                       |
| `google_application_default_credentials` | Read the key path from the `GOOGLE_APPLICATION_CREDENTIALS` environment variable. |

Set exactly one of the three credential settings.

Two behavior changes to note before you upgrade:

- **Durable write-back rejects unsafe settings and operations.** Before you upgrade, set `mode: file`, remove retention settings, and declare a single-column `primary_key` for each durable write-back dataset. The runtime rejects unsupported settings at load time. Submit writes inside a transaction as one `BEGIN; ...; COMMIT;` request. Write-back datasets reject `DELETE` and `TRUNCATE` when you issue those statements.
- **The ScyllaDB Data Connector is out of the default build.** Build with `--features scylladb`, or run `make install-scylladb`, to keep it. ODBC already worked this way.

## Cookbook Updates

No new cookbook recipes.

The [Spice Cookbook](https://spiceai.org/cookbook) includes more than 104 recipes to help you get started with Spice quickly and easily.

## Upgrading

To upgrade to v2.3.0, use one of the following methods:

**CLI**:

```console
spice upgrade
```

**Homebrew**:

```console
brew upgrade spiceai/spiceai/spice
```

**Docker**:

Pull the `spiceai/spiceai:2.3.0` image:

```console
docker pull spiceai/spiceai:2.3.0
```

For available tags, see [DockerHub](https://hub.docker.com/r/spiceai/spiceai/tags).

**Helm**:

```console
helm repo update
helm upgrade spiceai spiceai/spiceai --version 2.3.0
```

**AWS Marketplace**:

Spice is available in the [AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-jmf6jskjvnq7i).

## What's Changed

### Changelog

- fix(search): surface an Elasticsearch delete that only partially applied (fixes #12364) by [@claudespice](https://github.com/claudespice) in [#12720](https://github.com/spiceai/spiceai/pull/12720)
- fix(ci): bound every integration job, so a wedged one cannot hold the queue (refs #12718) by [@grokspice](https://github.com/grokspice) in [#12721](https://github.com/spiceai/spiceai/pull/12721)
- feat(tools): add pdf-parse tool to compare liteparse and pdf-inspector by [@Jeadie](https://github.com/Jeadie) in [#12807](https://github.com/spiceai/spiceai/pull/12807)
- fix(turso): refuse a stored list whose encoding predates the version marker (fixes #12632) by [@grokspice](https://github.com/grokspice) in [#12837](https://github.com/spiceai/spiceai/pull/12837)
- Use TypedParams for reranker parameters by [@Jeadie](https://github.com/Jeadie) in [#13046](https://github.com/spiceai/spiceai/pull/13046)
- fix(search): correctness, async-safety, and performance fixes across the search subsystem by [@Jeadie](https://github.com/Jeadie) in [#13065](https://github.com/spiceai/spiceai/pull/13065)
- Decide full-text CDC-attachment at construction, not after by [@Jeadie](https://github.com/Jeadie) in [#13075](https://github.com/spiceai/spiceai/pull/13075)
- fix(scylladb): decline the physical sort pushdown CQL cannot serve (fixes #10775) by [@claudespice](https://github.com/claudespice) in [#13107](https://github.com/spiceai/spiceai/pull/13107)
- fix(cache): stop Pingora table invalidation from promoting every key it reads (fixes #12674) by [@claudespice](https://github.com/claudespice) in [#13117](https://github.com/spiceai/spiceai/pull/13117)
- fix(cayenne): stop a failed statistics read publishing a partial count as exact (fixes #13010) by [@claudespice](https://github.com/claudespice) in [#13125](https://github.com/spiceai/spiceai/pull/13125)
- fix(cayenne): refuse a widened CDC batch a partitioned acceleration cannot apply (fixes #13051) by [@claudespice](https://github.com/claudespice) in [#13133](https://github.com/spiceai/spiceai/pull/13133)
- fix(catalogs): report the Glue tables Spice cannot read instead of dropping them silently (fixes #13102) by [@claudespice](https://github.com/claudespice) in [#13149](https://github.com/spiceai/spiceai/pull/13149)
- fix(ci): gate every tracked Rust source tree in the merge queue's change filter (fixes #13120) by [@claudespice](https://github.com/claudespice) in [#13151](https://github.com/spiceai/spiceai/pull/13151)
- fix(cache): bound max_size on the memory an entry holds, not its array bytes (fixes #12931) by [@claudespice](https://github.com/claudespice) in [#13154](https://github.com/spiceai/spiceai/pull/13154)
- fix(cayenne): honour the sort-merge row floor on the memory-gated path (fixes #12958) by [@claudespice](https://github.com/claudespice) in [#13157](https://github.com/spiceai/spiceai/pull/13157)
- fix(cache): serve a Pingora hit without hiding it from a concurrent reader (fixes #12987) by [@claudespice](https://github.com/claudespice) in [#13158](https://github.com/spiceai/spiceai/pull/13158)
- fix(vortex): stop a file opened mid-retirement from repopulating the cleared path (fixes #12963) by [@claudespice](https://github.com/claudespice) in [#13161](https://github.com/spiceai/spiceai/pull/13161)
- fix(vortex): evict a retired file's footer with its segments (fixes #12953) by [@claudespice](https://github.com/claudespice) in [#13162](https://github.com/spiceai/spiceai/pull/13162)
- feat(google): switch from Google AI Studio to Vertex AI by [@krinart](https://github.com/krinart) in [#13210](https://github.com/spiceai/spiceai/pull/13210)
- Run TEI candle model-load filesystem and tokenizer work on a blocking thread by [@Jeadie](https://github.com/Jeadie) in [#13223](https://github.com/spiceai/spiceai/pull/13223)
- fix(correctness): validate Decimal128 precision and overflow on write paths by [@lukekim](https://github.com/lukekim) in [#13224](https://github.com/spiceai/spiceai/pull/13224)
- fix(runtime): stop clearing stateless connector registries on shutdown by [@Jeadie](https://github.com/Jeadie) in [#13225](https://github.com/spiceai/spiceai/pull/13225)
- fix(search): use one encoding path for full-text index writes and deletes by [@Jeadie](https://github.com/Jeadie) in [#13226](https://github.com/spiceai/spiceai/pull/13226)
- fix(docker): install ARM64 linker toolchain by [@Jeadie](https://github.com/Jeadie) in [#13266](https://github.com/spiceai/spiceai/pull/13266)
- ci: build the runtime integration archives with one feature set by [@bjchambers](https://github.com/bjchambers) in [#13270](https://github.com/spiceai/spiceai/pull/13270)
- ci: archive every integration test target in one invocation by [@bjchambers](https://github.com/bjchambers) in [#13273](https://github.com/spiceai/spiceai/pull/13273)
- fix(sql): bump the datafusion pin so a bounded EXISTS refuses instead of returning wrong rows (fixes #13277) by [@claudespice](https://github.com/claudespice) in [#13280](https://github.com/spiceai/spiceai/pull/13280)
- ci: have the compiler-cache action own its credentials by [@bjchambers](https://github.com/bjchambers) in [#13282](https://github.com/spiceai/spiceai/pull/13282)
- fix(cpu-budget): warn when a configured cores value exceeds the container's real CPU ceiling (fixes #13275) by [@claudespice](https://github.com/claudespice) in [#13284](https://github.com/spiceai/spiceai/pull/13284)
- fix(databricks): publish a MAP column with the non-nullable entries field Arrow requires (fixes #7307) by [@claudespice](https://github.com/claudespice) in [#13288](https://github.com/spiceai/spiceai/pull/13288)
- fix(search): bound a search result set by the requested limit, not just the index read (fixes #13274) by [@claudespice](https://github.com/claudespice) in [#13290](https://github.com/spiceai/spiceai/pull/13290)
- fix(vortex): bound the segment-cache retirement drain so a stuck put cannot hold a delete open (fixes #12964) by [@claudespice](https://github.com/claudespice) in [#13300](https://github.com/spiceai/spiceai/pull/13300)
- Reach the accelerator contract without going through the runtime by [@bjchambers](https://github.com/bjchambers) in [#13304](https://github.com/spiceai/spiceai/pull/13304)
- fix(postgres): rebuild the datasets already streaming when a join replaces their replication slot (fixes #13229) by [@claudespice](https://github.com/claudespice) in [#13313](https://github.com/spiceai/spiceai/pull/13313)
- fix(cli): resolve spiced beside the CLI before the managed install, and never from PATH by [@claudespice](https://github.com/claudespice) in [#13317](https://github.com/spiceai/spiceai/pull/13317)
- fix(iceberg): refuse a delete an equality key cannot express by [@lukekim](https://github.com/lukekim) in [#13322](https://github.com/spiceai/spiceai/pull/13322)
- Stop the accelerator engines reaching up into the runtime by [@bjchambers](https://github.com/bjchambers) in [#13324](https://github.com/spiceai/spiceai/pull/13324)
- fix(benchmarks): open snapshot-update PRs against the dispatch branch, not always trunk by [@krinart](https://github.com/krinart) in [#13335](https://github.com/spiceai/spiceai/pull/13335)
- chore(deps): bump datafusion-table-providers for NUMERIC scale fidelity by [@lukekim](https://github.com/lukekim) in [#13349](https://github.com/spiceai/spiceai/pull/13349)
- feat(cloud-connect): export metrics every 10s by [@phillipleblanc](https://github.com/phillipleblanc) in [#13353](https://github.com/spiceai/spiceai/pull/13353)
- Move the accelerator engines into their own crates by [@bjchambers](https://github.com/bjchambers) in [#13354](https://github.com/spiceai/spiceai/pull/13354)
- perf(cayenne): shard the encode by what the write is, not by what the table declares by [@lukekim](https://github.com/lukekim) in [#13356](https://github.com/spiceai/spiceai/pull/13356)
- fix(cli): attribute a project listing to the org it was requested for by [@lukekim](https://github.com/lukekim) in [#13357](https://github.com/spiceai/spiceai/pull/13357)
- Enable Oracle TPC-H result validation, fix the loader that trimmed leading spaces by [@sgrebnov](https://github.com/sgrebnov) in [#13360](https://github.com/spiceai/spiceai/pull/13360)
- Readme: Change PG catalog status from Alpha to Beta by [@sgrebnov](https://github.com/sgrebnov) in [#13361](https://github.com/spiceai/spiceai/pull/13361)
- Upgrade iceberg-rust to v0.10.1 by [@krinart](https://github.com/krinart) in [#13365](https://github.com/spiceai/spiceai/pull/13365)
- perf(cayenne): cut a rewrite's shards at equal row mass, not equal width by [@lukekim](https://github.com/lukekim) in [#13373](https://github.com/spiceai/spiceai/pull/13373)
- fix(deps): bump datafusion-table-providers for AVG/division NUMERIC scale rounding by [@krinart](https://github.com/krinart) in [#13387](https://github.com/spiceai/spiceai/pull/13387)
- fix(databricks): forward runtime's spark feature to the databricks connector by [@sgrebnov](https://github.com/sgrebnov) in [#13389](https://github.com/spiceai/spiceai/pull/13389)
- Move the Cayenne accelerator into its own crate by [@bjchambers](https://github.com/bjchambers) in [#13391](https://github.com/spiceai/spiceai/pull/13391)
- fix(testoperator): compare exact decimals on their mantissas, not through f64 by [@bjchambers](https://github.com/bjchambers) in [#13407](https://github.com/spiceai/spiceai/pull/13407)
- Configure an accelerator engine through its constructor instead of a published global by [@bjchambers](https://github.com/bjchambers) in [#13409](https://github.com/spiceai/spiceai/pull/13409)
- fix(opentelemetry): merge resource attributes into metric data points and close OTLP-to-sink ingest races by [@peasee](https://github.com/peasee) in [#13412](https://github.com/spiceai/spiceai/pull/13412)
- fix(arrow): refuse a relabel that changes what the array's buffers mean (fixes #13423) by [@claudespice](https://github.com/claudespice) in [#13435](https://github.com/spiceai/spiceai/pull/13435)
- fix(tools): record one task_history task per proxied MCP tool (fixes #13338) by [@claudespice](https://github.com/claudespice) in [#13437](https://github.com/spiceai/spiceai/pull/13437)
- fix(release): read release notes from a file and trim them to GitHub's body limit by [@sgrebnov](https://github.com/sgrebnov) in [#13457](https://github.com/spiceai/spiceai/pull/13457)
- fix(ci): probe the cc toolchain before reaching for brew in setup-cc by [@claudespice](https://github.com/claudespice) in [#13466](https://github.com/spiceai/spiceai/pull/13466)
- fix(cayenne): refuse a teardown that would delete a metastore no parameter names (fixes #13436) by [@claudespice](https://github.com/claudespice) in [#13471](https://github.com/spiceai/spiceai/pull/13471)
- fix(spiced): stop warning about the default pods watcher by [@phillipleblanc](https://github.com/phillipleblanc) in [#13494](https://github.com/spiceai/spiceai/pull/13494)
- fix(arrow): align a nested nullability difference instead of advertising it (fixes #13285) by [@claudespice](https://github.com/claudespice) in [#13496](https://github.com/spiceai/spiceai/pull/13496)
- fix(acceleration): make the refresh-completion signal level-triggered (fixes #13086) by [@claudespice](https://github.com/claudespice) in [#13505](https://github.com/spiceai/spiceai/pull/13505)
- fix(acceleration): apply a partitioned scan's LIMIT as a fetch, not a skip by [@vatsalp2008](https://github.com/vatsalp2008) in [#13507](https://github.com/spiceai/spiceai/pull/13507)
- fix(ci): stage the retention OOM test binary without its debug info by [@claudespice](https://github.com/claudespice) in [#13511](https://github.com/spiceai/spiceai/pull/13511)
- fix(ci): let sign-off run from a worktree nested inside the checkout, and add --skip-targeted by [@bjchambers](https://github.com/bjchambers) in [#13520](https://github.com/spiceai/spiceai/pull/13520)
- Authenticate the stargazers dataset with a PAT and drop qa_analytics by [@lukekim](https://github.com/lukekim) in [#13529](https://github.com/spiceai/spiceai/pull/13529)
- fix(ci): isolate sccache per job on shared self-hosted Macs by [@lukekim](https://github.com/lukekim) in [#13531](https://github.com/spiceai/spiceai/pull/13531)
- chore(scylladb): take the ScyllaDB connector out of the default build by [@lukekim](https://github.com/lukekim) in [#13532](https://github.com/spiceai/spiceai/pull/13532)
- feat(github): add review, release, milestone, user and repo tables, plus repo/owner columns by [@lukekim](https://github.com/lukekim) in [#13545](https://github.com/spiceai/spiceai/pull/13545)
- fix(ci): match the allowed refresh-task warning at its current module path by [@claudespice](https://github.com/claudespice) in [#13547](https://github.com/spiceai/spiceai/pull/13547)
- fix(flight): normalize MAP entries nullability at every Arrow decode point (fixes #13495) by [@claudespice](https://github.com/claudespice) in [#13550](https://github.com/spiceai/spiceai/pull/13550)
- fix(anthropic): default to a model Anthropic still serves (fixes #13557) by [@claudespice](https://github.com/claudespice) in [#13563](https://github.com/spiceai/spiceai/pull/13563)
- fix(postgres): rebuild an emptied CDC acceleration rather than resume its surviving position (refs #13546) by [@claudespice](https://github.com/claudespice) in [#13566](https://github.com/spiceai/spiceai/pull/13566)
- chore(spicepod): accelerate GitHub datasets with Cayenne instead of DuckDB by [@lukekim](https://github.com/lukekim) in [#13571](https://github.com/spiceai/spiceai/pull/13571)
- fix(arrow): refuse a relabel that declares away nulls the array still holds (fixes #13433) by [@claudespice](https://github.com/claudespice) in [#13585](https://github.com/spiceai/spiceai/pull/13585)
- fix(cayenne): refuse a catalog whose data directory would hold its metastore (fixes #13105) by [@claudespice](https://github.com/claudespice) in [#13593](https://github.com/spiceai/spiceai/pull/13593)
- fix(ci): exempt every App account from the assignee gate, not just Dependabot (fixes #13115) by [@grokspice](https://github.com/grokspice) in [#13594](https://github.com/spiceai/spiceai/pull/13594)
- fix(mysql): assign the binlog dump session's net_write_timeout floor as an integer literal (fixes #13307) by [@grokspice](https://github.com/grokspice) in [#13595](https://github.com/spiceai/spiceai/pull/13595)
- fix(ci): let the E2E macOS build share the fleet's Cargo home (fixes #13299) by [@grokspice](https://github.com/grokspice) in [#13596](https://github.com/spiceai/spiceai/pull/13596)
- fix(ci): call a test binary the runner cannot load an infrastructure failure (fixes #13518) by [@grokspice](https://github.com/grokspice) in [#13597](https://github.com/spiceai/spiceai/pull/13597)
- fix(spicepod): say which acceleration settings enabled: false discards (fixes #13514) by [@grokspice](https://github.com/grokspice) in [#13602](https://github.com/spiceai/spiceai/pull/13602)
- feat(caching): bound a caching accelerator by size, count and entry lifetime (closes #13525) by [@bjchambers](https://github.com/bjchambers) in [#13604](https://github.com/spiceai/spiceai/pull/13604)
- fix(ci): catch a stale Cargo.lock before the merge queue, not after a 55-minute build (fixes #13598) by [@grokspice](https://github.com/grokspice) in [#13606](https://github.com/spiceai/spiceai/pull/13606)
- fix(bedrock): say which credential AWS rejected instead of "unhandled error" (refs #12396) by [@claudespice](https://github.com/claudespice) in [#13616](https://github.com/spiceai/spiceai/pull/13616)
- feat(caching): serve results stale after an acceleration refresh instead of evicting them by [@krinart](https://github.com/krinart) in [#13618](https://github.com/spiceai/spiceai/pull/13618)
- fix(write-back): never delete a source row for a key the accelerator did not return by [@phillipleblanc](https://github.com/phillipleblanc) in [#13638](https://github.com/spiceai/spiceai/pull/13638)
- fix(cayenne): fold a staged append's unpublished keys into the PK-keyset rebuild (fixes #13639) by [@claudespice](https://github.com/claudespice) in [#13644](https://github.com/spiceai/spiceai/pull/13644)
- fix(tools): forward strict() and as_mcp_proxy() from a renamed tool (fixes #13443) by [@claudespice](https://github.com/claudespice) in [#13649](https://github.com/spiceai/spiceai/pull/13649)
- fix(delta_lake): order a column-mapping relabel target the way the scan reads it (fixes #13434) by [@claudespice](https://github.com/claudespice) in [#13655](https://github.com/spiceai/spiceai/pull/13655)
- fix(ci): raise the e2e Linux build bound above the worst legitimate run (fixes #13674) by [@grokspice](https://github.com/grokspice) in [#13675](https://github.com/spiceai/spiceai/pull/13675)
- fix: comment out refresh_append_overlap in the sample spicepod by [@lukekim](https://github.com/lukekim) in [#13680](https://github.com/spiceai/spiceai/pull/13680)
- fix(anthropic): refuse a log-probability request instead of narrowing sampling (fixes #13581) by [@claudespice](https://github.com/claudespice) in [#13682](https://github.com/spiceai/spiceai/pull/13682)
- fix(cayenne): record a pipelined non-conflict staged append's primary keys (fixes #13642) by [@claudespice](https://github.com/claudespice) in [#13686](https://github.com/spiceai/spiceai/pull/13686)
- fix(schema): stop an illegal Arrow Map entries declaration from being stored or compared (fixes #13549) by [@claudespice](https://github.com/claudespice) in [#13695](https://github.com/spiceai/spiceai/pull/13695)
- build(rust): upgrade toolchain to 1.97.1 by [@lukekim](https://github.com/lukekim) in [#13696](https://github.com/spiceai/spiceai/pull/13696)
- fix(ci): stop a CI git push from blocking forever on a credential prompt (fixes #13701) by [@claudespice](https://github.com/claudespice) in [#13702](https://github.com/spiceai/spiceai/pull/13702)
- fix(search): remove the vector a rejected write left behind (fixes #13504) by [@claudespice](https://github.com/claudespice) in [#13705](https://github.com/spiceai/spiceai/pull/13705)
- fix(acceleration): stop a refresh already running from answering a later waiter (refs #13544) by [@claudespice](https://github.com/claudespice) in [#13709](https://github.com/spiceai/spiceai/pull/13709)
- fix(search): remove a chunked row's stale chunks when its text goes away (refs #13704) by [@claudespice](https://github.com/claudespice) in [#13716](https://github.com/spiceai/spiceai/pull/13716)
- fix(catalogs): install the Spice function deny-list on the SQL catalog connectors (refs #13664) by [@claudespice](https://github.com/claudespice) in [#13731](https://github.com/spiceai/spiceai/pull/13731)
- fix(runtime-tools): declare what the mcp feature actually needs (fixes #13648) by [@grokspice](https://github.com/grokspice) in [#13733](https://github.com/spiceai/spiceai/pull/13733)
- fix(runtime): re-resolve a table after its refresh lands, before acting on the completion (fixes #13603) by [@claudespice](https://github.com/claudespice) in [#13735](https://github.com/spiceai/spiceai/pull/13735)
- fix(flight): count a discarded batch by its IPC header, not its body length (fixes #13636) by [@grokspice](https://github.com/grokspice) in [#13736](https://github.com/spiceai/spiceai/pull/13736)
- docs(makefile): say what SPICED_DATA_FEATURES actually is (fixes #13678) by [@grokspice](https://github.com/grokspice) in [#13738](https://github.com/spiceai/spiceai/pull/13738)
- fix(anthropic): classify a streaming failure by Anthropic's error type, not its message text (fixes #13562) by [@claudespice](https://github.com/claudespice) in [#13748](https://github.com/spiceai/spiceai/pull/13748)
- fix(views): apply a view's acceleration.ready_state instead of dropping it (fixes #13615) by [@claudespice](https://github.com/claudespice) in [#13750](https://github.com/spiceai/spiceai/pull/13750)
- fix(ci): resolve a Python 3.11+ interpreter for the lint-rust guards (refs #13754) by [@grokspice](https://github.com/grokspice) in [#13755](https://github.com/spiceai/spiceai/pull/13755)
- fix(bigquery): emit valid pushed-down SQL by [@phillipleblanc](https://github.com/phillipleblanc) in [#13768](https://github.com/spiceai/spiceai/pull/13768)
- fix(bigquery): safely federate regexp_match null checks by [@krinart](https://github.com/krinart) in [#13771](https://github.com/spiceai/spiceai/pull/13771)
- feat(hash-index): verify the bloom filter's block index with Verus by [@lukekim](https://github.com/lukekim) in [#13777](https://github.com/spiceai/spiceai/pull/13777)
- fix(adbc): federate a BigQuery statement spanning datasets as one query by [@phillipleblanc](https://github.com/phillipleblanc) in [#13780](https://github.com/spiceai/spiceai/pull/13780)
- fix(adbc): cancel an abandoned query, stop the BigQuery job, free the connection by [@phillipleblanc](https://github.com/phillipleblanc) in [#13782](https://github.com/spiceai/spiceai/pull/13782)
- fix(snapshot): conform a recorded snapshot schema to the Arrow map layout (fixes #13694) by [@claudespice](https://github.com/claudespice) in [#13786](https://github.com/spiceai/spiceai/pull/13786)
- fix(cayenne): materialize the in-memory CDC tier before a scanning DELETE by [@lukekim](https://github.com/lukekim) in [#13798](https://github.com/spiceai/spiceai/pull/13798)
- fix(caching): say when a caching accelerator has nothing bounding it (fixes #13525) by [@claudespice](https://github.com/claudespice) in [#13805](https://github.com/spiceai/spiceai/pull/13805)
- fix(bigquery): run three federated statement shapes BigQuery was refusing by [@phillipleblanc](https://github.com/phillipleblanc) in [#13812](https://github.com/spiceai/spiceai/pull/13812)
- fix(duckdb): rewrite DataFusion's btrim to DuckDB's trim (fixes #13794) by [@claudespice](https://github.com/claudespice) in [#13821](https://github.com/spiceai/spiceai/pull/13821)
- fix(federation): stop pushing btrim to SQLite and MySQL, which have no btrim (fixes #13840) by [@claudespice](https://github.com/claudespice) in [#13823](https://github.com/spiceai/spiceai/pull/13823)
- fix(ci): give the throughput workflow the postgres fixtures bench provisions by [@krinart](https://github.com/krinart) in [#13830](https://github.com/spiceai/spiceai/pull/13830)
- fix(acceleration,cayenne): Resolve quoted columns in keys, name which primary key columns are null by [@peasee](https://github.com/peasee) in [#13845](https://github.com/spiceai/spiceai/pull/13845)
- fix: pin listing-table Parquet reads to one object generation by [@phillipleblanc](https://github.com/phillipleblanc) in [#13847](https://github.com/spiceai/spiceai/pull/13847)
- fix(duckdb): lower-case the hex digits a federated to_hex gets back (fixes #13818) by [@claudespice](https://github.com/claudespice) in [#13852](https://github.com/spiceai/spiceai/pull/13852)
- fix(bigquery): carry the merged unparser fixes through the dialect wrapper by [@phillipleblanc](https://github.com/phillipleblanc) in [#13853](https://github.com/spiceai/spiceai/pull/13853)
- fix(databricks): allow Unity Catalog streaming tables and views through the table-type check by [@krinart](https://github.com/krinart) in [#13855](https://github.com/spiceai/spiceai/pull/13855)
- fix(acceleration): report a retention policy that cannot start instead of silently building none (fixes #13804) by [@claudespice](https://github.com/claudespice) in [#13857](https://github.com/spiceai/spiceai/pull/13857)
- fix(search): evict a key whose deciding row the index rejected (fixes #13848) by [@claudespice](https://github.com/claudespice) in [#13859](https://github.com/spiceai/spiceai/pull/13859)
- fix(duckdb): Restore intermediate index materialization optimization by [@sgrebnov](https://github.com/sgrebnov) in [#13864](https://github.com/spiceai/spiceai/pull/13864)
- fix(federation): refuse a user function the deny-list snapshot was built before (fixes #13726) by [@claudespice](https://github.com/claudespice) in [#13868](https://github.com/spiceai/spiceai/pull/13868)
- fix(duckdb): decode a federated sha256 back to the digest's bytes (fixes #13850) by [@claudespice](https://github.com/claudespice) in [#13869](https://github.com/spiceai/spiceai/pull/13869)
- fix(runtime): Add TCP_NODELAY to HTTP server by [@peasee](https://github.com/peasee) in [#13874](https://github.com/spiceai/spiceai/pull/13874)
- fix(query): surface a panicking query as an error, never an empty success (fixes #13876) by [@claudespice](https://github.com/claudespice) in [#13878](https://github.com/spiceai/spiceai/pull/13878)
- fix(bigquery): keep the built-in date_trunc, forward two dialect renderings, repin the unparser by [@phillipleblanc](https://github.com/phillipleblanc) in [#13882](https://github.com/spiceai/spiceai/pull/13882)
- fix(llms): classify a provider refusal from its typed fields, not its message (refs #13747) by [@claudespice](https://github.com/claudespice) in [#13884](https://github.com/spiceai/spiceai/pull/13884)
- fix(vortex): keep control-byte field names unescaped in physical schema by [@lukekim](https://github.com/lukekim) in [#13886](https://github.com/spiceai/spiceai/pull/13886)
- perf(vortex): push DataFusion `array_length` down as Vortex `list_length` by [@lukekim](https://github.com/lukekim) in [#13888](https://github.com/spiceai/spiceai/pull/13888)
- fix(duckdb): render a federated concat as || so a NULL argument propagates (fixes #13849) by [@claudespice](https://github.com/claudespice) in [#13889](https://github.com/spiceai/spiceai/pull/13889)
- fix(acceleration): let a schema repair correct a checkpoint without resetting the freshness clock (fixes #13817) by [@claudespice](https://github.com/claudespice) in [#13894](https://github.com/spiceai/spiceai/pull/13894)
- fix(duckdb): screen a federated inner_product so a non-finite result is NULL (fixes #13787) by [@claudespice](https://github.com/claudespice) in [#13895](https://github.com/spiceai/spiceai/pull/13895)
- refactor(postgres): report an acceleration re-read as a refresh, not a bespoke metric by [@bjchambers](https://github.com/bjchambers) in [#13896](https://github.com/spiceai/spiceai/pull/13896)
- fix(search): classify a partially non-finite embedding as unindexable on every backend (fixes #13872) by [@claudespice](https://github.com/claudespice) in [#13902](https://github.com/spiceai/spiceai/pull/13902)
- fix(duckdb): pin a connector's DuckDB session to UTC so a dataset's schema does not carry the host timezone (fixes #13899) by [@claudespice](https://github.com/claudespice) in [#13903](https://github.com/spiceai/spiceai/pull/13903)
- fix(cayenne): keep a file's statistics the same whichever source serves them (refs #13829) by [@claudespice](https://github.com/claudespice) in [#13904](https://github.com/spiceai/spiceai/pull/13904)
- fix(bigquery): preserve results and federation for temporal and recursive queries by [@bjchambers](https://github.com/bjchambers) in [#13905](https://github.com/spiceai/spiceai/pull/13905)
- Make the SQL results cache hold what it says it holds by [@bjchambers](https://github.com/bjchambers) in [#13908](https://github.com/spiceai/spiceai/pull/13908)
- fix(duckdb): keep a call the dialect cannot render out of the federated plan (fixes #13900) by [@claudespice](https://github.com/claudespice) in [#13909](https://github.com/spiceai/spiceai/pull/13909)
- fix(functions): discard cached plans when a hot reload changes the function set (refs #13873) by [@claudespice](https://github.com/claudespice) in [#13911](https://github.com/spiceai/spiceai/pull/13911)
- fix(runtime): discard cached logical plans when a hot reload replaces a catalog (fixes #13910) by [@claudespice](https://github.com/claudespice) in [#13914](https://github.com/spiceai/spiceai/pull/13914)
- fix(cli): keep the API key on its origin in the SDK-built query client (fixes #12502) by [@grokspice](https://github.com/grokspice) in [#13923](https://github.com/spiceai/spiceai/pull/13923)
- fix(search): filter a chunked Elasticsearch delete on a field that can match the key (fixes #13714) by [@claudespice](https://github.com/claudespice) in [#13926](https://github.com/spiceai/spiceai/pull/13926)
- ci: align default and ODBC build features by [@phillipleblanc](https://github.com/phillipleblanc) in [#13933](https://github.com/spiceai/spiceai/pull/13933)
- fix(github): bound the pull request page to GitHub's per-request compute budget (refs #13762) by [@grokspice](https://github.com/grokspice) in [#13938](https://github.com/spiceai/spiceai/pull/13938)
- fix: stabilize GitHub tests and bound GraphQL registration (fixes #13762) by [@lukekim](https://github.com/lukekim) in [#13939](https://github.com/spiceai/spiceai/pull/13939)
- fix(ci): preserve Cargo discovery markers during runner disk sweeps by [@phillipleblanc](https://github.com/phillipleblanc) in [#13940](https://github.com/spiceai/spiceai/pull/13940)
- fix(bigquery): push down JSON scalar text and null checks by [@phillipleblanc](https://github.com/phillipleblanc) in [#13944](https://github.com/spiceai/spiceai/pull/13944)
- fix(models): read the HuggingFace chat token as hf_token again (fixes #13932) by [@claudespice](https://github.com/claudespice) in [#13946](https://github.com/spiceai/spiceai/pull/13946)
- fix(postgres): decode versioned JSONB binary replication values by [@phillipleblanc](https://github.com/phillipleblanc) in [#13962](https://github.com/spiceai/spiceai/pull/13962)
- fix(postgres): preserve microseconds in timestamp writeback by [@phillipleblanc](https://github.com/phillipleblanc) in [#13963](https://github.com/spiceai/spiceai/pull/13963)
- ci: upgrade spiceio setup action to v0.9.0 by [@lukekim](https://github.com/lukekim) in [#13971](https://github.com/spiceai/spiceai/pull/13971)

**Full Changelog**: [https://github.com/spiceai/spiceai/compare/v2.2.1...v2.3.0](https://github.com/spiceai/spiceai/compare/v2.2.1...v2.3.0)

Related Repositories