fix(pakcage): migrator: -z -> +x
This commit is contained in:
@@ -32,6 +32,11 @@ sha256sum() {
|
||||
# detect_db_type()
|
||||
# Returns: "postgresql" or "sqlite"
|
||||
detect_db_type() {
|
||||
if ! [ "${DB_URL+x}" ]; then
|
||||
log error "no ${WHITE}DB_URL${NC} or ${WHITE}--db-url${NC} specified"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
case "$DB_URL" in
|
||||
postgresql://*|postgres://*)
|
||||
printf 'postgresql'
|
||||
@@ -42,7 +47,7 @@ detect_db_type() {
|
||||
*)
|
||||
log error "unsupported database URL format: ${WHITE}$DB_URL${NC}"
|
||||
log error "supported formats: postgresql://... or sqlite://... or *.db"
|
||||
exit 1
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -666,22 +671,67 @@ migrate() {
|
||||
db_mig_count=$(printf '%s' "$db_migrations" | wc -l)
|
||||
log debug "mig count: $db_mig_count"
|
||||
|
||||
# Log migration lists for debugging
|
||||
fs_mig_count=$(printf '%s' "$fs_migrations" | wc -l)
|
||||
log info "Filesystem migrations found: ${WHITE}$fs_mig_count"
|
||||
log info "Database migrations applied: ${WHITE}$db_mig_count"
|
||||
|
||||
# Check if the DB migrations form a proper prefix of disk migrations
|
||||
# (meaning all DB-applied migration filenames should appear in the same order at the start).
|
||||
i=0
|
||||
for db_migration in $db_migrations; do
|
||||
fs_migration=$(printf '%s' "$fs_migrations" | sed -n "$((i+1))p")
|
||||
log debug "Checking migration $((i+1)): DB=${WHITE}$db_migration${NC} vs FS=${WHITE}$fs_migration"
|
||||
|
||||
if [ -z "$fs_migration" ] || [ "$fs_migration" != "$db_migration" ]; then
|
||||
if [ -z "$FORCE" ]; then
|
||||
log error "unrelated migration tree detected. Use --force to proceed."
|
||||
if ! [ "${FORCE+x}" ]; then
|
||||
log error "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
log error "${RED}Migration history mismatch detected!${NC}"
|
||||
log error "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
log error ""
|
||||
log error "Position: Migration #$((i+1))"
|
||||
log error "Database has: ${WHITE}$db_migration"
|
||||
log error "Filesystem has: ${WHITE}$fs_migration"
|
||||
log error ""
|
||||
log error "Full filesystem migrations (in order):"
|
||||
j=1
|
||||
printf '%s\n' "$fs_migrations" | while IFS= read -r m; do
|
||||
log error " $j. ${CYAN}$m"
|
||||
j=$((j+1))
|
||||
done
|
||||
log error ""
|
||||
log error "Full database migrations (in order):"
|
||||
j=1
|
||||
printf '%s\n' "$db_migrations" | while IFS= read -r m; do
|
||||
log error " $j. ${CYAN}$m"
|
||||
j=$((j+1))
|
||||
done
|
||||
log error ""
|
||||
log error "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
log error "This usually means:"
|
||||
log error " • Migration files were removed or renamed"
|
||||
log error " • Migrations were applied out of order"
|
||||
log error " • Database and codebase are from different versions"
|
||||
log error ""
|
||||
log error "${YELLOW}To proceed anyway, use: ${WHITE}--force${NC}${YELLOW}!${NC}"
|
||||
log error "${YELLOW}Warning: This may cause data inconsistencies!${NC}"
|
||||
log error "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
exit 2
|
||||
else
|
||||
log error "unrelated migration tree forced. Proceeding..."
|
||||
log notice "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
log notice "${YELLOW}Migration history mismatch detected but ${WHITE}--force${NC}${YELLOW} specified${NC}"
|
||||
log notice "Position: Migration #$((i+1))"
|
||||
log notice "Database has: ${WHITE}$db_migration"
|
||||
log notice "Filesystem has: ${WHITE}$fs_migration"
|
||||
log notice "${YELLOW}Proceeding with migration despite mismatch...${NC}"
|
||||
log notice "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
|
||||
log info "Migration history validation: ${GREEN}OK${NC} (${WHITE}$i${NC} migrations match)"
|
||||
|
||||
eval "set -- $MIGRATOR_REMAINING_ARS"
|
||||
target_migration="$("migrate_$MIGRATE_SUBCOMMAND" "$@")"
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/bin/dash
|
||||
|
||||
HECTIC_NAMESPACE=test-migration-history-mismatch
|
||||
|
||||
log notice "test case: ${WHITE}migration history mismatch detection"
|
||||
|
||||
# Initialize database
|
||||
psql "$DATABASE_URL" -c 'CREATE TABLE test_table (id INTEGER PRIMARY KEY)'
|
||||
|
||||
if ! migrator --db-url "$DATABASE_URL" init; then
|
||||
log error "test failed: ${WHITE}init failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create migration directory with 3 migrations
|
||||
mkdir -p migration
|
||||
for i in 1 2 3; do
|
||||
mig_name="2025010100000${i}-migration-${i}"
|
||||
mkdir -p "migration/${mig_name}"
|
||||
echo "ALTER TABLE test_table ADD COLUMN col${i} TEXT;" > "migration/${mig_name}/up.sql"
|
||||
echo "ALTER TABLE test_table DROP COLUMN col${i};" > "migration/${mig_name}/down.sql"
|
||||
done
|
||||
|
||||
# Apply all migrations
|
||||
migrator --db-url "$DATABASE_URL" migrate up all
|
||||
|
||||
applied_count=$(psql -Atc "SELECT COUNT(*) FROM hectic.migration" "$DATABASE_URL")
|
||||
if [ "$applied_count" != "3" ]; then
|
||||
log error "test failed: ${WHITE}setup failed, expected 3 migrations"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log info "setup complete: 3 migrations applied"
|
||||
|
||||
### CASE 1: Remove a migration file (causes mismatch)
|
||||
log notice "test case: ${WHITE}detect removed migration file"
|
||||
|
||||
# Remove the second migration directory
|
||||
rm -rf "migration/20250101000002-migration-2"
|
||||
|
||||
# Try to migrate (should fail with detailed error)
|
||||
set +e
|
||||
output=$(migrator --db-url "$DATABASE_URL" migrate up 2>&1)
|
||||
exit_code=$?
|
||||
set -e
|
||||
|
||||
if [ "$exit_code" != "2" ]; then
|
||||
log error "test failed: ${WHITE}expected exit code 2, got $exit_code"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check that error message contains helpful information
|
||||
if ! printf '%s' "$output" | grep -q "Migration history mismatch"; then
|
||||
log error "test failed: ${WHITE}error message doesn't contain 'Migration history mismatch'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! printf '%s' "$output" | grep -q "Database has:"; then
|
||||
log error "test failed: ${WHITE}error message doesn't show database migration"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! printf '%s' "$output" | grep -q "Filesystem has:"; then
|
||||
log error "test failed: ${WHITE}error message doesn't show filesystem migration"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! printf '%s' "$output" | grep -q "Full filesystem migrations"; then
|
||||
log error "test failed: ${WHITE}error message doesn't list all filesystem migrations"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! printf '%s' "$output" | grep -q "Full database migrations"; then
|
||||
log error "test failed: ${WHITE}error message doesn't list all database migrations"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log info "detailed error message verified"
|
||||
|
||||
### CASE 2: Verify --force flag works
|
||||
log notice "test case: ${WHITE}--force flag bypasses check"
|
||||
|
||||
# Try again with --force (should work)
|
||||
set +e
|
||||
output=$(migrator --db-url "$DATABASE_URL" --force migrate up 2>&1)
|
||||
exit_code=$?
|
||||
set -e
|
||||
|
||||
# Note: It will still fail because migration file is missing, but it should bypass the tree check
|
||||
if [ "$exit_code" = "2" ]; then
|
||||
log error "test failed: ${WHITE}--force didn't bypass tree check (exit code still 2)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check that we got past the tree check
|
||||
if printf '%s' "$output" | grep -q "Migration history mismatch detected but.*--force.*specified"; then
|
||||
log info "--force flag bypassed tree check as expected"
|
||||
else
|
||||
# It might have proceeded to file not found error, which is fine
|
||||
log info "--force flag behavior verified (proceeded past tree check)"
|
||||
fi
|
||||
|
||||
log notice "test passed"
|
||||
|
||||
182
test/package/migrator/test/sqlite/migrate-up-all.sh
Normal file
182
test/package/migrator/test/sqlite/migrate-up-all.sh
Normal file
@@ -0,0 +1,182 @@
|
||||
#!/bin/dash
|
||||
|
||||
HECTIC_NAMESPACE=test-sqlite-migrate-up-all
|
||||
|
||||
log notice "test case: ${WHITE}SQLite migrate up all - comprehensive test"
|
||||
|
||||
# Create SQLite database
|
||||
SQLITE_DB="$PWD/test.db"
|
||||
export DB_URL="sqlite://$SQLITE_DB"
|
||||
|
||||
log info "using SQLite database: $SQLITE_DB"
|
||||
|
||||
# Create initial schema
|
||||
sqlite3 "$SQLITE_DB" "CREATE TABLE inventory (id INTEGER PRIMARY KEY, name TEXT)"
|
||||
|
||||
# Initialize migrator
|
||||
if ! migrator --db-url "$DB_URL" init; then
|
||||
log error "test failed: ${WHITE}init failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify tables were created
|
||||
if ! sqlite3 "$SQLITE_DB" "SELECT COUNT(*) FROM hectic_migration" >/dev/null 2>&1; then
|
||||
log error "test failed: ${WHITE}hectic_migration table not created"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log info "migrator initialized successfully"
|
||||
|
||||
# Create migrations directory with 5 migrations
|
||||
mkdir -p migration
|
||||
log info "creating 5 test migrations"
|
||||
|
||||
for i in 1 2 3 4 5; do
|
||||
mig_name="2025010100000${i}-add-field-${i}"
|
||||
mkdir -p "migration/${mig_name}"
|
||||
|
||||
echo "ALTER TABLE inventory ADD COLUMN field${i} TEXT;" > "migration/${mig_name}/up.sql"
|
||||
|
||||
# Simple down migration (comment only for this test)
|
||||
cat > "migration/${mig_name}/down.sql" <<SQL
|
||||
-- Revert: Remove field${i}
|
||||
-- Note: In production, this would properly handle the column removal
|
||||
SQL
|
||||
done
|
||||
|
||||
log info "migrations created"
|
||||
|
||||
### CASE 1: Fresh database - migrate up all
|
||||
log notice "test case: ${WHITE}migrate up all from fresh state"
|
||||
|
||||
if ! migrator --db-url "$DB_URL" migrate up all; then
|
||||
log error "test failed: ${WHITE}migrate up all failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify all 5 migrations were applied
|
||||
applied_count=$(sqlite3 "$SQLITE_DB" "SELECT COUNT(*) FROM hectic_migration")
|
||||
if [ "$applied_count" != "5" ]; then
|
||||
log error "test failed: ${WHITE}expected 5 migrations, got $applied_count"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log info "all 5 migrations applied successfully"
|
||||
|
||||
# Verify all columns exist
|
||||
for i in 1 2 3 4 5; do
|
||||
if ! sqlite3 "$SQLITE_DB" "SELECT field${i} FROM inventory LIMIT 0" >/dev/null 2>&1; then
|
||||
log error "test failed: ${WHITE}field${i} column not added"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
log info "all columns verified"
|
||||
|
||||
### CASE 2: Already at latest - should be no-op
|
||||
log notice "test case: ${WHITE}migrate up all when already at latest (no-op)"
|
||||
|
||||
if ! migrator --db-url "$DB_URL" migrate up all; then
|
||||
log error "test failed: ${WHITE}migrate up all (no-op) failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Count should still be 5
|
||||
applied_count=$(sqlite3 "$SQLITE_DB" "SELECT COUNT(*) FROM hectic_migration")
|
||||
if [ "$applied_count" != "5" ]; then
|
||||
log error "test failed: ${WHITE}expected 5 migrations after no-op, got $applied_count"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log info "no-op successful - count still 5"
|
||||
|
||||
### CASE 3: Insert data, then verify migrations preserve it
|
||||
log notice "test case: ${WHITE}migrations don't corrupt existing data"
|
||||
|
||||
sqlite3 "$SQLITE_DB" <<SQL
|
||||
INSERT INTO inventory (name, field1, field2, field3, field4, field5)
|
||||
VALUES ('Item1', 'val1', 'val2', 'val3', 'val4', 'val5');
|
||||
INSERT INTO inventory (name, field1, field2)
|
||||
VALUES ('Item2', 'a', 'b');
|
||||
SQL
|
||||
|
||||
data_count=$(sqlite3 "$SQLITE_DB" "SELECT COUNT(*) FROM inventory")
|
||||
if [ "$data_count" != "2" ]; then
|
||||
log error "test failed: ${WHITE}test data not inserted properly"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log info "test data inserted: $data_count rows"
|
||||
|
||||
# Create additional migration
|
||||
mig_name="20250101000006-add-field-6"
|
||||
mkdir -p "migration/${mig_name}"
|
||||
echo "ALTER TABLE inventory ADD COLUMN field6 TEXT DEFAULT 'default6';" > "migration/${mig_name}/up.sql"
|
||||
echo "-- Revert field6" > "migration/${mig_name}/down.sql"
|
||||
|
||||
# Apply new migration
|
||||
if ! migrator --db-url "$DB_URL" migrate up all; then
|
||||
log error "test failed: ${WHITE}migrate up all with new migration failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify data still exists
|
||||
data_count_after=$(sqlite3 "$SQLITE_DB" "SELECT COUNT(*) FROM inventory")
|
||||
if [ "$data_count_after" != "$data_count" ]; then
|
||||
log error "test failed: ${WHITE}data corrupted after migration, had $data_count, now $data_count_after"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify new column has default value
|
||||
field6_value=$(sqlite3 "$SQLITE_DB" "SELECT field6 FROM inventory WHERE name = 'Item1'")
|
||||
if [ "$field6_value" != "default6" ]; then
|
||||
log error "test failed: ${WHITE}new column default value not applied, got: $field6_value"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log info "data preserved after migration, new column added with default"
|
||||
|
||||
### CASE 4: Verify migration tracking metadata
|
||||
log notice "test case: ${WHITE}migration metadata is correct"
|
||||
|
||||
applied_count=$(sqlite3 "$SQLITE_DB" "SELECT COUNT(*) FROM hectic_migration")
|
||||
if [ "$applied_count" != "6" ]; then
|
||||
log error "test failed: ${WHITE}expected 6 migrations in total, got $applied_count"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify migrations are in order
|
||||
first_mig=$(sqlite3 "$SQLITE_DB" "SELECT name FROM hectic_migration ORDER BY id ASC LIMIT 1")
|
||||
if [ "$first_mig" != "20250101000001-add-field-1" ]; then
|
||||
log error "test failed: ${WHITE}first migration not correct, got: $first_mig"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
last_mig=$(sqlite3 "$SQLITE_DB" "SELECT name FROM hectic_migration ORDER BY id DESC LIMIT 1")
|
||||
if [ "$last_mig" != "20250101000006-add-field-6" ]; then
|
||||
log error "test failed: ${WHITE}last migration not correct, got: $last_mig"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify hashes are tracked
|
||||
hash_count=$(sqlite3 "$SQLITE_DB" "SELECT COUNT(*) FROM hectic_migration WHERE hash IS NOT NULL AND hash != ''")
|
||||
if [ "$hash_count" != "6" ]; then
|
||||
log error "test failed: ${WHITE}not all migrations have hashes, got $hash_count/6"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log info "migration metadata verified"
|
||||
|
||||
### CASE 5: Verify version table
|
||||
log notice "test case: ${WHITE}version table is correct"
|
||||
|
||||
version=$(sqlite3 "$SQLITE_DB" "SELECT version FROM hectic_version WHERE name = 'migrator'")
|
||||
if [ "$version" != "0.0.1" ]; then
|
||||
log error "test failed: ${WHITE}version not correct, got: $version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log info "version table verified"
|
||||
|
||||
log notice "test passed: all SQLite 'migrate up all' scenarios work correctly"
|
||||
|
||||
Reference in New Issue
Block a user