morningman opened a new pull request, #67900:
URL: https://github.com/apache/doris/pull/67900

   ### What problem does this PR solve?
   
   Issue Number: #67577
   
   Related PR: #67883 (`ResultSender`), #67835 (`ProtocolAdapter`), #67789 
(protocol goldens)
   
   Problem Summary:
   
   Third and last refactor step of stage 1 of #67577. After #67835 and #67883 
the connection half and the result-encoding half of the two wire protocols live 
behind `ProtocolAdapter` / `ResultSender`, but the execution layer still asked 
`ctx.getConnectType()` in a dozen places: five times in `StmtExecutor`, in 
`ConnectContext.supportHandleByFe`, in nine insert / transaction `Command`s 
(each resetting the MySQL channel), in both coordinators, in the short-circuit 
rewrite rule and in `FEOpExecutor`; and the Flight-only `returnResultFromLocal` 
flag was flipped from four places outside the adapter. This PR replaces every 
one of them with a capability or a lifecycle hook on the adapter, one method 
per use:
   
   ```
     +---------------------------+                             
+-----------------------------+
     |       MySQL client        |                             |   Arrow Flight 
SQL client   |
     +-------------+-------------+                             
+--------------+--------------+
                   |                                                          |
                   v                                                          v
     +---------------------------+                             
+-----------------------------+
     | MysqlServer               |                             | 
DorisFlightSqlProducer      |
     |   AcceptListener          |                             |   every call 
goes through   |
     |   ReadListener            |                             |   
adapter.runCommand()      |
     +-------------+-------------+                             |   
executeQueryStatement:    |
                   |                                           |   
adapter.beginRequest() NEW|
                   v                                           
+--------------+--------------+
     +---------------------------+                                            |
     |   MysqlConnectProcessor   |                                            v
     |   COM_FIELD_LIST          |                             
+-----------------------------+
     |   handleExecute:          |                             |  
FlightSqlConnectProcessor  |
     |     adapter.beforeStatement  NEW                        |                
             |
     |   finalizeCommand() =     |                             |                
             |
     |   adapter.finishCommand() |                             |                
             |
     +-------------+-------------+                             
+--------------+--------------+
                   |                                                          |
                   +----------------------------+-----------------------------+
                                                |
                                                v
     
+-------------------------------------------------------------------------------------------+
     | ConnectProcessor.executeQuery  --  parse, one StmtExecutor per 
statement, audit           |
     |     for each statement:  adapter.beforeStatement(ctx)                   
<-- NEW           |
     |                          executor.execute()                              
                 |
     |                          adapter.finishStatement(ctx, executor, i, n)    
                 |
     |   proxyExecute (master side): 
MysqlProtocolAdapter.restoreFromForwardRequest  <-- NEW     |
     
+---------------------------------------------+---------------------------------------------+
                                                   |
                                                   v
     
+-------------------------------------------------------------------------------------------+
     | StmtExecutor  --  plans and runs one statement, protocol-agnostic        
                  |
     |                                                                          
                 |
     |   forwarding a query to the master:   
adapter.canReplayForwardedQueryResult()   <-- NEW   |
     |   retry after a failed attempt:       adapter.canRetryQuery(ctx)         
       <-- NEW   |
     |   executeAndSendResult:               adapter.beforeQuery(ctx), then the 
coordinator;     |
     |                                       relay rows unless 
!ctx.isReturnResultFromLocal()    |
     |   FEOpExecutor.buildStmtForwardParams: adapter.fillForwardRequest(ctx, 
request) <-- NEW   |
     |                                                                          
                 |
     |   Commands (insert / txn):  no channel reset any more, beforeStatement 
did it             |
     |   Coordinator / NereidsCoordinator / QueryProcessor:  
ctx.isReturnResultFromLocal()       |
     |       decides receivers vs. Flight endpoints, no ConnectType assertion   
                 |
     |   LogicalResultSinkToShortCircuitPointQuery:  
adapter.supportsShortCircuitPointQuery() NEW|
     |   no ConnectType branch and no getMysqlChannel() left in qe/** and 
nereids/**             |
     
+---------------------------------------------+---------------------------------------------+
                                                   |
                                                   v
     
+-------------------------------------------------------------------------------------------+
     | ConnectContext  --  the session, one per connection                      
                 |
     |   protocolAdapter : ProtocolAdapter          getResultSender() = 
adapter.resultSender(this)|
     |   supportHandleByFe() = adapter.supportsFeSideResult() && command != 
COM_STMT_EXECUTE     |
     |   isReturnResultFromLocal() = adapter.returnsResultFromLocal(this)   
(setter is gone)     |
     
+---------------------------------------------+---------------------------------------------+
                                                   |
                                                   v
                          +-----------------------------------------------+
                          |  <<interface>>  qe.protocol.ProtocolAdapter   |
                          |   type() remoteHostPortString(ctx)            |
                          |   resultSinkType() resultSender(ctx)          |
                          |   connectPool(scheduler) closeConnection(ctx) |
                          |                                               |
                          |   capabilities:                               |
                          |     supportsSqlCacheReplay()                  |
                          |     canReplayForwardedQueryResult()  <-- NEW  |
                          |     supportsFeSideResult()           <-- NEW  |
                          |     supportsShortCircuitPointQuery() <-- NEW  |
                          |     canRetryQuery(ctx)               <-- NEW  |
                          |                                               |
                          |   statement lifecycle:                        |
                          |     beforeStatement(ctx)             <-- NEW  |
                          |     beforeQuery(ctx)                 <-- NEW  |
                          |     returnsResultFromLocal(ctx)      <-- NEW  |
                          |     finishStatement(ctx, executor, i, n)      |
                          |     afterStatement(ctx)                       |
                          |                                               |
                          |   forwarding:                                 |
                          |     fillForwardRequest(ctx, request) <-- NEW  |
                          +-----------------------+-----------------------+
                                                  |
                             +--------------------+----------------------------+
                             |                                                 |
     +-----------------------+----------------------+  
+-----------------------+----------------------+
     | mysql.protocol.MysqlProtocolAdapter          |  | 
arrowflight.protocol.FlightProtocolAdapter   |
     |   capabilities: all true;                    |  |   capabilities: all 
false (the reasons are   |
     |     canRetryQuery = nothing flushed yet      |  |     documented on each 
method)               |
     |   beforeStatement: channel.reset()  -- drops |  |   beforeStatement: the 
statement's result is |
     |     what the previous statement of the       |  |     on this frontend 
until beforeQuery says  |
     |     request left unsent                      |  |     a backend produces 
it; the coordinator   |
     |   beforeQuery: nothing, rows are relayed     |  |     then registers the 
endpoints             |
     |   returnsResultFromLocal: true               |  |   
returnsResultFromLocal: that flag          |
     |   fillForwardRequest: capability flags,      |  |   fillForwardRequest: 
nothing                 |
     |     deprecate-EOF, execute packet + cursor   |  |   beginRequest(): 
deferred executors, result |
     |   restoreFromForwardRequest (master side)    |  |     cache, endpoints, 
flag  (Flight-private) |
     |   proxyResultPackets() (master side)         |  |                        
                      |
     +-----------------------+----------------------+  
+-----------------------+----------------------+
                             |                                                 |
                             v                                                 v
     +----------------------------------------------+  
+----------------------------------------------+
     | <<interface>> qe.protocol.ResultSender        |  |                       
                       |
     |   sendResultSet(rs, fieldInfos, binaryRows)   |  |                       
                       |
     |   sendFields(names, fieldInfos, types)        |  |                       
                       |
     |   sendRow(wireRow)                            |  |                       
                       |
     |   reset()  -- now only for a retried query    |  |                       
                       |
     +----------------------------------------------+  
+----------------------------------------------+
     | mysql.protocol.MysqlResultSender (unchanged) |  | 
arrowflight.protocol.FlightResultSender      |
     |                                              |  |   sendResultSet no 
longer touches the        |
     |                                              |  |   result-location flag 
(EXPLAIN never marks  |
     |                                              |  |   the result as coming 
from a backend now)   |
     +----------------------------------------------+  
+----------------------------------------------+
   ```
   
   **Capabilities.** `canReplayForwardedQueryResult()` guards the refusal to 
forward a query to the master on a Flight session (#67569; the message is 
unchanged). `supportsFeSideResult()` is the protocol half of 
`ConnectContext.supportHandleByFe()`; `supportsShortCircuitPointQuery()` is the 
protocol half of 
`LogicalResultSinkToShortCircuitPointQuery.scanMatchShortCircuitCondition` 
(#67368); `canRetryQuery(ctx)` is the retry condition of 
`handleQueryWithRetry`: for MySQL "nothing was flushed to the socket yet", for 
Flight false (the endpoints a failed attempt registered would have to be 
withdrawn first; nothing does that, as before). The reason each Flight answer 
is what it is moves onto the Flight implementation, out of the call sites.
   
   **Statement lifecycle.** `beforeStatement(ctx)` is called by 
`ConnectProcessor.executeQuery` before each statement (and by 
`MysqlConnectProcessor.handleExecute`): the MySQL adapter resets the channel 
there, which is what the query path and the nine `Command`s did each on their 
own; the Flight adapter puts the statement's result on this frontend. 
`beforeQuery(ctx)` is called at the top of `executeAndSendResult`, before a 
coordinator is built: the Flight adapter marks the result as staying on the 
backends, and `Coordinator` / `NereidsCoordinator` / `QueryProcessor` keep 
reading `ctx.isReturnResultFromLocal()` to register endpoints instead of 
receivers (the `checkState(ARROW_FLIGHT_SQL)` assertions go, the decision 
itself does not move). Because `beforeQuery` runs only where a coordinator 
follows, an `EXPLAIN` -- handled earlier in `handleQueryStmt` -- never marks 
its result as coming from a backend, and the `setReturnResultFromLocal(true)` 
that `FlightResultSender.sendResultSet` had
  to do in #67883 is gone with the setter. The four flips in `StmtExecutor` / 
`FlightSqlConnectProcessor` are gone; what a Flight request drops from its 
predecessor (deferred executors, result cache, endpoints, the flag) is 
`FlightProtocolAdapter.beginRequest()`.
   
   **Forwarding.** `fillForwardRequest(ctx, request)` adds to a 
`TMasterOpRequest` what the master needs to know about the client: the MySQL 
adapter writes the negotiated capability flags, `CLIENT_DEPRECATE_EOF` and, for 
a `COM_STMT_EXECUTE`, the execute packet and the cursor flag (formerly two 
blocks in `FEOpExecutor.buildStmtForwardParams`, one of them behind `if 
(MYSQL)`); the Flight adapter writes nothing, its session consumes the master's 
status and rows rather than its packets (`carryForwardedOutcome`). The master's 
side, `ConnectProcessor.restoreForwardedMysqlContext`, becomes 
`MysqlProtocolAdapter.restoreFromForwardRequest`, and `proxyExecute` reads the 
proxy channel's packets through `MysqlProtocolAdapter.proxyResultPackets()` 
instead of `StmtExecutor.getProxyQueryResultBufList()` casting the channel.
   
   After this PR `grep -rn 'ConnectType\.\|getMysqlChannel()' 
fe-core/src/main/java/org/apache/doris/{qe,nereids}` outside `*/protocol/` 
finds only the `ConnectContext.getMysqlChannel()` delegate itself and two lines 
of `MysqlConnectProcessor` (reading the client's packet, the auth-switch 
handshake), which is MySQL protocol code by definition.
   
   **One behavior change, on the MySQL side, recorded in the golden.** The 
channel used to be reset at the start of a *query* and inside the insert / 
transaction commands, and nowhere else. A client that did not negotiate 
`CLIENT_MULTI_STATEMENTS` gets no intermediate response between the statements 
of a request, so whatever a query wrote stayed in the send buffer until the 
next query or insert reset it. When the next statement was neither -- `select 
1; set @a = 1` -- the buffered result set of the `SELECT` went out together 
with the `OK` of the `SET`: a result set terminated by a `0x00` OK packet, 
which no MySQL client parses (it reads the OK as a row and waits for more). 
With the reset at the start of every statement, such a request delivers only 
its last statement's outcome, which is what 
`MysqlProtocolAdapter.finishStatement` has documented as the intent all along. 
The first commit makes `RecordingMysqlChannel` model the send buffer (a reset 
drops what was written after the last 
 flush, the way `MysqlChannel.reset()` clears it) and records `select 1; set @a 
= 1` with and without the capability as it is today; the second commit's golden 
diff is exactly that: the three packets of `select 1` disappear from 
`multi-statement-without-capability-query-then-set`, the `OK` keeps its 
sequence id 4. The existing `multi-statement-without-capability` case (`select 
1; select 2`) loses the three packets of `select 1` in the first commit only, 
because the recording channel now shows what reaches the client -- its sequence 
ids, 4 to 7, already were the ones on the wire. That the delivered response 
does not start at sequence id 1 is pre-existing and not touched here: 
`MysqlChannel.reset()` clears the buffer but does not rewind the sequence id, 
so a client that checks sequence ids (pymysql, libmysqlclient; Connector/J does 
not by default) already fails `select 1; select 2` without the capability with 
"Packet sequence number wrong - got 5 expected 1", before and after this PR. 
 That deserves its own small fix.
   
   Not in this PR: an internal adapter for the no-client context (it is still a 
MySQL context over a `DummyMysqlChannel`, now with nothing in the execution 
layer keyed on that), and the stage 1 performance baseline.
   
   ### Release note
   
   None
   
   ### Check List (For Author)
   
   - Test <!-- At least one of them must be included. -->
       - [x] Regression test
       - [x] Unit Test
       - [ ] Manual test (add detailed scripts or steps below)
       - [ ] No need to test or manual test. Explain why:
   
       `FlightResultGoldenTest` (9 statements) is byte-identical. 
`MysqlPacketGoldenTest` gains two cases in the first commit and changes in 
exactly one of them in the second, as explained above. New cases in 
`MysqlProtocolAdapterTest` (capabilities, `beforeStatement` / `canRetryQuery` 
against the recording channel, `fillForwardRequest` -> 
`restoreFromForwardRequest` round trip) and `FlightProtocolAdapterTest` 
(capabilities, `fillForwardRequest` writes nothing, the result-location 
lifecycle across `beforeStatement` / `beforeQuery` / `sendResultSet` / 
`beginRequest`); `FEOpExecutorMysqlProtocolTest` and 
`DorisFlightSqlProducerTest` follow the moved methods. Local regression (single 
FE built from this branch, BE from `f35dd8285aa`): `arrow_flight_sql_p0` 8/8, 
`prepared_stmt_p0` 6/6, `load_p0/mysql_load` 7/7, `point_query_p0` 16/16, 
`query_p0/cache` 12/12, `query_p0/explain` 9/9, `query_p0/dry_run` 1/1, 
`query_p0/system` 13/14, `insert_overwrite_p0` 13/13, `insert_p0/transaction` 
16/16,
  `insert_p0/test_jdbc`, 
`unique_with_mow_p0/partial_update/test_partial_update_multi_stmt`, 
`mtmv_p0/ivm/test_ivm_refresh_dry_run`. The one red, `test_query_sys_tables`, 
is `catalog_meta_cache_statistics` failing on the `MAX_WEIGHT` column #67726 
added to the FE side, which the local BE predates -- a version skew of the test 
setup, not of this PR.
   
   - Behavior changed:
       - [ ] No.
       - [x] Yes. <!-- Explain the behavior change -->
   
       A multi-statement request from a MySQL client without 
`CLIENT_MULTI_STATEMENTS` whose last statement is not a query (`select 1; set 
@a = 1`) now returns only the last statement's response; it used to return the 
buffered result set of the query followed by that response, a stream no client 
can parse.
   
   - Does this need documentation?
       - [x] No.
       - [ ] Yes. <!-- Add document PR link here. eg: 
https://github.com/apache/doris-website/pull/1214 -->
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   
   https://claude.ai/code/session_01WXWFq9NqEnNjsywm5xnmu4
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to