An ORM or database driver sits under every query your application runs, so test more than the unit suite. Run the tests against a real database of the production version, check query results and types, exercise transactions and connection pooling, run migrations forward and back, and compare slow query behavior before and after.
Why this upgrade is wider than it looks
A security fix to a driver or ORM might only touch one code path, but your application depends on the whole library. Type conversions, error codes, connection handling, timeouts and generated SQL all pass through it. A change in any of them reaches production data.
Unit tests with mocks often miss this, since a mock returns whatever you tell it. The bugs show up against a real database.
Test against a real database
Run your integration tests against the same database engine and version you use in production, not a lighter substitute. Many teams run it in a container in CI. If you run several database versions across environments, test each.
The checklist
- Queries. Run the main read and write paths and compare results with the old version. Pay attention to ordering, null handling, and any raw SQL.
- Types. Check dates and time zones, decimals, large integers, booleans, JSON and binary columns. Drivers sometimes change how they convert these.
- Parameter handling. Confirm queries still use bound parameters. A fix that touches escaping should come with a test using hostile looking input, such as quotes and special characters, which must be treated as data.
- Transactions. Test commit, rollback, nested transactions and isolation behavior. Check what happens when a statement fails mid transaction.
- Connection pool. Confirm pool size, idle timeout and reconnect behavior. Kill the database connection during a test and check that the application recovers.
- Error handling. Error types and codes may change. Code that catches a specific exception for a duplicate key or a deadlock can silently stop matching.
- Migrations. Run the migration tool forward on a copy of a recent schema, and back if you support it. Confirm schema changes are the ones you expect.
- Performance. Compare the query plans and timing of your slowest queries before and after. A changed query builder can generate different SQL.
- Timeouts and cancelation. Check that long queries are cancelled as before.
What to compare
| Area | How to compare |
|---|---|
| Generated SQL | Log queries for a fixed test scenario on both versions and diff the logs |
| Results | Run a set of known queries and compare output row by row |
| Error behavior | Trigger a duplicate key, a deadlock and a timeout, and compare what the code receives |
| Latency | Time a fixed set of queries three times each |
Take care with logs. Query logs can include parameter values, so scrub them or use test data only.
Plan the rollout
Merge to a canary or a small share of traffic first if you can. Watch error rates, slow queries and connection counts. Keep the previous version ready, and make sure any schema migration is compatible with both versions during the rollout. The expand then contract pattern is made for this. Our post on expand and contract database migrations explains it.
Safeguard's dependency scanning and SBOMs show which services use a given driver or ORM and which versions, so you can plan the rollout across all of them. You can try a free scan at gold.safeguard.sh/scan.
Try it yourself
Click through it at your own pace, or open it full screen.
Open the inventory and look for how a data layer package shows up across components, then picture which services you would test first.
Quick check
Can I trust unit tests with mocks for this upgrade?
Not on their own. Mocks return what you tell them. Run integration tests against a real database of the production engine and version.
Do I need to test every query?
No. Cover the main read and write paths, your raw SQL, your slowest queries and the error cases your code catches by type or code.
Last reviewed: 19 October 2026.