Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .github/workflows/sqlx-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ jobs:
- ubuntu-latest
# FIXME: migrations tests fail on Windows for whatever reason
# - windows-latest
- macOS-13
- macOS-latest

timeout-minutes: 30
Expand Down Expand Up @@ -302,7 +301,6 @@ jobs:
os:
- ubuntu-latest
- windows-latest
- macOS-13
- macOS-latest
include:
- os: ubuntu-latest
Expand All @@ -312,9 +310,6 @@ jobs:
- os: windows-latest
target: x86_64-pc-windows-msvc
bin: target/debug/cargo-sqlx.exe
- os: macOS-13
target: x86_64-apple-darwin
bin: target/debug/cargo-sqlx
- os: macOS-latest
target: aarch64-apple-darwin
bin: target/debug/cargo-sqlx
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/sqlx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,17 @@ jobs:
SQLX_OFFLINE_DIR: .sqlx
RUSTFLAGS: --cfg mysql_${{ matrix.mysql }}

# Run template database cloning tests
- run: >
cargo test
--test mysql-template
--no-default-features
--features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }}
env:
DATABASE_URL: mysql://root:password@localhost:3306/sqlx?ssl-mode=disabled
SQLX_OFFLINE_DIR: .sqlx
RUSTFLAGS: --cfg mysql_${{ matrix.mysql }}

# MySQL 5.7 supports TLS but not TLSv1.3 as required by RusTLS.
- if: ${{ !(matrix.mysql == '5_7' && matrix.tls == 'rustls') }}
run: >
Expand Down Expand Up @@ -472,6 +483,17 @@ jobs:
SQLX_OFFLINE_DIR: .sqlx
RUSTFLAGS: --cfg mariadb="${{ matrix.mariadb }}"

# Run template database cloning tests
- run: >
cargo test
--test mysql-template
--no-default-features
--features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }}
env:
DATABASE_URL: mysql://root:password@localhost:3306/sqlx
SQLX_OFFLINE_DIR: .sqlx
RUSTFLAGS: --cfg mariadb="${{ matrix.mariadb }}"

# Remove test artifacts
- run: cargo clean -p sqlx

Expand Down
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,11 @@ name = "mysql-test-attr"
path = "tests/mysql/test-attr.rs"
required-features = ["mysql", "macros", "migrate"]

[[test]]
name = "mysql-template"
path = "tests/mysql/template.rs"
required-features = ["mysql", "macros", "migrate"]

[[test]]
name = "mysql-migrate"
path = "tests/mysql/migrate.rs"
Expand Down
61 changes: 53 additions & 8 deletions sqlx-core/src/testing/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use std::future::Future;
use std::time::Duration;

use base64::{engine::general_purpose::URL_SAFE, Engine as _};
use base64::{
engine::general_purpose::{URL_SAFE, URL_SAFE_NO_PAD},
Engine as _,
};
pub use fixtures::FixtureSnapshot;
use sha2::{Digest, Sha512};
use sha2::{Digest, Sha256, Sha512};

use crate::connection::{ConnectOptions, Connection};
use crate::database::Database;
Expand All @@ -14,6 +17,35 @@ use crate::pool::{Pool, PoolConnection, PoolOptions};

mod fixtures;

/// Compute a combined hash of all migrations for template invalidation.
///
/// This hash is used to name template databases. When migrations change,
/// a new hash is generated, resulting in a new template being created.
pub fn migrations_hash(migrator: &Migrator) -> String {
let mut hasher = Sha256::new();

for migration in migrator.iter() {
// Include version, type, and checksum in the hash
hasher.update(migration.version.to_le_bytes());
hasher.update([migration.migration_type as u8]);
hasher.update(&*migration.checksum);
}

let hash = hasher.finalize();
// Use first 16 bytes (128 bits) for a reasonably short but unique name
URL_SAFE_NO_PAD.encode(&hash[..16])
}

/// Generate a template database name from a migrations hash.
///
/// Template names follow the pattern `_sqlx_template_<hash>` and are kept
/// under 63 characters to respect database identifier limits.
pub fn template_db_name(migrations_hash: &str) -> String {
// Replace any characters that might cause issues in database names
let safe_hash = migrations_hash.replace(['-', '+', '/'], "_");
format!("_sqlx_template_{}", safe_hash)
}

pub trait TestSupport: Database {
/// Get parameters to construct a `Pool` suitable for testing.
///
Expand Down Expand Up @@ -82,6 +114,9 @@ pub struct TestContext<DB: Database> {
pub pool_opts: PoolOptions<DB>,
pub connect_opts: <DB::Connection as Connection>::Options,
pub db_name: String,
/// Whether this test database was created from a template.
/// When true, migrations have already been applied and should be skipped.
pub from_template: bool,
}

impl<DB, Fut> TestFn for fn(Pool<DB>) -> Fut
Expand Down Expand Up @@ -226,7 +261,12 @@ where
.await
.expect("failed to connect to setup test database");

setup_test_db::<DB>(&test_context.connect_opts, &args).await;
setup_test_db::<DB>(
&test_context.connect_opts,
&args,
test_context.from_template,
)
.await;

let res = test_fn(test_context.pool_opts, test_context.connect_opts).await;

Expand All @@ -246,6 +286,7 @@ where
async fn setup_test_db<DB: Database>(
copts: &<DB::Connection as Connection>::Options,
args: &TestArgs,
from_template: bool,
) where
DB::Connection: Migrate + Sized,
for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>,
Expand All @@ -255,11 +296,15 @@ async fn setup_test_db<DB: Database>(
.await
.expect("failed to connect to test database");

if let Some(migrator) = args.migrator {
migrator
.run_direct(None, &mut conn)
.await
.expect("failed to apply migrations");
// Skip migrations if the database was cloned from a template
// (migrations were already applied to the template)
if !from_template {
if let Some(migrator) = args.migrator {
migrator
.run_direct(None, &mut conn)
.await
.expect("failed to apply migrations");
}
}

for fixture in args.fixtures {
Expand Down
Loading