v0.2.0
This commit is contained in:
1 parent
fc81ecafd6
commit
dc57aa7089
89 files changed
+5290
-1551
No files matched your search
+78
-19
@@ -20,10 +20,20 @@ const SQLITE_SCHEMA: &str = include_str!("../../migrations/sqlite/schema.sql");
|
||||
const MYSQL_SCHEMA: &str = include_str!("../../migrations/mysql/schema.sql");
|
||||
const PGSQL_SCHEMA: &str = include_str!("../../migrations/pgsql/schema.sql");
|
||||
|
||||
// Future migrations start at V047. Each entry must exist for every supported engine.
|
||||
const SQLITE_MIGRATIONS: &[(u32, &str)] = &[];
|
||||
const MYSQL_MIGRATIONS: &[(u32, &str)] = &[];
|
||||
const PGSQL_MIGRATIONS: &[(u32, &str)] = &[];
|
||||
// The squashed legacy baseline is V046. Runtime migrations start at V047 and
|
||||
// are stored as real files for every supported SQL engine.
|
||||
const SQLITE_MIGRATIONS: &[(u32, &str)] = &[(
|
||||
47,
|
||||
include_str!("../../migrations/sqlite/V047__blob_keys_and_search_outbox.sql"),
|
||||
)];
|
||||
const MYSQL_MIGRATIONS: &[(u32, &str)] = &[(
|
||||
47,
|
||||
include_str!("../../migrations/mysql/V047__blob_keys_and_search_outbox.sql"),
|
||||
)];
|
||||
const PGSQL_MIGRATIONS: &[(u32, &str)] = &[(
|
||||
47,
|
||||
include_str!("../../migrations/pgsql/V047__blob_keys_and_search_outbox.sql"),
|
||||
)];
|
||||
|
||||
const TENANT_TABLES: &[&str] = &[
|
||||
"calendar_events",
|
||||
@@ -48,6 +58,7 @@ const TENANT_TABLES: &[&str] = &[
|
||||
"pgp_preferences",
|
||||
"response_templates",
|
||||
"saved_searches",
|
||||
"search_outbox",
|
||||
"special_folders",
|
||||
"tags",
|
||||
"trusted_senders",
|
||||
@@ -79,6 +90,7 @@ const IMPORT_TABLES: &[&str] = &[
|
||||
"pgp_preferences",
|
||||
"response_templates",
|
||||
"saved_searches",
|
||||
"search_outbox",
|
||||
"special_folders",
|
||||
"trusted_senders",
|
||||
"user_meta",
|
||||
@@ -96,7 +108,7 @@ impl DatabaseBackend {
|
||||
match value.trim().to_ascii_lowercase().as_str() {
|
||||
"sqlite" => Ok(Self::Sqlite),
|
||||
"mysql" => Ok(Self::Mysql),
|
||||
"pgsql" | "postgres" | "postgresql" => Ok(Self::Pgsql),
|
||||
"pgsql" => Ok(Self::Pgsql),
|
||||
other => Err(format!("unsupported database backend: {other}")),
|
||||
}
|
||||
}
|
||||
@@ -291,6 +303,46 @@ pub fn configured_backend() -> DatabaseBackend {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list_account_user_hashes() -> Result<Vec<String>, String> {
|
||||
match DATABASE.get() {
|
||||
Some(ConfiguredDatabase::Sqlite { path }) => {
|
||||
let conn = rusqlite::Connection::open(path)
|
||||
.map_err(|e| format!("failed to open SQLite account registry: {e}"))?;
|
||||
let mut stmt = conn
|
||||
.prepare("SELECT user_hash FROM rustbox_accounts ORDER BY user_hash")
|
||||
.map_err(|e| format!("failed to prepare account registry query: {e}"))?;
|
||||
let rows = stmt
|
||||
.query_map([], |row| row.get::<_, String>(0))
|
||||
.map_err(|e| format!("failed to query account registry: {e}"))?;
|
||||
rows.map(|row| row.map_err(|e| format!("failed to read account registry row: {e}")))
|
||||
.collect()
|
||||
}
|
||||
Some(ConfiguredDatabase::Mysql { pool, runtime }) => {
|
||||
let pool = pool.clone();
|
||||
runtime_wait(runtime, async move {
|
||||
sqlx::query_scalar::<_, String>(
|
||||
"SELECT user_hash FROM rustbox_accounts ORDER BY user_hash",
|
||||
)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(|e| format!("failed to query MySQL account registry: {e}"))
|
||||
})
|
||||
}
|
||||
Some(ConfiguredDatabase::Pgsql { pool, runtime }) => {
|
||||
let pool = pool.clone();
|
||||
runtime_wait(runtime, async move {
|
||||
sqlx::query_scalar::<_, String>(
|
||||
"SELECT user_hash FROM rustbox_accounts ORDER BY user_hash",
|
||||
)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(|e| format!("failed to query PostgreSQL account registry: {e}"))
|
||||
})
|
||||
}
|
||||
None => Err("database is not configured".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! dbq {
|
||||
($module:ident::$name:ident) => {{
|
||||
@@ -1562,12 +1614,16 @@ fn read_legacy_sqlite(path: &Path) -> Result<Vec<LegacyTableData>, String> {
|
||||
.map_err(|e| format!("failed to read legacy SQLite schema version: {e}"))?;
|
||||
if version < BASE_SCHEMA_VERSION {
|
||||
return Err(format!(
|
||||
"legacy SQLite schema version {version} is older than the RustBox 0.1.2 baseline {BASE_SCHEMA_VERSION}; upgrade with RustBox 0.1.0 first"
|
||||
"legacy SQLite schema version {version} is older than the legacy RustBox baseline {BASE_SCHEMA_VERSION}; upgrade with RustBox 0.1.0 first"
|
||||
));
|
||||
}
|
||||
if version > BASE_SCHEMA_VERSION {
|
||||
let latest_supported_sqlite = SQLITE_MIGRATIONS
|
||||
.last()
|
||||
.map(|(migration_version, _)| *migration_version)
|
||||
.unwrap_or(BASE_SCHEMA_VERSION);
|
||||
if version > latest_supported_sqlite {
|
||||
return Err(format!(
|
||||
"legacy SQLite schema version {version} is newer than the RustBox 0.1.2 baseline {BASE_SCHEMA_VERSION}"
|
||||
"legacy SQLite schema version {version} is newer than the latest SQLite schema supported by this build ({latest_supported_sqlite})"
|
||||
));
|
||||
}
|
||||
let mut output = Vec::new();
|
||||
@@ -1904,7 +1960,7 @@ fn ensure_sqlite_tenant(
|
||||
.map_err(|e| format!("failed to initialize SQLite tenant defaults: {e}"))?;
|
||||
tx.execute(
|
||||
"INSERT INTO rustbox_accounts(user_hash, table_prefix, schema_version) VALUES(?1, ?2, ?3)",
|
||||
rusqlite::params![user_hash, prefix, BASE_SCHEMA_VERSION],
|
||||
rusqlite::params![user_hash, prefix, latest_schema_version()],
|
||||
)
|
||||
.map_err(|e| format!("failed to register SQLite account schema: {e}"))?;
|
||||
tx.commit().map_err(|e| e.to_string())?;
|
||||
@@ -1922,7 +1978,7 @@ fn apply_sqlite_migrations(
|
||||
) -> Result<(), String> {
|
||||
if current < BASE_SCHEMA_VERSION {
|
||||
return Err(format!(
|
||||
"database schema version {current} is older than the RustBox 0.1.2 baseline {BASE_SCHEMA_VERSION}; upgrade with RustBox 0.1.0 first"
|
||||
"database schema version {current} is older than the legacy RustBox baseline {BASE_SCHEMA_VERSION}; upgrade with RustBox 0.1.0 first"
|
||||
));
|
||||
}
|
||||
let latest = latest_schema_version();
|
||||
@@ -1967,7 +2023,8 @@ fn ensure_server_tenant(
|
||||
user_hash: &str,
|
||||
prefix: &str,
|
||||
) -> Result<(), String> {
|
||||
let legacy_path = Path::new(data_dir).join(user_hash).join("db.sqlite");
|
||||
let legacy_path = (!data_dir.trim().is_empty())
|
||||
.then(|| Path::new(data_dir).join(user_hash).join("db.sqlite"));
|
||||
|
||||
let imported_legacy = match configured {
|
||||
ConfiguredDatabase::Mysql { pool, runtime } => {
|
||||
@@ -2015,8 +2072,8 @@ fn ensure_server_tenant(
|
||||
.bind(&prefix).fetch_optional(&pool).await.map_err(|e| e.to_string())?;
|
||||
if collision.is_some() { return Err("database account-prefix collision detected".into()); }
|
||||
|
||||
let legacy_data = if legacy_path.is_file() {
|
||||
read_legacy_sqlite(&legacy_path)?
|
||||
let legacy_data = if let Some(path) = legacy_path.as_ref().filter(|path| path.is_file()) {
|
||||
read_legacy_sqlite(path)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
@@ -2035,7 +2092,7 @@ fn ensure_server_tenant(
|
||||
return Err(format!("failed to initialize MySQL tenant defaults: {error}"));
|
||||
}
|
||||
if let Err(error) = sqlx::query("INSERT INTO rustbox_accounts(user_hash, table_prefix, schema_version) VALUES(?, ?, ?)")
|
||||
.bind(&user_hash).bind(&prefix).bind(i64::from(BASE_SCHEMA_VERSION)).execute(&pool).await
|
||||
.bind(&user_hash).bind(&prefix).bind(i64::from(latest_schema_version())).execute(&pool).await
|
||||
{
|
||||
drop_mysql_tenant(&pool, &prefix).await;
|
||||
return Err(format!("failed to register MySQL account schema: {error}"));
|
||||
@@ -2090,8 +2147,8 @@ fn ensure_server_tenant(
|
||||
.bind(&prefix).fetch_optional(&pool).await.map_err(|e| e.to_string())?;
|
||||
if collision.is_some() { return Err("database account-prefix collision detected".into()); }
|
||||
|
||||
let legacy_data = if legacy_path.is_file() {
|
||||
read_legacy_sqlite(&legacy_path)?
|
||||
let legacy_data = if let Some(path) = legacy_path.as_ref().filter(|path| path.is_file()) {
|
||||
read_legacy_sqlite(path)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
@@ -2110,7 +2167,7 @@ fn ensure_server_tenant(
|
||||
return Err(format!("failed to initialize PostgreSQL tenant defaults: {error}"));
|
||||
}
|
||||
if let Err(error) = sqlx::query("INSERT INTO rustbox_accounts(user_hash, table_prefix, schema_version) VALUES($1, $2, $3)")
|
||||
.bind(&user_hash).bind(&prefix).bind(i64::from(BASE_SCHEMA_VERSION)).execute(&pool).await
|
||||
.bind(&user_hash).bind(&prefix).bind(i64::from(latest_schema_version())).execute(&pool).await
|
||||
{
|
||||
drop_pgsql_tenant(&pool, &prefix).await;
|
||||
return Err(format!("failed to register PostgreSQL account schema: {error}"));
|
||||
@@ -2128,7 +2185,9 @@ fn ensure_server_tenant(
|
||||
};
|
||||
|
||||
if imported_legacy {
|
||||
backup_legacy_sqlite(&legacy_path);
|
||||
if let Some(path) = legacy_path.as_ref() {
|
||||
backup_legacy_sqlite(path);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -2215,7 +2274,7 @@ pub fn open_test_db() -> Connection {
|
||||
.unwrap();
|
||||
raw.execute(
|
||||
"INSERT INTO rustbox_accounts(user_hash, table_prefix, schema_version) VALUES(?1, ?2, ?3)",
|
||||
rusqlite::params![user_hash, prefix, BASE_SCHEMA_VERSION],
|
||||
rusqlite::params![user_hash, prefix, latest_schema_version()],
|
||||
)
|
||||
.unwrap();
|
||||
Connection {
|
||||
|
||||
Reference in new issue
Block a user