feat: add transaction-aware listPrimaryUsersByEmail/ByPhoneNumber reads - #415
Conversation
Implement listPrimaryUsersByEmail_Transaction and listPrimaryUsersByPhoneNumber_Transaction (PLAN-018 Unit 3): the same reads as the non-transaction forms, run on the caller's transaction connection instead of borrowing a fresh pooled connection, to avoid nested same-pool connection acquisition. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI triageTwo red checks, both expected and neither a defect in this diff:
|
CI triage — update after base merge-forwardStack-sync merge-forwarded two
|
Part of PLAN-018, Unit 3.
Problem / root cause
AuthRecipeSQLStoragecallers that already hold a transaction connection had noconnection-reusing way to run the "list primary users by email / phone" reads.
Calling the non-transaction
listPrimaryUsersByEmail/listPrimaryUsersByPhoneNumberfrom inside a transaction borrows a second connection from the same Hikari pool
while the caller still holds the first — a nested same-pool acquisition that, under
load, contributes to pool exhaustion. PLAN-018 eliminates these nested acquisitions.
Fix
Implements the two new plugin-interface
_Transactionreads (contract fromsupertokens-plugin-interface#227) in postgres:
Start.listPrimaryUsersByEmail_Transaction(tenantIdentifier, con, email)Start.listPrimaryUsersByPhoneNumber_Transaction(tenantIdentifier, con, phoneNumber)Both delegate to new
GeneralQueries.*_Transactionmethods that mirror the existingnon-transaction forms exactly — same migration-mode dispatch (
_newvs_legacy),same query strings, same de-duplication and
time_joinedordering — but run every readon the caller's connection via
QueryExecutorTemplate.execute(con, …)instead ofexecute(start, …). To keep every read on that one connection, connection-taking twinswere added to the recipe query helpers the fan-out uses:
EmailPasswordQueries.getPrimaryUserIdUsingEmail_TransactionPasswordlessQueries.getPrimaryUserIdUsingEmail_Transaction/getPrimaryUserByPhoneNumber_TransactionThirdPartyQueries.getPrimaryUserIdUsingEmail_TransactionAccountInfoQueries.listPrimaryUserIdsByEmail_Transaction/listPrimaryUserIdsByPhoneNumber_TransactionWebAuthN already exposed a connection-taking twin
(
getPrimaryUserIdForTenantUsingEmail_Transaction) andgetPrimaryUserInfoForUserIds_Transactionalready existed — both are reused, notduplicated. These are plain reads: no
FOR UPDATE.No schema change, no new index, no migration script, no
manifest.jsonentry — this ispurely a connection-ownership change over the existing tables/SQL. No version bump.
Tests added
src/test/java/.../ListPrimaryUsersTransactionParityTest.java: seeds three unlinkedprimary users sharing an email across emailpassword / thirdparty / passwordless (plus a
phone on the passwordless user), then asserts the
_Transactionreads return exactly thesame users, in the same order, as the non-transaction reads — run in both migration-mode
branches (
LEGACY= read-old andDUAL_WRITE_READ_NEW= read-new).What I ran locally
src/main/java(javac 21)against the plugin-interface#227 branch classes + the plugin's compile dependencies:
clean. Compiled the same sources against the integration base without fix: removing restriction of connection pool size for bulk import #227: fails
exactly on the two
@Overridemethods inStart.java(method does not override … a supertype) — confirming the cross-repo contract is the only thing gating compilationand that this PR wires to it correctly.
src/testbatch compilewere pre-existing, in other test files, from unrelated test-only deps not on my
classpath).
What I did NOT verify locally
contract wired into supertokens-root together with a matching core and a live postgres.
In CI that wiring happens automatically via same-branch-name pairing once fix: removing restriction of connection pool size for bulk import #227 is on
the shared integration base; locally it requires standing up the whole trio, which I did
not do. Expected CI note: until plugin-interface#227 merges into
fix/nested-conn-acquisition-cleanup, the "Run tests" job resolves plugin-interface toits default branch (which lacks the new interface methods) and this repo will fail to
compile there — that failure is the cross-repo dependency, not a defect in this diff, and
clears once fix: removing restriction of connection pool size for bulk import #227 merges.
Cross-SDK note
supertokens-node is the reference implementation; supertokens-plugin-interface#227 is the
contract and the core-side caller (PLAN-018 Unit 3, core repo) consumes these methods. No
port to this PR is needed beyond that. Per policy, supertokens-mysql-plugin is out of scope
and the in-memory (sqlite) impl travels with the core Unit-3 ticket.
Blocked by supertokens/supertokens-plugin-interface#227
Part of PLAN-018
Fixes #413