Skip to content

Commit c32a72d

Browse files
authored
Add ListMetadataRepository for paginated list state tracking (#1069)
* Add list metadata repository to wp_mobile_cache Introduces database infrastructure for tracking list metadata, enabling efficient cache invalidation and stale detection for paginated lists. Changes: - Add migration for list metadata tables - Add DbListMetadata types for database operations - Add ListMetadata domain types - Add ListMetadataRepository with full CRUD operations - Update PostsRepository with stale detection helpers Extracted from prototype/metadata-collection branch. Original commits: - 14b01d80 (Implement stale detection by comparing modified_gmt timestamps) - e47cec89 (Add database foundation for MetadataService) - 2440a13f (Add list metadata repository concurrency helpers) - cc0c8a58 (Reset stale fetching states on app launch) - d64142fb (Split collection observers for data vs state updates) - fe7435c9 (make fmt-rust) - 2918b339 (Add parent and menu_order fields to list metadata items) - 25f88a49 (Rename last_updated_at to last_fetched_at in list metadata) - 1d709e70 (Simplify ListMetadataReader trait with combined ListInfo query) Note: Since we use a rebase/squash merge strategy, these commits may show "does not belong to any branch" on GitHub but remain accessible via URL. * Store ListState as INTEGER instead of TEXT Changes the database storage for `ListState` from TEXT strings to INTEGER values for better performance and type safety. Changes: - Update migration to use `INTEGER NOT NULL DEFAULT 0` for state column - Add `#[repr(i32)]` to `ListState` enum with explicit discriminant values - Implement `ToSql` and `FromSql` traits for direct rusqlite integration - Remove string-based `as_db_str()` and `From<&str>` implementations - Update all callers to use the enum directly with rusqlite params * Return error for invalid ListState values instead of silent fallback The `FromSql` implementation now returns a proper error when encountering an unknown integer value, rather than silently defaulting to `Idle`. This makes data corruption issues visible instead of hiding them. * Convert ListMetadataRepository methods to associated functions The repository struct has no state, so methods are converted from instance methods (&self) to associated functions. This removes the need to instantiate the struct before calling methods. Before: ListMetadataRepository.get_header(&conn, &site, key) After: ListMetadataRepository::get_header(&conn, &site, key) * Use batch insert for list metadata items Replaces individual INSERT statements with a single batch INSERT for better performance when inserting multiple items. Uses functional style with try_for_each and flat_map. Items are chunked to stay under SQLite's variable limit (999). * Use JOIN query internally in get_state_by_key Replaces two separate queries (get_header + get_state) with a single JOIN query via get_header_with_state for better efficiency. * Add ListKey newtype for type-safe list key handling Replaces raw `&str` parameters with `&ListKey` throughout the repository API. This prevents accidental misuse of arbitrary strings as list keys and makes the API more self-documenting. The ListKey type provides: - Type safety at compile time - Conversion from &str and String via From trait - as_str() for SQL parameter usage - Display impl for debug output * Simplify reset_stale_fetching_states and return Result - Condense doc comment, remove references to non-existent MetadataService - Return Result<usize, SqliteDbError> instead of logging internally - Ignore errors at call site with explanatory comment * Add FK from list_metadata_items to list_metadata Normalizes the schema by replacing (db_site_id, key) in items table with a foreign key to list_metadata. This: - Ensures referential integrity - Enables cascade delete (simplifies delete_list) - Reduces storage per item * Add log crate for structured logging Adds the `log` facade crate to enable proper logging. Debug logs added to key list metadata operations: - begin_refresh, begin_fetch_next_page - complete_sync, complete_sync_with_error - set_items, append_items, delete_list Replaces eprintln! with log::warn! for unknown table warnings. * Add ToSql/FromSql for ListKey and log stale state reset errors Implement ToSql/FromSql traits for ListKey to simplify repository code by using the type directly in SQL params instead of .as_str(). Also add logging for reset_stale_fetching_states failures instead of silently ignoring errors. Changes: - Add ToSql/FromSql implementations for ListKey - Replace key.as_str() with key in all SQL params - Log warning on reset_stale_fetching_states failure * Remove unused update hook helper functions * Remove select_modified_gmt_by_ids from this PR * Split get_items and get_item_count into by_list_metadata_id and by_list_key variants Allows callers who already have the list_metadata_id to skip an extra header lookup query. * Remove redundant check_version function * Add by_list_metadata_id variants for write operations Split functions that take site+key to allow callers with an existing list_metadata_id to skip the header lookup query. Renamed functions for consistency: - set_items → set_items_by_list_metadata_id / set_items_by_list_key - append_items → append_items_by_list_metadata_id / append_items_by_list_key - update_header → update_header_by_list_metadata_id / update_header_by_list_key - increment_version → increment_version_by_list_metadata_id / increment_version_by_list_key - get_state → get_state_by_list_metadata_id - get_state_by_key → get_state_by_list_key - update_state → update_state_by_list_metadata_id - update_state_by_key → update_state_by_list_key * Replace unwrap with expect for better panic messages * Update migration count in Kotlin and Swift tests * Handle race condition in get_or_create Add `ConstraintViolation` variant to `SqliteDbError` to distinguish UNIQUE constraint violations from other SQLite errors. Use this in `ListMetadataRepository::get_or_create` to handle the rare case where another thread creates the same header between our SELECT and INSERT. Changes: - Add `SqliteDbError::ConstraintViolation` variant - Update `From<rusqlite::Error>` to detect constraint violations - Update `get_or_create` to catch constraint violations and re-fetch * Add optimized get_or_create_and_increment_version method Add `get_or_create_and_increment_version` that uses a single `INSERT ... ON CONFLICT DO UPDATE ... RETURNING` query to atomically create or update a header while incrementing its version. This reduces `begin_refresh` from 5-6 queries to 2 queries: - Before: get_or_create (1-2) + increment_version (2) + update_state (1) + get_header (1) - After: get_or_create_and_increment_version (1) + update_state (1) Changes: - Add `HeaderVersionInfo` struct for the return type - Add `get_or_create_and_increment_version` method using RETURNING clause - Update `begin_refresh` to use the new optimized method - Add tests for the new method * Remove standalone increment_version methods Remove `increment_version_by_list_metadata_id` and `increment_version_by_list_key` since version increment should only happen through `get_or_create_and_increment_version` as part of a proper refresh flow, not called arbitrarily. * Remove concurrency helpers from repository layer Move concurrency orchestration logic out of the repository layer. These helpers (`begin_refresh`, `begin_fetch_next_page`, `complete_sync`, `complete_sync_with_error`) belong in a service layer since they: - Orchestrate multiple repository operations - Apply business rules (checking page counts, deciding state transitions) - Return domain-specific result types The repository now provides clean primitives that a service layer can compose: - `get_or_create_and_increment_version` for atomic create/increment - `update_state_by_list_metadata_id` for state changes - `get_header`, `get_items`, etc. for reads * Move reset_stale_fetching_states to ListMetadataRepository Move `reset_stale_fetching_states_internal` from `WpApiCache` to `ListMetadataRepository` where it belongs. The repository owns the state table and should provide all operations on it. `WpApiCache::perform_migrations` still calls this method after migrations complete, but now delegates to the repository. * Make per_page a required parameter with validation The repository layer should not dictate per_page values - this must be set by the service layer to match networking configuration. Changes: - Add PerPageMismatch error variant to SqliteDbError - Make per_page a required parameter in get_or_create - Make per_page required in get_or_create_and_increment_version - Update set_items_by_list_key to require per_page - Update append_items_by_list_key to require per_page - Update update_state_by_list_key to require per_page - Return error when existing header has different per_page * Use PRAGMA-based column enum tests for list_metadata Update schema validation tests to use `get_table_column_names` helper, which verifies that column enum indices match actual database schema positions via PRAGMA table_info. This matches the pattern used by posts repository tests and provides stronger guarantees that the column enums won't break if migrations reorder columns.
1 parent 2389f4d commit c32a72d

File tree

12 files changed

+1684
-5
lines changed

12 files changed

+1684
-5
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ hyper-util = "0.1.19"
4545
indoc = "2.0"
4646
itertools = "0.14.0"
4747
linkify = "0.10.0"
48+
log = "0.4"
4849
parse_link_header = "0.4"
4950
paste = "1.0"
5051
proc-macro-crate = "3.4.0"

native/kotlin/api/kotlin/src/integrationTest/kotlin/WordPressApiCacheTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ class WordPressApiCacheTest {
1010

1111
@Test
1212
fun testThatMigrationsWork() = runTest {
13-
assertEquals(6, WordPressApiCache().performMigrations())
13+
assertEquals(7, WordPressApiCache().performMigrations())
1414
}
1515
}

native/swift/Tests/wordpress-api-cache/WordPressApiCacheTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ actor Test {
1616

1717
@Test func testMigrationsWork() async throws {
1818
let migrationsPerformed = try await self.cache.performMigrations()
19-
#expect(migrationsPerformed == 6)
19+
#expect(migrationsPerformed == 7)
2020
}
2121

2222
#if !os(Linux)

wp_mobile_cache/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ default = ["rusqlite/bundled"]
88
test-helpers = ["dep:chrono", "dep:integration_test_credentials", "dep:rstest"]
99

1010
[dependencies]
11+
log = { workspace = true }
1112
rusqlite = { workspace = true, features = ["hooks"] }
1213
serde = { workspace = true }
1314
serde_json = { workspace = true }
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
-- Table 1: List header/pagination info
2+
CREATE TABLE `list_metadata` (
3+
`rowid` INTEGER PRIMARY KEY AUTOINCREMENT,
4+
`db_site_id` INTEGER NOT NULL,
5+
`key` TEXT NOT NULL, -- e.g., "edit:posts:publish"
6+
`total_pages` INTEGER,
7+
`total_items` INTEGER,
8+
`current_page` INTEGER NOT NULL DEFAULT 0,
9+
`per_page` INTEGER NOT NULL DEFAULT 20,
10+
`last_first_page_fetched_at` TEXT,
11+
`last_fetched_at` TEXT,
12+
`version` INTEGER NOT NULL DEFAULT 0,
13+
14+
FOREIGN KEY (db_site_id) REFERENCES db_sites(id) ON DELETE CASCADE
15+
) STRICT;
16+
17+
CREATE UNIQUE INDEX idx_list_metadata_unique_key ON list_metadata(db_site_id, key);
18+
19+
-- Table 2: List items (rowid = insertion order = display order)
20+
CREATE TABLE `list_metadata_items` (
21+
`rowid` INTEGER PRIMARY KEY AUTOINCREMENT,
22+
`list_metadata_id` INTEGER NOT NULL,
23+
`entity_id` INTEGER NOT NULL, -- post/comment/etc ID
24+
`modified_gmt` TEXT, -- nullable for entities without it
25+
`parent` INTEGER, -- parent post ID (for hierarchical post types like pages)
26+
`menu_order` INTEGER, -- menu order (for hierarchical post types)
27+
28+
FOREIGN KEY (list_metadata_id) REFERENCES list_metadata(rowid) ON DELETE CASCADE
29+
) STRICT;
30+
31+
CREATE INDEX idx_list_metadata_items_list ON list_metadata_items(list_metadata_id);
32+
CREATE INDEX idx_list_metadata_items_entity ON list_metadata_items(list_metadata_id, entity_id);
33+
34+
-- Table 3: Sync state (FK to list_metadata, not duplicating key)
35+
CREATE TABLE `list_metadata_state` (
36+
`rowid` INTEGER PRIMARY KEY AUTOINCREMENT,
37+
`list_metadata_id` INTEGER NOT NULL,
38+
`state` INTEGER NOT NULL DEFAULT 0, -- 0=idle, 1=fetching_first_page, 2=fetching_next_page, 3=error
39+
`error_message` TEXT,
40+
`updated_at` TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
41+
42+
FOREIGN KEY (list_metadata_id) REFERENCES list_metadata(rowid) ON DELETE CASCADE
43+
) STRICT;
44+
45+
CREATE UNIQUE INDEX idx_list_metadata_state_unique ON list_metadata_state(list_metadata_id);

wp_mobile_cache/src/db_types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod db_list_metadata;
12
pub mod db_site;
23
pub mod db_term_relationship;
34
pub mod helpers;
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
use crate::{
2+
SqliteDbError,
3+
db_types::row_ext::{ColumnIndex, RowExt},
4+
list_metadata::{
5+
DbListHeaderWithState, DbListMetadata, DbListMetadataItem, DbListMetadataState, ListState,
6+
},
7+
};
8+
use rusqlite::Row;
9+
10+
/// Column indexes for list_metadata table.
11+
/// These must match the order of columns in the CREATE TABLE statement.
12+
#[repr(usize)]
13+
#[derive(Debug, Clone, Copy)]
14+
pub enum ListMetadataColumn {
15+
Rowid = 0,
16+
DbSiteId = 1,
17+
Key = 2,
18+
TotalPages = 3,
19+
TotalItems = 4,
20+
CurrentPage = 5,
21+
PerPage = 6,
22+
LastFirstPageFetchedAt = 7,
23+
LastFetchedAt = 8,
24+
Version = 9,
25+
}
26+
27+
impl ColumnIndex for ListMetadataColumn {
28+
fn as_index(&self) -> usize {
29+
*self as usize
30+
}
31+
}
32+
33+
impl DbListMetadata {
34+
/// Construct a list metadata entity from a database row.
35+
pub fn from_row(row: &Row) -> Result<Self, SqliteDbError> {
36+
use ListMetadataColumn as Col;
37+
38+
Ok(Self {
39+
row_id: row.get_column(Col::Rowid)?,
40+
db_site_id: row.get_column(Col::DbSiteId)?,
41+
key: row.get_column(Col::Key)?,
42+
total_pages: row.get_column(Col::TotalPages)?,
43+
total_items: row.get_column(Col::TotalItems)?,
44+
current_page: row.get_column(Col::CurrentPage)?,
45+
per_page: row.get_column(Col::PerPage)?,
46+
last_first_page_fetched_at: row.get_column(Col::LastFirstPageFetchedAt)?,
47+
last_fetched_at: row.get_column(Col::LastFetchedAt)?,
48+
version: row.get_column(Col::Version)?,
49+
})
50+
}
51+
}
52+
53+
/// Column indexes for list_metadata_items table.
54+
/// These must match the order of columns in the CREATE TABLE statement.
55+
#[repr(usize)]
56+
#[derive(Debug, Clone, Copy)]
57+
pub enum ListMetadataItemColumn {
58+
Rowid = 0,
59+
ListMetadataId = 1,
60+
EntityId = 2,
61+
ModifiedGmt = 3,
62+
Parent = 4,
63+
MenuOrder = 5,
64+
}
65+
66+
impl ColumnIndex for ListMetadataItemColumn {
67+
fn as_index(&self) -> usize {
68+
*self as usize
69+
}
70+
}
71+
72+
impl DbListMetadataItem {
73+
/// Construct a list metadata item from a database row.
74+
pub fn from_row(row: &Row) -> Result<Self, SqliteDbError> {
75+
use ListMetadataItemColumn as Col;
76+
77+
Ok(Self {
78+
row_id: row.get_column(Col::Rowid)?,
79+
list_metadata_id: row.get_column(Col::ListMetadataId)?,
80+
entity_id: row.get_column(Col::EntityId)?,
81+
modified_gmt: row.get_column(Col::ModifiedGmt)?,
82+
parent: row.get_column(Col::Parent)?,
83+
menu_order: row.get_column(Col::MenuOrder)?,
84+
})
85+
}
86+
}
87+
88+
/// Column indexes for list_metadata_state table.
89+
/// These must match the order of columns in the CREATE TABLE statement.
90+
#[repr(usize)]
91+
#[derive(Debug, Clone, Copy)]
92+
pub enum ListMetadataStateColumn {
93+
Rowid = 0,
94+
ListMetadataId = 1,
95+
State = 2,
96+
ErrorMessage = 3,
97+
UpdatedAt = 4,
98+
}
99+
100+
impl ColumnIndex for ListMetadataStateColumn {
101+
fn as_index(&self) -> usize {
102+
*self as usize
103+
}
104+
}
105+
106+
impl DbListMetadataState {
107+
/// Construct a list metadata state from a database row.
108+
pub fn from_row(row: &Row) -> Result<Self, SqliteDbError> {
109+
use ListMetadataStateColumn as Col;
110+
111+
Ok(Self {
112+
row_id: row.get_column(Col::Rowid)?,
113+
list_metadata_id: row.get_column(Col::ListMetadataId)?,
114+
state: row.get_column(Col::State)?,
115+
error_message: row.get_column(Col::ErrorMessage)?,
116+
updated_at: row.get_column(Col::UpdatedAt)?,
117+
})
118+
}
119+
}
120+
121+
/// Column indexes for the header + state JOIN query.
122+
///
123+
/// Query: SELECT m.total_pages, m.total_items, m.current_page, m.per_page, s.state, s.error_message
124+
/// FROM list_metadata m LEFT JOIN list_metadata_state s ON ...
125+
#[repr(usize)]
126+
#[derive(Debug, Clone, Copy)]
127+
pub enum ListHeaderWithStateColumn {
128+
TotalPages = 0,
129+
TotalItems = 1,
130+
CurrentPage = 2,
131+
PerPage = 3,
132+
State = 4,
133+
ErrorMessage = 5,
134+
}
135+
136+
impl ColumnIndex for ListHeaderWithStateColumn {
137+
fn as_index(&self) -> usize {
138+
*self as usize
139+
}
140+
}
141+
142+
impl DbListHeaderWithState {
143+
/// Construct from a JOIN query row.
144+
///
145+
/// Expects columns in order: total_pages, total_items, current_page, per_page, state, error_message
146+
pub fn from_row(row: &Row) -> Result<Self, SqliteDbError> {
147+
use ListHeaderWithStateColumn as Col;
148+
149+
// state is nullable due to LEFT JOIN - default to Idle
150+
let state: Option<ListState> = row.get_column(Col::State)?;
151+
152+
Ok(Self {
153+
state: state.unwrap_or(ListState::Idle),
154+
error_message: row.get_column(Col::ErrorMessage)?,
155+
current_page: row.get_column(Col::CurrentPage)?,
156+
total_pages: row.get_column(Col::TotalPages)?,
157+
total_items: row.get_column(Col::TotalItems)?,
158+
per_page: row.get_column(Col::PerPage)?,
159+
})
160+
}
161+
}

wp_mobile_cache/src/lib.rs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput};
33
use rusqlite::{Connection, Result as SqliteResult, params};
44
use std::sync::Mutex;
55

6+
use crate::repository::list_metadata::ListMetadataRepository;
7+
68
pub mod context;
79
pub mod db_types;
810
pub mod entity;
11+
pub mod list_metadata;
912
pub mod repository;
1013
pub mod term_relationships;
1114

@@ -15,13 +18,18 @@ pub mod test_fixtures;
1518
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, uniffi::Error)]
1619
pub enum SqliteDbError {
1720
SqliteError(String),
21+
ConstraintViolation(String),
1822
TableNameMismatch { expected: DbTable, actual: DbTable },
23+
PerPageMismatch { expected: i64, actual: i64 },
1924
}
2025

2126
impl std::fmt::Display for SqliteDbError {
2227
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2328
match self {
2429
SqliteDbError::SqliteError(message) => write!(f, "SqliteDbError: message={}", message),
30+
SqliteDbError::ConstraintViolation(message) => {
31+
write!(f, "Constraint violation: {}", message)
32+
}
2533
SqliteDbError::TableNameMismatch { expected, actual } => {
2634
write!(
2735
f,
@@ -30,12 +38,24 @@ impl std::fmt::Display for SqliteDbError {
3038
actual.table_name()
3139
)
3240
}
41+
SqliteDbError::PerPageMismatch { expected, actual } => {
42+
write!(
43+
f,
44+
"per_page mismatch: expected {}, but list has {}",
45+
expected, actual
46+
)
47+
}
3348
}
3449
}
3550
}
3651

3752
impl From<rusqlite::Error> for SqliteDbError {
3853
fn from(err: rusqlite::Error) -> Self {
54+
if let rusqlite::Error::SqliteFailure(sqlite_err, _) = &err
55+
&& sqlite_err.code == rusqlite::ErrorCode::ConstraintViolation
56+
{
57+
return SqliteDbError::ConstraintViolation(err.to_string());
58+
}
3959
SqliteDbError::SqliteError(err.to_string())
4060
}
4161
}
@@ -64,6 +84,12 @@ pub enum DbTable {
6484
DbSites,
6585
/// Term relationships (post-category, post-tag associations)
6686
TermRelationships,
87+
/// List metadata headers (pagination, version)
88+
ListMetadata,
89+
/// List metadata items (entity IDs with ordering)
90+
ListMetadataItems,
91+
/// List metadata sync state (idle, fetching, error)
92+
ListMetadataState,
6793
}
6894

6995
impl DbTable {
@@ -79,6 +105,9 @@ impl DbTable {
79105
DbTable::SelfHostedSites => "self_hosted_sites",
80106
DbTable::DbSites => "db_sites",
81107
DbTable::TermRelationships => "term_relationships",
108+
DbTable::ListMetadata => "list_metadata",
109+
DbTable::ListMetadataItems => "list_metadata_items",
110+
DbTable::ListMetadataState => "list_metadata_state",
82111
}
83112
}
84113
}
@@ -107,6 +136,9 @@ impl TryFrom<&str> for DbTable {
107136
"self_hosted_sites" => Ok(DbTable::SelfHostedSites),
108137
"db_sites" => Ok(DbTable::DbSites),
109138
"term_relationships" => Ok(DbTable::TermRelationships),
139+
"list_metadata" => Ok(DbTable::ListMetadata),
140+
"list_metadata_items" => Ok(DbTable::ListMetadataItems),
141+
"list_metadata_state" => Ok(DbTable::ListMetadataState),
110142
_ => Err(DbTableError::UnknownTable(table_name.to_string())),
111143
}
112144
}
@@ -249,7 +281,17 @@ impl WpApiCache {
249281
pub fn perform_migrations(&self) -> Result<i64, SqliteDbError> {
250282
self.execute(|connection| {
251283
let mut mgr = MigrationManager::new(connection)?;
252-
mgr.perform_migrations().map_err(SqliteDbError::from)
284+
let version = mgr.perform_migrations().map_err(SqliteDbError::from)?;
285+
286+
// Reset stale fetching states after migrations complete.
287+
// Errors are logged but not propagated: this is a best-effort cleanup,
288+
// and failure doesn't affect core functionality (worst case: UI shows
289+
// stale loading state).
290+
if let Err(e) = ListMetadataRepository::reset_stale_fetching_states(connection) {
291+
log::warn!("Failed to reset stale fetching states: {}", e);
292+
}
293+
294+
Ok(version)
253295
})
254296
}
255297

@@ -271,7 +313,7 @@ impl WpApiCache {
271313
// Ignore SQLite system tables (sqlite_sequence, sqlite_master, etc.)
272314
// and migration tracking table (_migrations)
273315
if !table_name.starts_with("sqlite_") && table_name != "_migrations" {
274-
eprintln!("Warning: Unknown table in update hook: {}", table_name);
316+
log::warn!("Unknown table in update hook: {}", table_name);
275317
}
276318
}
277319
}
@@ -350,13 +392,14 @@ impl From<Connection> for WpApiCache {
350392
}
351393
}
352394

353-
static MIGRATION_QUERIES: [&str; 6] = [
395+
static MIGRATION_QUERIES: [&str; 7] = [
354396
include_str!("../migrations/0001-create-sites-table.sql"),
355397
include_str!("../migrations/0002-create-posts-table.sql"),
356398
include_str!("../migrations/0003-create-term-relationships.sql"),
357399
include_str!("../migrations/0004-create-posts-view-context-table.sql"),
358400
include_str!("../migrations/0005-create-posts-embed-context-table.sql"),
359401
include_str!("../migrations/0006-create-self-hosted-sites-table.sql"),
402+
include_str!("../migrations/0007-create-list-metadata-tables.sql"),
360403
];
361404

362405
pub struct MigrationManager<'a> {

0 commit comments

Comments
 (0)