Skip to content

feat(migrate): Alembic and goose output for planned migrations; fix Python quoting (#148) - #163

Merged
cultron merged 1 commit into
feat/migration-planfrom
feat/migration-plan-pygo
Oct 28, 2026
Merged

cultron merged 1 commit into
feat/migration-planfrom
feat/migration-plan-pygo

Conversation

@cultron

@cultron cultron commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Into feat/migration-plan, not main. Fixes #148.

What changes

  • apso migrate and apso migrate generate now write the planned migration in the service's own format. TypeScript gets a TypeORM class (as before). Python gets an Alembic revision in alembic/versions/, with down_revision chained to the current head. Go gets a goose SQL file in migrations/. The plan JSON is still saved beside it. latestPlannedMigration and migrationsDir know each language's folder and file extension.
  • Python and Go files are named YYYYMMDDHHMMSS_<snake_name>, the same as goose's own files, so goose sorts them in the right order.
  • Verify for Python and Go does not use TypeORM:
    1. Apso's DDL writer builds the deployed schema in PGlite.
    2. The seed rows load, and the migration's SQL runs exactly as written in the file.
    3. The data-loss and --check steps run as they do for TypeScript.
    4. Drift is checked with catalogDiff against a fresh PGlite build of the new .apsorc.
    5. down() runs, and a second catalog diff must match the deployed schema. This step is skipped when down() is irreversible.
  • TypeScript verify is unchanged.
  • migrateUnsupported is removed. --typeorm still requires a TypeScript service.
  • apso migrate: generated Python (Alembic) migrations do not compile #148: SQL now goes into Python as a JSON string literal, which is also a valid Python string literal, instead of """...""". This applies to the new Alembic writer and to the old PythonGenerator.generateMigration. On the Go side, goose output wraps any statement that contains its own ; in StatementBegin/StatementEnd. The old Go generator gets the same wrapping.

Tests

  • test/lib/ddl/wrappers.test.ts:
    • Alembic and goose snapshots for all 8 plan fixtures.
    • python3 -m py_compile on every Alembic file. If python3 is missing, the test falls back to checking that each SQL literal round-trips.
    • The SQL read back from each file must equal the rendered SQL.
    • Regression test for apso migrate: generated Python (Alembic) migrations do not compile #148.
    • Tests for irreversible down, goose statement wrapping, and Alembic head detection.
    • verify runs end to end for python and go on rename-field, split-field and extract-entity, using each fixture's seed.sql and checks.sql, and must pass every step. split-field passes --accept-loss contact.fullName.
    • A drift test: removing the rename from the file must make verify fail.
  • The answers map and answeredPlan helper move from change.test.ts to test/lib/ddl/answers.ts so both test files can use them.
  • Full suite: 486 passed, 19 skipped (the opt-in suite). npm run lint: 0 errors.
  • Manual check: two chained Python revisions written by writePlannedMigration load in real Alembic 1.16.4. alembic history shows the chain, and alembic upgrade head --sql prints the expected SQL.

Not verified

  • goose itself was not run because it is not installed here. The Up/Down parsing used by verify is checked, but the goose CLI never parsed the files.
  • Neither wrapper was run against a real Postgres server. All database checks ran on PGlite.
  • The old PythonGenerator.generateMigration (migrate sandbox) still writes down_revision = None, so a second sandbox migration creates a second Alembic head. I did not change this.

…ython quoting (#148)

apso migrate and apso migrate generate now write the planned migration in the
service's own wrapper: a TypeORM class (TypeScript), an Alembic revision in
alembic/versions/ (Python, down_revision chained to the current head), or a
goose SQL file in migrations/ (Go, YYYYMMDDHHMMSS_<name>.sql so it sorts with
goose's own files). The plan JSON is saved beside it as before, and
latestPlannedMigration finds it in each language's folder.

Verify for Python and Go runs without TypeORM: Apso's DDL writer builds the
deployed schema in PGlite, the seed loads, the migration's SQL runs as
written, the data-loss and --check steps run as for TypeScript, and drift is
a catalogDiff against a fresh build of the new .apsorc. down then runs and
must give back the deployed schema. TypeScript verify is unchanged.
migrateUnsupported is gone; --typeorm still needs a TypeScript service.

literal) instead of """...""", which broke on SQL ending in a quoted
identifier. The old sandbox generators get the same fix, and goose output
fences any statement with its own ";" in StatementBegin/End.

Tests: Alembic and goose snapshots for all 8 plan fixtures, py_compile of
each Alembic file, and verify passing for python and go on rename-field,
split-field and extract-entity with their seed and checks.
@cultron
cultron merged commit 8567f16 into feat/migration-plan Oct 28, 2026
4 of 5 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