Skip to content

Commit 9869c19

Browse files
committed
bound migration version allocation
1 parent c71b7fc commit 9869c19

3 files changed

Lines changed: 41 additions & 1 deletion

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,8 @@ and `gen structs` atomically replace their destination; `migrate new` remains
697697
exclusive and never overwrites an existing migration. If concurrent creators
698698
select the same next version, the loser rescans and retries up to a fixed bound
699699
instead of leaving a partial file or immediately surfacing the filename race.
700+
The four-digit version field is minimum padding, not a ceiling; exhaustion at
701+
`u64` maximum returns `MigrationVersionConflict` instead of wrapping to zero.
700702

701703
Generated struct files import `zsql` themselves, map supported SQL domain types
702704
to `zsql.types.*`, and preserve database nullability with optional Zig fields.

cli/main.zig

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,44 @@ test "migration creation bounds repeated version collisions" {
656656
try std.testing.expectEqual(max_migration_create_attempts, collisions.attempts);
657657
}
658658

659+
test "migration creation preserves versions wider than the minimum padding" {
660+
var tmp = std.testing.tmpDir(.{});
661+
defer tmp.cleanup();
662+
try tmp.dir.createDirPath(std.testing.io, "migrations");
663+
try tmp.dir.writeFile(std.testing.io, .{
664+
.sub_path = "migrations/V9999__previous.sql",
665+
.data = migration_template,
666+
});
667+
668+
const path = try createNextMigrationFile(
669+
std.testing.allocator,
670+
tmp.dir,
671+
std.testing.io,
672+
"migrations",
673+
"wide_version",
674+
);
675+
defer std.testing.allocator.free(path);
676+
try std.testing.expectEqualStrings("migrations/V10000__wide_version.sql", path);
677+
678+
const id = try zsql.migrate.parseFilename(path);
679+
try std.testing.expectEqual(@as(u64, 10_000), id.version);
680+
}
681+
682+
test "migration version discovery rejects u64 exhaustion" {
683+
var tmp = std.testing.tmpDir(.{});
684+
defer tmp.cleanup();
685+
try tmp.dir.createDirPath(std.testing.io, "migrations");
686+
try tmp.dir.writeFile(std.testing.io, .{
687+
.sub_path = "migrations/V18446744073709551615__last.sql",
688+
.data = migration_template,
689+
});
690+
691+
try std.testing.expectError(
692+
error.MigrationVersionConflict,
693+
nextMigrationVersion(tmp.dir, std.testing.io, "migrations"),
694+
);
695+
}
696+
659697
fn nextMigrationVersion(root: std.Io.Dir, io: std.Io, dir_path: []const u8) !u64 {
660698
var dir = root.openDir(io, dir_path, .{ .iterate = true }) catch return 1;
661699
defer dir.close(io);

docs/FEATURE_MATRIX.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ coverage; PostgreSQL rows marked live also run against PostgreSQL 16 in CI.
2222
| Explicit owned rows | Complete | `OwnedRow`, `Row.getOwned`, `zsql.freeOwnedRows`; documented invalidation boundary for borrowed values | allocator-backed core/driver tests plus external SQLite survival after rows, connection, and database teardown |
2323
| Connection pooling | Complete | `Pool(D)`, `Lease(D)`, health-aware consuming release, OOM-safe idle return and owned-result unwind, shutdown draining/wakeup, stats and timeouts | SQLite release-OOM/result-unwind/recovery tests; PostgreSQL live release-OOM/result-unwind/shutdown tests |
2424
| Transactions and savepoints | Complete | explicit nested/idle/aborted states; PostgreSQL `25P02` mapping; prepared-statement transition safety; failed-state savepoint recovery; `Tx(D)`, `Savepoint(D)`, `withTx` | SQLite state tests and PostgreSQL direct/prepared/pool live tests |
25-
| Migrations | Complete | transactional apply, durable dirty failures, checksum-guarded API/CLI repair; allocator-owned post-connection `MigrationStatus`; exclusive atomic and collision-retrying `migrate new`; `Migrator(D).up`, `.status` | cross-driver status OOM/teardown tests, SQLite repair workflow, PostgreSQL live repair tests, CLI atomic-create/concurrent-collision/parser tests, migration example |
25+
| Migrations | Complete | transactional apply, durable dirty failures, checksum-guarded API/CLI repair; allocator-owned post-connection `MigrationStatus`; exclusive atomic and collision-retrying `migrate new`; checked `u64` version discovery; `Migrator(D).up`, `.status` | cross-driver status OOM/teardown tests, SQLite repair workflow, PostgreSQL live repair tests, CLI atomic-create/concurrent-collision/version-boundary/parser tests, migration example |
2626
| Schema inspection | Complete | allocator-owned post-connection schema graph; driver `inspectSchema`; dialect-tagged CLI `inspect`; atomic schema/codegen replacement; structured PostgreSQL schema/table identity; self-contained nullable struct generation with exact fields and schema-aware collision-free table types | SQLite exhaustive graph OOM tests; PostgreSQL post-connection live test; inspector/codegen syntax and CLI atomic-replacement tests |
2727
| Optional offline query checks | Complete within documented bounded scope | `zsql.check`, `zsql.checkedQuery`; dialect-aware unquoted lookup and structured `schema.table` / `schema.table.column` PostgreSQL resolution; exact escaped quoted identifiers; SQL-correct alias visibility; exact bind contracts; outer-CTE anchoring with opaque-relation rejection; projection/alias-bound row shapes; portable COUNT/MIN/MAX inference; WHERE/HAVING/JOIN ON/USING/GROUP/ORDER refs; explicit capacity failures; typed domain wrappers; numeric width checks | parser/dialect/schema-qualified/CTE/quoted-identifier/projection/aggregate/clause/join/scope/shape/type/nullability/narrowing tests; `zig build check-sql` |
2828
| Query builder | Complete | `QueryBuilder`, `ident`, `identPath`, `bind`, `rawUnsafe` | core unit tests |

0 commit comments

Comments
 (0)