8Examples / work
Case study · SQL Server to Postgres
Bloomberg

Three thousand databases, nobody watching.

A reference implementation for migrating a twenty-year-old SQL Server estate to Aurora Postgres: schema conversion, AI repair of what the tools cannot translate, and a differential test suite that proves source and target behave identically, one database at a time. Built as the working proof of concept for a migration engagement now underway at Bloomberg.

The Problem

Migrations do not fail at the tables.

Moving a schema is the easy part. The hard part is the code living inside the database: stored procedures, triggers, and functions written over twenty years, by people who have long since moved on, encoding rules nobody alive can fully recite. The conversion tools translate what they can. What they get wrong, they get wrong silently.

At estate scale the problem compounds. One database can be converted by hand and eyeballed by an expert. Three thousand cannot. Whatever discipline survives has to survive being run three thousand times with nobody watching.

The Belief

Proof, not review.

Code review cannot prove a migration correct, because the defects are behavioral, not textual. A ported procedure can read perfectly and still compute the wrong number. So the strategy reduces the whole problem to a single predicate: source must equal target.

Every test is written once and executed against both engines: the same inputs into SQL Server and Postgres, the same assertions on what comes back. If behavior diverges, the migration is wrong, no matter how clean the diff looked.

The Walkthrough

One database first, by hand.

Before automating anything, one representative database went through the pipeline manually: 39 tables, 9 views, 20 functions, 12 procedures, 4 triggers. AWS Schema Conversion Tool translated most of it and stumbled exactly where legacy code lives: MERGE not translated at all, PIVOT and OPENJSON views emitted as error stubs, 17 of 57 foreign keys dropped.

After the repairs, the differential suite ran: 136 tests, 81 of them data-driven SQL cases. It found three bugs in the repaired port that no reading of the code would catch: a CROSS JOIN that needed to be LATERAL, an exception handler that dropped its temp tables mid-loop, a nested call missing an INOUT argument. All three compiled cleanly. All three were invisible until executed.

The suite also caught divergences nobody was looking for: a tax year that was locked on the source but never on the target, and audit payloads that differed in casing and boolean encoding for every row written after cutover. That is what a behavioral gate buys: it catches the failures you did not think to look for.

3,000
databases the loop is built for
136
differential tests, one body, two engines
81
data-driven SQL parity cases
3
bugs invisible until executed
1
pass/fail predicate: source == target
The Loop

Then teach a machine to do it.

The manual pass becomes the specification for the autonomous one. One command takes a database from scratch through conversion, repair, data copy, and verification into a timestamped result folder. Conversion failures land in an error queue. A Bedrock remediation agent proposes a fix, applies it, and retries, within bounds. Databases that pass the gate move on; databases that do not go back around.

SQL Server estatedb 1 .. 3,000 · source of truthSchema Conversion Toolschema, procedures, triggerswhat it cannot translateError queueSQS + DynamoDB stateBedrock remediationagentproposes · applies · retriesAurora PostgreSQLschema per databaseDMS data copyfull load per databaseDifferential test suiteone test body, both enginesBedrock-generated parity inputsdivergence foundsource == targetthe only pass/fail predicateShadowwritesTerraform primary · CloudFormation shadow stack, drift-diffedevery database exits through the same gate, or goes back around
The loop, end to end. The happy path runs down the middle; everything the tools cannot translate detours through the error queue and the remediation agent, then re-enters the same gate as everything else.

Around the loop sits the estate machinery: Terraform as the primary infrastructure, a CloudFormation shadow stack diffed for drift, DMS moving the data, shadow writes comparing live traffic against the new engine, and the same differential suite gating every single database. The agent has autonomy inside the loop and none outside it. The predicate never changes.

MIGRATION CONTROL PLANEStep Functionsone run per databaseDynamoDBmigration stateS3artifacts · resultsREPAIR LOOPSQS error queue+ DLQLambdaremediation workerBedrock agentguardrailed · bounded retriesorchestrates the runconversion · test failuresrepairs re-appliedVPC · PRIVATE SUBNETSSQL Server on EC2db 1 .. 3,000 · SSM-onlyDMSreplication instanceECS tasksSCT · differential suiteAurora PostgreSQLcluster · schema per dbRDS Proxyshadow writesAROUND THE LOOPCloudWatchdashboards · alarms · drift checksSNSescalations to humansSecrets Managercredentials · KMS
The loop as the AWS estate Terraform provisions it. Step Functions runs each database through SCT and the differential suite as ECS tasks, with DMS copying data into Aurora; what fails queues to SQS, where a Lambda worker drives the guardrailed Bedrock agent and its repairs re-enter the same gate. Terraform is the primary stack, with a CloudFormation shadow stack diffed for drift.
The Point

Autonomy is earned by the gate.

The interesting part is not that an AI repairs SQL. It is that the repair is allowed to be wrong, because nothing wrong can get through the gate. Once correctness is mechanical, autonomy stops being a leap of faith and becomes an engineering decision.

Same discipline as everything else on this site: express the behavior concretely, prove it, and only then scale it. The whole pipeline, the schemas, the tests, and the runbook are public in the repository above. Walk through it.