Repository navigation
feat(mssql): a SQL Server engine family (P4) - #2
Merged
Merged
Conversation
treeman gives every Conductor worktree its own Postgres database and a clone per test worker. A project whose source of truth is SQL Server got nothing: one shared container, hardcoded ids, and a test tier that two worktrees — or two jest workers — cannot run at once. SQL Server has no `CREATE DATABASE … TEMPLATE`, and the two things that look like one are not: `DBCC CLONEDATABASE` copies schema and statistics without rows, and a database snapshot is read-only. The only copy a client can drive is BACKUP + RESTORE, so a template here is a pair — the template database, which is what treeman's cache-hit probe asks about for every engine, plus one `.bak` beside it that each clone restores from. Captured once, hydrated N times. Everything runs over TDS, file paths included: `TO DISK`, `FROM DISK` and `MOVE … TO` all take variables, so only database names are interpolated, through the same strict `[A-Za-z0-9_]` validator the other engines use. Three engine differences worth knowing: * Restored files carry a random token rather than the database's name. The spare claim is `ALTER DATABASE … MODIFY NAME`, which moves the database but not its files, so a claimed spare keeps files named for the spare — and naming by database would collide on the next restore of that freed name. * Drops take the database single-user first. There is no pg_terminate_backend, and DROP fails outright while anyone is connected, which a jest worker's idle pool routinely is. * `dump:` is rejected, with the reason in the message: RESTORE reads a path on the server, which for a containerised server is a different filesystem. Seed from `seed:` instead, which is a command and can get a file there. `db_dump` refuses for the same reason. `WriteWatermark` declines rather than guessing. The caller SKIPS a capture when the token is unchanged, and SQL Server's per-database write counters are per index — writes to a heap move nothing — so a token that can miss a write is worse than none. Verified against SQL Server 2022 and 2025 (Developer): 12 e2e tests covering the prepare cycle end to end and the driver's own behaviour — eight concurrent restores of one template, the collation reaching the databases treeman creates, LIKE-escaping against the sibling-wipe, the rename race having exactly one winner, a drop with a live connection open, and a restore after the backup file was removed behind the template's back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Upstream's `ci.yml` triggers on PRs to main/master only, so a PR into `fsh` — which is every patch this fork carries — reaches merge with no build, vet or test gate at all. The three vet jobs answer questions about upstream and about the shape of our diff; none of them compiles anything. Not a copy of upstream's job: no lint (the flake-pinned golangci-lint is a nix download this job's egress policy would have to admit, and a finding in upstream's tree is not ours to gate on), no macOS matrix, no e2e tier — that one needs docker engines and is run by hand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
treeman gives every Conductor worktree its own Postgres database plus a clone per test worker. A project whose source of truth is SQL Server got nothing — one shared container on a fixed name and port, hardcoded row ids, and a test tier that two worktrees (or two jest workers) cannot run at the same time.
This adds
mssqlas a full engine family: per-worktree databases, cached snapshot templates, per-test-worker clones, pre-warmed spares, prefix reclaim,branch_scoped, doctor/MCP coverage.How a clone works, and why it isn't a
TEMPLATESQL Server has no
CREATE DATABASE … TEMPLATE, and the two things that look like one are not:DBCC CLONEDATABASEcopies schema and statistics without rows, and a database snapshot is read-only. The only database copy a client can drive isBACKUP+RESTORE.So a template here is a pair — the template database (which is what treeman's
DatabaseExistscache-hit probe asks about, identically for every engine) plus one.bakfile beside it that every clone restores from. Captured once, hydrated N times.Everything goes over TDS, the file paths included:
TO DISK,FROM DISKandMOVE … TOall accept variables, so paths are bound as parameters and only database names are interpolated — through the same strict[A-Za-z0-9_]validator the other engines use (ident.QuoteMSSQL, bracket-quoted soQUOTED_IDENTIFIERcan't matter).Three engine differences worth a reviewer's attention
ALTER DATABASE … MODIFY NAME, which moves the database but not its files — so a claimed spare keeps files named for the spare, and naming files after the database would collide on the next restore of that freed name.TestClaimSpareRenamecovers exactly that sequence.pg_terminate_backend, andDROP DATABASEfails outright while anybody is connected — which a jest worker's idle pool routinely is.TestDropMatchingEvictsConnectionsholds a live connection open across the drop.dump:is rejected, with the reason in the message.RESTOREreads a path on the server, which for a containerised server is a different filesystem; a host path would silently restore whatever happens to sit there, or nothing. Seed fromseed:instead — it is a command, so it can copy the file to the server.db_dumprefuses for the same reason (sqlcmdis not apg_dumpequivalent).WriteWatermarkdeclines (returns"") rather than guessing. The caller skips a capture when the token is unchanged, and SQL Server's per-database write counters are per index — writes to a heap move nothing, and the rows vanish on restart — so a token that can miss a write is worse than none.Beyond the registry
docs/internals.mdpromised "≤4 touchpoints". The registry does cover prepare, teardown, GC and recovery, but four places switch on the concrete adapter type instead, and each fails silently:prepareFamily,connectBranchEngine(branch_scopedquietly no-ops),branchreap'suntrackedMarker+listDurableCandidates(durables leak forever), andresolve's credential copy-back (apassword: $NAMEref resolves to empty). That section now names all four.snapshot.SpareCountswas also hardcoded to Postgres — stale since MySQL became prewarm-capable — so it now walks every prewarm-capable family.Verification
go test ./...— 1252 pass.golangci-lint run ./...— clean (two pre-existing findings ininternal/daemon/socket_unix_other.goremain, confirmed present before this branch).golangci-lint fmt --diffclean. Schemas and generated docs regenerated.go test -tags e2e ./e2e/mssql/— 12 pass, against SQL Server 2022 (the suite's image) and 2025 (by hand). The prepare cycle end to end — cold build → cache hit → input edit → rebuild → fan-out → teardown keeping the cache — plus the driver's own behaviour: eight concurrent restores of one template, the configured collation reaching the databases treeman creates (and SIMPLE recovery reaching the restored ones), LIKE-escaping against the sibling-wipe, the rename race having exactly one winner and leaving the loser usable, a drop with a live connection open, and a restore after the backup file was deleted behind the template's back.BATCHES[engines], sojust test-e2e enginesruns it. Note SQL Server ships no arm64 image: on an Apple Silicon host the suite runs under emulation (it does pass there — that is where these numbers come from), and the compose file caps the server at 2 GB because its default is "all of it".Fork bookkeeping
This is the first patch that edits upstream's own files rather than only adding ours — an engine family has to touch the engine registry, the config schema, prepare, resolve and the docs.
FORK.mdcarries the P4 row (why, upstream intent, drop-when) andfsh-vet.yml's allowlist carries the blast radius; the allowlist was checked against the actual diff and covers it exactly.One dependency added,
github.com/microsoft/go-mssqldb v1.11.2: no OSV advisory of its own, and it dragsgolang.org/x/cryptofrom 0.33.0 to 0.55.0, clearing 34 of the 37 advisories that single stale indirect contributes to the baseline. The three left on 0.55.0 are all already baselined, so the deps check stays green with no re-baseline. Its Azure, Key Vault and Kerberos modules are in itsgo.modbut not in ours — they belong to packages we do not import, and module-graph pruning keeps them out ofgo.sum.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.