feat(package): migrator: ! sqlite support

This commit is contained in:
2025-12-17 03:24:59 +00:00
parent bb2ae34758
commit 956239ab79
79 changed files with 608 additions and 59 deletions

View File

@@ -28,6 +28,105 @@ sha256sum() {
cksum --algorithm=sha256 --untagged "$file" | awk '{printf $1}' cksum --algorithm=sha256 --untagged "$file" | awk '{printf $1}'
} }
# detect_db_type()
# Returns: "postgresql" or "sqlite"
detect_db_type() {
case "$DB_URL" in
postgresql://*|postgres://*)
printf 'postgresql'
;;
sqlite://*|*.db|*.sqlite|*.sqlite3)
printf 'sqlite'
;;
*)
log error "unsupported database URL format: ${WHITE}$DB_URL${NC}"
log error "supported formats: postgresql://... or sqlite://... or *.db"
exit 1
;;
esac
}
# get_sqlite_path()
get_sqlite_path() {
case "$DB_URL" in
sqlite://*)
printf '%s' "$DB_URL" | sed 's|^sqlite://||'
;;
*)
printf '%s' "$DB_URL"
;;
esac
}
# db_exec(sql)
db_exec() {
local sql="$1"
local db_type
db_type=$(detect_db_type)
case "$db_type" in
postgresql)
local psql_args
psql_args="$(form_psql_args)"
# shellcheck disable=SC2086
printf '%s' "$sql" | psql $psql_args "$DB_URL"
;;
sqlite)
local db_path
db_path=$(get_sqlite_path)
printf '%s' "$sql" | sqlite3 "$db_path"
;;
esac
}
# db_query(sql)
db_query() {
local sql="$1"
local db_type
db_type=$(detect_db_type)
case "$db_type" in
postgresql)
psql "$DB_URL" --no-align --tuples-only --quiet --command "$sql" | awk NF
;;
sqlite)
local db_path
db_path=$(get_sqlite_path)
sqlite3 "$db_path" "$sql"
;;
esac
}
# db_exec_file(file_path)
db_exec_file() {
local file_path="$1"
local db_type
db_type=$(detect_db_type)
case "$db_type" in
postgresql)
local psql_args escaped_path
psql_args="$(form_psql_args)"
escaped_path=$(printf '%s' "$file_path" | sed "s/'/''/g")
# shellcheck disable=SC2086
psql $psql_args "$DB_URL" <<SQL
BEGIN;
\i '$escaped_path'
COMMIT;
SQL
;;
sqlite)
local db_path
db_path=$(get_sqlite_path)
sqlite3 "$db_path" <<SQL
BEGIN;
.read $file_path
COMMIT;
SQL
;;
esac
}
# shellcheck disable=SC2120 # shellcheck disable=SC2120
init() { init() {
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
@@ -59,9 +158,15 @@ init() {
error_handler_no_db_url error_handler_no_db_url
psql_args="$(form_psql_args)" db_type=$(detect_db_type)
# INHERITS is PostgreSQL-only feature
[ ${INHERITS_LIST+x} ] && { [ ${INHERITS_LIST+x} ] && {
if [ "$db_type" != "postgresql" ]; then
log error "INHERITS is only supported for PostgreSQL"
exit 1
fi
oldIFS="$IFS" oldIFS="$IFS"
IFS=',' IFS=','
check_inherits= check_inherits=
@@ -75,15 +180,13 @@ init() {
"$check_inherits" \ "$check_inherits" \
'COMMIT;') 'COMMIT;')
# shellcheck disable=SC2086 if ! db_exec "$check_inherits"; then
if ! psql $psql_args -c "$check_inherits"; then
log error "init failed: ${WHITE}one of inherits table does not exists: ${CYAN}$INHERITS_LIST" log error "init failed: ${WHITE}one of inherits table does not exists: ${CYAN}$INHERITS_LIST"
exit 5 exit 5
fi fi
} }
# shellcheck disable=SC2086 if ! db_exec "$(init_sql)"; then
if ! psql $psql_args -c "$(init_sql)"; then
log error "init failed" log error "init failed"
exit 13 exit 13
fi fi
@@ -92,10 +195,11 @@ init() {
# error_handler_no_db_url() # error_handler_no_db_url()
error_handler_no_db_url() { error_handler_no_db_url() {
[ "${DB_URL+x}" ] || { log error "no ${WHITE}DB_URL${NC} or ${WHITE}--db-url${NC} specified"; exit 3; } [ "${DB_URL+x}" ] || { log error "no ${WHITE}DB_URL${NC} or ${WHITE}--db-url${NC} specified"; exit 3; }
check_db_dependencies
} }
init_sql() { init_sql_postgresql() {
local sql local sql inherits
inherits= inherits=
[ ${INHERITS_LIST+x} ] && inherits="$(printf 'INHERITS(%s)' "$INHERITS_LIST")" [ ${INHERITS_LIST+x} ] && inherits="$(printf 'INHERITS(%s)' "$INHERITS_LIST")"
@@ -103,7 +207,7 @@ init_sql() {
sql="$(cat <<EOF sql="$(cat <<EOF
BEGIN; BEGIN;
DO \$$ DO \$\$
DECLARE DECLARE
version TEXT; version TEXT;
BEGIN BEGIN
@@ -120,18 +224,18 @@ BEGIN
) THEN ) THEN
SELECT hectic.version.version FROM hectic.version WHERE name = 'migrator' INTO version; SELECT hectic.version.version FROM hectic.version WHERE name = 'migrator' INTO version;
IF version != '$VERSION' THEN IF version != '$VERSION' THEN
RAISE EXCEPTION 'Incampetible migrator versions: % and $VERSION', version; -- TODO(yukkop): show versions RAISE EXCEPTION 'Incompatible migrator versions: % and $VERSION', version;
END IF; END IF;
ELSE ELSE
CREATE DOMAIN hectic.migration_name AS TEXT CHECK (VALUE ~ '^[0-9]{14}-.*'); 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 DOMAIN hectic.sha256 AS CHAR(64) CHECK (VALUE ~ '^[0-9a-f]{64}\$');
CREATE FUNCTION hectic.sha256_lower() RETURNS trigger AS \$fn$ CREATE FUNCTION hectic.sha256_lower() RETURNS trigger AS \$fn\$
BEGIN BEGIN
NEW.hash = lower(NEW.hash); NEW.hash = lower(NEW.hash);
RETURN NEW; RETURN NEW;
END; END;
\$fn$ LANGUAGE plpgsql; \$fn\$ LANGUAGE plpgsql;
CREATE TABLE hectic.version ( CREATE TABLE hectic.version (
name TEXT PRIMARY KEY, name TEXT PRIMARY KEY,
@@ -153,7 +257,7 @@ BEGIN
FOR EACH ROW EXECUTE FUNCTION hectic.sha256_lower(); FOR EACH ROW EXECUTE FUNCTION hectic.sha256_lower();
END IF; END IF;
END; END;
\$$; \$\$;
COMMIT; COMMIT;
EOF EOF
@@ -162,6 +266,59 @@ EOF
printf '%s' "$sql" printf '%s' "$sql"
} }
init_sql_sqlite() {
local sql
sql="$(cat <<'EOF'
BEGIN;
CREATE TABLE IF NOT EXISTS hectic_version (
name TEXT PRIMARY KEY,
version TEXT NOT NULL,
installed_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS hectic_migration (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL CHECK (name GLOB '[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-*'),
hash TEXT UNIQUE NOT NULL CHECK (length(hash) = 64 AND lower(hash) = hash),
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Check version compatibility
INSERT OR IGNORE INTO hectic_version (name, version) VALUES ('migrator', 'VERSION_PLACEHOLDER');
-- Verify version if it already exists
SELECT CASE
WHEN version != 'VERSION_PLACEHOLDER' AND name = 'migrator'
THEN RAISE(ABORT, 'Incompatible migrator versions')
ELSE 1
END FROM hectic_version WHERE name = 'migrator';
COMMIT;
EOF
)"
# Replace version placeholder
sql=$(printf '%s' "$sql" | sed "s/VERSION_PLACEHOLDER/$VERSION/g")
printf '%s' "$sql"
}
init_sql() {
local db_type
db_type=$(detect_db_type)
case "$db_type" in
postgresql)
init_sql_postgresql
;;
sqlite)
init_sql_sqlite
;;
esac
}
help() { help() {
# inherits: List one or more tables the migration table must inherit from # inherits: List one or more tables the migration table must inherit from
echo help echo help
@@ -334,11 +491,16 @@ migrate() {
fs_migrations=$(migration_list) fs_migrations=$(migration_list)
db_migrations=$( db_type=$(detect_db_type)
psql "$DB_URL" --no-align --tuples-only --quiet \
--command "SELECT name FROM hectic.migration ORDER BY name ASC" \ case "$db_type" in
| awk NF postgresql)
) db_migrations=$(db_query "SELECT name FROM hectic.migration ORDER BY name ASC")
;;
sqlite)
db_migrations=$(db_query "SELECT name FROM hectic_migration ORDER BY name ASC")
;;
esac
log debug "db mig: $db_migrations" log debug "db mig: $db_migrations"
db_mig_count=$(printf '%s' "$db_migrations" | wc -l) db_mig_count=$(printf '%s' "$db_migrations" | wc -l)
@@ -415,6 +577,10 @@ migrate() {
mig_hash=$(sha256sum "$mig_path") mig_hash=$(sha256sum "$mig_path")
log info "applying migration ${WHITE}$fs_migration${NC} (up)" log info "applying migration ${WHITE}$fs_migration${NC} (up)"
case "$db_type" in
postgresql)
local psql_args
psql_args="$(form_psql_args)"
# shellcheck disable=SC2086 # shellcheck disable=SC2086
if ! psql $psql_args "$DB_URL" <<SQL if ! psql $psql_args "$DB_URL" <<SQL
BEGIN; BEGIN;
@@ -426,6 +592,22 @@ SQL
log error "migration failed: ${WHITE}$fs_migration${NC}" log error "migration failed: ${WHITE}$fs_migration${NC}"
exit 4 exit 4
fi fi
;;
sqlite)
local db_path
db_path=$(get_sqlite_path)
if ! sqlite3 "$db_path" <<SQL
BEGIN;
.read $mig_path
INSERT INTO hectic_migration (name, hash) VALUES ('$escaped_name', '$mig_hash');
COMMIT;
SQL
then
log error "migration failed: ${WHITE}$fs_migration${NC}"
exit 4
fi
;;
esac
i=$((i + 1)) i=$((i + 1))
done done
@@ -451,6 +633,10 @@ SQL
log info "reverting migration ${WHITE}$fs_migration${NC} (down)" log info "reverting migration ${WHITE}$fs_migration${NC} (down)"
case "$db_type" in
postgresql)
local psql_args
psql_args="$(form_psql_args)"
# shellcheck disable=SC2086 # shellcheck disable=SC2086
if ! psql $psql_args "$DB_URL" <<SQL if ! psql $psql_args "$DB_URL" <<SQL
BEGIN; BEGIN;
@@ -462,6 +648,22 @@ SQL
log error "migration rollback failed: ${WHITE}$fs_migration${NC}" log error "migration rollback failed: ${WHITE}$fs_migration${NC}"
exit 4 exit 4
fi fi
;;
sqlite)
local db_path
db_path=$(get_sqlite_path)
if ! sqlite3 "$db_path" <<SQL
BEGIN;
.read $mig_path
DELETE FROM hectic_migration WHERE name = '$escaped_name';
COMMIT;
SQL
then
log error "migration rollback failed: ${WHITE}$fs_migration${NC}"
exit 4
fi
;;
esac
i=$((i - 1)) i=$((i - 1))
done done
@@ -506,10 +708,11 @@ SQL
} }
form_psql_args() { form_psql_args() {
psql_args="-d $DB_URL -v ON_ERROR_STOP=1" local psql_args="-v ON_ERROR_STOP=1"
for var in ${VARIABLE_LIST:-}; do for var in ${VARIABLE_LIST:-}; do
psql_args="$psql_args -v $var" psql_args="$psql_args -v $var"
done done
printf '%s' "$psql_args"
} }
create() { create() {
@@ -609,10 +812,28 @@ generate_word() {
printf '%s' "$w" printf '%s' "$w"
} }
if ! command -v psql >/dev/null; then check_db_dependencies() {
log error "Required tool (psql) are not installed." [ "${DB_URL+x}" ] || return 0 # Skip if no DB_URL yet
db_type=$(detect_db_type)
case "$db_type" in
postgresql)
if ! command -v psql >/dev/null; then
log error "Required tool (psql) is not installed."
log error "PostgreSQL client tools are required for postgresql:// URLs"
exit 127 exit 127
fi fi
;;
sqlite)
if ! command -v sqlite3 >/dev/null; then
log error "Required tool (sqlite3) is not installed."
log error "SQLite3 client is required for sqlite:// URLs"
exit 127
fi
;;
esac
}
if ! [ "${AS_LIBRARY+x}" ]; then if ! [ "${AS_LIBRARY+x}" ]; then
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do

View File

@@ -0,0 +1,164 @@
# Migrator Test Suite
This directory contains comprehensive tests for the database migration tool supporting both PostgreSQL and SQLite.
## Test Structure
```
test/package/migrator/
├── default.nix # Nix test builder - auto-detects test type
├── lauch.sh # PostgreSQL test launcher
├── lauch-sqlite.sh # SQLite test launcher
├── util.sh # Shared test utilities
└── test/ # Test cases
├── <test-name>/ # PostgreSQL tests (default)
└── sqlite-<name>/ # SQLite tests (prefix with "sqlite-")
```
## Test Types
### PostgreSQL Tests (Default)
Any test directory or `.sh` file in `test/` will use PostgreSQL by default:
- Automatic PostgreSQL setup (initdb, pg_ctl, createdb)
- `DATABASE_URL` set to PostgreSQL connection string
- Requires: `pkgs.postgresql`
**Examples:**
- `migrate-up-single/`
- `migrate-down-multiple/`
- `init-migrator.sh`
### SQLite Tests
Tests with names starting with `sqlite-` use SQLite:
- Simple file-based database
- `DATABASE_URL` set to `sqlite:///path/to/test.db`
- Requires: `pkgs.sqlite`
**Examples:**
- `sqlite-basic/`
- `sqlite-migration-test/`
## Test Categories
### Core Functionality
- `init-migrator.sh` - Initialization
- `init-migrator-with-inherits.sh` - PostgreSQL INHERITS feature
- `migrate-up-single/` - Single step up migration
- `migrate-up-multiple/` - Multiple step up migrations
- `migrate-down-single/` - Single step down migration
- `migrate-down-multiple/` - Multiple step down migrations
- `migrate-to-forward/` - Migrate to specific version (forward)
- `migrate-to-backward/` - Migrate to specific version (backward)
- `migrate-already-at-target/` - Edge case: no-op migration
### Existing Database Support
- `migrate-existing-database/` - Add migrator to production DB
- `migrate-existing-with-conflicts/` - Handle schema conflicts
- `migrate-existing-data-migration/` - Transform existing data
### SQLite Support
- `sqlite-basic/` - Basic SQLite functionality
### Helper Functions
- `function-index-of.sh` - Test index_of helper
- `function-migration-list.sh` - Test migration_list helper
- `function-generate-word.sh` - Test word generator
### Utilities
- `create-migration.sh` - Test migration creation
- `migrations-list/` - Test migration listing
- `arguments.sh` - Test argument parsing
## Creating New Tests
### PostgreSQL Test
```bash
mkdir -p test/<test-name>/migration/<timestamp>-<name>
cat > test/<test-name>/run.sh <<'EOF'
#!/bin/dash
HECTIC_NAMESPACE=test-my-test
log notice "test case: ${WHITE}my test"
# $DATABASE_URL is automatically set to PostgreSQL
migrator --db-url "$DATABASE_URL" init
# ... your test code ...
log notice "test passed"
EOF
# Create up.sql and down.sql migration files
```
### SQLite Test
Same as above, but prefix the directory name with `sqlite-`:
```bash
mkdir -p test/sqlite-<test-name>/migration/<timestamp>-<name>
# ... rest is the same
```
## Running Tests
Tests are built and run via Nix:
```bash
# Run all tests
nix build .#checks.x86_64-linux
# Run specific test
nix build .#checks.x86_64-linux.migrator-test-<test-name>
# Run SQLite tests
nix build .#checks.x86_64-linux.migrator-test-sqlite-basic
```
## Test Isolation
Each test runs in complete isolation:
- **PostgreSQL**: Fresh PostgreSQL cluster per test
- **SQLite**: Fresh database file per test
- Clean working directory
- Independent environment variables
## Available Test Utilities
From `util.sh`:
- `columns(table)` - Get column names from table
- `is_number(var)` - Check if variable is numeric
From test environment:
- `log <level> <message>` - Logging (trace, debug, info, notice, error)
- `migrator` - The migrator binary under test
- `$DATABASE_URL` - Database connection string (auto-configured)
## Test Conventions
1. **Naming**: Use descriptive names with hyphens
2. **Logging**: Use `log` for output, not `echo`
3. **Exit codes**:
- 0 = success
- 1 = test failure
- Other = specific error conditions
4. **Cleanup**: Tests are automatically cleaned up by Nix
5. **Assertions**: Explicit checks with meaningful error messages
## Database-Specific Notes
### PostgreSQL
- Full schema support (`hectic.migration`)
- Domains with regex validation
- Triggers and functions
- INHERITS support
- TIMESTAMPTZ support
### SQLite
- Simple table names (`hectic_migration`)
- CHECK constraints instead of domains
- No triggers needed
- TEXT timestamps with datetime()
- Table recreation for column removal (older SQLite versions)

View File

@@ -1,37 +1,41 @@
{ inputs, self, pkgs, system, ... }: let { inputs, self, pkgs, system, ... }: let
lib = inputs.nixpkgs.lib; lib = inputs.nixpkgs.lib;
# turn anything under ./test into a derivation that exposes $out/run.sh # turn anything under test directory into a derivation that exposes $out/run.sh
mkTestDrv = name: type: mkTestDrv = folder: name: type:
if type == "directory" then if type == "directory" then
pkgs.runCommand "test-${name}" {} '' pkgs.runCommand "test-${name}" {} ''
if ! [ -f ${./test + "/${name}" + /run.sh} ]; then if ! [ -f ${"${folder}/${name}/run.sh"} ]; then
echo no run.sh in test/${name} echo no run.sh in test/${name}
exit 1 exit 1
fi fi
mkdir -p "$out" mkdir -p "$out"
cp -r ${./test + "/${name}"}/* "$out/" cp -r ${"${folder}/${name}"}/* "$out/"
chmod +x "$out/run.sh" chmod +x "$out/run.sh"
'' ''
else if lib.hasSuffix ".sh" name then else if lib.hasSuffix ".sh" name then
pkgs.runCommand "test-${lib.removeSuffix ".sh" name}" {} '' pkgs.runCommand "test-${lib.removeSuffix ".sh" name}" {} ''
mkdir -p "$out" mkdir -p "$out"
install -Dm755 ${./test + "/${name}"} "$out/run.sh" install -Dm755 ${"${folder}/${name}"} "$out/run.sh"
'' ''
else else
null; null;
testDir = builtins.readDir ./test; testDir = folder: builtins.readDir folder;
# attrset: testName -> drv with run.sh # attrset: testName -> drv with run.sh
testDrvs = testDrvs = folder:
lib.mapAttrs' (n: v: lib.mapAttrs' (n: v:
lib.nameValuePair (lib.removeSuffix ".sh" n) v lib.nameValuePair (lib.removeSuffix ".sh" n) v
) (lib.filterAttrs (_: v: v != null) ) (lib.filterAttrs (_: v: v != null)
(lib.mapAttrs (n: t: mkTestDrv n t) testDir)); (lib.mapAttrs (n: t: mkTestDrv folder n t) (testDir folder)));
postgresqlTestDrvs = testDrvs ./test/postgresql;
sqliteTestDrvs = testDrvs ./test/sqlite;
migrator = self.packages.${system}.migrator; migrator = self.packages.${system}.migrator;
mkPgTest = testName: testDrv: pkgs.runCommand "migrator-test-${testName}" mkPgTest = testName: testDrv: pkgs.runCommand "migrator-test-${testName}"
{ {
nativeBuildInputs = [ pkgs.coreutils pkgs.gnugrep pkgs.gnused ]; nativeBuildInputs = [ pkgs.coreutils pkgs.gnugrep pkgs.gnused ];
@@ -41,10 +45,26 @@
test=${testDrv} test=${testDrv}
export HECTIC_LOG=trace export HECTIC_LOG=trace
${builtins.readFile ./util.sh} ${builtins.readFile ./util.sh}
${builtins.readFile ./lauch.sh} ${builtins.readFile ./lauch-postgresql.sh}
# success marker for Nix # success marker for Nix
# shellcheck disable=SC2154 # shellcheck disable=SC2154
mkdir -p "$out" mkdir -p "$out"
''; '';
in lib.mapAttrs (name: drv: mkPgTest name drv) testDrvs
mkSqliteTest = testName: testDrv: pkgs.runCommand "migrator-test-${testName}"
{
nativeBuildInputs = [ pkgs.coreutils pkgs.gnugrep pkgs.gnused ];
buildInputs = [ pkgs.which migrator pkgs.sqlite ];
} ''
${builtins.readFile self.legacyPackages.${system}.helpers.posix-shell.log}
test=${testDrv}
export HECTIC_LOG=trace
${builtins.readFile ./util.sh}
${builtins.readFile ./lauch-sqlite.sh}
# success marker for Nix
# shellcheck disable=SC2154
mkdir -p "$out"
'';
in (lib.mapAttrs (name: drv: mkPgTest name drv) postgresqlTestDrvs) // (lib.mapAttrs (name: drv: mkSqliteTest name drv) sqliteTestDrvs)

View File

@@ -6,13 +6,12 @@
HECTIC_NAMESPACE=test-laucher HECTIC_NAMESPACE=test-laucher
export HECTIC_LOG=trace export HECTIC_LOG=trace
# shellcheck disable=SC2154
test_derivation="$(basename "$test")" test_derivation="$(basename "$test")"
test_name="${test_derivation#*-*-}" test_name="${test_derivation#*-*-}"
set -eu set -eu
root_dir="$(dirname $0)"
HECTIC_LOG= HECTIC_LOG=
# save path to pg_ctl in case $PATH will change # save path to pg_ctl in case $PATH will change
@@ -65,8 +64,10 @@ log info "run test ${WHITE}${test_name}${NC}"
mkdir './test' mkdir './test'
cp -r "$test"/* './test/' cp -r "$test"/* './test/'
cd './test' cd './test'
# shellcheck disable=SC1091
. './run.sh' . './run.sh'
# shellcheck disable=SC2034
HECTIC_NAMESPACE=test-laucher HECTIC_NAMESPACE=test-laucher
log info "finish test pipeline" log info "finish test pipeline"

View File

@@ -0,0 +1,45 @@
#!/bin/dash
# $out - nix derivation output
# $test - test and assertion file
HECTIC_NAMESPACE=test-laucher
export HECTIC_LOG=trace
# shellcheck disable=SC2154
test_derivation="$(basename "$test")"
test_name="${test_derivation#*-*-}"
set -eu
HECTIC_LOG=
log info 'start test pipeline (SQLite)'
# temp dirs
wd="$PWD"
db_file="$wd/test.db"
# Set up SQLite database URL
DATABASE_URL="sqlite://$db_file"
export DATABASE_URL
log info "using SQLite database: $db_file"
log info "run test ${WHITE}${test_name}${NC}"
# run test
mkdir './test'
cp -r "$test"/* './test/'
cd './test'
# shellcheck disable=SC1091
. './run.sh'
# shellcheck disable=SC2034
HECTIC_NAMESPACE=test-laucher
log info "finish test pipeline"
# success marker for Nix
# shellcheck disable=SC2154
mkdir -p "$out"

View File

@@ -2,18 +2,6 @@
HECTIC_NAMESPACE=test-init-migrator HECTIC_NAMESPACE=test-init-migrator
### CASE 1
log notice "test case: ${WHITE}dry run"
# NOTE: does not matter exist inherits tables or not, it must not connect to db
if ! migration_table_sql="$(migrator --inherits tablename --inherits 'table name' init --dry-run)"; then
log error "test failed: ${WHITE}error on migration table init dry run"
exit 1
fi
printf '%s' "$migration_table_sql" | grep -Eq 'INHERITS[[:space:]]*\([[:space:]]*"tablename"[[:space:]]*,[[:space:]]*"table name"[[:space:]]*\)' ||
{ log error "test failed: ${WHITE}not correct migration table inherits"; exit 1; }
### CASE 2 ### CASE 2
log notice "test case: ${WHITE}error: table inherit tables that not exists" log notice "test case: ${WHITE}error: table inherit tables that not exists"

View File

@@ -0,0 +1,6 @@
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
);

View File

@@ -0,0 +1,12 @@
-- SQLite doesn't support DROP COLUMN directly before 3.35.0
-- We need to recreate the table
CREATE TABLE users_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
);
INSERT INTO users_new (id, name) SELECT id, name FROM users;
DROP TABLE users;
ALTER TABLE users_new RENAME TO users;

View File

@@ -0,0 +1,3 @@
ALTER TABLE users ADD COLUMN email TEXT;

View File

@@ -0,0 +1,86 @@
#!/bin/dash
HECTIC_NAMESPACE=test-sqlite-basic
log notice "test case: ${WHITE}SQLite basic migration"
# Create SQLite database
SQLITE_DB="$PWD/test.db"
export DB_URL="sqlite://$SQLITE_DB"
log info "using SQLite database: $SQLITE_DB"
# Initialize migrator with SQLite
if ! migrator --db-url "$DB_URL" init; then
log error "test failed: ${WHITE}init failed for SQLite"
exit 1
fi
# Verify tables were created
if ! sqlite3 "$SQLITE_DB" "SELECT name FROM hectic_version WHERE name = 'migrator'" >/dev/null 2>&1; then
log error "test failed: ${WHITE}hectic_version table not created"
exit 1
fi
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 tables created successfully"
# Apply first migration
if ! migrator --db-url "$DB_URL" migrate up; then
log error "test failed: ${WHITE}first migration failed"
exit 1
fi
# Verify migration was applied
migration_count=$(sqlite3 "$SQLITE_DB" "SELECT COUNT(*) FROM hectic_migration")
if [ "$migration_count" != "1" ]; then
log error "test failed: ${WHITE}expected 1 migration, got $migration_count"
exit 1
fi
# Verify table was created
if ! sqlite3 "$SQLITE_DB" "SELECT COUNT(*) FROM users" >/dev/null 2>&1; then
log error "test failed: ${WHITE}users table not created"
exit 1
fi
log info "first migration applied successfully"
# Apply second migration
if ! migrator --db-url "$DB_URL" migrate up; then
log error "test failed: ${WHITE}second migration failed"
exit 1
fi
# Verify email column exists
if ! sqlite3 "$SQLITE_DB" "SELECT email FROM users LIMIT 0" >/dev/null 2>&1; then
log error "test failed: ${WHITE}email column not added"
exit 1
fi
# Migrate down
if ! migrator --db-url "$DB_URL" migrate down; then
log error "test failed: ${WHITE}migrate down failed"
exit 1
fi
# Verify only 1 migration remains
migration_count=$(sqlite3 "$SQLITE_DB" "SELECT COUNT(*) FROM hectic_migration")
if [ "$migration_count" != "1" ]; then
log error "test failed: ${WHITE}expected 1 migration after down, got $migration_count"
exit 1
fi
# Verify email column removed
if sqlite3 "$SQLITE_DB" "SELECT email FROM users LIMIT 0" >/dev/null 2>&1; then
log error "test failed: ${WHITE}email column should be removed"
exit 1
fi
log notice "test passed: SQLite support works correctly"