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