From bb2ae34758615db2cf9e8e93e3adefae1f14e6f7 Mon Sep 17 00:00:00 2001 From: yukkop Date: Tue, 16 Dec 2025 17:28:36 +0000 Subject: [PATCH] feat(`package`): `migrator`: mvp --- package/migrator/migrator.sh | 160 ++++++++++++++++-- sus/bfs.xray.yaml | 6 +- test/package/hemar/lauch.sh | 8 +- .../20250101000001-add-name/down.sql | 2 + .../migration/20250101000001-add-name/up.sql | 2 + .../test/migrate-already-at-target/run.sh | 47 +++++ .../20250101000001-add-user-id/down.sql | 2 + .../20250101000001-add-user-id/up.sql | 2 + .../20250101000002-add-total/down.sql | 2 + .../migration/20250101000002-add-total/up.sql | 2 + .../20250101000003-add-status/down.sql | 2 + .../20250101000003-add-status/up.sql | 2 + .../20250101000004-add-created-at/down.sql | 2 + .../20250101000004-add-created-at/up.sql | 2 + .../test/migrate-down-multiple/run.sh | 53 ++++++ .../20250101000001-add-price/down.sql | 2 + .../migration/20250101000001-add-price/up.sql | 2 + .../20250101000002-add-description/down.sql | 2 + .../20250101000002-add-description/up.sql | 2 + .../migrator/test/migrate-down-single/run.sh | 53 ++++++ .../down.sql | 4 + .../up.sql | 9 + .../migrate-existing-data-migration/run.sh | 98 +++++++++++ .../20250101000001-add-user-bio/down.sql | 3 + .../20250101000001-add-user-bio/up.sql | 3 + .../20250101000002-add-comments/down.sql | 3 + .../20250101000002-add-comments/up.sql | 9 + .../test/migrate-existing-database/run.sh | 151 +++++++++++++++++ .../20250101000001-add-test-table/down.sql | 3 + .../20250101000001-add-test-table/up.sql | 6 + .../migrate-existing-with-conflicts/run.sh | 73 ++++++++ .../20250101000001-add-user-id/down.sql | 2 + .../20250101000001-add-user-id/up.sql | 2 + .../20250101000002-add-token/down.sql | 2 + .../migration/20250101000002-add-token/up.sql | 2 + .../20250101000003-add-expires-at/down.sql | 2 + .../20250101000003-add-expires-at/up.sql | 2 + .../migrator/test/migrate-to-backward/run.sh | 53 ++++++ .../20250101000001-add-content/down.sql | 2 + .../20250101000001-add-content/up.sql | 2 + .../20250101000002-add-user-id/down.sql | 2 + .../20250101000002-add-user-id/up.sql | 2 + .../migrator/test/migrate-to-forward/run.sh | 36 ++++ .../20250101000001-add-content/down.sql | 2 + .../20250101000001-add-content/up.sql | 2 + .../20250101000002-add-author/down.sql | 2 + .../20250101000002-add-author/up.sql | 2 + .../20250101000003-add-published-at/down.sql | 2 + .../20250101000003-add-published-at/up.sql | 2 + .../migrator/test/migrate-up-multiple/run.sh | 36 ++++ .../20250101000001-add-email/down.sql | 2 + .../migration/20250101000001-add-email/up.sql | 2 + .../migrator/test/migrate-up-single/run.sh | 36 ++++ .../20251104192425-add-info-to-profile/up.sql | 1 - test/package/migrator/test/migrate-up/run.sh | 21 --- 55 files changed, 890 insertions(+), 46 deletions(-) create mode 100644 test/package/migrator/test/migrate-already-at-target/migration/20250101000001-add-name/down.sql create mode 100644 test/package/migrator/test/migrate-already-at-target/migration/20250101000001-add-name/up.sql create mode 100644 test/package/migrator/test/migrate-already-at-target/run.sh create mode 100644 test/package/migrator/test/migrate-down-multiple/migration/20250101000001-add-user-id/down.sql create mode 100644 test/package/migrator/test/migrate-down-multiple/migration/20250101000001-add-user-id/up.sql create mode 100644 test/package/migrator/test/migrate-down-multiple/migration/20250101000002-add-total/down.sql create mode 100644 test/package/migrator/test/migrate-down-multiple/migration/20250101000002-add-total/up.sql create mode 100644 test/package/migrator/test/migrate-down-multiple/migration/20250101000003-add-status/down.sql create mode 100644 test/package/migrator/test/migrate-down-multiple/migration/20250101000003-add-status/up.sql create mode 100644 test/package/migrator/test/migrate-down-multiple/migration/20250101000004-add-created-at/down.sql create mode 100644 test/package/migrator/test/migrate-down-multiple/migration/20250101000004-add-created-at/up.sql create mode 100644 test/package/migrator/test/migrate-down-multiple/run.sh create mode 100644 test/package/migrator/test/migrate-down-single/migration/20250101000001-add-price/down.sql create mode 100644 test/package/migrator/test/migrate-down-single/migration/20250101000001-add-price/up.sql create mode 100644 test/package/migrator/test/migrate-down-single/migration/20250101000002-add-description/down.sql create mode 100644 test/package/migrator/test/migrate-down-single/migration/20250101000002-add-description/up.sql create mode 100644 test/package/migrator/test/migrate-down-single/run.sh create mode 100644 test/package/migrator/test/migrate-existing-data-migration/migration/20250101000001-transform-price-data/down.sql create mode 100644 test/package/migrator/test/migrate-existing-data-migration/migration/20250101000001-transform-price-data/up.sql create mode 100644 test/package/migrator/test/migrate-existing-data-migration/run.sh create mode 100644 test/package/migrator/test/migrate-existing-database/migration/20250101000001-add-user-bio/down.sql create mode 100644 test/package/migrator/test/migrate-existing-database/migration/20250101000001-add-user-bio/up.sql create mode 100644 test/package/migrator/test/migrate-existing-database/migration/20250101000002-add-comments/down.sql create mode 100644 test/package/migrator/test/migrate-existing-database/migration/20250101000002-add-comments/up.sql create mode 100644 test/package/migrator/test/migrate-existing-database/run.sh create mode 100644 test/package/migrator/test/migrate-existing-with-conflicts/migration/20250101000001-add-test-table/down.sql create mode 100644 test/package/migrator/test/migrate-existing-with-conflicts/migration/20250101000001-add-test-table/up.sql create mode 100644 test/package/migrator/test/migrate-existing-with-conflicts/run.sh create mode 100644 test/package/migrator/test/migrate-to-backward/migration/20250101000001-add-user-id/down.sql create mode 100644 test/package/migrator/test/migrate-to-backward/migration/20250101000001-add-user-id/up.sql create mode 100644 test/package/migrator/test/migrate-to-backward/migration/20250101000002-add-token/down.sql create mode 100644 test/package/migrator/test/migrate-to-backward/migration/20250101000002-add-token/up.sql create mode 100644 test/package/migrator/test/migrate-to-backward/migration/20250101000003-add-expires-at/down.sql create mode 100644 test/package/migrator/test/migrate-to-backward/migration/20250101000003-add-expires-at/up.sql create mode 100644 test/package/migrator/test/migrate-to-backward/run.sh create mode 100644 test/package/migrator/test/migrate-to-forward/migration/20250101000001-add-content/down.sql create mode 100644 test/package/migrator/test/migrate-to-forward/migration/20250101000001-add-content/up.sql create mode 100644 test/package/migrator/test/migrate-to-forward/migration/20250101000002-add-user-id/down.sql create mode 100644 test/package/migrator/test/migrate-to-forward/migration/20250101000002-add-user-id/up.sql create mode 100644 test/package/migrator/test/migrate-to-forward/run.sh create mode 100644 test/package/migrator/test/migrate-up-multiple/migration/20250101000001-add-content/down.sql create mode 100644 test/package/migrator/test/migrate-up-multiple/migration/20250101000001-add-content/up.sql create mode 100644 test/package/migrator/test/migrate-up-multiple/migration/20250101000002-add-author/down.sql create mode 100644 test/package/migrator/test/migrate-up-multiple/migration/20250101000002-add-author/up.sql create mode 100644 test/package/migrator/test/migrate-up-multiple/migration/20250101000003-add-published-at/down.sql create mode 100644 test/package/migrator/test/migrate-up-multiple/migration/20250101000003-add-published-at/up.sql create mode 100644 test/package/migrator/test/migrate-up-multiple/run.sh create mode 100644 test/package/migrator/test/migrate-up-single/migration/20250101000001-add-email/down.sql create mode 100644 test/package/migrator/test/migrate-up-single/migration/20250101000001-add-email/up.sql create mode 100644 test/package/migrator/test/migrate-up-single/run.sh delete mode 100644 test/package/migrator/test/migrate-up/migration/20251104192425-add-info-to-profile/up.sql delete mode 100644 test/package/migrator/test/migrate-up/run.sh diff --git a/package/migrator/migrator.sh b/package/migrator/migrator.sh index 6f42b25..52f02c3 100644 --- a/package/migrator/migrator.sh +++ b/package/migrator/migrator.sh @@ -123,7 +123,7 @@ BEGIN RAISE EXCEPTION 'Incampetible migrator versions: % and $VERSION', version; -- TODO(yukkop): show versions END IF; ELSE - CREATE DOMAIN hectic.migration_name AS TEXT CHECK (VALUE ~ '^[0-9]{15}-.*'); + CREATE DOMAIN hectic.migration_name AS TEXT CHECK (VALUE ~ '^[0-9]{14}-.*'); CREATE DOMAIN hectic.sha256 AS CHAR(64) CHECK (VALUE ~ '^[0-9a-f]{64}$'); CREATE FUNCTION hectic.sha256_lower() RETURNS trigger AS \$fn$ @@ -180,13 +180,34 @@ migrate_down() { exit 1; ;; *) - DOWN_NUMBER=$2 - shift 2; + DOWN_NUMBER=$1 + shift; ;; esac done - : "$DOWN_NUMBER" + # Calculate target migration: current - DOWN_NUMBER + if [ -z "$db_migrations" ]; then + log error "cannot migrate down: no migrations applied" + exit 1 + fi + + current_migration=$(printf '%s\n' "$db_migrations" | tail -n1) + current_idx=$(index_of "$fs_migrations" "$current_migration") + target_line=$((current_idx - DOWN_NUMBER)) + + if [ "$target_line" -lt 0 ]; then + log error "cannot migrate down $DOWN_NUMBER step(s): would go before first migration" + exit 1 + fi + + # target_line of 0 means migrate down to nothing (revert all) + if [ "$target_line" -eq 0 ]; then + printf '' + else + target_migration=$(printf '%s' "$fs_migrations" | sed -n "${target_line}p") + printf '%s' "$target_migration" + fi } migrate_up() { @@ -202,14 +223,29 @@ migrate_up() { exit 1; ;; *) - UP_NUMBER=$2 - shift 2; + UP_NUMBER=$1 + shift; ;; esac done - : "$UP_NUMBER" - #ls "$MIGRATION_DIR" -1 | sort + # Calculate target migration: current + UP_NUMBER + if [ -z "$db_migrations" ]; then + target_line=$UP_NUMBER + else + current_migration=$(printf '%s\n' "$db_migrations" | tail -n1) + current_idx=$(index_of "$fs_migrations" "$current_migration") + target_line=$((current_idx + UP_NUMBER)) + fi + + target_migration=$(printf '%s' "$fs_migrations" | sed -n "${target_line}p") + + if [ -z "$target_migration" ]; then + log error "cannot migrate up $UP_NUMBER step(s): not enough migrations" + exit 1 + fi + + printf '%s' "$target_migration" } migrate_to() { @@ -339,24 +375,107 @@ migrate() { log debug "[$WHITE$fs_migrations$NC]" log debug "$target_migration" - target_idx=$(index_of "$fs_migrations" "$target_migration") + if [ -z "$target_migration" ]; then + target_idx=0 + else + target_idx=$(index_of "$fs_migrations" "$target_migration") + fi log debug "indexes $WHITE$current_idx$NC $WHITE${target_idx}" if [ "$target_idx" -eq "$current_idx" ]; then - log notice "database already at ${WHITE}$target_migration${NC}" + if [ "$target_idx" -eq 0 ]; then + log notice "database already at clean state (no migrations)" + else + log notice "database already at ${WHITE}$target_migration${NC}" + fi exit 0 fi + + # Apply migrations + psql_args="$(form_psql_args)" + + if [ "$target_idx" -gt "$current_idx" ]; then + # Migrate UP + log info "migrating up from index $current_idx to $target_idx" + + i=$((current_idx + 1)) + while [ "$i" -le "$target_idx" ]; do + fs_migration=$(printf '%s' "$fs_migrations" | sed -n "${i}p") + + escaped_name=$(printf '%s' "$fs_migration" | sed "s/'/''/g") + mig_path="$MIGRATION_DIR/$fs_migration/up.sql" + escaped_path=$(printf '%s' "$mig_path" | sed "s/'/''/g") + + if [ ! -f "$mig_path" ]; then + log error "migration file not found: ${WHITE}$mig_path${NC}" + exit 1 + fi + + mig_hash=$(sha256sum "$mig_path") + log info "applying migration ${WHITE}$fs_migration${NC} (up)" + + # shellcheck disable=SC2086 + if ! psql $psql_args "$DB_URL" </dev/null 2>&1; then + log error "test failed: ${WHITE}not all columns added" + exit 1 +fi + +# Migrate down 3 steps (should leave only first migration) +if ! migrator --db-url "$DATABASE_URL" migrate down 3; then + log error "test failed: ${WHITE}migrate down 3 failed" + exit 1 +fi + +# Verify only 1 migration remains +applied_count=$(psql -Atc "SELECT COUNT(*) FROM hectic.migration" "$DATABASE_URL") +if [ "$applied_count" != "1" ]; then + log error "test failed: ${WHITE}expected 1 migration, got $applied_count" + exit 1 +fi + +# Verify only user_id column remains +if ! psql -Atc "SELECT user_id FROM orders LIMIT 0" "$DATABASE_URL" >/dev/null 2>&1; then + log error "test failed: ${WHITE}user_id should still exist" + exit 1 +fi + +if psql -Atc "SELECT total FROM orders LIMIT 0" "$DATABASE_URL" >/dev/null 2>&1; then + log error "test failed: ${WHITE}total column should be removed" + exit 1 +fi + +log notice "test passed" + diff --git a/test/package/migrator/test/migrate-down-single/migration/20250101000001-add-price/down.sql b/test/package/migrator/test/migrate-down-single/migration/20250101000001-add-price/down.sql new file mode 100644 index 0000000..0bcb276 --- /dev/null +++ b/test/package/migrator/test/migrate-down-single/migration/20250101000001-add-price/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE products DROP COLUMN price; + diff --git a/test/package/migrator/test/migrate-down-single/migration/20250101000001-add-price/up.sql b/test/package/migrator/test/migrate-down-single/migration/20250101000001-add-price/up.sql new file mode 100644 index 0000000..133f8a0 --- /dev/null +++ b/test/package/migrator/test/migrate-down-single/migration/20250101000001-add-price/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE products ADD COLUMN price DECIMAL(10,2); + diff --git a/test/package/migrator/test/migrate-down-single/migration/20250101000002-add-description/down.sql b/test/package/migrator/test/migrate-down-single/migration/20250101000002-add-description/down.sql new file mode 100644 index 0000000..2b7d49d --- /dev/null +++ b/test/package/migrator/test/migrate-down-single/migration/20250101000002-add-description/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE products DROP COLUMN description; + diff --git a/test/package/migrator/test/migrate-down-single/migration/20250101000002-add-description/up.sql b/test/package/migrator/test/migrate-down-single/migration/20250101000002-add-description/up.sql new file mode 100644 index 0000000..3b77155 --- /dev/null +++ b/test/package/migrator/test/migrate-down-single/migration/20250101000002-add-description/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE products ADD COLUMN description TEXT; + diff --git a/test/package/migrator/test/migrate-down-single/run.sh b/test/package/migrator/test/migrate-down-single/run.sh new file mode 100644 index 0000000..0eb4754 --- /dev/null +++ b/test/package/migrator/test/migrate-down-single/run.sh @@ -0,0 +1,53 @@ +#!/bin/dash + +HECTIC_NAMESPACE=test-migrate-down-single + +log notice "test case: ${WHITE}migrate down single step" + +# Create initial schema +psql "$DATABASE_URL" -c 'CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT)' + +# Initialize migrator +if ! migrator --db-url "$DATABASE_URL" init; then + log error "test failed: ${WHITE}init failed" + exit 1 +fi + +# Apply 2 migrations +if ! migrator --db-url "$DATABASE_URL" migrate up 2; then + log error "test failed: ${WHITE}migrate up failed" + exit 1 +fi + +# Verify both columns exist +if ! psql -Atc "SELECT price, description FROM products LIMIT 0" "$DATABASE_URL" >/dev/null 2>&1; then + log error "test failed: ${WHITE}columns not added" + exit 1 +fi + +# Migrate down one step +if ! migrator --db-url "$DATABASE_URL" migrate down; then + log error "test failed: ${WHITE}migrate down failed" + exit 1 +fi + +# Verify only 1 migration remains +applied_count=$(psql -Atc "SELECT COUNT(*) FROM hectic.migration" "$DATABASE_URL") +if [ "$applied_count" != "1" ]; then + log error "test failed: ${WHITE}expected 1 migration, got $applied_count" + exit 1 +fi + +# Verify description column was removed but price remains +if psql -Atc "SELECT description FROM products LIMIT 0" "$DATABASE_URL" >/dev/null 2>&1; then + log error "test failed: ${WHITE}description column should be removed" + exit 1 +fi + +if ! psql -Atc "SELECT price FROM products LIMIT 0" "$DATABASE_URL" >/dev/null 2>&1; then + log error "test failed: ${WHITE}price column should still exist" + exit 1 +fi + +log notice "test passed" + diff --git a/test/package/migrator/test/migrate-existing-data-migration/migration/20250101000001-transform-price-data/down.sql b/test/package/migrator/test/migrate-existing-data-migration/migration/20250101000001-transform-price-data/down.sql new file mode 100644 index 0000000..d92ae29 --- /dev/null +++ b/test/package/migrator/test/migrate-existing-data-migration/migration/20250101000001-transform-price-data/down.sql @@ -0,0 +1,4 @@ +-- Remove transformed columns (original data preserved) +ALTER TABLE products DROP COLUMN price_display; +ALTER TABLE products DROP COLUMN price_dollars; + diff --git a/test/package/migrator/test/migrate-existing-data-migration/migration/20250101000001-transform-price-data/up.sql b/test/package/migrator/test/migrate-existing-data-migration/migration/20250101000001-transform-price-data/up.sql new file mode 100644 index 0000000..cc0ca1d --- /dev/null +++ b/test/package/migrator/test/migrate-existing-data-migration/migration/20250101000001-transform-price-data/up.sql @@ -0,0 +1,9 @@ +-- Add new columns and transform existing data +ALTER TABLE products ADD COLUMN price_dollars DECIMAL(10,2); +ALTER TABLE products ADD COLUMN price_display TEXT; + +-- Transform existing data +UPDATE products +SET + price_dollars = price_cents::DECIMAL / 100, + price_display = '$' || to_char(price_cents::DECIMAL / 100, 'FM999999999.00'); diff --git a/test/package/migrator/test/migrate-existing-data-migration/run.sh b/test/package/migrator/test/migrate-existing-data-migration/run.sh new file mode 100644 index 0000000..8ca5cbe --- /dev/null +++ b/test/package/migrator/test/migrate-existing-data-migration/run.sh @@ -0,0 +1,98 @@ +#!/bin/dash + +HECTIC_NAMESPACE=test-migrate-existing-data-migration + +log notice "test case: ${WHITE}data migration on existing populated table" + +# Create existing table with data +log info "creating existing table with data" +psql "$DATABASE_URL" -v ON_ERROR_STOP=1 </dev/null 2>&1; then + log error "test failed: ${WHITE}new columns not added" + exit 1 +fi + +# Verify data was transformed correctly +widget_price=$(psql -Atc "SELECT price_dollars FROM products WHERE name = 'Widget'" "$DATABASE_URL") +widget_display=$(psql -Atc "SELECT price_display FROM products WHERE name = 'Widget'" "$DATABASE_URL") + +if [ "$widget_price" != "10.00" ]; then + log error "test failed: ${WHITE}price_dollars not calculated correctly, got: $widget_price" + exit 1 +fi + +if [ "$widget_display" != "\$10.00" ]; then + log error "test failed: ${WHITE}price_display not formatted correctly, got: $widget_display" + exit 1 +fi + +log info "data transformation successful" + +# Verify all products were transformed +transformed_count=$(psql -Atc "SELECT COUNT(*) FROM products WHERE price_dollars IS NOT NULL" "$DATABASE_URL") +if [ "$transformed_count" != "3" ]; then + log error "test failed: ${WHITE}not all products transformed, got: $transformed_count" + exit 1 +fi + +# Test rollback of data migration +log info "rolling back data migration" +if ! migrator --db-url "$DATABASE_URL" migrate down; then + log error "test failed: ${WHITE}rollback failed" + exit 1 +fi + +# Verify columns removed +if psql -Atc "SELECT price_dollars FROM products LIMIT 0" "$DATABASE_URL" >/dev/null 2>&1; then + log error "test failed: ${WHITE}columns should be removed after rollback" + exit 1 +fi + +# Verify original data still intact +original_count=$(psql -Atc "SELECT COUNT(*) FROM products WHERE price_cents IS NOT NULL" "$DATABASE_URL") +if [ "$original_count" != "3" ]; then + log error "test failed: ${WHITE}original data corrupted after rollback" + exit 1 +fi + +widget_original=$(psql -Atc "SELECT price_cents FROM products WHERE name = 'Widget'" "$DATABASE_URL") +if [ "$widget_original" != "1000" ]; then + log error "test failed: ${WHITE}original price data corrupted, got: $widget_original" + exit 1 +fi + +log notice "test passed: data migration works on existing populated tables" + diff --git a/test/package/migrator/test/migrate-existing-database/migration/20250101000001-add-user-bio/down.sql b/test/package/migrator/test/migrate-existing-database/migration/20250101000001-add-user-bio/down.sql new file mode 100644 index 0000000..0dfca2d --- /dev/null +++ b/test/package/migrator/test/migrate-existing-database/migration/20250101000001-add-user-bio/down.sql @@ -0,0 +1,3 @@ +-- Remove column from existing table +ALTER TABLE users DROP COLUMN bio; + diff --git a/test/package/migrator/test/migrate-existing-database/migration/20250101000001-add-user-bio/up.sql b/test/package/migrator/test/migrate-existing-database/migration/20250101000001-add-user-bio/up.sql new file mode 100644 index 0000000..e5bbc1c --- /dev/null +++ b/test/package/migrator/test/migrate-existing-database/migration/20250101000001-add-user-bio/up.sql @@ -0,0 +1,3 @@ +-- Add column to existing table +ALTER TABLE users ADD COLUMN bio TEXT; + diff --git a/test/package/migrator/test/migrate-existing-database/migration/20250101000002-add-comments/down.sql b/test/package/migrator/test/migrate-existing-database/migration/20250101000002-add-comments/down.sql new file mode 100644 index 0000000..ff9653a --- /dev/null +++ b/test/package/migrator/test/migrate-existing-database/migration/20250101000002-add-comments/down.sql @@ -0,0 +1,3 @@ +-- Remove new table +DROP TABLE comments; + diff --git a/test/package/migrator/test/migrate-existing-database/migration/20250101000002-add-comments/up.sql b/test/package/migrator/test/migrate-existing-database/migration/20250101000002-add-comments/up.sql new file mode 100644 index 0000000..5737192 --- /dev/null +++ b/test/package/migrator/test/migrate-existing-database/migration/20250101000002-add-comments/up.sql @@ -0,0 +1,9 @@ +-- Add new table that references existing tables +CREATE TABLE comments ( + id SERIAL PRIMARY KEY, + post_id INTEGER NOT NULL REFERENCES posts(id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + content TEXT NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW() +); + diff --git a/test/package/migrator/test/migrate-existing-database/run.sh b/test/package/migrator/test/migrate-existing-database/run.sh new file mode 100644 index 0000000..ab40397 --- /dev/null +++ b/test/package/migrator/test/migrate-existing-database/run.sh @@ -0,0 +1,151 @@ +#!/bin/dash + +HECTIC_NAMESPACE=test-migrate-existing-database + +log notice "test case: ${WHITE}add migrator to existing database with data" + +# Simulate existing database with tables and data +log info "creating existing database schema and data" +psql "$DATABASE_URL" -v ON_ERROR_STOP=1 </dev/null 2>&1; then + log error "test failed: ${WHITE}hectic.migration table not created" + exit 1 +fi + +# Verify existing data is still intact +user_count_after=$(psql -Atc "SELECT COUNT(*) FROM users" "$DATABASE_URL") +post_count_after=$(psql -Atc "SELECT COUNT(*) FROM posts" "$DATABASE_URL") + +if [ "$user_count_after" != "$user_count" ] || [ "$post_count_after" != "$post_count" ]; then + log error "test failed: ${WHITE}existing data was affected by migrator init" + exit 1 +fi + +log info "existing data preserved: $user_count_after users, $post_count_after posts" + +# Apply a migration that modifies existing table +log info "applying migration to existing table" +if ! migrator --db-url "$DATABASE_URL" migrate up; then + log error "test failed: ${WHITE}migration on existing table failed" + exit 1 +fi + +# Verify migration was applied +if ! psql -Atc "SELECT bio FROM users LIMIT 0" "$DATABASE_URL" >/dev/null 2>&1; then + log error "test failed: ${WHITE}bio column not added to existing table" + exit 1 +fi + +# Verify existing data still intact with NULL in new column +alice_bio=$(psql -Atc "SELECT bio FROM users WHERE username = 'alice'" "$DATABASE_URL") +if [ "$alice_bio" != "" ]; then + log error "test failed: ${WHITE}new column should be NULL for existing rows, got: $alice_bio" + exit 1 +fi + +# Verify we can still query existing data +alice_email=$(psql -Atc "SELECT email FROM users WHERE username = 'alice'" "$DATABASE_URL") +if [ "$alice_email" != "alice@example.com" ]; then + log error "test failed: ${WHITE}existing data corrupted" + exit 1 +fi + +log info "migration applied successfully to existing table" + +# Apply second migration that adds a new table +if ! migrator --db-url "$DATABASE_URL" migrate up; then + log error "test failed: ${WHITE}second migration failed" + exit 1 +fi + +# Verify new table exists +if ! psql -Atc "SELECT COUNT(*) FROM comments" "$DATABASE_URL" >/dev/null 2>&1; then + log error "test failed: ${WHITE}comments table not created" + exit 1 +fi + +# Verify we can add data that references existing data +psql "$DATABASE_URL" -v ON_ERROR_STOP=1 </dev/null 2>&1; then + log error "test failed: ${WHITE}comments table should be removed" + exit 1 +fi + +# Verify existing tables still intact +final_user_count=$(psql -Atc "SELECT COUNT(*) FROM users" "$DATABASE_URL") +final_post_count=$(psql -Atc "SELECT COUNT(*) FROM posts" "$DATABASE_URL") + +if [ "$final_user_count" != "$user_count" ] || [ "$final_post_count" != "$post_count" ]; then + log error "test failed: ${WHITE}existing data affected by rollback" + exit 1 +fi + +log notice "test passed: migrator works correctly with existing database" + diff --git a/test/package/migrator/test/migrate-existing-with-conflicts/migration/20250101000001-add-test-table/down.sql b/test/package/migrator/test/migrate-existing-with-conflicts/migration/20250101000001-add-test-table/down.sql new file mode 100644 index 0000000..cc93a6f --- /dev/null +++ b/test/package/migrator/test/migrate-existing-with-conflicts/migration/20250101000001-add-test-table/down.sql @@ -0,0 +1,3 @@ +-- Remove test table +DROP TABLE hectic.test_table; + diff --git a/test/package/migrator/test/migrate-existing-with-conflicts/migration/20250101000001-add-test-table/up.sql b/test/package/migrator/test/migrate-existing-with-conflicts/migration/20250101000001-add-test-table/up.sql new file mode 100644 index 0000000..2b83bf0 --- /dev/null +++ b/test/package/migrator/test/migrate-existing-with-conflicts/migration/20250101000001-add-test-table/up.sql @@ -0,0 +1,6 @@ +-- Add a table to hectic schema alongside user's existing tables +CREATE TABLE hectic.test_table ( + id SERIAL PRIMARY KEY, + value TEXT +); + diff --git a/test/package/migrator/test/migrate-existing-with-conflicts/run.sh b/test/package/migrator/test/migrate-existing-with-conflicts/run.sh new file mode 100644 index 0000000..7a98756 --- /dev/null +++ b/test/package/migrator/test/migrate-existing-with-conflicts/run.sh @@ -0,0 +1,73 @@ +#!/bin/dash + +HECTIC_NAMESPACE=test-migrate-existing-with-conflicts + +log notice "test case: ${WHITE}migrator with conflicting existing schema" + +# Create a database that already has a 'hectic' schema (potential conflict) +log info "creating existing database with hectic schema" +psql "$DATABASE_URL" -v ON_ERROR_STOP=1 </dev/null 2>&1; then + log error "test failed: ${WHITE}hectic.migration not created" + exit 1 +fi + +if ! psql -Atc "SELECT COUNT(*) FROM hectic.version" "$DATABASE_URL" >/dev/null 2>&1; then + log error "test failed: ${WHITE}hectic.version not created" + exit 1 +fi + +# Verify existing user data still intact +existing_data_after=$(psql -Atc "SELECT data FROM hectic.user_data" "$DATABASE_URL") +if [ "$existing_data_after" != "$existing_data" ]; then + log error "test failed: ${WHITE}existing hectic.user_data was corrupted" + exit 1 +fi + +log info "existing hectic.user_data preserved" + +# Apply a migration +if ! migrator --db-url "$DATABASE_URL" migrate up; then + log error "test failed: ${WHITE}migration failed" + exit 1 +fi + +# Verify both user table and migrator tables coexist +tables_in_hectic=$(psql -Atc "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'hectic'" "$DATABASE_URL") +if [ "$tables_in_hectic" -lt 4 ]; then + log error "test failed: ${WHITE}expected at least 4 tables in hectic schema (user_data, migration, version, test_table), got $tables_in_hectic" + exit 1 +fi + +log info "hectic schema contains $tables_in_hectic tables (user + migrator tables)" + +log notice "test passed: migrator coexists with existing hectic schema" + diff --git a/test/package/migrator/test/migrate-to-backward/migration/20250101000001-add-user-id/down.sql b/test/package/migrator/test/migrate-to-backward/migration/20250101000001-add-user-id/down.sql new file mode 100644 index 0000000..5587bbb --- /dev/null +++ b/test/package/migrator/test/migrate-to-backward/migration/20250101000001-add-user-id/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE sessions DROP COLUMN user_id; + diff --git a/test/package/migrator/test/migrate-to-backward/migration/20250101000001-add-user-id/up.sql b/test/package/migrator/test/migrate-to-backward/migration/20250101000001-add-user-id/up.sql new file mode 100644 index 0000000..88fdf5f --- /dev/null +++ b/test/package/migrator/test/migrate-to-backward/migration/20250101000001-add-user-id/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE sessions ADD COLUMN user_id INTEGER; + diff --git a/test/package/migrator/test/migrate-to-backward/migration/20250101000002-add-token/down.sql b/test/package/migrator/test/migrate-to-backward/migration/20250101000002-add-token/down.sql new file mode 100644 index 0000000..0789787 --- /dev/null +++ b/test/package/migrator/test/migrate-to-backward/migration/20250101000002-add-token/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE sessions DROP COLUMN token; + diff --git a/test/package/migrator/test/migrate-to-backward/migration/20250101000002-add-token/up.sql b/test/package/migrator/test/migrate-to-backward/migration/20250101000002-add-token/up.sql new file mode 100644 index 0000000..9eb8e02 --- /dev/null +++ b/test/package/migrator/test/migrate-to-backward/migration/20250101000002-add-token/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE sessions ADD COLUMN token TEXT; + diff --git a/test/package/migrator/test/migrate-to-backward/migration/20250101000003-add-expires-at/down.sql b/test/package/migrator/test/migrate-to-backward/migration/20250101000003-add-expires-at/down.sql new file mode 100644 index 0000000..16bf714 --- /dev/null +++ b/test/package/migrator/test/migrate-to-backward/migration/20250101000003-add-expires-at/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE sessions DROP COLUMN expires_at; + diff --git a/test/package/migrator/test/migrate-to-backward/migration/20250101000003-add-expires-at/up.sql b/test/package/migrator/test/migrate-to-backward/migration/20250101000003-add-expires-at/up.sql new file mode 100644 index 0000000..8c65868 --- /dev/null +++ b/test/package/migrator/test/migrate-to-backward/migration/20250101000003-add-expires-at/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE sessions ADD COLUMN expires_at TIMESTAMPTZ; + diff --git a/test/package/migrator/test/migrate-to-backward/run.sh b/test/package/migrator/test/migrate-to-backward/run.sh new file mode 100644 index 0000000..fd29322 --- /dev/null +++ b/test/package/migrator/test/migrate-to-backward/run.sh @@ -0,0 +1,53 @@ +#!/bin/dash + +HECTIC_NAMESPACE=test-migrate-to-backward + +log notice "test case: ${WHITE}migrate to (backward) specific migration" + +# Create initial schema +psql "$DATABASE_URL" -c 'CREATE TABLE sessions (id INTEGER PRIMARY KEY)' + +# Initialize migrator +if ! migrator --db-url "$DATABASE_URL" init; then + log error "test failed: ${WHITE}init failed" + exit 1 +fi + +# Apply all 3 migrations +if ! migrator --db-url "$DATABASE_URL" migrate up 3; then + log error "test failed: ${WHITE}migrate up failed" + exit 1 +fi + +# Verify all 3 columns exist +if ! psql -Atc "SELECT user_id, token, expires_at FROM sessions LIMIT 0" "$DATABASE_URL" >/dev/null 2>&1; then + log error "test failed: ${WHITE}not all columns added" + exit 1 +fi + +# Migrate back to first migration +if ! migrator --db-url "$DATABASE_URL" migrate to 20250101000001-add-user-id; then + log error "test failed: ${WHITE}migrate to (backward) failed" + exit 1 +fi + +# Verify only 1 migration remains +applied_count=$(psql -Atc "SELECT COUNT(*) FROM hectic.migration" "$DATABASE_URL") +if [ "$applied_count" != "1" ]; then + log error "test failed: ${WHITE}expected 1 migration, got $applied_count" + exit 1 +fi + +# Verify only user_id exists +if ! psql -Atc "SELECT user_id FROM sessions LIMIT 0" "$DATABASE_URL" >/dev/null 2>&1; then + log error "test failed: ${WHITE}user_id should exist" + exit 1 +fi + +if psql -Atc "SELECT token FROM sessions LIMIT 0" "$DATABASE_URL" >/dev/null 2>&1; then + log error "test failed: ${WHITE}token should be removed" + exit 1 +fi + +log notice "test passed" + diff --git a/test/package/migrator/test/migrate-to-forward/migration/20250101000001-add-content/down.sql b/test/package/migrator/test/migrate-to-forward/migration/20250101000001-add-content/down.sql new file mode 100644 index 0000000..8b55dec --- /dev/null +++ b/test/package/migrator/test/migrate-to-forward/migration/20250101000001-add-content/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE comments DROP COLUMN content; + diff --git a/test/package/migrator/test/migrate-to-forward/migration/20250101000001-add-content/up.sql b/test/package/migrator/test/migrate-to-forward/migration/20250101000001-add-content/up.sql new file mode 100644 index 0000000..883e20b --- /dev/null +++ b/test/package/migrator/test/migrate-to-forward/migration/20250101000001-add-content/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE comments ADD COLUMN content TEXT; + diff --git a/test/package/migrator/test/migrate-to-forward/migration/20250101000002-add-user-id/down.sql b/test/package/migrator/test/migrate-to-forward/migration/20250101000002-add-user-id/down.sql new file mode 100644 index 0000000..74ebec7 --- /dev/null +++ b/test/package/migrator/test/migrate-to-forward/migration/20250101000002-add-user-id/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE comments DROP COLUMN user_id; + diff --git a/test/package/migrator/test/migrate-to-forward/migration/20250101000002-add-user-id/up.sql b/test/package/migrator/test/migrate-to-forward/migration/20250101000002-add-user-id/up.sql new file mode 100644 index 0000000..aa28f0a --- /dev/null +++ b/test/package/migrator/test/migrate-to-forward/migration/20250101000002-add-user-id/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE comments ADD COLUMN user_id INTEGER; + diff --git a/test/package/migrator/test/migrate-to-forward/run.sh b/test/package/migrator/test/migrate-to-forward/run.sh new file mode 100644 index 0000000..e0e777a --- /dev/null +++ b/test/package/migrator/test/migrate-to-forward/run.sh @@ -0,0 +1,36 @@ +#!/bin/dash + +HECTIC_NAMESPACE=test-migrate-to-forward + +log notice "test case: ${WHITE}migrate to (forward) specific migration" + +# Create initial schema +psql "$DATABASE_URL" -c 'CREATE TABLE comments (id INTEGER PRIMARY KEY)' + +# Initialize migrator +if ! migrator --db-url "$DATABASE_URL" init; then + log error "test failed: ${WHITE}init failed" + exit 1 +fi + +# Migrate to second migration (skipping intermediate) +if ! migrator --db-url "$DATABASE_URL" migrate to 20250101000002-add-user-id; then + log error "test failed: ${WHITE}migrate to failed" + exit 1 +fi + +# Verify 2 migrations were applied +applied_count=$(psql -Atc "SELECT COUNT(*) FROM hectic.migration" "$DATABASE_URL") +if [ "$applied_count" != "2" ]; then + log error "test failed: ${WHITE}expected 2 migrations, got $applied_count" + exit 1 +fi + +# Verify both columns exist +if ! psql -Atc "SELECT content, user_id FROM comments LIMIT 0" "$DATABASE_URL" >/dev/null 2>&1; then + log error "test failed: ${WHITE}columns not added" + exit 1 +fi + +log notice "test passed" + diff --git a/test/package/migrator/test/migrate-up-multiple/migration/20250101000001-add-content/down.sql b/test/package/migrator/test/migrate-up-multiple/migration/20250101000001-add-content/down.sql new file mode 100644 index 0000000..35bb7b6 --- /dev/null +++ b/test/package/migrator/test/migrate-up-multiple/migration/20250101000001-add-content/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE posts DROP COLUMN content; + diff --git a/test/package/migrator/test/migrate-up-multiple/migration/20250101000001-add-content/up.sql b/test/package/migrator/test/migrate-up-multiple/migration/20250101000001-add-content/up.sql new file mode 100644 index 0000000..e644f2a --- /dev/null +++ b/test/package/migrator/test/migrate-up-multiple/migration/20250101000001-add-content/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE posts ADD COLUMN content TEXT; + diff --git a/test/package/migrator/test/migrate-up-multiple/migration/20250101000002-add-author/down.sql b/test/package/migrator/test/migrate-up-multiple/migration/20250101000002-add-author/down.sql new file mode 100644 index 0000000..7d856d1 --- /dev/null +++ b/test/package/migrator/test/migrate-up-multiple/migration/20250101000002-add-author/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE posts DROP COLUMN author; + diff --git a/test/package/migrator/test/migrate-up-multiple/migration/20250101000002-add-author/up.sql b/test/package/migrator/test/migrate-up-multiple/migration/20250101000002-add-author/up.sql new file mode 100644 index 0000000..1b544e8 --- /dev/null +++ b/test/package/migrator/test/migrate-up-multiple/migration/20250101000002-add-author/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE posts ADD COLUMN author TEXT; + diff --git a/test/package/migrator/test/migrate-up-multiple/migration/20250101000003-add-published-at/down.sql b/test/package/migrator/test/migrate-up-multiple/migration/20250101000003-add-published-at/down.sql new file mode 100644 index 0000000..805b0e8 --- /dev/null +++ b/test/package/migrator/test/migrate-up-multiple/migration/20250101000003-add-published-at/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE posts DROP COLUMN published_at; + diff --git a/test/package/migrator/test/migrate-up-multiple/migration/20250101000003-add-published-at/up.sql b/test/package/migrator/test/migrate-up-multiple/migration/20250101000003-add-published-at/up.sql new file mode 100644 index 0000000..8d612fa --- /dev/null +++ b/test/package/migrator/test/migrate-up-multiple/migration/20250101000003-add-published-at/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE posts ADD COLUMN published_at TIMESTAMPTZ; + diff --git a/test/package/migrator/test/migrate-up-multiple/run.sh b/test/package/migrator/test/migrate-up-multiple/run.sh new file mode 100644 index 0000000..82f5fec --- /dev/null +++ b/test/package/migrator/test/migrate-up-multiple/run.sh @@ -0,0 +1,36 @@ +#!/bin/dash + +HECTIC_NAMESPACE=test-migrate-up-multiple + +log notice "test case: ${WHITE}migrate up multiple steps" + +# Create initial schema +psql "$DATABASE_URL" -c 'CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT)' + +# Initialize migrator +if ! migrator --db-url "$DATABASE_URL" init; then + log error "test failed: ${WHITE}init failed" + exit 1 +fi + +# Apply 3 migrations at once +if ! migrator --db-url "$DATABASE_URL" migrate up 3; then + log error "test failed: ${WHITE}migrate up 3 failed" + exit 1 +fi + +# Verify all 3 migrations were applied +applied_count=$(psql -Atc "SELECT COUNT(*) FROM hectic.migration" "$DATABASE_URL") +if [ "$applied_count" != "3" ]; then + log error "test failed: ${WHITE}expected 3 migrations, got $applied_count" + exit 1 +fi + +# Verify all columns were added +if ! psql -Atc "SELECT content, author, published_at FROM posts LIMIT 0" "$DATABASE_URL" >/dev/null 2>&1; then + log error "test failed: ${WHITE}not all columns were added" + exit 1 +fi + +log notice "test passed" + diff --git a/test/package/migrator/test/migrate-up-single/migration/20250101000001-add-email/down.sql b/test/package/migrator/test/migrate-up-single/migration/20250101000001-add-email/down.sql new file mode 100644 index 0000000..19fbf90 --- /dev/null +++ b/test/package/migrator/test/migrate-up-single/migration/20250101000001-add-email/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE users DROP COLUMN email; + diff --git a/test/package/migrator/test/migrate-up-single/migration/20250101000001-add-email/up.sql b/test/package/migrator/test/migrate-up-single/migration/20250101000001-add-email/up.sql new file mode 100644 index 0000000..cc71fa5 --- /dev/null +++ b/test/package/migrator/test/migrate-up-single/migration/20250101000001-add-email/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE users ADD COLUMN email TEXT; + diff --git a/test/package/migrator/test/migrate-up-single/run.sh b/test/package/migrator/test/migrate-up-single/run.sh new file mode 100644 index 0000000..a84f35a --- /dev/null +++ b/test/package/migrator/test/migrate-up-single/run.sh @@ -0,0 +1,36 @@ +#!/bin/dash + +HECTIC_NAMESPACE=test-migrate-up-single + +log notice "test case: ${WHITE}migrate up single step" + +# Create initial schema +psql "$DATABASE_URL" -c 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)' + +# Initialize migrator +if ! migrator --db-url "$DATABASE_URL" init; then + log error "test failed: ${WHITE}init failed" + exit 1 +fi + +# Apply first migration +if ! migrator --db-url "$DATABASE_URL" migrate up; then + log error "test failed: ${WHITE}migrate up failed" + exit 1 +fi + +# Verify migration was applied +applied_count=$(psql -Atc "SELECT COUNT(*) FROM hectic.migration" "$DATABASE_URL") +if [ "$applied_count" != "1" ]; then + log error "test failed: ${WHITE}expected 1 migration, got $applied_count" + exit 1 +fi + +# Verify column was added +if ! psql -Atc "SELECT email FROM users LIMIT 0" "$DATABASE_URL" >/dev/null 2>&1; then + log error "test failed: ${WHITE}email column not added" + exit 1 +fi + +log notice "test passed" + diff --git a/test/package/migrator/test/migrate-up/migration/20251104192425-add-info-to-profile/up.sql b/test/package/migrator/test/migrate-up/migration/20251104192425-add-info-to-profile/up.sql deleted file mode 100644 index 818abfc..0000000 --- a/test/package/migrator/test/migrate-up/migration/20251104192425-add-info-to-profile/up.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE profile ADD COLUMN info TEXT; diff --git a/test/package/migrator/test/migrate-up/run.sh b/test/package/migrator/test/migrate-up/run.sh deleted file mode 100644 index c9104d0..0000000 --- a/test/package/migrator/test/migrate-up/run.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/dash - -HECTIC_NAMESPACE=test-migration-list - - - -log notice "test case: ${WHITE}migration up" -psql "$DATABASE_URL" 'CREATE TABLE profile ( - id INTEGER, - username TEXT -)' - -if ! migrator --db-url "$DATABASE_URL" migrate to 20251104192425-add-info-to-profile; then - log error "test failed: ${WHITE}error on migration up" - exit 1 -fi - -log notice "$(columns profile)" - - -exit 1