knowledge: captured-call argument completeness, pg_trgm short-pattern degeneration, raw JDBC inside a JPA transaction - #73
Conversation
- testing/mocking/captured-call-arguments: record the whole call, assert every caller-decided argument; split the constant's value from the call site passing it on; re-prove per argument after extracting a resolver. - databases/indexing/trigram-index-short-patterns: a pg_trgm wildcard segment under 3 chars yields no extractable trigrams, so GIN degenerates to a full-index scan while the plan still reads Bitmap Index Scan. - backend/java/jpa/raw-jdbc-inside-a-jpa-transaction: @transactional(timeout) reaches JdbcTemplate only via a bound ConnectionHolder, and the JPA and raw paths raise different Spring exceptions (boundary at Spring Framework 7.0.0).
…e *2 as a second source for the mechanism
|
Two amendments landed after the PR body was captured (the body reflects commit 1 only; 1. Open-PR check correction (commit 2. pg_bigm citations re-derived (commit
Cross-Check: mechanical citation audit (21 byte-level checks against fetched sources) in place of an LLM second opinion; one fabricated-verbatim-quote defect found and fixed, no claim left resting on a summarizer's paraphrase. Label: |
…under test The queue held 4 pending rows, not 3: wc -l undercounted a file whose last line had no trailing newline. testing/quality/default-values-under-test — a spec-named constructor default is guarded by neither a mechanism test that passes the value in nor a defaults-constructing test that never exercises it; push it to its observable point and require red in both mutation directions (the grow direction is the one no incidental test catches).
|
Scope correction: this PR carries 4 insights, not 3 (commit The queue held 4 pending rows. I had counted it with New page: Reproduced (Python 3, TTL + cap store, 4 pre-existing tests — two constructing with defaults, two passing values in):
This sharpened the harvested candidate, which stated the survival as a flat property. It is direction-dependent: shrinking a cap below what some existing test happens to exercise is caught incidentally (row 3), while the grow direction was uncatchable in every configuration tried — a test that issues N items passes for every cap ≥ N, and one that consumes immediately passes for every TTL > 0. The page leads with that asymmetry. Kept separate from Structural re-validation after this landed: every |
Knowledge flush — 3 insight(s)
Queue drained:
1717316a-…jsonl(1 row),ab5516dc-…jsonl(2 rows). All threeingested as new pages; none dropped.
Verified best-practice
1. A spy that captures one argument leaves the same call's siblings unasserted
Claim. When a review flags one argument of one wiring call and you hold the fix
with a spy, record the whole call and assert every caller-decided argument;
asserting the constant's value is a different claim from asserting that the call
site passes it on, and extracting a resolver moves that gap up a layer.
Sources checked.
assert_called_withis"a convenient way of asserting that the last call has been made in a particular
way" (whole-call, last-call only); a spec'd mock "will introspect the
specification object's signature when matching calls … regardless of whether
they were passed positionally or by name", and "using autospec will catch
mistakes where the mock is called with the wrong signature".
.toHaveBeenCalledWithchecks arguments "withthe same algorithm that
.toEqualuses";expect.objectContainingmatches "areceived object which contains properties that are present in the expected
object" — a subset match, which is why omitted keys stay unasserted (this
became an edge-case row rather than a recommendation).
— "If you are using argument matchers, all arguments have to be provided by
matchers."
was not detected by the covering test" (how step 5 reads a green per-argument run).
How verified. Reproduced both halves locally this session (Python 3,
unittest.mock), not just cited:kw["port"])host0.0.0.0→127.0.0.1undetectedMock(spec=…)+assert_called_with(host=…, port=…, tls=…)The green cell on the correct call is the no-op control: the stronger assertion
discriminates rather than always failing. A second run held
DEFAULT_PORT == 8914green across three call-site variants (reads the constant,reads an extracted
resolve_port(), hardcodes8770) while the recorded-callassertion was green for the first two and red only for the hardcoded one — the
"value vs wiring" split, and the evidence that the wiring assertion survives the
refactor.
Confidence: verified (official docs for every API claim + local reproduction).
The field evidence behind the candidate (a 6-round audit where
port, then theextracted resolver, then
hosteach survived in turn, the last being a changewhose only failure surface is a Kubernetes readiness probe) is recorded in the
page's Sources as a field measurement, kept distinct from the reproduction.
2. pg_trgm degenerates to a full-index scan below three characters
Claim. A
LIKE/ILIKEwildcard segment of fewer than three characters yieldsno extractable trigrams, so a pg_trgm GIN/GiST index is scanned in full and the
cost moves into the heap recheck — while the plan still reads
Bitmap Index Scan.Sources checked.
LIKEandregular-expression searches, keep in mind that a pattern with no extractable
trigrams will degenerate to a full-index scan." Also the padding rule ("Each
word is considered to have two spaces prefixed and one space suffixed…"), which
I checked precisely because it is a trap:
show_trgm('cat')returns fourtrigrams, so "short strings have no trigrams" is wrong as stated — padding
applies to a word being indexed, while a
%…%pattern asserts no wordboundary to pad against. The page states it that way.
(2013-05-31): "get_wildcard_trigrams return no trigrams for wildcard part 'st'
since charlen < 3"; "Hence, GIN_SEARCH_MODE_ALL mode is used and results in full
index scan instead of trigrams being used." This is what lets the page state the
rule per wildcard-delimited segment rather than per pattern.
its comparison table rates 1–2 character keyword search "Fast" vs pg_trgm's
"slow", and lists pg_bigm's operators as "LIKE only" vs pg_trgm's "LIKE
(
), ILIKE (*), ~, ~*". That constraint corrects the candidate, whichsuggested pg_bigm without noting it is not a drop-in for an
ILIKEworkload;the page splits those into two decision rows.
How verified. Doc quotes fetched and read this session. The quantitative half
is the candidate's own field
EXPLAIN (ANALYZE, BUFFERS)on a 4.64M-row table(3-char 17 ms / 4 buffers vs 2-char 18,789 ms / 121,837 buffers,
Rows Removed by Index Recheck: 4,640,486, 3 rows matched) — not re-run here:no PostgreSQL was reachable in this environment (
psqlabsent, Docker daemondown, no postgres pod in the local cluster). It is labelled a field measurement
with its date and table size, and no claim in the page depends on my having
re-run it.
Confidence: verified (mechanism doc-sourced; magnitude field-measured).
3.
@Transactional(timeout=N)does not reach a raw JdbcTemplate path by itselfClaim. The declared timeout reaches
JdbcTemplateonly through aConnectionHolderbound byJpaTransactionManager; when that bind is skipped theraw path runs unbounded, and the two paths raise different Spring exceptions.
Sources checked (source read at pinned tags, not from memory).
JpaTransactionManagerjavadoc — "To be able to register a DataSource'sConnection for plain JDBC code, this instance needs to be aware of the
DataSource (
setDataSource(DataSource))"; "will autodetect the DataSource usedas the connection factory of the EntityManagerFactory, so you usually don't need
to explicitly specify the 'dataSource' property"; "this requires a
vendor-specific
JpaDialectto be configured".JpaTransactionManager.java@ v6.2.0 —conHolder.setTimeoutInSeconds(...)sits inside
if (getDataSource() != null)and requiresgetJpaDialect().getJdbcConnection(em, …) != null, else it logs "Not exposingJPA transaction … does not support JDBC Connection retrieval".
DefaultJpaDialect.getJdbcConnectionreturnsnull. This is a correction:the candidate named only the DataSource wiring, so a Boot app (where the
DataSource is autodetected) would have looked exempt; the dialect branch and the
DataSource-instance-identity branch are separate failure modes, and the debug
log line is a checkable diagnostic. The page's step 2 is a 4-row table because
of this.
DataSourceUtilsjavadoc +DataSourceUtils.java@ v6.2.0 —applyTimeoutapplies "the current transaction timeout, if any"; it looks the holder up by
the
DataSourceinstance and otherwise falls back to the passed timeout onlyif (timeout >= 0).JdbcTemplate.applyStatementSettingscalls it withgetQueryTimeout(), whose field default isprivate int queryTimeout = -1—so with no holder, nothing is set at all.
PostgreSQLDialect.javamaps SQLState"57014"→org.hibernate.QueryTimeoutException, andHibernateJpaDialect.java@ v6.2.0converts that to
org.springframework.dao.QueryTimeoutException. On the rawpath, PgJDBC's
PSQLException extends SQLExceptionwithPSQLState.QUERY_CANCELED = "57014", soSQLExceptionSubclassTranslator'sinstanceof SQLTimeoutExceptionbranch misses and itsSQLStateSQLExceptionTranslatorfallback maps class57(
Set.of("08","53","54","57","58")) →DataAccessResourceFailureException.mainspecial-cases"57014".equals(sqlState)→QueryTimeoutException. Ifetched the file at seven released tags to find where it starts:
"57014".equalspresentmainSo the candidate's exception claim is correct for Spring Framework ≤ 6.2.x and
inverts at 7.0.0. The page states both, and an
Instead ofrow requires pinningthe framework version any single-branch handler assumes.
How verified. Every quote above was fetched this session; the version table
came from downloading the same file at each tag and grepping it. The timing half
(Hibernate cancelled at 10,012 ms → 400 vs raw JdbcTemplate 151,558 ms /
163,489 ms → 500 on one annotated endpoint, 11/11 errors over 30 days matching the
per-path status split) is the candidate's production p6spy measurement, labelled as
such.
Confidence: verified (framework behaviour read from pinned source + javadoc;
production magnitudes field-measured).
Existing-layer check
Routed via
INDEX.md→ the three domain indexes, then read every page whose "loadwhen" line overlapped. Full-body reads:
testing-quality-tests-that-cannot-fail,testing-mocking-what-to-mock,databases-indexing-index-selection. Targetedreads (grep for timeout/JdbcTemplate/trigram/LIKE/arg-capture terms, to establish
absence of coverage): the remaining ids below.
Pages read: testing-quality-tests-that-cannot-fail, testing-mocking-what-to-mock, testing-quality-behavior-not-implementation, backend-common-change-impact-call-site-enumeration, databases-indexing-index-selection, databases-query-optimization-reading-execution-plans, databases-indexing-partial-and-expression-indexes, databases-indexing-covering-indexes, backend-common-orm-transaction-boundaries, backend-common-reliability-timeouts-and-retries, backend-common-errors-exception-handling, backend-java-jpa-persistence-context, backend-java-spring-proxy-pitfalls
Overlaps found, and why each is composition rather than duplication.
testing-mocking-what-to-mockrelated:and a pointer on the outbound-contract rowtesting-quality-tests-that-cannot-failrelated:databases-indexing-index-selectionLIKE '%term%'to "a trigram or full-text index type"related:linkdatabases-query-optimization-reading-execution-plansRows Removed by Index Recheck)backend-common-reliability-timeouts-and-retriesrelated:addedbackend-common-orm-transaction-boundariesrelated:addedbackend-java-spring-proxy-pitfallsbackend-java-jpa-persistence-context,backend-common-errors-exception-handling,databases-indexing-partial-and-expression-indexes,databases-indexing-covering-indexesConflicts flagged: none. No existing page states a contradicting directive.
Coverage gaps confirmed by grep before creating:
trgm|trigram|ILIKEmatchesexactly one file in the whole wiki (
index-selection.md, the one row above);call_args|assert_called_with|toHaveBeenCalledWith|argument captormatches exactlyone (
what-to-mock.md); no page mentionsstatement_timeout,JdbcTemplate, orQueryTimeout.Format invariants checked mechanically after writing: body lines 94 / 75 / 90
(limit 120); every
related:id and inline[page-id]reference resolves to apage in this checkout (16/16); no banned vague qualifier in any directive (the two
usually/Considerhits were a verbatim Spring javadoc quote in Sources, leftintact, and one
Instead ofanti-pattern label, reworded); every prohibition wordoccurs only inside an
Instead ofrow or a quoted source.Open-PR check
Listed all 17 open
knowledge/*heads. Three of them (#72, #52, #49) producedsuspiciously empty
wiki/diffs on a first pass, becausegit fetch origin <branch>andrepos/choiyounggi/dev-loop/git/refs/heads/<branch>both 404 forthem. Rather than read an empty diff as "no overlap", I re-read all three through
refs/pull/<n>/head, and then established the actual cause: those heads live onthe contributor fork
dch0202-rsquare/dev-loop(this flush's own account), not onupstream — all three refs resolve there (
6a3ff08,bd03fbe,346dd95). They arealive and pushable, so
foldwas a genuinely available verdict for them; it wasnot taken for the content reasons below. This PR is likewise opened from the fork.
Files touched by each open head, matched against the three candidates:
testing/quality/source-text-wiring-assertions.md; #49 addstesting/quality/value-preserving-refactor-assertions.md+unasserted-return-fields.md; #47/#52 modifytests-that-cannot-fail.mdwiki/databases/**backend/common/**orbackend/python/**; zero touchwiki/backend/java/**Why candidate 1 is
newand notfold, having read all three in-flight pagesin full or in relevant part:
#52 source-text-wiring-assertions— same word "wiring", different subject: itis about asserting by regex over source text when no seam exists (anchor
uniqueness, comment stripping, bound sizing). The new page is about the case
where a seam does exist and a recorder captured the call. Its own "Instead
of" even routes away from text guards when the behaviour is reachable, which is
the situation the new page occupies. No directive is duplicated.
#49 value-preserving-refactor-assertions— nearest in spirit (a literalreplaced by a config read; sentinel substitution to prove the dependency). Its
trigger is a value-preserving refactor of a source of truth; the new page's is
a reviewed fix to one argument of one call, and its distinct content is
argument-set completeness across one call — the thing knowledge: 3 verified testing-quality insights (value-preserving refactor tests, unasserted return fields, stale artifact baselines) #49 does not address.
#49 unasserted-return-fields— the mirror direction (fields a functionreturns that no assertion reads). The new page is the call/argument
direction. Deliberately kept as siblings.
No candidate is a pending duplicate, so nothing was dropped and no sibling
duplicate PR is opened here.
Merge-order note for the owner: this branch adds one id to
wiki/testing/quality/tests-that-cannot-fail.md'srelated:list, a file #47 and#52 also modify. It is a single-line frontmatter addition. If any of #47/#49/#52
merge first, the reciprocal links to
testing-quality-source-text-wiring-assertions,-value-preserving-refactor-assertionsand-unasserted-return-fieldsbecomeresolvable and are worth adding to the new page then — they are intentionally
omitted now because AGENTS.md invariant 4 requires every
related:id to resolve,and those pages do not exist on
main.Routing decision
testing/mockingwiki/testing/mocking/captured-call-arguments.md(testing-mocking-captured-call-arguments)mockingis the category that owns stub/spy mechanics;qualityowns whether a test can fail (already cited), and the subject here is what the double recordsdatabases/indexingwiki/databases/indexing/trigram-index-short-patterns.md(databases-indexing-trigram-index-short-patterns)indexingowns index-type suitability; the case is a precondition on one index type, andquery-optimization/reading-execution-plansstays the owner of plan readingbackend/java→jpawiki/backend/java/jpa/raw-jdbc-inside-a-jpa-transaction.md(backend-java-jpa-raw-jdbc-inside-a-jpa-transaction)JpaTransactionManager/JpaDialect, so it belongs in thejpacategory rather thanspring(which owns proxy-level "the annotation did nothing") orbackend/common(language-agnostic principles; this is stack-specific source behaviour)Plumbing updated:
wiki/testing/index.md,wiki/databases/index.md,wiki/backend/java/index.mdeach +1 "load when" row;log.md+1 ingest entry.INDEX.mdunchanged — all three domains are already listed and their "route herewhen" lines already cover these cases.