Skip to content

Expanded Migrations (Flyway, Liquibase XML/YAML, Laravel) & Adds Native DBDiff Migration CLI & Supabase Support#142

Merged
jasdeepkhalsa merged 12 commits intomasterfrom
feature/expanded-migrations
Mar 3, 2026
Merged

Expanded Migrations (Flyway, Liquibase XML/YAML, Laravel) & Adds Native DBDiff Migration CLI & Supabase Support#142
jasdeepkhalsa merged 12 commits intomasterfrom
feature/expanded-migrations

Conversation

@jasdeepkhalsa
Copy link
Copy Markdown
Member

@jasdeepkhalsa jasdeepkhalsa commented Mar 3, 2026

  • Migration runner: new migration:up/down/status/validate/repair/baseline commands; YYYYMMDDHHMMSS_name.up/down.sql file pairs; SHA-256 checksum tracking via _dbdiff_migrations table
  • Output formats: --format flag on diff; supports native, Flyway, Liquibase XML/YAML, and Laravel migration class
  • Supabase / DSN URLs: --server1-url/--server2-url and --db-url accept full connection URLs; auto-detects Supabase hosts, sets SSL, enables pgbouncer on port 6543
  • CLI: migrated from aura/cli to Symfony Console; dbdiff.yml config file support added
  • Unit tests: 108 tests across DsnParser, all 5 format classes, and MigrationFile; no DB required
  • CI: new unit-tests job (PHP 8.0–8.5, no service); PHP 7.x removed from all matrices
  • Drop PHP 7.x: EOL Nov 2022; codebase already used PHP 8.0+ syntax

Migration Output Formats
- Add FormatInterface + FormatRegistry (src/Migration/Format/)
- NativeFormat         — default UP/DOWN SQL file (replaces legacy Templater output)
- FlywayFormat         — V{ts}__{desc}.sql + optional U{ts}__{desc}.sql
- LiquibaseXmlFormat   — changelog.xml with <changeSet>/<rollback> blocks
- LiquibaseYamlFormat  — changelog.yaml equivalent via symfony/yaml
- LaravelFormat        — anonymous Migration class with DB::unprepared() up/down

Native Migration Runner
- MigrationFile (src/Migration/Runner/) — YYYYMMDDHHMMSS_name.up.sql / .down.sql pairs
  * timestamp-ordered, self-describing names (no cryptic V/U prefix)
  * scaffold(), scanDir(), getChecksum() helpers
- MigrationHistory — _dbdiff_migrations tracking table (MySQL/Postgres/SQLite DDL)
  * recordSuccess, recordFailure, repairFailed, recordBaseline
- MigrationRunner — up(), down(), status(), validate(), repair(), baseline()
  * transaction-per-migration safety model (Flyway-style halt on failure)
  * SHA-256 checksum verification

CLI — migrated from aura/cli to symfony/console
- Add symfony/console to composer.json
- DiffCommand             — full replacement for legacy aura/cli diff flow; --format flag
- MigrationNewCommand      (migration:new)
- MigrationUpCommand       (migration:up [--target])
- MigrationDownCommand     (migration:down [--last --target])
- MigrationStatusCommand   (migration:status)
- MigrationValidateCommand (migration:validate)
- MigrationRepairCommand   (migration:repair)
- MigrationBaselineCommand (migration:baseline)
- Refactor dbdiff / dbdiff.php entry points to Symfony Application
- `diff` is the default command — legacy `dbdiff db1:db2` invocations unchanged
- ConfigOptionTrait — shared YAML config loading for all migration commands

Config
- MigrationConfig (src/Migration/Config/) — YAML config loader (dbdiff.yml)
- Add dbdiff.yml.example
- DBDiff::getDiffResult() — new lower-level method for programmatic use
…--server2-url, pgbouncer)

- DsnParser: parse mysql/pgsql/postgres/sqlite DSN URLs; auto-detect Supabase
  hosts (.supabase.co / .supabase.com) and set sslmode=require; auto-enable
  pgbouncer on port 6543; toServerAndDb() bridge for CLIGetter-style structs
- MigrationConfig: url: YAML key + db_url override (highest priority);
  pgbouncer property; applyDsn() private helper; toConnectionConfig() emits
  PDO::ATTR_EMULATE_PREPARES when pgbouncer detected
- ConfigOptionTrait: addDbUrlOption() helper; --db-url option wired into
  loadConfig() with highest priority
- All 7 migration:* commands: --db-url option added via addDbUrlOption()
- DiffCommand: input argument OPTIONAL; --server1-url / --server2-url added;
  applyServerUrls() auto-builds input struct from URL db names; parseServer()
  now accepts full DSN URLs alongside legacy user:pass@host:port format
- dbdiff.yml.example: Supabase connection guide (url:, pgbouncer port,
  explicit fields, and --server1-url CLI example)
New unit test suite (no database required):

  tests/Migration/Config/DsnParserTest.php   — 22 tests
    • parse() for mysql/pgsql/postgres/postgresql/sqlite
    • default port assignment per driver
    • SQLite absolute and relative path handling
    • ?sslmode and ?pgbouncer query-string extras
    • Supabase host heuristics (auto-SSL, port-6543 pgbouncer)
    • URL-encoded credentials (user + password)
    • isSupabaseHost() for .supabase.co and .supabase.com
    • toServerAndDb() output shape and SQLite path bridging
    • Error cases: unsupported scheme, malformed URL

  tests/Migration/Format/NativeFormatTest.php      — 10 tests
  tests/Migration/Format/FlywayFormatTest.php      — 10 tests
  tests/Migration/Format/LiquibaseXmlFormatTest.php — 10 tests
  tests/Migration/Format/LiquibaseYamlFormatTest.php — 11 tests
  tests/Migration/Format/LaravelFormatTest.php     — 13 tests
    • render() return type (string vs array as appropriate)
    • file naming conventions (Flyway V/U prefix, Laravel date format)
    • description slugification and special-char stripping
    • UP/DOWN SQL embedding
    • empty-content placeholders
    • valid XML / YAML / PHP output
    • getExtension() / getLabel()

  tests/Migration/Runner/MigrationFileTest.php     — 18 tests
    • scaffold() creates .up.sql + .down.sql with correct names
    • scaffold() slugifies description, sets version
    • scaffold() creates directory if missing
    • scaffold() throws on duplicate
    • getChecksum() is SHA-256 hex, idempotent, content-sensitive
    • hasDown() / getDownSql() with and without DOWN file present
    • getBaseName() format
    • scanDir() ascending version order, skips non-matching files,
      handles empty and non-existent directories

Config:
  tests/phpunit.v9.xml  — added 'Unit' test suite pointing at ./Migration
  scripts/run-tests.sh  — added --unit flag (runs Unit suite, no DB needed)
… false

PHP's parse_url() treats the empty authority in sqlite:///abs/path as a
malformed URL and returns false, causing an InvalidArgumentException.

Relative paths (sqlite://./rel) worked because '.' was parsed as 'host'.

Fix: detect the sqlite: scheme via regex before calling parse_url, then
extract the path directly from the match groups. Removes all dependency
on parse_url for SQLite URLs.

Caught by DsnParserTest::testSqliteAbsolutePath running under php:8.3-cli.
- New 'unit-tests' job: runs --unit testsuite on PHP 8.0/8.3/8.4/8.5
  with no DB service. Gives fast feedback on every push without waiting
  for MySQL/Postgres containers to spin up.

- Removed PHP 7.4 from all three matrices (MySQL, SQLite, Postgres).
  composer.json now declares require php >=8.0; 7.4 would fail on
  composer install. The unit-tests job starts coverage from PHP 8.0.
PHP 7.x reached end of life in November 2022. The codebase already uses
PHP 8.0+ features (#[AsCommand] attributes, match expressions, str_contains,
str_starts_with) making it a parse error on PHP 7.x regardless.

Changes:
- docker-compose.yml: remove cli-php74-{mysql80,84,93,96} service blocks
- .env / .env.example: remove PHP_VERSION_74 variable
- .travis.yml: deleted (was PHP 5.4–7.1 only; superseded by GHA entirely)
- README.md: remove PHP 7.4 from pre-requisites and supported versions list;
  remove '7.3' from standalone Docker CLI section heading
- DOCKER.md: remove PHP 7.4 from versions list, CLI services list,
  run examples, test examples, and CI matrix table
- GHA tests.yml: already updated in previous commit (8.0+ only)
@jasdeepkhalsa jasdeepkhalsa force-pushed the feature/expanded-migrations branch from 0eb0b15 to 6edf548 Compare March 3, 2026 18:59
@jasdeepkhalsa jasdeepkhalsa changed the title Feature/expanded migrations Expanded Migrations (Flyway, Liquibase XML/YAML, Laravel) & Adds Native DBDiff Migration CLI & Supabase Support Mar 3, 2026
DiffCommand:
- Reduce execute() cognitive complexity (33→~5) by extracting
  buildParams(), resolveConnections(), runAndWrite(), writeRendered()
- Reduce execute() return count (6→2) via top-level try/catch wrapper
- applyServerUrls() now mutates $params directly and returns $autoInput only

DsnParser:
- Rename $SCHEME_MAP → $schemeMap (match camelCase naming convention)
- Reduce parse() cognitive complexity (18→~5) by extracting
  resolveSqlitePath() and applySupabaseHeuristics() helpers

MigrationHistory:
- Extract DATE_FORMAT constant to eliminate 4× duplicated 'Y-m-d H:i:s' literal

MigrationFile:
- Merge nested !is_dir / !mkdir if-statements into single &&-condition
- Replace generic \RuntimeException with dedicated MigrationException

MigrationConfig:
- Replace generic \RuntimeException with dedicated ConfigException

New:
- src/Migration/Exceptions/MigrationException.php
- src/Migration/Exceptions/ConfigException.php

All 108 unit tests still pass (PHP 8.3).
…w code

Fix root cause of "No tests executed!" on GHA:
- Add Unit testsuite to tests/phpunit.xml (was only in phpunit.v9.xml)
- PHPUnit 10+/11+ (used by GHA) uses phpunit.xml; Unit suite was missing

New test files (all verified passing locally — PHP 8.4, 147 tests total):

tests/Migration/Command/DiffCommandTest.php (20 tests):
  - resolveConnections() error paths: missing input, missing --server1-url,
    missing --server2-url (via CommandTester)
  - parseInput() validation: missing colon, mismatched depth, unknown format
  - applyServerUrls() invalid scheme propagation
  - normaliseInclude() all branches: both/all→both, down, up, unknown,
    uppercase variants (via @dataProvider + reflection)
  - parseInput() happy paths: db-level and table-level structures (reflection)
  - buildParams() defaults and custom values (reflection + ArrayInput)

tests/Migration/Config/MigrationConfigTest.php (17 tests):
  - Default property values when constructed with no file
  - YAML loading: database section (individual fields), DSN url, mixed
  - YAML loading: migrations section (dir, history_table, out_of_order)
  - ConfigException for missing file and invalid YAML
  - applyOverrides() direct key→property mapping, db_url priority,
    Supabase host auto-heuristics through db_url
  - toConnectionConfig() for MySQL, pgsql, pgsql+pgbouncer, SQLite
  - resolveMigrationsDir() absolute, relative, trailing-slash strip

tests/Migration/Config/DsnParserTest.php (2 new tests):
  - testSqliteBarePathGetsLeadingSlash: sqlite://rel/path → /rel/path
    (exercises the `else` branch of resolveSqlitePath())
  - testSqliteSchemeWithoutDoubleSlash: sqlite:/abs/path → /abs/path
Add config.platform.php = "8.0" to composer.json so Composer resolves
dependencies against the minimum supported PHP version regardless of the
host PHP. This ensures the generated lock file is compatible with all
matrix entries (PHP 8.0, 8.3, 8.4, 8.5) and prevents platform_check.php
from blocking older PHP versions during composer install on GHA.
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Mar 3, 2026

@jasdeepkhalsa jasdeepkhalsa merged commit 903a2f1 into master Mar 3, 2026
35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant