TT-17841: improved tests for persistent storage - #158
Conversation
|
CLA Assistant Lite bot: I have read the CLA Document and I hereby sign the CLA 1 out of 2 committers have signed the CLA. |
|
This pull request introduces a driver-agnostic conformance test suite for the persistent storage layer to ensure consistent behavior across different database implementations. The new test suite uncovered several critical correctness bugs in the PostgreSQL driver, which have been fixed. The changes include making Files Changed Analysis
Architecture & Impact Assessment
graph TD |
Security Issues (1)
Architecture Issues (1)
Security Issues (1)
Quality Issues (1)
Powered by Visor from Probelabs Last updated: 2026-08-06T02:21:06.184Z | Triggered by: pr_updated | Commit: a5ccf30 💡 TIP: You can chat with Visor using |
🚨 Jira Linter FailedCommit: The Jira linter failed to validate your PR. Please check the error details below: 🔍 Click to view error detailsNext Steps
This comment will be automatically deleted once the linter passes. |
|




Description
Add a driver-agnostic conformance test suite for the persistent storage layer, and fix the PostgreSQL driver bugs that suite uncovered.
The
persistentpackage supports multiple database drivers (mgo, official MongoDB, PostgreSQL) behind a singlePersistentStorageinterface, but there was no shared way to guarantee every driver behaves identically against that contract. Each driver had its own ad-hoc tests, so behavioral drift between MongoDB and PostgreSQL went undetected.This PR introduces a contract-based conformance test suite (
persistent/internal/testutil/suite.go) that runs the same behavioral assertions against every driver (conformance_mgo_test.go,conformance_mongo_test.go,conformance_postgres_test.go). Running the suite against PostgreSQL surfaced several correctness bugs, which are fixed here.What we're doing
1. Conformance test framework
Suite+RunSuiteharness validating any driver against thePersistentStorageinterface (Ping, HasTable, Migrate/Drop, CRUD, Update, Upsert, query translation, indexes).2. PostgreSQL driver fixes
Update:Updatenow runs inside a real DB transaction. An existence pre-check (COUNT) plusSaveare made atomic to avoid a TOCTOU race where a concurrentDELETEcould let GORM's upsert-flavoredSaveINSERT a "ghost" row instead of returningsql.ErrNoRows. A missing record now correctly returnssql.ErrNoRows.Upsert:Upsertacquires apg_advisory_xact_lock(keyed on table + query) to serialize concurrent upserts of the same logical record and prevent duplicate inserts. Existence is now determined viaCOUNTrather thanRowsAffected, so an upsert with an empty update map no longer wrongly falls through to INSERT for an existing record.$orquery translation: multi-field conditions inside a single$orclause are now correctly grouped withANDin a nested sub-expression (previously they were flattened, producing incorrect boolean logic). Nested field names also get the same.→_conversion and identifier sanitization as the non-$orpath.GetIndexesnow readsindex_metadatato correctly flagIsTTLIndexand populateTTL, tolerating the case where the metadata table doesn't yet exist.3. CI / build tooling
postgres_test_dsnmatching the containerized Postgres credentials so the Postgres conformance tests run.-coverpkgacross the storage tree and only iterates packages that actually have tests under the active build tag; empty-line coverage files (Go 1.25 +-coverpkg) are stripped beforegocovmergeto prevent merge failures.internal/testutil.Related Issue
https://tyktech.atlassian.net/browse/TT-17841
Motivation and Context
There was no shared contract test guaranteeing consistent behavior across the persistent drivers, allowing behavioral drift (especially Postgres vs Mongo) to go unnoticed. Building the conformance suite exposed real correctness bugs in the PostgreSQL driver around Update atomicity, Upsert concurrency,
$orquery logic, and TTL index reporting — all fixed here.Acceptance Criteria
Updateon a non-existent record returnssql.ErrNoRowsand never creates a new row.Updateis atomic: a concurrent delete between the existence check and the save cannot produce a ghost insert.Upsertcalls for the same query do not create duplicate records.Upsertwith an empty update map on an existing record updates/returns that record rather than inserting a duplicate.$orquery with multiple fields per clause produces correct(a AND b) OR (c AND d)semantics, with proper field-name sanitization.GetIndexescorrectly reportsIsTTLIndexandTTLfor TTL indexes, and does not error when no TTL metadata table exists.gocovmergefailures) and the newtestutilcode is included in Sonar analysis.make lint(gofumpt + golangci-lint) and the full test matrix pass.Test Coverage For This Change
task test-persistent DB=postgres DB_VERSION=16.10,DB=mongo DB_VERSION=7.0, and the mgo variant — all run the conformance suite (with-race).basic_operations_test.go,query_test.gocovering the Update/Upsert/$orfixes.-raceto confirm no data races and no duplicate rows under concurrent Update/Upsert.task merge-coverageproduces a validmerged-coverage.cov.Notes / Trade-offs
UpdateandUpserteach add one extraCOUNTround-trip for correctness.pg_advisory_xact_lockinUpsertserializes concurrent upserts of the same key (intentional, for correctness).fmt.Sprintffallback for non-serializable values.Types of changes
Checklist
master!masterbranch (left side). Also, it would be best if you started your change off our latestmaster.go mod tidy && go mod vendorgofmt -s -w .go vet ./...Ticket Details
TT-17841
Generated at: 2026-08-06 02:19:57