Skip to content

How SQL Server, PostgreSQL & MySQL report errors

You’ve spent five modules reading SQL Server logs. The method holds on every engine — locate the phase, read the artifact, name the failure — but the reading changes. The same broken deploy lands on PostgreSQL, and the error isn’t where you’d reach for it. It’s a different door.

Hey folks. I’m Forge Barrett, master of the Content Forge here at SchemaSmith.

This is the last of Course 8’s failure modules. One last skill — reading the three engines’ dialects — and then we write the thing this whole course was building toward: your team’s diagnostic runbook. Let’s close it out.

SchemaSmith runs the same convergence engine on all three databases. But each database hands progress and errors back through its own mechanism:

  • SQL Server raises InfoMessage events. Real errors (severity above 10) land in both the progress log and the errors log — the errors log entry carries the message and the line number.
  • PostgreSQL streams Notice messages. The SQLSTATE prints right in the progress log; for a failure in a phase the engine computes, the errors log stays empty.
  • MySQL has no async message event at all. It runs the whole deploy on one connection, so it can’t push a notice mid-work — instead the engine writes progress to a SchemaSmith_StatusMessages table and a separate connection polls it every couple hundred milliseconds.

Same engine, three doors. Let’s watch one failure come through all of them.

MariaDB comes through the MySQL door — same client path, same SchemaSmith_StatusMessages table polled on a second connection. It’s a separate platform selection with its own native DDL, but its error-reporting mechanism is the MySQL one. Four platforms, three doors.

The lab seeds two customers sharing an email, then flips IX_Customer_Email to unique. It fails at the index phase on every engine — the same failure. Read the progress log and the errors log on each:

EngineProgress.logErrors.log
SQL ServerThe CREATE UNIQUE INDEX statement terminated… duplicate key… (ana.f@shop.test)populated — same message + at Line: 504
PostgreSQL23505: could not create unique index "ix_customer_email"empty
MySQLDuplicate entry 'ana.f@shop.test' for key 'Customer.IX_Customer_Email'empty

There’s the headline: for a failure in a phase the engine computes, only SQL Server populates the errors log. On PostgreSQL and MySQL, the fault detail — SQLSTATE and all — is in the progress log, and the errors log is empty. Go looking in the wrong file on the wrong engine and you’ll swear the tool told you nothing. It told you plenty; it just used a different door.

Your own failures follow a different rule. A failed user script’s Unable to quench '<path>': … line lands in the errors log on every engine — Module 4’s backfill writes it on all three. A failed data delivery leaves the errors log empty everywhere, SQL Server included. So the progress log is the one file that always has the fault.

And the code is different every time: 1505 on SQL Server (it prints the message, not the number), 23505 on PostgreSQL (SQLSTATE, printed literally), 1062 on MySQL. One failure, three codes. That’s what the runbook’s lookup table is for.

Scripts are chatty — PRINT lines and low-severity warnings on SQL Server, RAISE NOTICE on PostgreSQL. SchemaSmith mutes that chatter by default so the log stays readable, and gives you one dial to unmute it: VerboseLogging (SQL Server and PostgreSQL). The lab’s verbose package adds a script that reports a line every deploy. Run it plain, then run it with the dial up:

schemaquench --ConfigFile:quench.settings.verbose.json --LogPath:"$PWD/logs-plain"
schemaquench --ConfigFile:quench.settings.verbose.json --LogPath:"$PWD/logs-verbose" --VerboseLogging=true

On SQL Server the PRINT line is gone from the plain log and there in the verbose one — your script’s output, suppressed by default, brought back on demand. PostgreSQL works the same way: the RAISE NOTICE line, Course 8 M6: VerboseLogging demo notice from a user script., shows only in the verbose run. On MySQL the line shows up nowhere: your scripts have no progress channel at all, so there’s nothing to unmute. One dial, two engines, one job — and it only touches your output; SchemaSmith’s own phase progress always shows.

Good deal. Set it on the command line in either form, --VerboseLogging=true or --VerboseLogging:true, or put it in your settings file.

Here’s the payoff. Every module gave you a piece; now they become one page you keep.

Open runbook.md in the lab. It’s a fill-in team diagnostic runbook, and it pulls the whole course together:

  • Open Failures.log first — who failed, why, and which artifact to open, without hunting through the full progress trail. Progress.log below is the deeper core loop.
  • The phase map — the order the engine runs, so you know what “the index phase” comes after.
  • The core loop — locate the phase, read the artifact, pick the recovery.
  • Whose problem is it — the per-item line that tells a mechanical failure (FAILED to quench:) from your script (Unable to quench '<path>') from your data (Error delivering <table>).
  • Where to look, per engine — the door table you just learned.
  • The error codes — one failure, three codes, across all three engines.
  • The recovery decision tree — plain re-run, --ResumeQuench, or mark-done.
  • Your incidents — a blank log that becomes your fastest section over time.

Copy it into your own repo. That’s the deliverable — a one-page answer to “the deploy failed, now what?” that works on any engine your shop runs.

Check yourself: A deploy fails on PostgreSQL. You open the errors log to read the fault — and it's empty. Did the tool fail to report the error?

No — it reported it through PostgreSQL’s door. On PostgreSQL (and MySQL), a failure in a phase the engine computes puts its detail in the progress log, and the errors log stays empty; only SQL Server writes that kind of failure to the errors log (with the message and line number). A failed user script would have reached the errors log on any engine — which is why an empty one tells you to look in the progress log, not that nothing was reported. PostgreSQL prints the SQLSTATE — 23505, 23503, 23502 and the like — right in the progress log. So the fix is to read the right file: Progress.log on PostgreSQL and MySQL, Errors.log (or either) on SQL Server. Same failure, different door — which is exactly why the runbook’s per-engine “where to look” table earns its place.

That’s the method. You can walk into any failed deploy — on SQL Server, PostgreSQL, or MySQL — locate the phase from the progress log, read the artifact the engine left behind, name whose problem it is, and pick the recovery that fits. And now you’ve got a runbook so the next person doesn’t start from scratch. For the lookup tables on their own, the SchemaSmith reference docs carry a per-platform error-codes and reporting-channels page.

Six modules, one method, three engines. You came in reading a stack trace and hoping; you leave reading a phase map and knowing.


The forge doesn’t hide its work. Every engine has a different door, but behind all of them the same fire is doing the same shaping — and now you can read any of them by the light it throws. Take the runbook, keep it close, and let it grow with every incident your team survives together.

Got a runbook you’ve built from this course, or a failure that stumped it? Email me at forgebarrett@schemasmith.com — I read every one.

Next up: Course 8 · Module 7 — Before you share the log. Before that runbook’s first incident lands in a ticket, we check what the log you attach gives away.

Until then, may your doors open easy and your runbook never need its last page.

— Forge