wip: limited schema migration capability
This commit is contained in:
parent
dcfa8bd313
commit
a6e501f3e5
@ -172,4 +172,22 @@ async fn test_reference_without_content_address() {
|
||||
assert!(matches!(result, Err(StoreError::NoSuchContentAddress)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_schema_version_management() {
|
||||
let store = create_test_store().await;
|
||||
|
||||
// Verify the schema version is correctly set
|
||||
let version = store.get_current_schema_version().await.unwrap();
|
||||
assert_eq!(version, 1, "Schema version should be 1");
|
||||
|
||||
// Verify we can still perform basic operations
|
||||
let reference = Reference::new(
|
||||
Some("test_content".to_string()),
|
||||
"test_schema_version".to_string(),
|
||||
);
|
||||
|
||||
store.store_reference(&reference).await.unwrap();
|
||||
let retrieved = store.get_reference(&reference.id).await.unwrap();
|
||||
assert_eq!(retrieved.name, reference.name);
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,14 @@
|
||||
use std::sync::Arc;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
use thiserror::Error;
|
||||
use offline_web_model::Reference;
|
||||
use sqlx::{Pool, Row, Sqlite, SqlitePool};
|
||||
|
||||
// Schema version constants
|
||||
const CURRENT_SCHEMA_VERSION: i32 = 1;
|
||||
const INITIAL_SCHEMA_VERSION: i32 = 0;
|
||||
|
||||
pub struct SqliteReferenceStore {
|
||||
pool: Pool<Sqlite>,
|
||||
}
|
||||
@ -33,7 +38,95 @@ impl SqliteReferenceStore {
|
||||
.await
|
||||
.map_err(|e| StoreError::StorageError(Box::new(e)))?;
|
||||
|
||||
// Create tables if they don't exist
|
||||
let store = Self { pool };
|
||||
|
||||
// Check current schema version and migrate if necessary
|
||||
let current_version = store.get_current_schema_version().await?;
|
||||
if current_version != CURRENT_SCHEMA_VERSION {
|
||||
store.migrate_schema(current_version, CURRENT_SCHEMA_VERSION).await?;
|
||||
}
|
||||
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
async fn get_current_schema_version(&self) -> Result<i32, StoreError> {
|
||||
// First, ensure the schema_version table exists
|
||||
sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS schema_version (
|
||||
version INTEGER PRIMARY KEY,
|
||||
applied_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
description TEXT
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(|e| StoreError::StorageError(Box::new(e)))?;
|
||||
|
||||
// Get the current version
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
SELECT version FROM schema_version ORDER BY version DESC LIMIT 1
|
||||
"#,
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_err(|e| StoreError::StorageError(Box::new(e)))?;
|
||||
|
||||
match row {
|
||||
Some(row) => {
|
||||
let version: i32 = row.get("version");
|
||||
Ok(version)
|
||||
}
|
||||
None => {
|
||||
// No version found, this is a fresh database
|
||||
Ok(INITIAL_SCHEMA_VERSION)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn migrate_schema(&self, from_version: i32, to_version: i32) -> Result<(), StoreError> {
|
||||
if from_version == to_version {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if from_version > to_version {
|
||||
return Err(StoreError::StorageError(
|
||||
"Downward migrations not currently supported".into()
|
||||
));
|
||||
}
|
||||
|
||||
// Use a transaction for the entire migration process
|
||||
let mut tx = self.pool.begin().await
|
||||
.map_err(|e| StoreError::StorageError(Box::new(e)))?;
|
||||
|
||||
// Apply migrations step by step
|
||||
let mut current_version = from_version;
|
||||
while current_version < to_version {
|
||||
match current_version {
|
||||
0 => {
|
||||
// Migration from version 0 to 1: Initial schema setup
|
||||
self.migrate_to_v1(&mut tx).await?;
|
||||
current_version = 1;
|
||||
}
|
||||
_ => {
|
||||
return Err(StoreError::StorageError(
|
||||
format!("Unknown migration path from version {}", current_version).into()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Commit all migrations
|
||||
tx.commit().await
|
||||
.map_err(|e| StoreError::StorageError(Box::new(e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn migrate_to_v1(&self, tx: &mut sqlx::Transaction<'_, Sqlite>) -> Result<(), StoreError> {
|
||||
// Create the main application tables
|
||||
sqlx::query(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS ref_entries (
|
||||
@ -43,7 +136,7 @@ impl SqliteReferenceStore {
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(&pool)
|
||||
.execute(&mut **tx)
|
||||
.await
|
||||
.map_err(|e| StoreError::StorageError(Box::new(e)))?;
|
||||
|
||||
@ -58,7 +151,7 @@ impl SqliteReferenceStore {
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(&pool)
|
||||
.execute(&mut **tx)
|
||||
.await
|
||||
.map_err(|e| StoreError::StorageError(Box::new(e)))?;
|
||||
|
||||
@ -70,11 +163,24 @@ impl SqliteReferenceStore {
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.execute(&pool)
|
||||
.execute(&mut **tx)
|
||||
.await
|
||||
.map_err(|e| StoreError::StorageError(Box::new(e)))?;
|
||||
|
||||
Ok(Self { pool })
|
||||
// Record the schema version
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT OR REPLACE INTO schema_version (version, description)
|
||||
VALUES (?, ?)
|
||||
"#,
|
||||
)
|
||||
.bind(1)
|
||||
.bind("Initial schema with ref_entries, ref_dependencies, and content_store tables")
|
||||
.execute(&mut **tx)
|
||||
.await
|
||||
.map_err(|e| StoreError::StorageError(Box::new(e)))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn store_reference(&self, reference: &Reference) -> Result<(), StoreError> {
|
||||
@ -299,8 +405,8 @@ impl SqliteReferenceStore {
|
||||
.map_err(|e| StoreError::StorageError(Box::new(e)))?;
|
||||
|
||||
// Build the dependency tree iteratively
|
||||
let mut reference_map: std::collections::HashMap<String, Reference> = std::collections::HashMap::new();
|
||||
let mut children_map: std::collections::HashMap<String, Vec<String>> = std::collections::HashMap::new();
|
||||
let mut reference_map: HashMap<String, Reference> = HashMap::new();
|
||||
let mut children_map: HashMap<String, Vec<String>> = HashMap::new();
|
||||
|
||||
// First pass: create all references and build the children map
|
||||
for row in &rows {
|
||||
@ -321,7 +427,7 @@ impl SqliteReferenceStore {
|
||||
}
|
||||
|
||||
// Second pass: build the dependency tree from bottom up (highest depth first)
|
||||
let mut depth_groups: std::collections::BTreeMap<i32, Vec<String>> = std::collections::BTreeMap::new();
|
||||
let mut depth_groups: BTreeMap<i32, Vec<String>> = BTreeMap::new();
|
||||
for row in &rows {
|
||||
let id: String = row.get("id");
|
||||
let depth: i32 = row.get("depth");
|
||||
|
Loading…
x
Reference in New Issue
Block a user