You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This issue tracks the work that makes MySQL and Arrow Flight SQL equal front ends over one session
layer in the FE: one session object, one result-encoding abstraction, one Doris-to-Arrow type
mapping, one connection pool and quota, and one prepared-statement registry.
Part of #65615. The design is being written up as a DSIP; this issue is the implementation tracker.
If you hit one of the problems listed here, please open a separate bug report and link it back.
#67578 builds on this one: completing the Arrow Flight SQL server and shipping an official Doris
ADBC driver needs the session lifecycle, the result path and the prepared-statement registry that
this issue unifies. The two can be discussed in parallel, but that work only lands cleanly on top
of what is tracked here.
Relationship to the earlier protocol SPI work.#60355 / #60361 (both closed) decoupled the listeners: how a protocol server is discovered, configured and started. This issue covers
everything behind the listener - session state, the result path, type mapping, connection governance
and prepared statements - which is still written against the MySQL protocol and special-cased for
Arrow Flight SQL.
Why
An Arrow Flight SQL session already is a ConnectContext, SHOW PROCESSLIST lists it and KILL
works on it. What is not shared is most of what the execution layer does afterwards. A read of the
FE session/connection/result paths and the BE result sinks turned up the following; items with a PR
have been verified locally, the rest are code-reading conclusions and are marked as such.
The execution layer branches on the protocol in roughly 40 places (StmtExecutor 18, ConnectProcessor 8, nine Commands, plus Coordinator, NereidsCoordinator, StatementContext, FrontendServiceImpl) and reaches for MysqlChannel in roughly 50. A Flight
session throws from getMysqlChannel(), so every unguarded reach is a live failure rather than a
style problem - see [fix](arrow-flight) Forward a statement to the master FE without touching the MySQL channel #67569 for one that reached production.
There are three Doris-to-Arrow type mappings: the BE's convert_to_arrow_type, the FE's FlightSqlSchemaHelper.getArrowType (used by CommandGetTables), and FlightSqlChannel, which
encodes every FE-side result as utf8. The FE mirror already disagrees with the BE in four types.
Flight connections are governed by their own pool with their own limit
(arrow_flight_max_connections) plus a per-user token LRU, so qe_max_connection and max_user_connections do not apply to them, and a live session that the LRU evicts surfaces to the
client as invalid bearer token.
Flight prepared statements do not use the FE's prepared-statement machinery: createPreparedStatement stores the SQL text and returns a placeholder schema, and parameter
binding is not implemented.
Status convention
Same as #65615: [x] means merged or confirmed complete; [ ] means open or needs follow-up.
Stage 0 - independent bug fixes
Defects found by the survey that do not depend on the refactor. All three are merged and carry dev/4.2.x, so they reach the 4.2 branch ahead of the refactor.
A Flight session reports 0.0.0.0:0 as its client address:[fix](arrow-flight) Report the real client address of an Arrow Flight SQL session #67576, merged 2026-09-10.
The Host column of SHOW PROCESSLIST and information_schema.processlist, the audit log's client_ip, and the kill
and timeout warnings all showed the placeholder, although the real address is resolved when the
bearer token is issued.
Stage 1 - one session, one result path
Behavior-preserving refactor, guarded by golden tests that pin the MySQL packet bytes. Complete:
all five PRs merged between 2026-09-11 and 2026-09-14, and the performance baseline shows no change.
Golden tests first: [test](protocol) Record a golden baseline of MySQL packets and Arrow Flight results #67789, merged 2026-09-11. A recording MysqlChannel drives a statement set through MysqlConnectProcessor/StmtExecutor and the produced packets are compared byte for byte; the same
statements then run through the Flight processor and their schema and rows are compared. This is the
safety net for everything below, so it lands before any refactor. 27 MySQL cases and 8 Flight
statements, recorded as annotated hexdumps rather than binary blobs so a diff is reviewable. Two
responses are recorded as shape rather than bytes, because their payload moves with unrelated
changes: a parser error carries the grammar's whole keyword list, and an EXPLAIN carries the
current plan text. COM_STMT_EXECUTE is out of scope for the golden files because supportHandleByFe() is false for it, so its result always comes from a BE - the cursor-fetch
packet boundaries stay covered by prepared_stmt_p0 instead.
ProtocolAdapter: [refactor](session) Bind a ConnectContext to one ProtocolAdapter instead of a Flight subclass #67835, merged 2026-09-11. The per-protocol half of ConnectContext
(channel, capabilities, accept loop, close, the COM_STMT_EXECUTE state, the Flight result cache,
endpoints and deferred executors) moves behind an interface with a MySQL and a Flight
implementation; ConnectContext.forMysql / forMysqlProxy / forFlight create the bound
context. ConnectContext keeps its class name and getter signatures - it is referenced by ~900
files - and FlightSqlConnectContext goes away. Flight commands get a per-session lock: ConnectContext is not thread safe and Flight did not serialize a session's calls. Verified
against the [test](protocol) Record a golden baseline of MySQL packets and Arrow Flight results #67789 baseline: not a byte changed. The internal (no-client) context stays a MySQL
context over a DummyMysqlChannel for now, because the execution layer still keys its result
path on ConnectType; a distinct internal adapter follows once those branches are gone.
ResultSender: [refactor](session) Move result encoding behind ResultSender #67883, merged 2026-09-12. The result-encoding half moves out of StmtExecutor/ConnectProcessor: qe.protocol.ResultSender with MysqlResultSender / FlightResultSender, StmtExecutor without a serializer field or MysqlChannel parameters, the
three text-result paths (EXPLAIN, EXPLAIN PLAN PROCESS, REPLAY) through the one sendResultSet, and ConnectProcessor without its connectType field (the per-statement
protocol work of a multi-statement request is adapter.finishStatement). The MySQL golden is
byte-identical; the Flight golden changes in exactly one entry, EXPLAIN PLAN PROCESS, which
had no Flight branch before and now returns its rows. Also carries the leftovers of the [refactor](session) Bind a ConnectContext to one ProtocolAdapter instead of a Flight subclass #67835
review (lock wait bound = getExecTimeoutS(), FlightRuntimeException passed through the
producer's catch-alls, a WARN when the lock is given up).
Remaining protocol branches in the execution layer go to zero: [refactor](session) Replace the execution layer's ConnectType branches with protocol capabilities #67900, merged 2026-09-14. StmtExecutor, ConnectContext.supportHandleByFe, the nine insert / transaction Commands, both
coordinators, the short-circuit rule and FEOpExecutor stop asking getConnectType(); ProtocolAdapter gains one method per use: canReplayForwardedQueryResult, supportsFeSideResult, supportsShortCircuitPointQuery, canRetryQuery, the lifecycle hooks beforeStatement / beforeAttempt / beforeQuery / returnsResultFromLocal, and fillForwardRequest (with MysqlProtocolAdapter.restoreFromForwardRequest as the master's side). The Flight-only returnResultFromLocal flag is no longer flipped from outside the adapter, and FlightResultSender
no longer has to undo a flip for EXPLAIN. The behavior changes, all recorded in the MySQL golden
(28 -> 33 cases; the Flight golden is byte-identical): the channel is reset once, when a statement
starts, instead of in the query path and in nine commands, so a request from a client without CLIENT_MULTI_STATEMENTS whose last statement is not a query (select 1; set @a = 1) delivers
only the last response instead of the buffered result set of the SELECT followed by an OK -- a
stream no client parses; MysqlChannel.reset() now also rewinds the sequence id to the last packet
the client saw, so such a response (select 1; select 2, select 1; select * from no_such_table)
starts at sequence id 1 instead of leaving the hole pymysql and libmysqlclient rejected (the
pre-existing bug noted on 2026-09-13, fixed here because the reset-per-statement had widened it); ProxyMysqlChannel drops a failed attempt's packets on reset, so a forwarded query the master
retries no longer delivers both attempts' packets; and a Flight query replanned after a cloud NEED_REPLAN error starts every attempt through beforeAttempt, which withdraws exactly the
endpoints the failed attempt registered. The recording channel of the golden models the send buffer
and the sequence rewind, so the golden shows what reaches the client. Wired end to end by ProtocolCapabilityWiringTest (forward refusal, retry, an internal executor answering on the
caller's connection) and three new regression suites: test_arrow_flight_session_lifecycle, test_multi_statement_response (a raw MySQL-protocol client covers all four CLIENT_MULTI_STATEMENTS x CLIENT_DEPRECATE_EOF combinations) and the two-FE test_mysql_forward_to_master.
Performance baseline, run 2026-09-13: three FE builds - before Stage 1 (60042611fea), after
the package move (af333cc583d) and the [refactor](session) Replace the execution layer's ConnectType branches with protocol capabilities #67900 branch - against the same BE, data and metadata.
A 10M-row wide table drained over mysql --quick and over a raw socket, select 1 and one-row BE
queries over Connector/J at 1 and 8 threads, ADBC fetch_arrow_table / select 1 / show variables. Every difference is within the run-to-run noise of a single build (FE-side paths
within +/-1%; the few cells above 3% changed sign on interleaved re-runs).
#67520 (merged 2026-09-10) lands in the middle of this surface: it touches ConnectProcessor, MysqlConnectProcessor, StmtExecutor, FEOpExecutor, MysqlProto and ConnectContext, and adds MysqlCursorFetchCompatibility, a client-capability branch that decides where a cursor result's
terminators go. The adapter extraction absorbs it rather than works around it: that logic belongs to
the MySQL adapter (#67835 moved it there as MysqlProtocolAdapter.clientConsumesCursorMetadataTerminator). #67753 (merged 2026-09-11, right after #67835) adds one more thing the ResultSender step has to
carry: an internal query run on behalf of a client (the IVM dry run) streams its rows to that
client's MySQL channel, so StmtExecutor grew channel-taking overloads of its send methods. The
result sender is handed over by the caller in that case rather than taken from the executor's own
session.
Stage 2 - one type mapping
Deferred (2026-09-14). The BE Arrow type layer is being refactored separately - the refactor #67530 was told to wait for - and changing the mapping now would collide with it, so #67530 and #65789 were closed unmerged and this stage lands after that refactor, on top of its mapping. Until
then every new interface uses the current mapping exactly as it is, through one FE entry point
(FlightSqlSchemaHelper.getArrowType extracted into DorisArrowTypeMapping with no value
changed), and the known-wrong cells are kept deliberately rather than fixed piecemeal: GetTables
reports TIMESTAMPTZ with a fixed UTC zone and Null for TIMEV2 / VARBINARY / AGG_STATE;
AGG_STATE travels as utf8; doris_type metadata covers only top-level LARGEINT / IPV4 / IPV6, no ARROW:extension:name anywhere; FE-side results are all utf8; the oversized-column large_utf8
mismatch of #65789 stays. The server-side items of #67578 Part A (session actions, metadata
commands, prepared statements, cancellation, parallel endpoints, ingest) go first instead, with
Stage 3 between the first two, and the BE-touching ones stay clear of the type layer.
The one FE entry point first, done now: FlightSqlSchemaHelper.getArrowType and the field
builder it feeds extracted into org.apache.doris.arrow.DorisArrowTypeMapping with no value changed,
the mapping pinned as it stands by a table-driven unit test (one row per PrimitiveType, the
known-wrong cells included and marked) and by a regression suite that reads the GetTables schema
of a table of every type through a raw Flight SQL client. [refactor](arrow-flight) Extract the Doris-to-Arrow type mapping into DorisArrowTypeMapping #68315, opened 2026-09-21.
One enumerable Doris-to-Arrow mapping, shared by a table-driven BE unit test and an FE test that
validates the FE mirror against the same golden file, and the four known FE/BE disagreements fixed. [feature](timestamp_ns) Add end-to-end TIMESTAMP_NS support #66761 added TIMESTAMP_NS to both mappings separately - one more line in arrow_row_batch.cpp and
one in FlightSqlSchemaHelper - which is exactly the drift this stage removes; the golden file
covers it.
Field metadata completed on the BE: doris_type for every non-native type plus ARROW:extension:name (doris.largeint, doris.ipv4, doris.ipv6, doris.bitmap, doris.hll, doris.quantile_state, doris.agg_state, doris.time), with JSONB and VARIANT using the canonical arrow.json extension. Storage types do not change: convert_to_arrow_type also serves the
Spark and Flink connectors' read path and Python UDFs, so changing them would change those
protocols. In particular LARGEINT stays a decimal string rather than becoming decimal128(38,0),
which cannot represent an int128 (39 decimal digits; Arrow caps decimal128 precision at 38), and
IPV4/IPV6 stay int32/utf8 rather than fixed_size_binary.
FE-side results (SHOW, EXPLAIN, replayed proxy results) built as typed vectors instead of
all-utf8.
A cross-protocol regression suite: the same query over JDBC and over Flight, compared column by
column across the whole type matrix.
Stage 3 - one connection pool and quota
Builds on #67504 (merged 2026-09-07), which stopped a finished Flight query from holding its
coordinator until wait_timeout.
The prepared-statement registry is extracted from the MySQL COM_STMT_* handling so both front
ends share it.
Flight createPreparedStatement/getFlightInfoPreparedStatement/closePreparedStatement go
through it and return the real result and parameter schemas.
acceptPutPreparedStatementQuery binds Arrow parameter batches, so Flight SQL JDBC with useServerPrepStmts=true and the ADBC default path work.
Known gaps, not yet scheduled
Code-reading conclusions from the same survey. They are not blocking the stages above; each needs its
own report or PR.
A forwarded statement's result set is dropped over Flight. Even after [fix](arrow-flight) Forward a statement to the master FE without touching the MySQL channel #67569, a statement
that is forwarded to the master and returns rows gives the Flight client nothing, because the proxy
result is replayed by ConnectProcessor.finalizeCommand(), which is MySQL-only. Affects only
statements that both need forwarding and carry a result set.
arrow_flight_token_alive_time_second is applied in the wrong unit.createToken() converts
the configured seconds as if they were minutes, so the recorded expiry is 60x too far out; the token
is actually evicted by the Guava cache's own expiry, which makes the explicit expiry check dead code.
Gone with [refactor](arrow-flight) Make the bearer token the credential of exactly one session #68266 (merged 2026-09-21): the setting is a deprecated no-op and FlightTokenDetails is
deleted.
select 1 and select @@var always go to the BE on a Flight session, because supportHandleByFe() is hard-coded false for Arrow Flight SQL. Enabling FE-side results for Flight
is deliberately out of scope until the FE result path is typed (Stage 2).
SHOW TABLES / DATABASES / COLUMNS / TABLE STATUS ... WHERE hang on a Flight session. The
four commands run an internal query on the session's own context, so the plan gets an Arrow result
sink while the frontend pulls the rows over fetch_data; the request waits until it times out. The LIKE form takes the metadata path and is unaffected. Found while writing [refactor](session) Replace the execution layer's ConnectType branches with protocol capabilities #67900, which
deliberately leaves that path as it is (the coordinators keep deciding on the per-statement flag,
not on a protocol capability).
The master answers a forwarded query with statusCode 1105.ConnectProcessor.proxyExecute
reports 0 only when the state is OK; a query ends in EOF, so the follower sees a failure code
with a null message and its multi-statement loop stops after the first statement: select 1; select 2 on a follower with force_forward_all_queries runs only select 1.
The handshake does not advertise CLIENT_MULTI_STATEMENTS / CLIENT_MULTI_RESULTS.
Connector/J only asks for CLIENT_MULTI_STATEMENTS when the server advertised it, so a JDBC client
with allowMultiQueries=true never receives the intermediate responses of a multi-statement
request, although the server side handles them (the golden shows it, and the mysql CLI, which sets
the flag regardless, gets them). test_multi_statement_response reaches that path with a raw
protocol client instead.
[fix](arrow-flight) Keep the Doris type of a nested LARGEINT in the Arrow schema #67530 - keep the Doris type of a nested LARGEINT in the Arrow schema. Closed unmerged on
2026-09-14: the BE type-layer refactor it was told to wait for is going ahead separately, and
Stage 2 now lands after that refactor instead (note under Stage 2). The nested and JSON / VARIANT
metadata it carried is part of that stage.
2026-09-11: [refactor](arrow-flight) Move Arrow Flight SQL out of the service package into org.apache.doris.arrowflight #67866 opened and merged the same day - the service.arrowflight -> arrowflight
package move, a mechanical PR slotted in before ResultSender. Flight now lives in the top-level org.apache.doris.arrowflight package, a peer of org.apache.doris.mysql; ResultSender is next
and its implementations go to mysql.protocol / arrowflight.protocol.
2026-09-12: [refactor](session) Move result encoding behind ResultSender #67883 opened - the ResultSender extraction, second refactor PR of Stage 1. Only
the expected Flight golden entry changed (EXPLAIN PLAN PROCESS gains a result); everything
MySQL is byte for byte. What remains for Stage 1 is the capability-predicate PR (the branches
outside the result path) and the performance baseline.
2026-09-12: [refactor](session) Move result encoding behind ResultSender #67883 merged. StmtExecutor and ConnectProcessor no longer encode anything
themselves; every protocol branch left in the execution layer is now a candidate for a capability
predicate on ProtocolAdapter, and that PR is next. The list, on the merged master: five ConnectType checks in StmtExecutor (including the Flight forward refusal that [fix](arrow-flight) Forward a statement to the master FE without touching the MySQL channel #67569 added), supportHandleByFe(), the nine Commands that reset the MySQL channel, the two coordinators and QueryProcessor, StatementContext.close, the short-circuit rule and FEOpExecutor. The
performance baseline is still pending.
2026-09-13: [refactor](session) Replace the execution layer's ConnectType branches with protocol capabilities #67900 opened - the capability-predicate PR, last refactor PR of Stage 1. After it, grep 'ConnectType\.\|getMysqlChannel()' over qe/** and nereids/** (outside */protocol/)
finds only the ConnectContext.getMysqlChannel() delegate and two lines of MysqlConnectProcessor
that read the client's packets. Two things found on the way, filed for their own fixes rather than
folded in: on a Flight session, SHOW TABLES / DATABASES / COLUMNS / TABLE STATUS ... WHERE run an
internal query on the session's own context, so the plan gets an Arrow result sink while the
frontend tries to pull the rows over fetch_data (the request hangs until it times out); and MysqlChannel.reset() does not rewind the sequence id, so the response a client without CLIENT_MULTI_STATEMENTS gets for select 1; select 2 starts at sequence id 4, which pymysql and
libmysqlclient reject (Connector/J does not check). The internal adapter for the no-client context
and the performance baseline remain.
2026-09-14: [refactor](session) Replace the execution layer's ConnectType branches with protocol capabilities #67900 merged; Stage 1 is complete. The review added a per-attempt hook, beforeAttempt, called at the top of StmtExecutor.execute for the first attempt and for each
replan retry: a Flight attempt that failed after beforeQuery used to leave the next attempt in the
"result on the backends" state, and its endpoints next to the retry's. The sequence-id fix for MysqlChannel.reset() went into the same PR rather than its own, because the reset-per-statement
had widened it from select 1; select 2 to any request without CLIENT_MULTI_STATEMENTS whose
later statement fails. The performance baseline (three FE builds against one BE, MySQL and Flight
paths) shows no measurable change. Left for their own PRs, listed under Known gaps: the Flight SHOW ... WHERE hang, the master's 1105 for a forwarded query, and the un-advertised CLIENT_MULTI_STATEMENTS. The internal adapter for the no-client context is no longer a
prerequisite for anything - nothing in the execution layer keys on the context type now - so it is
not scheduled. Stage 2 still waits on [fix](arrow-flight) Keep the Doris type of a nested LARGEINT in the Arrow schema #67530.
2026-09-17: [feature](arrow-flight) Serve the Flight SQL session actions on the Doris session #67966 merged (76d9221dacc): Stage 5 item A1 done. [refactor](arrow-flight) One connection pool for MySQL connections and Arrow Flight SQL sessions #68101 opened - Stage 3, item 1: one
connection pool. FlightSqlConnectPoolMgr is deleted; ConnectPoolMgr registers either protocol
against qe_max_connection and max_user_connections, keeps a Flight sub-quota
(arrow_flight_max_connections, now defaulting to -1 = follow qe_max_connection, capped at it) and
a peer-identity index, and its unregisterConnection releases the protocol's session state through
the new ProtocolAdapter.releaseSession (the [feature](arrow-flight) Serve the Flight SQL session actions on the Doris session #67966 teardown contract kept); a Flight session
refused for a limit gets MySQL's sentence as RESOURCE_EXHAUSTED and its token invalidated; ProtocolAdapter.connectPool is gone. Regression test_connection_quota uses max_user_connections = 4 because the per-user token LRU still evicts the oldest session at max_user_connections / 2 tokens - that cache goes with the next item. Next: the bearer token as
the session credential.
2026-09-20: [refactor](arrow-flight) One connection pool for MySQL connections and Arrow Flight SQL sessions #68101 merged (46e1c671220): Stage 3, item 1 done. [refactor](arrow-flight) Make the bearer token the credential of exactly one session #68266 opened - Stage 3, item 2:
the bearer token is the peer identity a session is registered in the pool under and nothing else.
The session opens at the handshake that authenticates the password (a session that does not fit is
refused there, in MySQL's words, and no token is issued), a token is valid exactly as long as its
session, and the session ends only with CloseSession, KILL CONNECTION or wait_timeout - the
very next call under the token is UNAUTHENTICATED. Decided for it: a full quota refuses rather
than evicting the least recently used session (the eviction was a side effect of the Guava cache,
not a design), and there is no idle timeout of Flight's own - wait_timeout, settable through the
session options, governs both protocols. The tokens package, both caches, the created-session
flag and the creation lock are deleted; the two token settings are deprecated no-ops reported at
startup; test_connection_quota is back to max_user_connections = 1, and test_bearer_token_lifecycle covers the three ways a session ends. Next: the Protocol column.
2026-09-20: Stage 3, items 3 and 4 pushed onto [refactor](arrow-flight) Make the bearer token the credential of exactly one session #68266 (still open) as three more commits. A Protocol column (MySQL / ArrowFlightSQL) is the last column of SHOW PROCESSLIST, information_schema.processlist and /rest/v1/session; the audit event carries it as Protocol
and audit_log gains a protocol column; a row of a frontend on the other version is padded or
cut to the reader's columns (FE and BE scanner alike). test_connection_governance is the
operator's view of a MySQL connection and a Flight session together: both processlists agree
column for column, the Flight session's own SHOW PROCESSLIST, the shared quota's refusal over
either protocol, KILL CONNECTION in both directions, wait_timeout. Found on the way and fixed
in its own commit (worth picking): since [Chore](alter) add TQueryGlobals/TQueryOptions params to TAlterTabletReqV2 #58085 an alter job created without a session sent an
empty TQueryGlobals, so InternalSchemaInitializer's upgrade of audit_log was cancelled by the
backend (now_string missing) and every audit column added since only existed on clusters created
after it. Next: DorisArrowTypeMapping entry (Stage 2, the one item done now) and the metadata
commands (Stage 5 A3).
This issue tracks the work that makes MySQL and Arrow Flight SQL equal front ends over one session
layer in the FE: one session object, one result-encoding abstraction, one Doris-to-Arrow type
mapping, one connection pool and quota, and one prepared-statement registry.
Part of #65615. The design is being written up as a DSIP; this issue is the implementation tracker.
If you hit one of the problems listed here, please open a separate bug report and link it back.
#67578 builds on this one: completing the Arrow Flight SQL server and shipping an official Doris
ADBC driver needs the session lifecycle, the result path and the prepared-statement registry that
this issue unifies. The two can be discussed in parallel, but that work only lands cleanly on top
of what is tracked here.
Relationship to the earlier protocol SPI work. #60355 / #60361 (both closed) decoupled the
listeners: how a protocol server is discovered, configured and started. This issue covers
everything behind the listener - session state, the result path, type mapping, connection governance
and prepared statements - which is still written against the MySQL protocol and special-cased for
Arrow Flight SQL.
Why
An Arrow Flight SQL session already is a
ConnectContext,SHOW PROCESSLISTlists it andKILLworks on it. What is not shared is most of what the execution layer does afterwards. A read of the
FE session/connection/result paths and the BE result sinks turned up the following; items with a PR
have been verified locally, the rest are code-reading conclusions and are marked as such.
StmtExecutor18,ConnectProcessor8, nineCommands, plusCoordinator,NereidsCoordinator,StatementContext,FrontendServiceImpl) and reaches forMysqlChannelin roughly 50. A Flightsession throws from
getMysqlChannel(), so every unguarded reach is a live failure rather than astyle problem - see [fix](arrow-flight) Forward a statement to the master FE without touching the MySQL channel #67569 for one that reached production.
convert_to_arrow_type, the FE'sFlightSqlSchemaHelper.getArrowType(used byCommandGetTables), andFlightSqlChannel, whichencodes every FE-side result as
utf8. The FE mirror already disagrees with the BE in four types.(
arrow_flight_max_connections) plus a per-user token LRU, soqe_max_connectionandmax_user_connectionsdo not apply to them, and a live session that the LRU evicts surfaces to theclient as
invalid bearer token.createPreparedStatementstores the SQL text and returns a placeholder schema, and parameterbinding is not implemented.
Status convention
Same as #65615:
[x]means merged or confirmed complete;[ ]means open or needs follow-up.Stage 0 - independent bug fixes
Defects found by the survey that do not depend on the refactor. All three are merged and carry
dev/4.2.x, so they reach the 4.2 branch ahead of the refactor.FEOpExecutor.buildStmtForwardParams()readsCLIENT_DEPRECATE_EOFstraight off the MySQL channel(introduced by [fix](protocol) Support CLIENT_DEPRECATE_EOF to fix empty result with MySQL driver 9.5.0 #61050), so on a multi-FE deployment every statement a Flight connection has to
forward - any DDL issued to a follower or observer, or anything at all under
force_forward_all_queries- fails withgetMysqlChannel not in mysql connection.ConnectTypedeclared twice, plus an unreachable dispatch on the forward path: [refactor](qe) Merge the duplicate ConnectType enum and drop the unreachable forward branch #67572,merged 2026-09-08. Cleanup, no behavior change. One leftover, deliberately deferred to the
ProtocolAdapterPR:ConnectProcessor.connectTypeis still a redundant mirror ofctx.getConnectType().0.0.0.0:0as its client address: [fix](arrow-flight) Report the real client address of an Arrow Flight SQL session #67576, merged 2026-09-10.The
Hostcolumn ofSHOW PROCESSLISTandinformation_schema.processlist, the audit log'sclient_ip, and the killand timeout warnings all showed the placeholder, although the real address is resolved when the
bearer token is issued.
Stage 1 - one session, one result path
Behavior-preserving refactor, guarded by golden tests that pin the MySQL packet bytes. Complete:
all five PRs merged between 2026-09-11 and 2026-09-14, and the performance baseline shows no change.
MysqlChanneldrives a statement set throughMysqlConnectProcessor/StmtExecutorand the produced packets are compared byte for byte; the samestatements then run through the Flight processor and their schema and rows are compared. This is the
safety net for everything below, so it lands before any refactor. 27 MySQL cases and 8 Flight
statements, recorded as annotated hexdumps rather than binary blobs so a diff is reviewable. Two
responses are recorded as shape rather than bytes, because their payload moves with unrelated
changes: a parser error carries the grammar's whole keyword list, and an
EXPLAINcarries thecurrent plan text.
COM_STMT_EXECUTEis out of scope for the golden files becausesupportHandleByFe()is false for it, so its result always comes from a BE - the cursor-fetchpacket boundaries stay covered by
prepared_stmt_p0instead.ProtocolAdapter: [refactor](session) Bind a ConnectContext to one ProtocolAdapter instead of a Flight subclass #67835, merged 2026-09-11. The per-protocol half ofConnectContext(channel, capabilities, accept loop, close, the
COM_STMT_EXECUTEstate, the Flight result cache,endpoints and deferred executors) moves behind an interface with a MySQL and a Flight
implementation;
ConnectContext.forMysql/forMysqlProxy/forFlightcreate the boundcontext.
ConnectContextkeeps its class name and getter signatures - it is referenced by ~900files - and
FlightSqlConnectContextgoes away. Flight commands get a per-session lock:ConnectContextis not thread safe and Flight did not serialize a session's calls. Verifiedagainst the [test](protocol) Record a golden baseline of MySQL packets and Arrow Flight results #67789 baseline: not a byte changed. The internal (no-client) context stays a MySQL
context over a
DummyMysqlChannelfor now, because the execution layer still keys its resultpath on
ConnectType; a distinct internal adapter follows once those branches are gone.org.apache.doris.service.arrowflightbecomesorg.apache.doris.arrowflight. Flight was filed underservicein 2023 ([feature-wip](arrow-flight)(step3) Support authentication and user session #24772) next to the thriftFrontendServiceImpl; it is now a peer of the top-levelmysqlpackage, and [refactor](session) Bind a ConnectContext to one ProtocolAdapter instead of a Flight subclass #67835 already had tomirror a
protocolsub-package on both sides. A puregit mv(36 files, onlypackage/importlines change, nothing references the old name by string), sequenced before
ResultSenderso thenew implementations land in the final place.
ResultSender: [refactor](session) Move result encoding behind ResultSender #67883, merged 2026-09-12. The result-encoding half moves out ofStmtExecutor/ConnectProcessor:qe.protocol.ResultSenderwithMysqlResultSender/FlightResultSender,StmtExecutorwithout a serializer field orMysqlChannelparameters, thethree text-result paths (
EXPLAIN,EXPLAIN PLAN PROCESS,REPLAY) through the onesendResultSet, andConnectProcessorwithout itsconnectTypefield (the per-statementprotocol work of a multi-statement request is
adapter.finishStatement). The MySQL golden isbyte-identical; the Flight golden changes in exactly one entry,
EXPLAIN PLAN PROCESS, whichhad no Flight branch before and now returns its rows. Also carries the leftovers of the [refactor](session) Bind a ConnectContext to one ProtocolAdapter instead of a Flight subclass #67835
review (lock wait bound =
getExecTimeoutS(),FlightRuntimeExceptionpassed through theproducer's catch-alls, a WARN when the lock is given up).
StmtExecutor,ConnectContext.supportHandleByFe, the nine insert / transactionCommands, bothcoordinators, the short-circuit rule and
FEOpExecutorstop askinggetConnectType();ProtocolAdaptergains one method per use:canReplayForwardedQueryResult,supportsFeSideResult,supportsShortCircuitPointQuery,canRetryQuery, the lifecycle hooksbeforeStatement/beforeAttempt/beforeQuery/returnsResultFromLocal, andfillForwardRequest(withMysqlProtocolAdapter.restoreFromForwardRequestas the master's side). The Flight-onlyreturnResultFromLocalflag is no longer flipped from outside the adapter, andFlightResultSenderno longer has to undo a flip for
EXPLAIN. The behavior changes, all recorded in the MySQL golden(28 -> 33 cases; the Flight golden is byte-identical): the channel is reset once, when a statement
starts, instead of in the query path and in nine commands, so a request from a client without
CLIENT_MULTI_STATEMENTSwhose last statement is not a query (select 1; set @a = 1) deliversonly the last response instead of the buffered result set of the
SELECTfollowed by anOK-- astream no client parses;
MysqlChannel.reset()now also rewinds the sequence id to the last packetthe client saw, so such a response (
select 1; select 2,select 1; select * from no_such_table)starts at sequence id 1 instead of leaving the hole pymysql and libmysqlclient rejected (the
pre-existing bug noted on 2026-09-13, fixed here because the reset-per-statement had widened it);
ProxyMysqlChanneldrops a failed attempt's packets on reset, so a forwarded query the masterretries no longer delivers both attempts' packets; and a Flight query replanned after a cloud
NEED_REPLANerror starts every attempt throughbeforeAttempt, which withdraws exactly theendpoints the failed attempt registered. The recording channel of the golden models the send buffer
and the sequence rewind, so the golden shows what reaches the client. Wired end to end by
ProtocolCapabilityWiringTest(forward refusal, retry, an internal executor answering on thecaller's connection) and three new regression suites:
test_arrow_flight_session_lifecycle,test_multi_statement_response(a raw MySQL-protocol client covers all fourCLIENT_MULTI_STATEMENTSxCLIENT_DEPRECATE_EOFcombinations) and the two-FEtest_mysql_forward_to_master.60042611fea), afterthe package move (
af333cc583d) and the [refactor](session) Replace the execution layer's ConnectType branches with protocol capabilities #67900 branch - against the same BE, data and metadata.A 10M-row wide table drained over
mysql --quickand over a raw socket,select 1and one-row BEqueries over Connector/J at 1 and 8 threads, ADBC
fetch_arrow_table/select 1/show variables. Every difference is within the run-to-run noise of a single build (FE-side pathswithin +/-1%; the few cells above 3% changed sign on interleaved re-runs).
#67520 (merged 2026-09-10) lands in the middle of this surface: it touches
ConnectProcessor,MysqlConnectProcessor,StmtExecutor,FEOpExecutor,MysqlProtoandConnectContext, and addsMysqlCursorFetchCompatibility, a client-capability branch that decides where a cursor result'sterminators go. The adapter extraction absorbs it rather than works around it: that logic belongs to
the MySQL adapter (#67835 moved it there as
MysqlProtocolAdapter.clientConsumesCursorMetadataTerminator).#67753 (merged 2026-09-11, right after #67835) adds one more thing the
ResultSenderstep has tocarry: an internal query run on behalf of a client (the IVM dry run) streams its rows to that
client's MySQL channel, so
StmtExecutorgrew channel-taking overloads of its send methods. Theresult sender is handed over by the caller in that case rather than taken from the executor's own
session.
Stage 2 - one type mapping
FlightSqlSchemaHelper.getArrowTypeand the fieldbuilder it feeds extracted into
org.apache.doris.arrow.DorisArrowTypeMappingwith no value changed,the mapping pinned as it stands by a table-driven unit test (one row per
PrimitiveType, theknown-wrong cells included and marked) and by a regression suite that reads the
GetTablesschemaof a table of every type through a raw Flight SQL client. [refactor](arrow-flight) Extract the Doris-to-Arrow type mapping into DorisArrowTypeMapping #68315, opened 2026-09-21.
validates the FE mirror against the same golden file, and the four known FE/BE disagreements fixed.
[feature](timestamp_ns) Add end-to-end TIMESTAMP_NS support #66761 added TIMESTAMP_NS to both mappings separately - one more line in
arrow_row_batch.cppandone in
FlightSqlSchemaHelper- which is exactly the drift this stage removes; the golden filecovers it.
doris_typefor every non-native type plusARROW:extension:name(doris.largeint,doris.ipv4,doris.ipv6,doris.bitmap,doris.hll,doris.quantile_state,doris.agg_state,doris.time), with JSONB and VARIANT using the canonicalarrow.jsonextension. Storage types do not change:convert_to_arrow_typealso serves theSpark and Flink connectors' read path and Python UDFs, so changing them would change those
protocols. In particular LARGEINT stays a decimal string rather than becoming
decimal128(38,0),which cannot represent an int128 (39 decimal digits; Arrow caps decimal128 precision at 38), and
IPV4/IPV6 stay
int32/utf8rather thanfixed_size_binary.SHOW,EXPLAIN, replayed proxy results) built as typed vectors instead ofall-
utf8.column across the whole type matrix.
Stage 3 - one connection pool and quota
Builds on #67504 (merged 2026-09-07), which stopped a finished Flight query from holding its
coordinator until
wait_timeout.FlightSqlConnectPoolMgrfolds intoConnectPoolMgr, and Flight registration goesthrough
qe_max_connectionandmax_user_connectionslike any other connection. [refactor](arrow-flight) One connection pool for MySQL connections and Arrow Flight SQL sessions #68101, merged2026-09-20 (
46e1c671220);arrow_flight_max_connectionsis the sub-quota, defaulting to half ofqe_max_connection.lifetime.
arrow_flight_token_cache_sizeandarrow_flight_token_alive_time_secondare deprecatedwith a warning for one major version. [refactor](arrow-flight) Make the bearer token the credential of exactly one session #68266, merged 2026-09-21 (
4de617b58e0).Protocolcolumn onSHOW PROCESSLISTandinformation_schema.processlist(FE and the BEschema scanner), plus a
protocolfield in the audit log. In [refactor](arrow-flight) Make the bearer token the credential of exactly one session #68266 as well, merged 2026-09-21(
4de617b58e0), together with the connection governance regression suitetest_connection_governanceand a fix the audit column needs on an upgraded cluster: an alter jobcreated by a frontend daemon (
InternalSchemaInitializeradding a column toaudit_log) sent anempty
TQueryGlobalssince [Chore](alter) add TQueryGlobals/TQueryOptions params to TAlterTabletReqV2 #58085 and the backend cancelled it.Stage 4 - prepared statements
COM_STMT_*handling so both frontends share it.
createPreparedStatement/getFlightInfoPreparedStatement/closePreparedStatementgothrough it and return the real result and parameter schemas.
acceptPutPreparedStatementQuerybinds Arrow parameter batches, so Flight SQL JDBC withuseServerPrepStmts=trueand the ADBC default path work.Known gaps, not yet scheduled
Code-reading conclusions from the same survey. They are not blocking the stages above; each needs its
own report or PR.
that is forwarded to the master and returns rows gives the Flight client nothing, because the proxy
result is replayed by
ConnectProcessor.finalizeCommand(), which is MySQL-only. Affects onlystatements that both need forwarding and carry a result set.
arrow_flight_token_alive_time_secondis applied in the wrong unit.createToken()convertsthe configured seconds as if they were minutes, so the recorded expiry is 60x too far out; the token
is actually evicted by the Guava cache's own expiry, which makes the explicit expiry check dead code.
Gone with [refactor](arrow-flight) Make the bearer token the credential of exactly one session #68266 (merged 2026-09-21): the setting is a deprecated no-op and
FlightTokenDetailsisdeleted.
AGG_STATEis advertised asutf8while carrying non-UTF-8 bytes - the same shape as theIceberg
BINARYproblem in [Bug] Arrow Flight SQL: Iceberg BINARY column is declared as Arrow string but carries invalid UTF-8 #67371.select 1andselect @@varalways go to the BE on a Flight session, becausesupportHandleByFe()is hard-coded false for Arrow Flight SQL. Enabling FE-side results for Flightis deliberately out of scope until the FE result path is typed (Stage 2).
SHOW TABLES / DATABASES / COLUMNS / TABLE STATUS ... WHEREhang on a Flight session. Thefour commands run an internal query on the session's own context, so the plan gets an Arrow result
sink while the frontend pulls the rows over
fetch_data; the request waits until it times out. TheLIKEform takes the metadata path and is unaffected. Found while writing [refactor](session) Replace the execution layer's ConnectType branches with protocol capabilities #67900, whichdeliberately leaves that path as it is (the coordinators keep deciding on the per-statement flag,
not on a protocol capability).
statusCode1105.ConnectProcessor.proxyExecutereports 0 only when the state is
OK; a query ends inEOF, so the follower sees a failure codewith a null message and its multi-statement loop stops after the first statement:
select 1; select 2on a follower withforce_forward_all_queriesruns onlyselect 1.CLIENT_MULTI_STATEMENTS/CLIENT_MULTI_RESULTS.Connector/J only asks for
CLIENT_MULTI_STATEMENTSwhen the server advertised it, so a JDBC clientwith
allowMultiQueries=truenever receives the intermediate responses of a multi-statementrequest, although the server side handles them (the golden shows it, and the mysql CLI, which sets
the flag regardless, gets them).
test_multi_statement_responsereaches that path with a rawprotocol client instead.
Upstream PRs this work depends on
wait_timeout. Merged 2026-09-07; unblocks Stage 1.2026-09-14: the BE type-layer refactor it was told to wait for is going ahead separately, and
Stage 2 now lands after that refactor instead (note under Stage 2). The nested and JSON / VARIANT
metadata it carried is part of that stage.
2026-09-14; the mismatch itself remains and gets its own fix after the type-layer refactor, and
arrow_block_convertor.cppis left alone until then.Progress
dev/4.2.x.refactor PR runs both golden tests before and after, and since those PRs are behavior-preserving,
a changed byte means the refactor changed something it should not have. Stage 2 remains parked
on [fix](arrow-flight) Keep the Doris type of a nested LARGEINT in the Arrow schema #67530.
ProtocolAdapterextraction, firstrefactor PR of Stage 1. Both golden tests pass unchanged on it. Next is the
ResultSenderextraction, which also has to absorb the channel-taking send overloads [fix](ivm) Answer FE-computable dry runs on the frontend instead of a placeholder backend #67753 added.
service.arrowflight->arrowflightpackage move, a mechanical PR slotted in before
ResultSender. Flight now lives in the top-levelorg.apache.doris.arrowflightpackage, a peer oforg.apache.doris.mysql;ResultSenderis nextand its implementations go to
mysql.protocol/arrowflight.protocol.ResultSenderextraction, second refactor PR of Stage 1. Onlythe expected Flight golden entry changed (
EXPLAIN PLAN PROCESSgains a result); everythingMySQL is byte for byte. What remains for Stage 1 is the capability-predicate PR (the branches
outside the result path) and the performance baseline.
StmtExecutorandConnectProcessorno longer encode anythingthemselves; every protocol branch left in the execution layer is now a candidate for a capability
predicate on
ProtocolAdapter, and that PR is next. The list, on the merged master: fiveConnectTypechecks inStmtExecutor(including the Flight forward refusal that [fix](arrow-flight) Forward a statement to the master FE without touching the MySQL channel #67569 added),supportHandleByFe(), the nineCommands that reset the MySQL channel, the two coordinators andQueryProcessor,StatementContext.close, the short-circuit rule andFEOpExecutor. Theperformance baseline is still pending.
grep 'ConnectType\.\|getMysqlChannel()'overqe/**andnereids/**(outside*/protocol/)finds only the
ConnectContext.getMysqlChannel()delegate and two lines ofMysqlConnectProcessorthat read the client's packets. Two things found on the way, filed for their own fixes rather than
folded in: on a Flight session,
SHOW TABLES / DATABASES / COLUMNS / TABLE STATUS ... WHERErun aninternal query on the session's own context, so the plan gets an Arrow result sink while the
frontend tries to pull the rows over
fetch_data(the request hangs until it times out); andMysqlChannel.reset()does not rewind the sequence id, so the response a client withoutCLIENT_MULTI_STATEMENTSgets forselect 1; select 2starts at sequence id 4, which pymysql andlibmysqlclient reject (Connector/J does not check). The internal adapter for the no-client context
and the performance baseline remain.
beforeAttempt, called at the top ofStmtExecutor.executefor the first attempt and for eachreplan retry: a Flight attempt that failed after
beforeQueryused to leave the next attempt in the"result on the backends" state, and its endpoints next to the retry's. The sequence-id fix for
MysqlChannel.reset()went into the same PR rather than its own, because the reset-per-statementhad widened it from
select 1; select 2to any request withoutCLIENT_MULTI_STATEMENTSwhoselater statement fails. The performance baseline (three FE builds against one BE, MySQL and Flight
paths) shows no measurable change. Left for their own PRs, listed under Known gaps: the Flight
SHOW ... WHEREhang, the master's 1105 for a forwarded query, and the un-advertisedCLIENT_MULTI_STATEMENTS. The internal adapter for the no-client context is no longer aprerequisite for anything - nothing in the execution layer keys on the context type now - so it is
not scheduled. Stage 2 still waits on [fix](arrow-flight) Keep the Doris type of a nested LARGEINT in the Arrow schema #67530.
refactor (note under Stage 2). The order from here: session actions ([Tracking] Complete the Arrow Flight SQL server and ship an official Doris ADBC driver #67578 A1), Stage 3, the FE
mapping entry point (no value changes) together with the metadata commands ([Tracking] Complete the Arrow Flight SQL server and ship an official Doris ADBC driver #67578 A3), Stage 4
with
ExecuteSchema([Tracking] Complete the Arrow Flight SQL server and ship an official Doris ADBC driver #67578 A5), cancellation / renewal / polling (A6), then the BE-touching itemsthat stay clear of the type layer - parallel endpoints with self-contained tickets, ingest over the
existing Arrow load format, IPC compression - and Stage 2 last, once the refactored mapping is in.
PRs for the [Tracking] Complete the Arrow Flight SQL server and ship an official Doris ADBC driver #67578 items reference both trackers.
is the statement that sets it (
catalog->SWITCH,schema->USE, any other name ->SET SESSION), run throughFlightSqlConnectProcessoras a command of the session, answered pername;
GetSessionOptionsisSHOW VARIABLESas strings;CloseSessionwas already invalidatingthe token synchronously, so that half of A1 was verified rather than changed. No type mapping
involved. Next: Stage 3.
76d9221dacc): Stage 5 item A1 done. [refactor](arrow-flight) One connection pool for MySQL connections and Arrow Flight SQL sessions #68101 opened - Stage 3, item 1: oneconnection pool.
FlightSqlConnectPoolMgris deleted;ConnectPoolMgrregisters either protocolagainst
qe_max_connectionandmax_user_connections, keeps a Flight sub-quota(
arrow_flight_max_connections, now defaulting to -1 = followqe_max_connection, capped at it) anda peer-identity index, and its
unregisterConnectionreleases the protocol's session state throughthe new
ProtocolAdapter.releaseSession(the [feature](arrow-flight) Serve the Flight SQL session actions on the Doris session #67966 teardown contract kept); a Flight sessionrefused for a limit gets MySQL's sentence as
RESOURCE_EXHAUSTEDand its token invalidated;ProtocolAdapter.connectPoolis gone. Regressiontest_connection_quotausesmax_user_connections = 4because the per-user token LRU still evicts the oldest session atmax_user_connections / 2tokens - that cache goes with the next item. Next: the bearer token asthe session credential.
46e1c671220): Stage 3, item 1 done. [refactor](arrow-flight) Make the bearer token the credential of exactly one session #68266 opened - Stage 3, item 2:the bearer token is the peer identity a session is registered in the pool under and nothing else.
The session opens at the handshake that authenticates the password (a session that does not fit is
refused there, in MySQL's words, and no token is issued), a token is valid exactly as long as its
session, and the session ends only with
CloseSession,KILL CONNECTIONorwait_timeout- thevery next call under the token is
UNAUTHENTICATED. Decided for it: a full quota refuses ratherthan evicting the least recently used session (the eviction was a side effect of the Guava cache,
not a design), and there is no idle timeout of Flight's own -
wait_timeout, settable through thesession options, governs both protocols. The
tokenspackage, both caches, the created-sessionflag and the creation lock are deleted; the two token settings are deprecated no-ops reported at
startup;
test_connection_quotais back tomax_user_connections = 1, andtest_bearer_token_lifecyclecovers the three ways a session ends. Next: theProtocolcolumn.Protocolcolumn (MySQL/ArrowFlightSQL) is the last column ofSHOW PROCESSLIST,information_schema.processlistand/rest/v1/session; the audit event carries it asProtocoland
audit_loggains aprotocolcolumn; a row of a frontend on the other version is padded orcut to the reader's columns (FE and BE scanner alike).
test_connection_governanceis theoperator's view of a MySQL connection and a Flight session together: both processlists agree
column for column, the Flight session's own
SHOW PROCESSLIST, the shared quota's refusal overeither protocol,
KILL CONNECTIONin both directions,wait_timeout. Found on the way and fixedin its own commit (worth picking): since [Chore](alter) add TQueryGlobals/TQueryOptions params to TAlterTabletReqV2 #58085 an alter job created without a session sent an
empty
TQueryGlobals, soInternalSchemaInitializer's upgrade ofaudit_logwas cancelled by thebackend (
now_stringmissing) and every audit column added since only existed on clusters createdafter it. Next:
DorisArrowTypeMappingentry (Stage 2, the one item done now) and the metadatacommands (Stage 5 A3).
4de617b58e0. Stage 2's one present item opened as[refactor](arrow-flight) Extract the Doris-to-Arrow type mapping into DorisArrowTypeMapping #68315: the mapping moves out of
FlightSqlSchemaHelperintoDorisArrowTypeMappingunchanged,with a table-driven unit test recording every cell as it stands (the first commit runs the same
table against the old code) and a regression suite recording the
GetTablesschema a raw Flight SQLclient sees for a column of every type; the
UTCzone on TIMESTAMPTZ andNullfor AGG_STATE arepinned there deliberately, per the deferral above. Next: the metadata commands (Stage 5 A3).