Commit 427da91
committed
fix: 2PC connection pinning + scaffolding audit
Two big items from the review are now correct:
- 2PC actually works against real backends (pinning fix)
- Half-implemented scaffolding has been either finished or deleted
1115/1152 tests pass (0 failures, 37 skipped require Docker).
+12 new tests for pinning and coercion, existing tests still pass.
----- 2PC connection pinning (the critical fix) -----
The DistributedTransactionManager used to route every phase 1 and
phase 2 SQL through RemoteExecutor::execute_dml, which on
ThreadSafeMultiRemoteExecutor checks out a fresh pooled connection
for each call. That silently broke 2PC against real backends for
both dialects:
- MySQL XA: "Once XA START has been issued, the XA transaction is
associated with the current session." XA END and XA PREPARE must
be on the same session; a fresh pooled connection would respond
with XAER_NOTA / "Unknown XID".
- PostgreSQL: PREPARE TRANSACTION operates on the session's
currently active transaction. BEGIN on one connection, PREPARE
on another -- the second connection sees "no transaction in
progress" and the PREPARE fails.
The existing unit tests didn't catch this because
MockDistributedExecutor didn't enforce session identity, but a real
MySQL or PostgreSQL backend would have silently failed every
distributed commit.
Fix: new RemoteSession abstraction in remote_session.h. A session
owns a specific physical connection for its lifetime; every
execute/execute_dml call goes to that same connection. RemoteExecutor
gets a new checkout_session(backend) virtual with a default nullptr
implementation (legacy fallback).
Implementations:
- ThreadSafeMultiRemoteExecutor returns a PooledMySQLSession that
holds a MYSQL* checked out from the ConnectionPool until
destruction. Poisoned sessions close the connection rather than
returning it to the pool.
- MockDistributedExecutor returns a MockSession that records calls
under a unique session id, so tests can verify phase 1 and
phase 2 landed on the same session (which the new pinning tests
now do explicitly).
DistributedTransactionManager holds
unordered_map<backend, unique_ptr<RemoteSession>> sessions_
and uses it for every phase 1/phase 2/rollback statement on an
enlisted backend. Sessions are acquired in enlist_backend, held
across phases, and released on commit()/rollback() by clearing the
map (destructors return connections to the pool).
If an executor returns nullptr from checkout_session, the manager
falls back to the legacy unpinned path -- works for non-pooled
executors and mock tests. Real pooled executors now take the pinned
path automatically.
Also added a new public method:
DmlResult execute_participant_dml(backend, sql)
which routes in-transaction DML to the pinned session. Session.
execute_statement does NOT yet auto-route through this -- that's a
follow-up that requires Session to know about the txn manager.
New tests (DistributedTxnPinning suite):
- AllMysqlPhasesOnSameSession: XA START, XA END, XA PREPARE,
XA COMMIT all land on one session id.
- TwoBackendsUseTwoDistinctSessions: different backends get
different sessions.
- PostgreSqlPrepareAndCommitOnSameSession: BEGIN, PREPARE
TRANSACTION, COMMIT PREPARED share a session.
- RollbackPinsSession: XA END + XA ROLLBACK stay on the same
session as XA START.
- UnpinnedFallbackStillWorks: executors reporting no session
support still go through the legacy path.
- ExecuteParticipantDmlUsesSameSession: in-transaction DML
through execute_participant_dml lands on the same session as
phase 1 and phase 2.
----- Scaffolding audit -----
Three pieces of half-implemented scaffolding have been either
completed or deleted:
1. projection_pruning.h was a documented no-op that the optimizer
pipeline still called. Deleted the file and removed the call.
The Project operator already does column subsetting at eval
time, so the pruning rule's only remaining value was inserting
slimming Projects above Scans in long chains -- a real
optimization but not a correctness issue and out of scope.
Optimizer is now honest about running exactly three rules.
2. INTERVAL type (Value::TAG_INTERVAL, SqlType::INTERVAL,
value_interval() constructor, union member, tag_kind_map
entries, local_txn serialization, sqlengine formatter,
mysql_server formatter). Declared but no producer existed
anywhere in the codebase: the parser doesn't recognize INTERVAL
literals, expression_eval never constructs one, neither remote
executor maps a database type to it. Removed all of it.
Re-add alongside a real producer (e.g. PostgreSQL INTERVAL OID
parsing, or a DATE_ADD(date, INTERVAL N unit) parser path) if
and when that work is done.
3. Coercion.h had a default-returns-null behavior that looked like
missing support but was actually semantically correct for
PostgreSQL's strict type rules. However it WAS missing a real
feature: string -> temporal coercion in MySQL. Queries like
`WHERE date_col = '2026-04-10'` would fall through to a NULL
comparison because there was no path from STRING to DATE.
Added to MySQL CoercionRules:
- STRING -> DATE (parses "YYYY-MM-DD" leniently)
- STRING -> TIME (parses "HH:MM:SS[.uuuuuu]")
- STRING -> DATETIME / TIMESTAMP (parses full datetime)
- DATE -> DATETIME/TIMESTAMP (promoted to midnight UTC)
- DATETIME -> DATE (floor-divides to day boundary)
PostgreSQL CoercionRules gets the within-temporal promotions
(DATE -> TIMESTAMP) but still rejects string -> temporal with
NULL, matching PG's strict rules that require explicit CAST.
+13 new coercion tests covering MySQL lenient paths and PG strict
behavior.
----- Still deferred -----
- Session.execute_statement automatic enlistment + routing through
execute_participant_dml. Requires Session to hold a reference to
the TransactionManager, which is currently decoupled. Not done in
this commit to keep the refactor scoped.
- The connection-pinning fallback path (when checkout_session returns
nullptr) is only triggered by non-pooled executors like the mock.
Real workloads always take the pinned path, so the legacy code is
only kept for test compatibility.1 parent 15d65dd commit 427da91
16 files changed
Lines changed: 909 additions & 234 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
6 | 7 | | |
7 | 8 | | |
8 | 9 | | |
| |||
179 | 180 | | |
180 | 181 | | |
181 | 182 | | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
182 | 238 | | |
183 | 239 | | |
184 | 240 | | |
| |||
233 | 289 | | |
234 | 290 | | |
235 | 291 | | |
236 | | - | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
237 | 298 | | |
238 | 299 | | |
239 | 300 | | |
| |||
252 | 313 | | |
253 | 314 | | |
254 | 315 | | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
255 | 334 | | |
256 | 335 | | |
257 | 336 | | |
| |||
0 commit comments