feat: introduce migrate.create-schemas

This commit is contained in:
Austin Bonander
2025-01-22 15:32:50 -08:00
parent 45c0b85b4c
commit 3765f67aba
17 changed files with 383 additions and 185 deletions

View File

@@ -15,6 +15,7 @@ use std::time::Duration;
use std::time::Instant;
pub(crate) use sqlx_core::migrate::*;
use sqlx_core::query_scalar::query_scalar;
impl MigrateDatabase for Sqlite {
fn create_database(url: &str) -> BoxFuture<'_, Result<(), Error>> {
@@ -64,10 +65,35 @@ impl MigrateDatabase for Sqlite {
}
impl Migrate for SqliteConnection {
fn ensure_migrations_table<'e>(&'e mut self, table_name: &'e str) -> BoxFuture<'e, Result<(), MigrateError>> {
fn create_schema_if_not_exists<'e>(
&'e mut self,
schema_name: &'e str,
) -> BoxFuture<'e, Result<(), MigrateError>> {
Box::pin(async move {
// Check if the schema already exists; if so, don't error.
let schema_version: Option<i64> =
query_scalar(&format!("PRAGMA {schema_name}.schema_version"))
.fetch_optional(&mut *self)
.await?;
if schema_version.is_some() {
return Ok(());
}
Err(MigrateError::CreateSchemasNotSupported(
format!("cannot create new schema {schema_name}; creation of additional schemas in SQLite requires attaching extra database files"),
))
})
}
fn ensure_migrations_table<'e>(
&'e mut self,
table_name: &'e str,
) -> BoxFuture<'e, Result<(), MigrateError>> {
Box::pin(async move {
// language=SQLite
self.execute(&*format!(r#"
self.execute(&*format!(
r#"
CREATE TABLE IF NOT EXISTS {table_name} (
version BIGINT PRIMARY KEY,
description TEXT NOT NULL,
@@ -76,20 +102,23 @@ CREATE TABLE IF NOT EXISTS {table_name} (
checksum BLOB NOT NULL,
execution_time BIGINT NOT NULL
);
"#),
)
.await?;
"#
))
.await?;
Ok(())
})
}
fn dirty_version<'e>(&'e mut self, table_name: &'e str) -> BoxFuture<'e, Result<Option<i64>, MigrateError>> {
fn dirty_version<'e>(
&'e mut self,
table_name: &'e str,
) -> BoxFuture<'e, Result<Option<i64>, MigrateError>> {
Box::pin(async move {
// language=SQLite
let row: Option<(i64,)> = query_as(
&format!("SELECT version FROM {table_name} WHERE success = false ORDER BY version LIMIT 1"),
)
let row: Option<(i64,)> = query_as(&format!(
"SELECT version FROM {table_name} WHERE success = false ORDER BY version LIMIT 1"
))
.fetch_optional(self)
.await?;
@@ -97,13 +126,17 @@ CREATE TABLE IF NOT EXISTS {table_name} (
})
}
fn list_applied_migrations<'e>(&'e mut self, table_name: &'e str) -> BoxFuture<'e, Result<Vec<AppliedMigration>, MigrateError>> {
fn list_applied_migrations<'e>(
&'e mut self,
table_name: &'e str,
) -> BoxFuture<'e, Result<Vec<AppliedMigration>, MigrateError>> {
Box::pin(async move {
// language=SQLite
let rows: Vec<(i64, Vec<u8>)> =
query_as(&format!("SELECT version, checksum FROM {table_name} ORDER BY version"))
.fetch_all(self)
.await?;
let rows: Vec<(i64, Vec<u8>)> = query_as(&format!(
"SELECT version, checksum FROM {table_name} ORDER BY version"
))
.fetch_all(self)
.await?;
let migrations = rows
.into_iter()
@@ -145,12 +178,12 @@ CREATE TABLE IF NOT EXISTS {table_name} (
.map_err(|e| MigrateError::ExecuteMigration(e, migration.version))?;
// language=SQL
let _ = query(
&format!(r#"
let _ = query(&format!(
r#"
INSERT INTO {table_name} ( version, description, success, checksum, execution_time )
VALUES ( ?1, ?2, TRUE, ?3, -1 )
"#),
)
"#
))
.bind(migration.version)
.bind(&*migration.description)
.bind(&*migration.checksum)
@@ -167,13 +200,13 @@ CREATE TABLE IF NOT EXISTS {table_name} (
// language=SQL
#[allow(clippy::cast_possible_truncation)]
let _ = query(
&format!(r#"
let _ = query(&format!(
r#"
UPDATE {table_name}
SET execution_time = ?1
WHERE version = ?2
"#),
)
"#
))
.bind(elapsed.as_nanos() as i64)
.bind(migration.version)
.execute(self)