Skip to content

refactor: Quote SQL identifiers in tests via pq.QuoteIdentifier#2130

Merged
bjcoombs merged 2 commits intodevelopfrom
quote-sql-identifiers-in-tests
Apr 5, 2026
Merged

refactor: Quote SQL identifiers in tests via pq.QuoteIdentifier#2130
bjcoombs merged 2 commits intodevelopfrom
quote-sql-identifiers-in-tests

Conversation

@bjcoombs
Copy link
Copy Markdown
Collaborator

@bjcoombs bjcoombs commented Apr 5, 2026

Summary

  • Replace 10 instances of raw SQL string concatenation ("CREATE SCHEMA " + schemaName, "SELECT ... " + schemaName + ".table", etc.) with fmt.Sprintf + pq.QuoteIdentifier.
  • Follows the SQL audit we ran against the codebase: production code is clean, these were the only stragglers, all in tests or one CLI helper.
  • Not exploitable in practice (all values were hardcoded literals or known-safe slices), but the pattern was wrong and copy-paste-dangerous.

Why this matters

Production repositories already use pq.QuoteIdentifier uniformly for dynamic identifiers (schema names from validated tenant IDs, etc). The tests drifted from that convention and were using raw + concatenation. Three risks:

  1. Copy-paste into non-test code spreads the pattern to a place where the input is not a safe constant.
  2. If a test generates schema/table names from untrusted input in the future (e.g. fuzzing), these become injectable.
  3. Identifiers that happen to contain reserved words or mixed case break without proper quoting.

Using pq.QuoteIdentifier is the same mechanism the production code uses, so there is one convention across the whole repo.

Files touched

Tests (9):

  • services/reference-data/saga_migrations_test.go - tenant loop var
  • services/reference-data/migrations_test.go - tenant loop var
  • services/reference-data/saga/seeder_test.go - schemaName, cached quoted form
  • services/reference-data/saga/grpc_handler_test.go - schemaName, DROP SCHEMA
  • services/reference-data/saga/override_api_test.go - schemaName
  • services/reference-data/saga/e2e_seeder_test.go - schemaName, createSagaReferenceTable helper
  • services/market-information/adapters/persistence/testhelpers/testcontainer.go - masterSchema constant
  • services/operational-gateway/e2e/e2e_test.go - constraintName from query, table names in DELETE loop
  • shared/platform/testdb/integration_test.go - INSERT into tenant schema

Non-test (1):

  • cmd/position-tool/internal/exporter/exporter.go - hardcoded cursor name, quoted for pattern uniformity

Skipped from audit

  • shared/platform/db/pool.go:76 SET statement_timeout = '%dms' with an integer. Not a security risk (numeric value, not identifier) and PostgreSQL's SET does not accept parameterized values cleanly. Left as-is.

Test plan

  • go build ./... clean
  • go vet ./... clean
  • go test -short ./services/reference-data/saga/... ./shared/platform/testdb/... ./cmd/position-tool/internal/exporter/... all pass
  • pre-commit hooks (gitleaks, gofumpt, golangci-lint) all pass
  • CI green

The codebase audit for SQL string concatenation turned up 10 files
(all tests and one CLI helper) that built SQL by concatenating
unquoted identifiers (schema names, tenant loop variables, table
names, constraint names). None were exploitable in practice - the
values came from hardcoded test constants or known string slices -
but the pattern is wrong, inconsistent with production code (which
uses pq.QuoteIdentifier uniformly), and seeds bad hygiene if copied
into non-test code.

Wrap every dynamic identifier in pq.QuoteIdentifier and use
fmt.Sprintf to build the statement. Where the same schemaName is
used multiple times in one test, cache the quoted form as a local
variable to keep call sites compact.

Files touched:
- services/reference-data/saga_migrations_test.go
- services/reference-data/migrations_test.go
- services/reference-data/saga/seeder_test.go
- services/reference-data/saga/grpc_handler_test.go
- services/reference-data/saga/override_api_test.go
- services/reference-data/saga/e2e_seeder_test.go
- services/market-information/adapters/persistence/testhelpers/testcontainer.go
- services/operational-gateway/e2e/e2e_test.go
- shared/platform/testdb/integration_test.go
- cmd/position-tool/internal/exporter/exporter.go (hardcoded
  cursor name quoted uniformly with the rest of the codebase)
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b753864d-cfe6-4e6c-a68d-296ba354a0f2

📥 Commits

Reviewing files that changed from the base of the PR and between fd7865f and 005b5f7.

📒 Files selected for processing (4)
  • services/operational-gateway/adapters/persistence/setup_test.go
  • services/reference-data/migrations_test.go
  • services/reference-data/saga/grpc_handler_test.go
  • services/reference-data/saga_migrations_test.go
✅ Files skipped from review due to trivial changes (2)
  • services/reference-data/saga_migrations_test.go
  • services/reference-data/migrations_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • services/reference-data/saga/grpc_handler_test.go

📝 Walkthrough

Walkthrough

SQL identifier values (cursor, schema, table, constraint names) are now passed through pq.QuoteIdentifier and inserted via fmt.Sprintf when building raw SQL strings across production code and tests; control flow and logic remain unchanged.

Changes

Cohort / File(s) Summary
Exporter
cmd/position-tool/internal/exporter/exporter.go
Cursor name is quoted with pq.QuoteIdentifier for DECLARE ... CURSOR, FETCH ... FROM ..., and CLOSE ... SQL strings.
Market-information test container
services/market-information/adapters/persistence/testhelpers/testcontainer.go
Schema name is quoted when generating CREATE SCHEMA IF NOT EXISTS ... and ALTER DATABASE ... SET search_path TO ... statements.
Operational gateway tests
services/operational-gateway/e2e/e2e_test.go, services/operational-gateway/adapters/persistence/setup_test.go
Constraint and table identifiers are quoted with pq.QuoteIdentifier for ALTER TABLE ... DROP CONSTRAINT and DELETE FROM helper queries.
Reference-data saga tests & handlers
services/reference-data/saga/e2e_seeder_test.go, services/reference-data/saga/grpc_handler_test.go, services/reference-data/saga/override_api_test.go, services/reference-data/saga/seeder_test.go
Tenant schema identifiers are quoted and used via fmt.Sprintf in DDL and DML (CREATE SCHEMA, CREATE TABLE, INSERT, SELECT, COUNT) across multiple tests.
Reference-data migration tests
services/reference-data/migrations_test.go, services/reference-data/saga_migrations_test.go
Tenant identifiers are quoted for CREATE SCHEMA IF NOT EXISTS ... and SET search_path TO ... in migration isolation tests.
Shared platform tests
shared/platform/testdb/integration_test.go
Tenant-schema-qualified INSERT INTO now uses a quoted schema identifier via pq.QuoteIdentifier and fmt.Sprintf.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: quoting SQL identifiers in tests using pq.QuoteIdentifier, which is the primary focus across all file modifications.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the motivation, risk mitigation, and specific files affected by the SQL identifier quoting refactor.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch quote-sql-identifiers-in-tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Copy Markdown

claude Bot commented Apr 5, 2026

Claude Code Review

Commit: 005b5f7 | CI: running

Summary

Clean, well-scoped refactoring that replaces all instances of raw SQL string concatenation with fmt.Sprintf + pq.QuoteIdentifier across test files and one CLI helper. The pattern is already the established convention across production code (shared/platform/db/, shared/platform/testdb/, etc.), so this brings the stragglers into alignment.

Second commit addresses CodeRabbit feedback by quoting a few additional concatenation sites missed in the initial grep — same mechanical pattern throughout.

All changes are correct and consistent. The quoted identifier is computed once and reused within each scope. The PR description's rationale (copy-paste risk, future fuzzing, reserved-word safety) is sound. The explicit skip of pool.go:76 (SET statement_timeout = '%dms') is the right call — it's a numeric value, not an identifier.

Risk Assessment

Area Level Detail
Blast radius Low Identifier quoting is additive; lowercase names produce identical SQL semantics
Rollback Safe Pure refactor, revert is clean
Scale N/A No runtime behavior change
Cross-system Low No API or contract changes
Migration N/A No migration files touched

Findings

No issues found. The changes are correct and consistent across both commits.

Bot Review Notes

No unresolved bot threads at time of review.

Previously Flagged

None — first review found no issues.

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean refactoring. All 10 sites correctly use pq.QuoteIdentifier, consistent with the established production convention. No domain risks. See summary comment for details.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
services/reference-data/migrations_test.go (1)

549-561: ⚠️ Potential issue | 🟡 Minor

Identifier quoting here is correct, but later statements still use concatenation.

Line 600 and Line 611 still do SET search_path TO + tenant. Please switch those to quoted identifier formatting too so the test is fully consistent with this PR’s safety pattern.

Suggested follow-up
- _, err := tc.pool.Exec(ctx, `SET search_path TO `+tenant)
+ _, err := tc.pool.Exec(ctx, fmt.Sprintf("SET search_path TO %s", pq.QuoteIdentifier(tenant)))

...

- _, err := tc.pool.Exec(ctx, `SET search_path TO `+tenant)
+ _, err := tc.pool.Exec(ctx, fmt.Sprintf("SET search_path TO %s", pq.QuoteIdentifier(tenant)))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/reference-data/migrations_test.go` around lines 549 - 561, The test
currently constructs SET search_path using string concatenation with the raw
tenant variable; replace those concatenations with the already-constructed
quoted identifier (quoted) and use the same pattern as the CREATE SCHEMA and
earlier SET: call tc.pool.Exec with fmt.Sprintf("SET search_path TO %s", quoted)
wherever SET search_path is currently built via concatenation so all executions
(the ones that use tc.pool.Exec and reference tenant) use
pq.QuoteIdentifier-consistent quoting (refer to variables quoted and
tc.pool.Exec in this diff).
🧹 Nitpick comments (2)
cmd/position-tool/internal/exporter/exporter.go (1)

569-570: Consider aligning identifier quoting approach.

This line uses pgx.Identifier{}.Sanitize() while the new cursor code uses pq.QuoteIdentifier(). Both produce equivalent double-quoted identifiers, but using the same function throughout the file would be more consistent.

♻️ Optional: use pq.QuoteIdentifier here too
-	schemaName := pgx.Identifier{tenantID.SchemaName()}.Sanitize()
+	schemaName := pq.QuoteIdentifier(tenantID.SchemaName())
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/position-tool/internal/exporter/exporter.go` around lines 569 - 570, The
code currently builds schemaName using
pgx.Identifier{tenantID.SchemaName()}.Sanitize() and then injects it into query;
align this with the rest of the file by using pq.QuoteIdentifier instead (the
cursor code already uses pq.QuoteIdentifier). Replace the construction of
schemaName and its use in the query with a quoted identifier produced by
pq.QuoteIdentifier(tenantID.SchemaName()) so the file consistently uses the same
quoting routine when setting search_path.
services/reference-data/saga/seeder_test.go (1)

231-257: setupTenantSchemaForSeeder can drift from real migrations.

Line 229 says this mirrors the full migrated schema, but this DDL omits several migration-level constraints (e.g., status/script/preconditions checks). That can let tests pass against states production schema would reject.

Refactor direction
- // Create saga_definition table matching the full migrated schema
+ // Create saga_definition table for tests.
+ // Prefer applying the same migration SQL files used by migration tests
+ // (or include the same CHECK constraints here) to avoid schema drift.

If you keep inline DDL, add the key checks to stay aligned:

             CONSTRAINT uq_saga_definition_name_version UNIQUE (name, version)
+           ,CONSTRAINT chk_saga_definition_status
+              CHECK (status IN ('DRAFT', 'ACTIVE', 'DEPRECATED'))
+           ,CONSTRAINT chk_saga_definition_script_length
+              CHECK (script IS NULL OR length(script) <= 65536)
+           ,CONSTRAINT chk_saga_definition_preconditions_length
+              CHECK (preconditions_expression IS NULL OR length(preconditions_expression) <= 4096)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/reference-data/saga/seeder_test.go` around lines 231 - 257, The
inline DDL in setupTenantSchemaForSeeder (createTableSQL for saga_definition)
omits important production migration-level constraints, causing test schema
drift; either invoke the real migrations in the test setup (run the same
migration runner used in production) or augment createTableSQL to mirror those
checks: add the same CHECK constraint on status (allowed enum values), the
CHECK/NOT NULL rules applied to script and preconditions_expression (and any
dependent checks tying script presence to status/version), and any other
migration-added constraints/indices present on saga_definition so tests validate
the same schema invariants as production.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@services/reference-data/saga_migrations_test.go`:
- Around line 559-560: The test still builds "SET search_path TO " via unsafe
string concatenation with tenant in later Exec calls; update those calls in
saga_migrations_test.go to use the already-constructed quoted identifier (or
fmt.Sprintf with quoted) instead of "+ tenant". Specifically, replace
occurrences where tc.pool.Exec(ctx, "SET search_path TO "+tenant) (or similar)
with tc.pool.Exec(ctx, fmt.Sprintf("SET search_path TO %s", quoted)) so the
schema name is properly quoted and not concatenated unsafely.

In `@services/reference-data/saga/grpc_handler_test.go`:
- Around line 49-50: Several SQL statements in this test still concatenate
schemaName directly; replace those concatenations by quoting the identifier (as
done earlier) using quoted := pq.QuoteIdentifier(schemaName) and inject it with
fmt.Sprintf instead of string concatenation. Locate the SQL usages that build
queries with "... "+schemaName+" ..." (the Exec/Query calls constructing
schema-qualified names) and change them to use the quoted variable (e.g.,
fmt.Sprintf("... %s ...", quoted)) so all schema identifier insertions use
pq.QuoteIdentifier rather than raw concatenation.

---

Outside diff comments:
In `@services/reference-data/migrations_test.go`:
- Around line 549-561: The test currently constructs SET search_path using
string concatenation with the raw tenant variable; replace those concatenations
with the already-constructed quoted identifier (quoted) and use the same pattern
as the CREATE SCHEMA and earlier SET: call tc.pool.Exec with fmt.Sprintf("SET
search_path TO %s", quoted) wherever SET search_path is currently built via
concatenation so all executions (the ones that use tc.pool.Exec and reference
tenant) use pq.QuoteIdentifier-consistent quoting (refer to variables quoted and
tc.pool.Exec in this diff).

---

Nitpick comments:
In `@cmd/position-tool/internal/exporter/exporter.go`:
- Around line 569-570: The code currently builds schemaName using
pgx.Identifier{tenantID.SchemaName()}.Sanitize() and then injects it into query;
align this with the rest of the file by using pq.QuoteIdentifier instead (the
cursor code already uses pq.QuoteIdentifier). Replace the construction of
schemaName and its use in the query with a quoted identifier produced by
pq.QuoteIdentifier(tenantID.SchemaName()) so the file consistently uses the same
quoting routine when setting search_path.

In `@services/reference-data/saga/seeder_test.go`:
- Around line 231-257: The inline DDL in setupTenantSchemaForSeeder
(createTableSQL for saga_definition) omits important production migration-level
constraints, causing test schema drift; either invoke the real migrations in the
test setup (run the same migration runner used in production) or augment
createTableSQL to mirror those checks: add the same CHECK constraint on status
(allowed enum values), the CHECK/NOT NULL rules applied to script and
preconditions_expression (and any dependent checks tying script presence to
status/version), and any other migration-added constraints/indices present on
saga_definition so tests validate the same schema invariants as production.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a86962b9-cf2c-4ba8-bc3c-8331a254d51a

📥 Commits

Reviewing files that changed from the base of the PR and between 7964e77 and fd7865f.

📒 Files selected for processing (10)
  • cmd/position-tool/internal/exporter/exporter.go
  • services/market-information/adapters/persistence/testhelpers/testcontainer.go
  • services/operational-gateway/e2e/e2e_test.go
  • services/reference-data/migrations_test.go
  • services/reference-data/saga/e2e_seeder_test.go
  • services/reference-data/saga/grpc_handler_test.go
  • services/reference-data/saga/override_api_test.go
  • services/reference-data/saga/seeder_test.go
  • services/reference-data/saga_migrations_test.go
  • shared/platform/testdb/integration_test.go

Comment thread services/reference-data/saga_migrations_test.go
Comment thread services/reference-data/saga/grpc_handler_test.go
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 5, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

The initial grep missed concat sites nested deeper in the same test
files and one in a sibling test file that was not in the original
audit list. CodeRabbit flagged:

- saga_migrations_test.go:600,611 - SET search_path inside nested
  t.Run for the "same name+version" subtest
- migrations_test.go:600,611 - same pattern for instrument_definition
- saga/grpc_handler_test.go:582,618,1081 - INSERT/SELECT INTO
  "+schemaName+".saga_definition across three subtests
- operational-gateway/adapters/persistence/setup_test.go:195 - DELETE
  FROM table loop (same shape as the e2e_test.go version already
  fixed; missed because it lives in a sibling package)

Apply the same pq.QuoteIdentifier + fmt.Sprintf pattern as the rest
of the PR so no raw concat remains in any touched file. Verified via
`grep -rnE '\`\s*\+\s*(tenant|schemaName|tbl|masterSchema|constraintName|cursorName)' **/*.go`
which now returns only migration_runner.go:394 (the
processMigrationSQL rewriter doing legitimate string manipulation,
not SQL execution).
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed commit 005b5f7. Clean mechanical refactor — all concatenation sites correctly replaced with pq.QuoteIdentifier. See summary comment for details.

@bjcoombs bjcoombs merged commit 63d81f1 into develop Apr 5, 2026
39 checks passed
@bjcoombs bjcoombs deleted the quote-sql-identifiers-in-tests branch April 5, 2026 09:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant