Skip to content

feat(mbtiles): add a new cache schema#3009

Merged
CommanderStorm merged 15 commits into
maplibre:mainfrom
nyurik:cache
Jul 19, 2026
Merged

feat(mbtiles): add a new cache schema#3009
CommanderStorm merged 15 commits into
maplibre:mainfrom
nyurik:cache

Conversation

@nyurik

@nyurik nyurik commented Jul 14, 2026

Copy link
Copy Markdown
Member

adds a new caching schema to the .mbtiles files, which allows various tools to store web-retrieved tiles in a standard .mbtiles file with the expiration and etag. This way cache file can be examined / prepopulated / diffed / copied / ... and many other options as any other .mbtiles file.

Copilot AI review requested due to automatic review settings July 14, 2026 14:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new non-standard “cache” MBTiles schema intended for persistent on-disk tile caching with expires/etag metadata, while still exposing a spec-compatible tiles view so the resulting .mbtiles remains readable by standard MBTiles readers.

Changes:

  • Introduces the cache schema DDL (tile_cache + cache_data + tiles view) and Rust read/write APIs (get_cached, set_cached, purge_expired), including xxh3-based blob de-duplication.
  • Adds a new MbtilesPool::open_cache entry point for writable cache usage (WAL + busy timeout) and tests for persistence and purge behavior.
  • Updates schema-init logic to correctly append STRICT alongside other SQLite table options (e.g. WITHOUT ROWID) and expands documentation to describe the new schema.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
mbtiles/src/schemas/mod.rs Registers and re-exports the new cache schema helpers.
mbtiles/src/schemas/cache.rs Adds cache-schema detection + creation helpers (but currently has an unused import warning).
mbtiles/src/queries.rs Fixes STRICT table-option injection when other options exist.
mbtiles/src/pool.rs Adds open_cache plus pooled cache read/write/purge APIs and a new roundtrip test.
mbtiles/src/lib.rs Exposes new cache types (CacheEntryMeta, CachedTile) from the crate root.
mbtiles/src/errors.rs Adds CacheKeyExhausted error for collision probe exhaustion.
mbtiles/src/cache.rs Implements cache schema creation + cached tile read/write/purge logic and unit tests.
mbtiles/sql/init-cache.sql Defines the new cache schema tables and spec-compatible tiles view.
mbtiles/.sqlx/query-3bbfb455…json Adds sqlx offline metadata for the cache schema detection query.
justfile Sorts generated sqlx JSON deterministically (jq) in prepare-sqlite.
docs/content/mbtiles-schema.md Documents the new cache schema and includes its SQL definition.
Files not reviewed (1)
  • mbtiles/.sqlx/query-3bbfb4551cbb745ab79195a1a201176be1172dfe72cc60f1bae5958aa07e0cfb.json: Generated file

Comment thread mbtiles/src/schemas/cache.rs
Comment thread mbtiles/src/pool.rs Outdated
Comment on lines +324 to +336
pub async fn open_cache<P: AsRef<Path>>(filepath: P) -> MbtResult<Self> {
let mbtiles = Mbtiles::new(filepath)?;
let opt = SqliteConnectOptions::new()
.filename(mbtiles.filepath())
.create_if_missing(true)
.journal_mode(SqliteJournalMode::Wal)
.busy_timeout(Duration::from_secs(5));
let pool = SqlitePool::connect_with(opt).await?;
let mut conn = pool.acquire().await?;
mbtiles.create_cache_schema(&mut *conn, false).await?;
drop(conn);
Ok(Self { mbtiles, pool })
}
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown

Performance Comparison maincache

Total Elapsed Time: 116.83s → 116.79s (-0.0%)
CPU Baseline: 81.43µs → 83.29µs (+2.3%)
Benchmark ID: timing

timing - Function execution time metrics.

+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+
| Function                   | Calls                        | Avg                              | P95                              | Total                            | % Total                      |
+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+
| martin::main               | 1 → 1 (+0.0%)                | 116.83s → 116.79s (-0.0%)        | 116.84s → 116.84s (+0.0%)        | 116.83s → 116.79s (-0.0%)        | 100.00% → 100.00% (+0.0%)    |
+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+
| martin::start              | 1 → 1 (+0.0%)                | 116.81s → 116.78s (-0.0%)        | 116.84s → 116.84s (+0.0%)        | 116.81s → 116.78s (-0.0%)        | 99.98% → 99.99% (+0.0%)      |
+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+
| content::get_tile          | 3200800 → 3200800 (+0.0%)    | 17.74µs → 17.37µs (-2.1%)        | 28.75µs → 28.27µs (-1.7%)        | 56.78s → 55.60s (-2.1%)          | 48.60% → 47.61% (-2.0%)      |
+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+
| content::get_http_response | 2200600 → 2200600 (+0.0%)    | 18.06µs → 17.55µs (-2.8%)        | 27.52µs → 27.45µs (-0.3%)        | 39.74s → 38.63s (-2.8%)          | 34.02% → 33.08% (-2.8%)      |
+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+
| content::get_tile_content  | 2200600 → 2200600 (+0.0%)    | 15.40µs → 15.01µs (-2.5%)        | 24.99µs → 25.07µs (+0.3%)        | 33.90s → 33.04s (-2.5%)          | 29.01% → 28.29% (-2.5%)      |
+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+
| content::new               | 3200800 → 3200800 (+0.0%)    | 1.74µs → 1.70µs (-2.3%)          | 2.13µs → 2.10µs (-1.4%)          | 5.56s → 5.44s (-2.2%)            | 4.76% → 4.65% (-2.3%)        |
+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+
| source::get_sources        | 3200800 → 3200800 (+0.0%)    | 1.46µs → 1.41µs (-3.4%)          | 1.87µs → 1.81µs (-3.2%)          | 4.68s → 4.51s (-3.6%)            | 4.01% → 3.86% (-3.7%)        |
+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+
| content::recompress        | 2200600 → 2200600 (+0.0%)    | 1.54µs → 1.56µs (+1.3%)          | 100.00ns → 120.00ns (+20.0%)     | 3.40s → 3.43s (+0.9%)            | 2.91% → 2.94% (+1.0%)        |
+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+
| content::encode            | 100100 → 100100 (+0.0%)      | 32.64µs → 32.75µs (+0.3%)        | 46.49µs → 47.87µs (+3.0%)        | 3.27s → 3.28s (+0.3%)            | 2.80% → 2.81% (+0.4%)        |
+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+
| server::new_server         | 1 → 1 (+0.0%)                | 236.26µs → 290.55µs (+23.0%) ⚠️  | 236.29µs → 290.56µs (+23.0%) ⚠️  | 236.26µs → 290.55µs (+23.0%) ⚠️  | 0.00% → 0.00% (+0.0%)        |
+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+
| optimizer::encode          | 1 → 1 (+0.0%)                | 63.86µs → 44.30µs (-30.6%) 🚀    | 63.87µs → 44.32µs (-30.6%) 🚀    | 63.86µs → 44.30µs (-30.6%) 🚀    | 0.00% → 0.00% (+0.0%)        |
+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+
| optimizer::encode_into     | 4 → 4 (+0.0%)                | 8.60µs → 4.97µs (-42.2%) 🚀      | 16.99µs → 9.78µs (-42.4%) 🚀     | 34.42µs → 19.89µs (-42.2%) 🚀    | 0.00% → 0.00% (+0.0%)        |
+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+
| encode::write_to           | 4 → 4 (+0.0%)                | 7.95µs → 4.49µs (-43.5%) 🚀      | 15.44µs → 8.60µs (-44.3%) 🚀     | 31.79µs → 17.94µs (-43.6%) 🚀    | 0.00% → 0.00% (+0.0%)        |
+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+
| optimizer::analyze         | 1 → 1 (+0.0%)                | 10.81µs → 6.95µs (-35.7%) 🚀     | 10.81µs → 6.96µs (-35.6%) 🚀     | 10.81µs → 6.95µs (-35.7%) 🚀     | 0.00% → 0.00% (+0.0%)        |
+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+
| 🗑️ writer::with            | 40 → 0 (-100.0%) 🚀          | 348.00ns → 0.00ns (-100.0%) 🚀   | 561.00ns → 0.00ns (-100.0%) 🚀   | 13.93µs → 0.00ns (-100.0%) 🚀    | 0.00% → 0.00% (+0.0%)        |
+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+
| 🆕 tile::from_tile         | 0 → 4 (+100.0%) ⚠️           | 0.00ns → 1.76µs (+100.0%) ⚠️     | 0.00ns → 3.71µs (+100.0%) ⚠️     | 0.00ns → 7.05µs (+100.0%) ⚠️     | 0.00% → 0.00% (+0.0%)        |
+----------------------------+------------------------------+----------------------------------+----------------------------------+----------------------------------+------------------------------+

alloc-bytes - Exclusive allocation bytes by each function (excluding nested calls).

+--------------------------------+------------------------------+--------------------------------+--------------------------------+--------------------------------+----------------------------+
| Function                       | Calls                        | Avg                            | P95                            | Total                          | % Total                    |
+--------------------------------+------------------------------+--------------------------------+--------------------------------+--------------------------------+----------------------------+
| content::get_tile_content      | 2200600 → 2200600 (+0.0%)    | 93.0 KB → 93.0 KB (+0.0%)      | 181.9 KB → 181.9 KB (+0.0%)    | 195.2 GB → 195.2 GB (+0.0%)    | 83.91% → 83.91% (+0.0%)    |
+--------------------------------+------------------------------+--------------------------------+--------------------------------+--------------------------------+----------------------------+
| content::encode                | 100100 → 100100 (+0.0%)      | 344.5 KB → 344.5 KB (+0.0%)    | 347.2 KB → 347.2 KB (+0.0%)    | 32.9 GB → 32.9 GB (+0.0%)      | 14.14% → 14.14% (+0.0%)    |
+--------------------------------+------------------------------+--------------------------------+--------------------------------+--------------------------------+----------------------------+
| source::get_sources            | 3200800 → 3200800 (+0.0%)    | 1.1 KB → 1.1 KB (+0.0%)        | 2.4 KB → 2.4 KB (+0.0%)        | 3.3 GB → 3.3 GB (+0.0%)        | 1.43% → 1.43% (+0.0%)      |
+--------------------------------+------------------------------+--------------------------------+--------------------------------+--------------------------------+----------------------------+
| content::get_tile              | 3200800 → 3200800 (+0.0%)    | 287 B → 287 B (+0.0%)          | 296 B → 296 B (+0.0%)          | 876.6 MB → 876.6 MB (+0.0%)    | 0.37% → 0.37% (+0.0%)      |
+--------------------------------+------------------------------+--------------------------------+--------------------------------+--------------------------------+----------------------------+
| content::get_http_response     | 2200600 → 2200600 (+0.0%)    | 182 B → 182 B (+0.0%)          | 200 B → 200 B (+0.0%)          | 383.6 MB → 383.6 MB (+0.0%)    | 0.16% → 0.16% (+0.0%)      |
+--------------------------------+------------------------------+--------------------------------+--------------------------------+--------------------------------+----------------------------+
| martin::start                  | 1 → 1 (+0.0%)                | 3.0 MB → 3.0 MB (+0.0%)        | 3.0 MB → 3.0 MB (+0.0%)        | 3.0 MB → 3.0 MB (+0.0%)        | 0.00% → 0.00% (+0.0%)      |
+--------------------------------+------------------------------+--------------------------------+--------------------------------+--------------------------------+----------------------------+
| martin::main                   | 1 → 1 (+0.0%)                | 168.4 KB → 166.3 KB (-1.2%)    | 168.4 KB → 166.4 KB (-1.2%)    | 168.4 KB → 166.3 KB (-1.2%)    | 0.00% → 0.00% (+0.0%)      |
+--------------------------------+------------------------------+--------------------------------+--------------------------------+--------------------------------+----------------------------+
| optimizer::encode              | 1 → 1 (+0.0%)                | 67.5 KB → 67.5 KB (+0.0%)      | 67.5 KB → 67.5 KB (+0.0%)      | 67.5 KB → 67.5 KB (+0.0%)      | 0.00% → 0.00% (+0.0%)      |
+--------------------------------+------------------------------+--------------------------------+--------------------------------+--------------------------------+----------------------------+
| server::new_server             | 1 → 1 (+0.0%)                | 31.1 KB → 31.1 KB (+0.0%)      | 31.2 KB → 31.2 KB (+0.0%)      | 31.1 KB → 31.1 KB (+0.0%)      | 0.00% → 0.00% (+0.0%)      |
+--------------------------------+------------------------------+--------------------------------+--------------------------------+--------------------------------+----------------------------+
| tile::from_tile                | 4 → 4 (+0.0%)                | 264 B → 264 B (+0.0%)          | 264 B → 264 B (+0.0%)          | 1.0 KB → 1.0 KB (+0.0%)        | 0.00% → 0.00% (+0.0%)      |
+--------------------------------+------------------------------+--------------------------------+--------------------------------+--------------------------------+----------------------------+
| encode::dict_may_be_beneficial | 4 → 4 (+0.0%)                | 256 B → 256 B (+0.0%)          | 256 B → 256 B (+0.0%)          | 1.0 KB → 1.0 KB (+0.0%)        | 0.00% → 0.00% (+0.0%)      |
+--------------------------------+------------------------------+--------------------------------+--------------------------------+--------------------------------+----------------------------+
| writer::with                   | 40 → 40 (+0.0%)              | 22 B → 22 B (+0.0%)            | 308 B → 308 B (+0.0%)          | 900 B → 900 B (+0.0%)          | 0.00% → 0.00% (+0.0%)      |
+--------------------------------+------------------------------+--------------------------------+--------------------------------+--------------------------------+----------------------------+
| encode::write_to               | 4 → 4 (+0.0%)                | 194 B → 194 B (+0.0%)          | 448 B → 448 B (+0.0%)          | 776 B → 776 B (+0.0%)          | 0.00% → 0.00% (+0.0%)      |
+--------------------------------+------------------------------+--------------------------------+--------------------------------+--------------------------------+----------------------------+
| sort::sort                     | 4 → 4 (+0.0%)                | 120 B → 120 B (+0.0%)          | 160 B → 160 B (+0.0%)          | 480 B → 480 B (+0.0%)          | 0.00% → 0.00% (+0.0%)      |
+--------------------------------+------------------------------+--------------------------------+--------------------------------+--------------------------------+----------------------------+
| writer::write_header           | 4 → 4 (+0.0%)                | 43 B → 43 B (+0.0%)            | 86 B → 86 B (+0.0%)            | 172 B → 172 B (+0.0%)          | 0.00% → 0.00% (+0.0%)      |
+--------------------------------+------------------------------+--------------------------------+--------------------------------+--------------------------------+----------------------------+

Threads

Total Alloc: 4.2 MB → 4.2 MB (+0.0%)
Total Dealloc: 302.7 MB → 302.7 MB (+0.0%)
Mem Diff: -298.5 MB → -298.5 MB (+0.0%)

+--------+--------------------------+------------------------------+------------------------------+----------------------------+------------------------------+
| Thread | CPU % Avg                | CPU % Max                    | Alloc                        | Dealloc                    | Mem Diff                     |
+--------+--------------------------+------------------------------+------------------------------+----------------------------+------------------------------+
| martin | 0.00% → 0.00% (+0.0%)    | 11.90% → 15.80% (+32.8%) ⚠️  | 2.8 MB → 2.8 MB (+0.0%)      | 1.9 MB → 1.8 MB (-5.3%)    | 968.1 KB → 1.0 MB (+5.8%)    |
+--------+--------------------------+------------------------------+------------------------------+----------------------------+------------------------------+
| hp-mcp | 0.00% → 0.00% (+0.0%)    | 0.00% → 0.00% (+0.0%)        | 54.7 KB → 54.7 KB (+0.0%)    | 3.1 KB → 3.1 KB (+0.0%)    | 51.7 KB → 51.7 KB (+0.0%)    |
+--------+--------------------------+------------------------------+------------------------------+----------------------------+------------------------------+

Generated with hotpath-rs

@CommanderStorm CommanderStorm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, with one minor nit

Comment thread mbtiles/src/cache.rs Outdated
Comment thread mbtiles/src/cache.rs
@nyurik
nyurik force-pushed the cache branch 2 times, most recently from 725a985 to 80ad6bd Compare July 15, 2026 14:37
@nyurik nyurik changed the title chore: add mbtiles cache schema feat: add mbtiles cache schema Jul 15, 2026
@nyurik nyurik changed the title feat: add mbtiles cache schema feat(mbtiles): add a new cache schema Jul 15, 2026
@nyurik
nyurik force-pushed the cache branch 2 times, most recently from abe6f3d to 8f64ff3 Compare July 16, 2026 01:06
@CommanderStorm
CommanderStorm marked this pull request as draft July 16, 2026 12:29
@codecov

codecov Bot commented Jul 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.58603% with 67 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.33%. Comparing base (e99011d) to head (4c3970c).

Files with missing lines Patch % Lines
mbtiles/src/cache_pool.rs 85.61% 8 Missing and 12 partials ⚠️
mbtiles/src/cache.rs 94.55% 7 Missing and 12 partials ⚠️
mbtiles/src/bin/mbtiles.rs 50.00% 4 Missing and 5 partials ⚠️
mbtiles/src/queries.rs 18.18% 9 Missing ⚠️
mbtiles/src/copier.rs 90.32% 3 Missing ⚠️
mbtiles/src/mbtiles.rs 80.00% 2 Missing ⚠️
martin/src/bin/martin-cp.rs 0.00% 1 Missing ⚠️
mbtiles/src/bindiff.rs 50.00% 1 Missing ⚠️
mbtiles/src/patcher.rs 85.71% 1 Missing ⚠️
mbtiles/src/schemas/cache.rs 92.85% 0 Missing and 1 partial ⚠️
... and 1 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3009      +/-   ##
==========================================
+ Coverage   86.24%   86.33%   +0.09%     
==========================================
  Files         120      123       +3     
  Lines       17892    18465     +573     
  Branches    17892    18465     +573     
==========================================
+ Hits        15431    15942     +511     
- Misses       1745     1778      +33     
- Partials      716      745      +29     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

nyurik and others added 7 commits July 19, 2026 00:40
…ge additions

Address PR feedback and close the API gaps a web-fetching consumer needs:

- New `MbtilesCache` type: pooled writable cache handle (WAL, busy timeout,
  create-if-missing, incremental auto_vacuum for new files). `MbtilesPool`
  reverts to its read-only main-branch surface.
- `MbtilesCache::open` refuses files with a non-cache schema (NotACacheFile)
  instead of silently creating cache tables inside an existing tileset.
- `update_cached_meta`: bump expires/etag without rewriting the blob (HTTP
  304 revalidation path).
- `purge_cache_to_size`: evict soonest-expiring entries until the file's
  live size fits a byte budget.
- Purge functions now run `PRAGMA incremental_vacuum` so cache files shrink
  on disk when auto_vacuum is enabled.
- Document the empty-blob negative-caching convention; note that a set_cached
  overwrite may orphan a blob until the next purge.
- Tests: overwrite-orphan GC, metadata-only revalidation, size-bounded purge
  eviction order, negative-cache roundtrip, schema guard, auto_vacuum mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds `MbtType::Cache` (and `MbtTypeCli::Cache`), fulfilling the promise
that cache files can be examined/copied/diffed like any other mbtiles:

- detect_type recognizes the cache schema (checked last, so no behavior
  change for existing files); uniqueness constraint satisfied by the
  tile_cache WITHOUT-ROWID primary key.
- New `xxh3_64_int` SQLite scalar UDF (bit-identical to the runtime
  content_key) powers bulk SQL paths.
- copy: any schema -> cache (blobs keyed by content, NULL expires/etag),
  cache -> any schema (via the tiles view, expires/etag dropped), and
  cache -> cache verbatim (expires/etag and probed keys preserved, zoom/
  bbox filters and on-duplicate modes honored). Bulk copies verify no
  xxh3-64 collision occurred and fail with CacheCopyCollision otherwise.
- diff/apply-patch/bindiff/transcode into or onto a cache file are
  rejected with clear errors (deletion markers cannot be represented);
  cache files still work as the compared-against/patch-source side.
- validate: FK integrity (tile_cache -> cache_data) plus content-key
  check with the linear-probe tolerance window; orphaned blobs are legal.
- summary/meta-*/martin serving work unchanged via the tiles view.
- insert_tiles supports Cache, so `martin-cp --mbtiles-type cache` can
  prepopulate cache files.
- New `mbtiles cache-purge <file> [--max-size <MB>]` subcommand.
- Docs: mbtiles-schema.md gains a supported-operations matrix; cache
  module rustdoc documents bulk-copy vs runtime collision semantics.
- Tests: schema detection, convert round-trips (flat/hash/norm <-> cache),
  cache->cache metadata preservation, diff-input support, rejection
  errors, bulk-insert dedup, validation negatives, CLI golden files.

BREAKING CHANGE: `MbtType` and `MbtTypeCli` gain a `Cache` variant;
exhaustive matches on these enums must add an arm (mbtiles 0.19.0).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mirror the flat vs normalized split of the regular schemas:

- `CacheSchema::Flat`: a single tile_cache table with the blob inline -
  simple and fast when few tiles share content.
- `CacheSchema::Normalized` (previously the only layout): tile_cache
  pointing into the xxh3-64-keyed, de-duplicated cache_data blob table -
  the recommended default for web caches.

Both keep the tile_cache name for the coordinate/metadata table
(detection tells them apart by the sixth column: tile_data vs tile_id)
and both expose the spec-compatible tiles view.

- `MbtType::Cache { schema: CacheSchema }`, mirroring Normalized.
- CLI values `cache-flat` and `cache-normalized`; `cache` stays as an
  alias for cache-normalized.
- Runtime API takes the schema like insert_tiles takes mbt_type;
  `MbtilesCache` detects and stores the layout (`open` creates
  normalized; `open_with_schema` chooses).
- Copies preserve expires/etag in any cache-to-cache direction,
  including across layouts, via a shared canonical select; only
  normalized needs the blob-insert + collision-check path.
- Flat purges skip the orphan-blob GC; per-tile validation of flat
  caches has nothing to hash-check, like flat.
- Cache detection and reads use runtime SQL (the two layouts' tile_cache
  columns cannot coexist in the sqlx-prepare database), dropping two
  .sqlx entries.
- Tests parametrized over both layouts (unit, pool, copy round-trips,
  2x2 cache-to-cache matrix, diff inputs); CLI golden tests cover both;
  normalized golden outputs are byte-identical to before.

BREAKING CHANGE: `MbtType::Cache` gains a payload, `MbtTypeCli::Cache`
is replaced by `CacheFlat`/`CacheNormalized`, and the cache read/write
APIs take a `CacheSchema` parameter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both cache schemas gain a `fetched INTEGER` column (Unix-epoch seconds,
right before `expires`) recording when the tile was downloaded/added/
last refreshed - the input for HTTP Age computation and staleness
heuristics.

- CacheEntryMeta/CachedTile gain a `fetched` field (before `expires`);
  CacheEntryMeta::new is now (fetched, expires, etag).
- update_cached_meta bumps it on 304 revalidation along with
  expires/etag.
- Cache-to-cache copies (any layout direction) preserve it; bulk copies
  from non-cache sources leave it NULL rather than stamping copy time,
  keeping identical copy runs byte-identical (existing CLI golden
  outputs did not change).
- Schema detection now expects the six shared columns + one layout
  column.

BREAKING CHANGE: cache schema DDL and `CacheEntryMeta::new` signature
changed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop the normalized (de-duplicated) cache variant and everything that
existed only to support it, leaving one straightforward layout:

    tile_cache(zoom_level, tile_column, tile_row,
               fetched, expires, etag, tile_data)

plus the spec-compatible tiles view.

Removed: the CacheSchema enum and every schema-dispatching code path,
the cache_data blob table, the xxh3_64_int SQLite function, content-key
linear probing and its CacheKeyExhausted/CacheCopyCollision errors, the
copier's blob-insert + collision-verification passes, orphan-blob GC in
the purge functions, and the cache-normalized per-tile validation
(cache now skips hash checks exactly like flat).

MbtType::Cache is a unit variant again, the CLI value is just `cache`,
set_cached is a plain upsert, and cache-to-cache copies are a single
INSERT..SELECT that preserves fetched/expires/etag.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@nyurik
nyurik marked this pull request as ready for review July 19, 2026 05:13
Comment thread mbtiles/sql/init-cache.sql Outdated
Comment thread mbtiles/src/copier.rs Outdated
Comment thread tests/test.sh Outdated
Comment thread mbtiles/tests/copy.rs Outdated
Comment thread mbtiles/tests/copy.rs

@CommanderStorm CommanderStorm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not going to get better and if there is somethign wrong, we can also fix it in main.

@CommanderStorm
CommanderStorm enabled auto-merge (squash) July 19, 2026 09:46
@github-actions github-actions Bot removed the bless label Jul 19, 2026
@CommanderStorm
CommanderStorm merged commit f4140bd into maplibre:main Jul 19, 2026
49 checks passed
@nyurik
nyurik deleted the cache branch July 19, 2026 18:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants