Skip to content

Parameterize SQL and close JDBC resources in DbInteractions (#139, #140)#143

Merged
dmccoystephenson merged 1 commit into
mainfrom
fix/dbinteractions-parameterize-and-close
Jun 13, 2026
Merged

Parameterize SQL and close JDBC resources in DbInteractions (#139, #140)#143
dmccoystephenson merged 1 commit into
mainfrom
fix/dbinteractions-parameterize-and-close

Conversation

@dmccoystephenson

Copy link
Copy Markdown
Member

Summary

Hardens the hand-rolled data layer to fix the injection/breakage risk (#139) and the JDBC resource leak (#140) together, since both live in DbInteractions.

DbInteractions rework:

Parameterization (#139): the user-controlled name interpolations were moved off string concatenation to bound parameters:

  • EntityFactory / EnvironmentFactory inserts
  • EntityRepositoryImpl.save + updateName
  • EnvironmentRepositoryImpl.findByName + updateName

Integer ids are left inline (not an injection vector); the grid/location inserts in EnvironmentFactory are int-only and untouched. The affected repository test mocks were updated to the parameterized call signatures.

Tests

Adds DbInteractionsTest (H2 in-memory, test scope) — this is what actually exercises the new binding/resource behavior, since DbInteractions is mocked everywhere else:

⚠️ Needs human review — not auto-merged

This PR modifies pom.xml (adds the com.h2database:h2 test-scope dependency), which is on the do-not-auto-merge list. Flagging for manual review rather than merging automatically. The query() redesign (returning a CachedRowSet instead of a live ResultSet) is also a deliberate behavioral change in the core data layer worth a careful look.

Test plan

  • ./mvnw test226/226 pass (was 220; +6 DbInteractionsTest), JDK 21
  • DbInteractionsTest runs real SQL on H2, including the injection-payload case
  • Python client — n/a (no src/main/python change)

Follow-up note

The deeper fix for query() returning a ResultSet at all (a RowMapper-style API returning List<T>) would let every repository drop its manual ResultSet handling, but that is a much larger refactor; the CachedRowSet approach here fixes the leak with minimal blast radius.

Closes #139
Closes #140

🤖 Generated with Claude Code

Rework DbInteractions to use PreparedStatement with bound parameters and
try-with-resources:

- query(sql, params...) binds parameters, runs the statement inside
  try-with-resources, and returns a disconnected CachedRowSet (rows copied
  out) so the PreparedStatement and live ResultSet are always closed — fixing
  the Statement/ResultSet leak (#140). Callers keep using the ResultSet API.
- update(sql, params...) binds parameters and closes the statement via
  try-with-resources (#140).
- Both signatures are varargs, so existing parameterless call sites are
  unaffected.

Migrate the user-controlled (name) interpolations off string concatenation to
bound parameters (#139): EntityFactory / EnvironmentFactory inserts,
EntityRepositoryImpl save + updateName, EnvironmentRepositoryImpl findByName +
updateName. Integer ids are left inline (not an injection vector). Affected
repository test mocks updated to the parameterized call signatures.

Adds DbInteractionsTest (H2 in-memory, test scope) covering parameter binding,
that a value containing a SQL payload (`O'Brien'); DROP TABLE ...`) is stored
literally and does not inject, the disconnected-ResultSet behavior, and the
error paths. H2 added as a test-scope dependency.

Closes #139
Closes #140

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@dmccoystephenson dmccoystephenson left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Self-review rubric (adversarial; anchored on green CI run 27468231673 + the diff):

  • Scope: PASS — 10 files, all necessary: DbInteractions rework, the 4 name-bearing call sites (2 factories + 2 repos), their updated test mocks, the new H2 test, and the pom.xml test dep. No unrelated churn.
  • Resource fix (#140): PASS — both methods use try-with-resources on the PreparedStatement; query() copies rows into a CachedRowSet before the live ResultSet/statement close. DbInteractionsTest.query_returnsDisconnectedResultSet_readableAfterReturn reads the result fully after query() returns, which is only possible because it is disconnected.
  • Injection fix (#139): PASS — demonstrated, not asserted. parameterizedValueWithSqlPayload_isStoredLiterally_andDoesNotInject inserts O'Brien'); DROP TABLE person; -- as a bound parameter; the table survives and the value round-trips verbatim. The data layer no longer concatenates user-controlled name values.
  • Backward compatibility: PASS — both signatures are varargs, so the ~40 existing parameterless call sites compile and run unchanged; full suite is 226/226 (was 220). Repositories consume the CachedRowSet via the same ResultSet API (forward next() + getInt/getString by label/index), all supported.
  • Tests-fix mocks: PASS — the 12 repository-test stubs whose SQL changed (? placeholders + param arg) were updated; EntityFactoryTest was updated for the new varargs INSERT.
  • CI: PASS — build green on PR head; CI compiles and runs the H2 test.
  • DTO boundary / Spec / Java-Python parallelism: N/A — internal data layer; no endpoint, DTO, or Python-client surface changed (the Python client is an HTTP client with no DbInteractions mirror).
  • Override correctness: PASS — only method bodies changed; no @Override added or removed.

Honest caveats for the reviewer:

  1. query() now returns a CachedRowSet rather than a live ResultSet — a deliberate behavioral change in the core data layer (the point of the leak fix). Repositories iterate forward-only, so this is compatible, but it warrants a look.
  2. DbInteractionsTest covers getInt/getString by label and the factory's getInt(1) by index is standard CachedRowSet but not separately asserted here.
  3. Integer ids remain string-concatenated (not an injection vector); only user-controlled strings were parameterized to bound the diff.

This PR is intentionally not auto-merged — it touches pom.xml (do-not-auto-merge). Flagged for manual review.

@dmccoystephenson dmccoystephenson merged commit cbbb1e4 into main Jun 13, 2026
1 check passed
@dmccoystephenson dmccoystephenson deleted the fix/dbinteractions-parameterize-and-close branch June 13, 2026 13:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant