One package across a mixed-version database fleet
Dev’s on the newest engine. Two prod tenants are a couple releases back. One contract-bound tenant is stuck on a version so old you wince every time it comes up in standup. And you’ve got one schema change that has to land on all of them — today.
You don’t upgrade a whole farm in one night. Nobody does. The servers move at the speed of maintenance windows, budgets, and the one customer whose contract pins them to a version from another era.
Hey folks. I’m Forge Barrett, master of the Content Forge here at SchemaSmith.
This course is about that farm — one package, many engine versions, all live at once. Not “someday we’ll all be current.” The real fleet, the mixed one, deployed to as it actually is. Over the next six modules we’ll take the hardest, most common shape in schema management and forge a clean answer to it: let the engine adapt where it can, gate the rest yourself, and retire the gates when the last laggard finally catches up.
But before any of that, we have to get one distinction dead straight. It’s the thing that makes the whole course legible, and it’s the thing most people get tangled in.
Two numbers people conflate
Section titled “Two numbers people conflate”There are two versions in play on every deploy, and they answer different questions.
MinimumVersion lives in your Product.json. It answers “may I deploy here?” It’s your policy — a floor you declare, a guardrail SchemaSmith enforces at pre-flight. Set it to 2016 and a 2014 server gets refused before a single table changes. You chose that number. You can raise it, lower it, or leave it off.
The detected version is what the engine reports when SchemaSmith connects. It answers “what SQL will you generate?” That’s the engine’s decision — SchemaSmith reads what the server actually is and generates the DDL that server can take. This happens on every deploy, whether or not you ever set a MinimumVersion. You don’t pick it. The server publishes it; SchemaSmith reads it.
Here’s the trap: these feel like the same number, and they’re not even the same kind of number.
- One is a gate you set — may I?
- One is a fact the engine reads — what will I generate?
A server can clear your MinimumVersion and still get different generated DDL than the server next to it, because the two of them report different detected versions. The floor said yes to both; the codegen still adapted to each.
And the part that pays off at the very end of this course: your floor and SchemaSmith’s own floor are separate numbers, and yours can legitimately be higher. SchemaSmith reaches all the way back — SQL Server 2008, PostgreSQL 12, MySQL 5.7, MariaDB 10.2. That’s the product’s capability. It does not mean your package should stretch that far. You might declare MinimumVersion at 2016 and refuse everything older on purpose — because you stopped supporting 2014, even though the tool never did. Your policy sits on top of the product’s capability, and it’s allowed to be stricter.
Hold onto that. In Module 5, raising MinimumVersion is the move that retires a version gate — and it only makes sense if you’ve kept these two numbers apart from the start.
Reading the fire on a mixed fleet
Section titled “Reading the fire on a mixed fleet”You already know the tool for reading a server before you commit the metal — the pre-flight pair from Course 6, Module 2. --TestConnection connects, checks the floor, and reports. --PreviewTargets goes further and resolves the actual target roster. We’re not going to re-teach those switches here; go run that module if it’s fuzzy.
What’s new is what they show you across a mixed fleet. Point them at the farm and each target answers with its own detected version — and each engine answers in its own dialect:
Validate server version floor localhost,11433: detected SqlServer version 16.0.4260.1SQL Server hands back a four-part build number; PostgreSQL a bare major like 16; MySQL 8.0.45; MariaDB something like 11.4.12-MariaDB-ubu2404. Course 6 explains why those shapes differ per engine and why the digits past the major decide behavior — that’s where the CREATE OR ALTER-arrived-in-2016-SP1 story lives (13.0.4001 and 13.0.1601 are both “2016” and don’t act the same). Go read it there; I won’t re-forge it here.
What’s mine to make here is narrower, and it’s the reason this course exists. In Course 6 the pre-flight was a health check — is this one server ready? On a mixed fleet it becomes a map: line those strings up across servers that genuinely differ and you can see, before you strike, which tier will take the change and which won’t. That non-uniformity isn’t noise to tidy away — it’s the signal. And it turns straight into the question the rest of the course answers: a change that isn’t safe on every tier — now what?
The arc — three escalating answers, then the retirement
Section titled “The arc — three escalating answers, then the retirement”Once the fleet is real and the two numbers are straight, the rest of the course is a spine of escalating answers to exactly that question — a change that isn’t safe on every tier of the farm. There are three of them, and they escalate:
| # | The answer | Who decides |
|---|---|---|
| 1 | The engine adapts for you. Author the modern thing; SchemaSmith emits what each target can take, and records what it couldn’t. | SchemaSmith, for its own generated DDL |
| 2 | You gate it yourself. SchemaSmith won’t rewrite the scripts you wrote — so you gate them, by folder, component, or a per-script skip. | You, for your scripts |
| 3 | The wire format bends. On the oldest tier, even the payload encoding shifts — JSON or XML — and you choose which to shred. | The detected version, or your override |
That boundary between answer 1 and answer 2 is the whole conceptual payload of the course. SchemaSmith adapts the DDL it generates. It will not touch the SQL you authored. That’s not a limitation — it’s the line that tells you exactly where the tool’s job ends and yours begins. Everything in Modules 1 through 4 hangs off that line.
Here’s the module map:
- Module 0 — the mixed-version farm. You’re in it now. Read every target’s detected version off the live fleet.
- Module 1 — let the engine adapt. Author a feature the oldest tier can’t take; watch SchemaSmith skip it, record it in the
downgradedmanifest, or refuse outright — your call, one setting. - Module 2 — gate it yourself.
ShouldApplyExpression, three levers, and the compatibility-level footgun that catches nearly everyone the first time. - Module 3 — two shapes of one query. When both shapes are valid but one fits the new engine and one the old, converging on their own — and when the thing you gate on is a fact the server can’t even detect.
- Module 4 — the oldest tier. Floors as they land,
CompatEncoding, and the JSON-versus-XML choice — the course’s own pattern applied to your code. - Module 5 — retiring the gates. The last laggard upgrades. You collapse back to one shape, raise
MinimumVersion, and turn a silent runtime branch into a loud, early stop. This is where the two numbers pay off.
Notice the symmetry running through it. SchemaSmith adapts to the version in front of it — that’s answers 1 and 3, the tool’s own capability. And it hands you the same capability for the code you own — that’s answer 2. Same idea, both sides of the boundary. That symmetry is the course.
Before Module 1: stand up the fleet
Section titled “Before Module 1: stand up the fleet”The setup lab is the prerequisite for everything that follows. Run it now.
One honest note about this course, up front: it’s sandbox only. Every other course’s setup lab lets you point at your own server. This one can’t — a single server can’t be several engine versions at the same time, and the mixed fleet is the entire point. So the lab stands up floor-version containers alongside the current ones: PostgreSQL 12 beside 16, MySQL 5.7 beside 8.0, MariaDB 10.2 beside 11.4. SQL Server’s the one exception — instead of a second heavy container, three databases on one instance sit at different compatibility levels (learn_2022, learn_2016, learn_2008), because compatibility level, not the binary, is what actually gates the syntax. You’ll lean on that fact hard in Module 2.
The lab uses schemaquench built from source, same as every lab in this series. It brings the fleet up, waits for each engine to report healthy, and provisions every tier — then prints a ports-and-tiers table you’ll keep handy all course. New to the CLI? Install it back in Course 1, Module 1.
A good smith keeps two numbers on the wall and never confuses them: the oldest metal the forge can work, and the oldest metal you’ve agreed to. The tool’s reach is one number. Your policy is another. Keep them apart, and a mixed-version farm stops being a mess to dread and becomes just another shape you know how to strike.
Subscribe and stick around. In Module 1, we kindle the forge and let the engine do the adapting — author the modern thing, deploy it to a server that can’t take it, and watch SchemaSmith make the right call on its own.
Until then, may your one package land true on every tier of the farm, from the newest fire to the oldest, and may you never mistake the floor you declared for the version the engine reports.
— Forge
Check yourself: A server clears your package's MinimumVersion at pre-flight. Does that mean SchemaSmith will generate the same DDL for it as for every other server that also cleared the floor?
No. Those are two different numbers answering two different questions. MinimumVersion is your policy — a gate that answers “may I deploy here?” and refuses anything below it. The detected version is the engine’s own report, and it’s what SchemaSmith reads to decide “what SQL will I generate against this target?” — on every deploy, floor or no floor. Two servers can both clear the same MinimumVersion and still receive different generated DDL, because they report different detected versions. Clearing the floor is a yes/no gate; the codegen adapts to each server’s actual version independently.
Check yourself: SchemaSmith supports SQL Server back to 2008. Your team stopped supporting anything older than 2016 last year. What's the right way to make that real, and which number is it?
Set MinimumVersion to 2016 in your Product.json. That’s your policy floor, and it’s allowed to sit well above SchemaSmith’s own capability floor — the two are separate numbers, and yours can legitimately be stricter. SchemaSmith reaching 2008 is what the tool can do; MinimumVersion is what you’ve agreed to support. Raising it turns “we don’t support 2014 anymore” from tribal knowledge into a pre-flight refusal nobody can deploy past by accident. That’s exactly the move Module 5 uses to retire a version gate — which is why this course insists on keeping the two numbers apart from the very first module.