Skip to main content

mbtiles/schemas/
cache.rs

1//! The `cache` schema: a non-standard tile-cache layout used by [`crate::CachedTile`].
2//!
3//! A single `tile_cache` table stores tiles with `fetched`/`expires`/`etag` metadata next
4//! to the inline tile blob, plus a spec-compatible `tiles` view. This is not part of the
5//! `MBTiles` specification. See the `cache` module for the read/write API.
6
7use sqlx::{SqliteExecutor, query};
8use tracing::debug;
9
10use crate::errors::MbtResult;
11use crate::queries::create_schema;
12
13/// Check if the database uses the tile-cache schema: a `tile_cache` table with
14/// `zoom_level`, `tile_column`, `tile_row`, `fetched`, `expires`, `etag`, and
15/// `tile_data` columns.
16///
17/// This is a non-standard schema (not part of the `MBTiles` specification) used by
18/// [`crate::CachedTile`] to store tiles with cache metadata. See the `cache` module for
19/// the read/write API.
20pub async fn is_cache_tables_type<T>(conn: &mut T) -> MbtResult<bool>
21where
22    for<'e> &'e mut T: SqliteExecutor<'e>,
23{
24    let sql = query!(
25        "SELECT (
26             -- Has a 'tile_cache' table
27             SELECT COUNT(*) = 1
28             FROM sqlite_master
29             WHERE name = 'tile_cache' AND type = 'table'
30             --
31         ) AND (
32             -- 'tile_cache' table's columns and their types are as expected:
33             -- 7 columns (zoom_level, tile_column, tile_row, fetched, expires, etag,
34             -- tile_data). The order is not important
35             SELECT COUNT(*) = 7
36             FROM pragma_table_info('tile_cache')
37             WHERE ((name = 'zoom_level' AND type LIKE '%INT%')
38                 OR (name = 'tile_column' AND type LIKE '%INT%')
39                 OR (name = 'tile_row' AND type LIKE '%INT%')
40                 OR (name = 'fetched' AND type LIKE '%INT%')
41                 OR (name = 'expires' AND type LIKE '%INT%')
42                 OR (name = 'etag' AND type = 'TEXT')
43                 OR (name = 'tile_data' AND type = 'BLOB'))
44             --
45         ) AS is_valid;"
46    );
47
48    Ok(sql.fetch_one(&mut *conn).await?.is_valid == 1)
49}
50
51/// Create the tile-cache table and the standard `tiles` view (if they don't already exist).
52///
53/// - `tile_cache(zoom_level, tile_column, tile_row, fetched, expires, etag, tile_data)`
54///   clustered on `(zoom_level, tile_column, tile_row)`, blob stored inline.
55/// - `tiles` view: a spec-compatible `(zoom_level, tile_column, tile_row, tile_data)`
56///   view so the file can still be read as a normal `MBTiles` file.
57///
58/// See the `cache` module for the read/write API.
59pub async fn create_cache_tables<T>(conn: &mut T, strict: bool) -> MbtResult<()>
60where
61    for<'e> &'e mut T: SqliteExecutor<'e>,
62{
63    debug!(
64        "Creating if needed cache table and tiles view: tile_cache(z,x,y,fetched,expires,etag,data)"
65    );
66    create_schema(conn, include_str!("../../sql/init-cache.sql"), strict).await
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use crate::metadata::anonymous_mbtiles;
73    use crate::{
74        is_dedup_id_normalized_tables_type, is_flat_tables_type, is_flat_with_hash_tables_type,
75        is_normalized_tables_type,
76    };
77
78    #[actix_rt::test]
79    async fn create_and_detect_cache() {
80        let (_mbt, mut conn) = anonymous_mbtiles("").await;
81        create_cache_tables(&mut conn, false).await.unwrap();
82
83        assert!(is_cache_tables_type(&mut conn).await.unwrap());
84        assert!(!is_flat_tables_type(&mut conn).await.unwrap());
85        assert!(!is_flat_with_hash_tables_type(&mut conn).await.unwrap());
86        assert!(!is_normalized_tables_type(&mut conn).await.unwrap());
87        assert!(!is_dedup_id_normalized_tables_type(&mut conn).await.unwrap());
88    }
89}