github-actions[bot] commented on code in PR #67966:
URL: https://github.com/apache/doris/pull/67966#discussion_r4013282726


##########
fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java:
##########
@@ -1076,7 +1121,13 @@ public void finalizeQuery() {
         // received after unregisterQuery(), causing the instance profile to 
be lost, so we should wait
         // for the profile before unregisterQuery().
         updateProfile(true);
-        QeProcessorImpl.INSTANCE.unregisterQuery(context.queryId());
+        QeProcessorImpl.INSTANCE.unregisterQuery(queryId());
+    }
+
+    // The id this query runs under: the context's, until the query is 
deferred for Arrow Flight and
+    // the context may move on to another statement before the query is 
finalized.
+    private TUniqueId queryId() {

Review Comment:
   [P1] Freeze every delayed executor's query ID
   
   This fallback to `context.queryId()` is stale for non-deferred remote 
executors too. A Flight request containing two backend-result statements 
appends both executors to `returnResultFromRemoteExecutor` and finalizes them 
only after the shared context holds the second statement's ID; finalizing the 
first then unregisters the second ID, and subsequent calls repeat it, leaving 
the first coordinator-map entry, per-user instance count, profile completion, 
and query-finish callbacks behind. Capture each executor's 
execution/registration ID and use it for every delayed finalization, not only 
batch-split deferral, and add a two-remote-statement test that proves both IDs 
are removed.



##########
fe/fe-core/src/main/java/org/apache/doris/arrowflight/FlightSessionOptions.java:
##########
@@ -0,0 +1,312 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.arrowflight;
+
+import org.apache.doris.analysis.SetType;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.util.Util;
+import org.apache.doris.nereids.util.SqlLiteralUtils;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.QueryState.MysqlStateType;
+import org.apache.doris.qe.SqlModeHelper;
+import org.apache.doris.qe.VarAttrDef;
+import org.apache.doris.qe.VariableMgr;
+import org.apache.doris.qe.VariableMgr.VarContext;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.arrow.flight.SessionOptionValue;
+import org.apache.arrow.flight.SessionOptionValueFactory;
+import org.apache.arrow.flight.SessionOptionValueVisitor;
+import org.apache.arrow.flight.SetSessionOptionsResult;
+import org.apache.arrow.flight.SetSessionOptionsResult.ErrorValue;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The session options of an Arrow Flight SQL session, as the 
SetSessionOptions and GetSessionOptions
+ * actions see them.
+ *
+ * <p>An option stands for the statement of the session that sets it: {@code 
catalog} is the current
+ * catalog ({@code SWITCH}), {@code schema} the current database ({@code 
USE}), and any other name is
+ * the session variable {@code SHOW VARIABLES} lists under that name ({@code 
SET SESSION}). Those two
+ * names are what the ADBC Flight SQL driver puts on the wire for {@code 
adbc.connection.catalog} and
+ * {@code adbc.connection.db_schema}, and what the Flight SQL JDBC driver 
sends for its
+ * {@code catalog} property; every other option name the drivers pass through 
as given. Setting an
+ * option runs its statement as a command of the session, so it is checked, 
audited and takes effect
+ * exactly as if the client had sent the statement. Reading the options back 
gives what
+ * {@code SHOW VARIABLES} shows, every value as a string: the one 
representation {@code SET} accepts
+ * back, whatever the Java type of the variable behind it. The names an option 
goes by are exactly
+ * the names that are read back, spelled the same: a client looks an option up 
in the result of
+ * GetSessionOptions by the name it set it under. And what is read back can be 
set back: the empty
+ * value, Flight's way of unsetting an option, puts a variable back to its 
default and the session
+ * back into no database, as it started -- the state GetSessionOptions reports 
as an empty
+ * {@code schema}, which sets it too. A session is always in some catalog, so 
{@code catalog} has no
+ * empty value.
+ *
+ * <p>The result of setting an option is one of the three {@link ErrorValue}s 
per name and nothing
+ * else, so the reason a value was refused only reaches the frontend log.
+ */
+public final class FlightSessionOptions {
+    private static final Logger LOG = 
LogManager.getLogger(FlightSessionOptions.class);
+
+    /** The current catalog; set with {@code SWITCH}. */
+    public static final String CATALOG = "catalog";
+    /** The current database; set with {@code USE}. */
+    public static final String SCHEMA = "schema";
+
+    private FlightSessionOptions() {
+    }
+
+    /**
+     * Sets the options of one request, each on its own, and returns the error 
of every option that
+     * could not be set; an option absent from the result was set. {@code 
catalog} goes first and
+     * {@code schema} second, since {@code USE} names a database of the 
current catalog; the others
+     * follow in name order, so that a request applies the same way every 
time. Runs as a command of
+     * the session, under its command lock.
+     */
+    public static Map<String, SetSessionOptionsResult.Error> 
set(ConnectContext ctx,
+            Map<String, SessionOptionValue> options) {
+        List<String> names = new ArrayList<>(options.keySet());
+        Collections.sort(names);
+        names.remove(SCHEMA);
+        names.remove(CATALOG);
+        if (options.containsKey(SCHEMA)) {
+            names.add(0, SCHEMA);
+        }
+        if (options.containsKey(CATALOG)) {
+            names.add(0, CATALOG);
+        }
+        Map<String, SetSessionOptionsResult.Error> errors = new 
LinkedHashMap<>();
+        for (String name : names) {
+            ErrorValue error = setOne(ctx, name, options.get(name));
+            if (error != null) {
+                errors.put(name, new SetSessionOptionsResult.Error(error));
+            }
+        }
+        return errors;
+    }
+
+    /** Sets one option and returns why it could not be, or null when it was 
set. */
+    @VisibleForTesting
+    static ErrorValue setOne(ConnectContext ctx, String name, 
SessionOptionValue value) {
+        if (CATALOG.equals(name)) {
+            return switchCatalog(ctx, value);
+        }
+        if (SCHEMA.equals(name)) {
+            return useDatabase(ctx, value);
+        }
+        return setVariable(ctx, name, value);
+    }
+
+    /**
+     * The options of the session: the current catalog, the current database 
(an empty string when
+     * the session is in none, the value that puts it back there) and every 
session variable
+     * {@code SHOW VARIABLES} would list, in its text.
+     */
+    public static Map<String, SessionOptionValue> get(ConnectContext ctx) {
+        Map<String, SessionOptionValue> options = new LinkedHashMap<>();
+        options.put(CATALOG, 
SessionOptionValueFactory.makeSessionOptionValue(ctx.getDefaultCatalog()));
+        String database = ctx.getDatabase();
+        options.put(SCHEMA, 
SessionOptionValueFactory.makeSessionOptionValue(database == null ? "" : 
database));
+        for (List<String> row : VariableMgr.dump(SetType.SESSION, 
ctx.getSessionVariable(), null)) {
+            // A row is name, value, default value, changed.
+            options.put(row.get(0), 
SessionOptionValueFactory.makeSessionOptionValue(row.get(1)));

Review Comment:
   [P2] Preserve session-option scalar types in GET
   
   `get()` wraps every `SHOW VARIABLES` value with the string factory, even 
though this implementation accepts Flight's native integer, double, and boolean 
variants in SET. The ADBC Flight SQL driver sends those variants from its typed 
setters and requires the same exact variants in its typed getters, so 
`SetOptionInt(...query_timeout, 77)` succeeds here but `GetOptionInt` receives 
`"77"` and returns `StatusNotFound`; the boolean and double paths fail 
likewise. Please return a stable native Flight type for typed variables (or 
otherwise preserve a consistent SET/GET type contract) and cover the round trip 
through the ADBC typed getters rather than only a raw Flight client that 
expects strings.



##########
fe/fe-core/src/main/java/org/apache/doris/arrowflight/protocol/FlightProtocolAdapter.java:
##########
@@ -58,6 +61,25 @@
  * arrive on its own thread, and nothing in the transport serializes them. 
{@link #runCommand}
  * does, so that a session's {@link ConnectContext}, which is not thread-safe, 
is only ever used
  * by one command at a time.
+ *
+ * <p>Nor does a query end where the frontend's command does. The client pulls 
the results from the
+ * backends by itself (DoGet), and the frontend hears nothing of it -- not 
when it starts, not when
+ * it is done -- so a query may still be running on the backends while the 
session runs its next
+ * command here. Two things of a request therefore outlive the command that 
made them, and the
+ * contract for both is that the command stream owns them and nothing else 
does: the result a
+ * statement materialized on this frontend, cached on the channel until the 
client's DoGet takes it
+ * or the next request drops it; and the executors of queries whose 
coordinator must stay alive
+ * until the backends are done with it, the {@link #deferredExecutors}. A 
deferred executor is a
+ * closed object from the moment it is deferred: it carries what finalizing it 
needs and reads
+ * nothing of the session's live state afterwards, since the session moves on 
without it (a session
+ * option's SET, a metadata request, the next request) and since it may be 
finalized from a thread
+ * that runs no command of the session (the timeout checker, a token expiry). 
It is finalized
+ * exactly once, by whoever takes it out of the list under the list's lock -- 
the next request

Review Comment:
   [P1] Prevent teardown from racing deferred publication
   
   Teardown does not take `commandLock` and the adapter never enters a 
permanently closed state. If CloseSession/token expiry unregisters the context 
after a batch-split query sets `deferredForArrowFlight` but before 
`addDeferredExecutor`, teardown drains an empty list and removes the context, 
then the late add still succeeds; `FlightSqlConnectProcessor.close()` skips 
that executor, and no pool timeout, next request, or token owner remains to 
close its coordinator or unregister the query. This permanently retains the 
batch SplitSource and workload-group queue slot; teardown just after 
publication can also finalize the coordinator while the request is still 
fetching its Arrow schema. Please serialize permanent teardown with the command 
lifecycle or atomically tombstone the adapter so queued commands and late 
publications fail/finalize, and cover the boundary with latch-based tests.



##########
fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java:
##########
@@ -1076,7 +1121,13 @@ public void finalizeQuery() {
         // received after unregisterQuery(), causing the instance profile to 
be lost, so we should wait
         // for the profile before unregisterQuery().
         updateProfile(true);
-        QeProcessorImpl.INSTANCE.unregisterQuery(context.queryId());
+        QeProcessorImpl.INSTANCE.unregisterQuery(queryId());

Review Comment:
   [P1] Unregister failed replan attempts under their own IDs
   
   This still has no ID with which to clean up an earlier attempt of the same 
executor. On Flight, `beforeQuery` selects remote results before 
`registerQuery`; if `coordBase.exec()` then throws a cloud error recognized by 
`needRetryWithReplan`, `handleQueryWithRetry` rethrows and skips 
`finalizeQuery()` because result placement is remote. The outer `queryRetry()` 
replaces the context ID, while the failed attempt never reaches either 
delayed-executor list, so this eventual unregister can remove only the latest 
ID. The old Qe map entry, per-user instance count, profile completion, and 
query-finish callbacks remain and repeated recoverable churn can falsely 
exhaust `max_query_instances`. Unregister/finalize each registered failed 
attempt under its exact ID before replacing it, and inject a post-registration 
replan error in a Flight test.



##########
fe/fe-core/src/main/java/org/apache/doris/arrowflight/FlightSessionOptions.java:
##########
@@ -0,0 +1,312 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.arrowflight;
+
+import org.apache.doris.analysis.SetType;
+import org.apache.doris.common.AnalysisException;
+import org.apache.doris.common.ErrorCode;
+import org.apache.doris.common.util.Util;
+import org.apache.doris.nereids.util.SqlLiteralUtils;
+import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.QueryState.MysqlStateType;
+import org.apache.doris.qe.SqlModeHelper;
+import org.apache.doris.qe.VarAttrDef;
+import org.apache.doris.qe.VariableMgr;
+import org.apache.doris.qe.VariableMgr.VarContext;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.arrow.flight.SessionOptionValue;
+import org.apache.arrow.flight.SessionOptionValueFactory;
+import org.apache.arrow.flight.SessionOptionValueVisitor;
+import org.apache.arrow.flight.SetSessionOptionsResult;
+import org.apache.arrow.flight.SetSessionOptionsResult.ErrorValue;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The session options of an Arrow Flight SQL session, as the 
SetSessionOptions and GetSessionOptions
+ * actions see them.
+ *
+ * <p>An option stands for the statement of the session that sets it: {@code 
catalog} is the current
+ * catalog ({@code SWITCH}), {@code schema} the current database ({@code 
USE}), and any other name is
+ * the session variable {@code SHOW VARIABLES} lists under that name ({@code 
SET SESSION}). Those two
+ * names are what the ADBC Flight SQL driver puts on the wire for {@code 
adbc.connection.catalog} and
+ * {@code adbc.connection.db_schema}, and what the Flight SQL JDBC driver 
sends for its
+ * {@code catalog} property; every other option name the drivers pass through 
as given. Setting an
+ * option runs its statement as a command of the session, so it is checked, 
audited and takes effect
+ * exactly as if the client had sent the statement. Reading the options back 
gives what
+ * {@code SHOW VARIABLES} shows, every value as a string: the one 
representation {@code SET} accepts
+ * back, whatever the Java type of the variable behind it. The names an option 
goes by are exactly
+ * the names that are read back, spelled the same: a client looks an option up 
in the result of
+ * GetSessionOptions by the name it set it under. And what is read back can be 
set back: the empty
+ * value, Flight's way of unsetting an option, puts a variable back to its 
default and the session
+ * back into no database, as it started -- the state GetSessionOptions reports 
as an empty
+ * {@code schema}, which sets it too. A session is always in some catalog, so 
{@code catalog} has no
+ * empty value.
+ *
+ * <p>The result of setting an option is one of the three {@link ErrorValue}s 
per name and nothing
+ * else, so the reason a value was refused only reaches the frontend log.
+ */
+public final class FlightSessionOptions {
+    private static final Logger LOG = 
LogManager.getLogger(FlightSessionOptions.class);
+
+    /** The current catalog; set with {@code SWITCH}. */
+    public static final String CATALOG = "catalog";
+    /** The current database; set with {@code USE}. */
+    public static final String SCHEMA = "schema";
+
+    private FlightSessionOptions() {
+    }
+
+    /**
+     * Sets the options of one request, each on its own, and returns the error 
of every option that
+     * could not be set; an option absent from the result was set. {@code 
catalog} goes first and
+     * {@code schema} second, since {@code USE} names a database of the 
current catalog; the others
+     * follow in name order, so that a request applies the same way every 
time. Runs as a command of
+     * the session, under its command lock.
+     */
+    public static Map<String, SetSessionOptionsResult.Error> 
set(ConnectContext ctx,
+            Map<String, SessionOptionValue> options) {
+        List<String> names = new ArrayList<>(options.keySet());
+        Collections.sort(names);
+        names.remove(SCHEMA);
+        names.remove(CATALOG);
+        if (options.containsKey(SCHEMA)) {
+            names.add(0, SCHEMA);
+        }
+        if (options.containsKey(CATALOG)) {
+            names.add(0, CATALOG);
+        }
+        Map<String, SetSessionOptionsResult.Error> errors = new 
LinkedHashMap<>();
+        for (String name : names) {
+            ErrorValue error = setOne(ctx, name, options.get(name));
+            if (error != null) {
+                errors.put(name, new SetSessionOptionsResult.Error(error));
+            }
+        }
+        return errors;
+    }
+
+    /** Sets one option and returns why it could not be, or null when it was 
set. */
+    @VisibleForTesting
+    static ErrorValue setOne(ConnectContext ctx, String name, 
SessionOptionValue value) {
+        if (CATALOG.equals(name)) {
+            return switchCatalog(ctx, value);
+        }
+        if (SCHEMA.equals(name)) {
+            return useDatabase(ctx, value);
+        }
+        return setVariable(ctx, name, value);
+    }
+
+    /**
+     * The options of the session: the current catalog, the current database 
(an empty string when
+     * the session is in none, the value that puts it back there) and every 
session variable
+     * {@code SHOW VARIABLES} would list, in its text.
+     */
+    public static Map<String, SessionOptionValue> get(ConnectContext ctx) {
+        Map<String, SessionOptionValue> options = new LinkedHashMap<>();
+        options.put(CATALOG, 
SessionOptionValueFactory.makeSessionOptionValue(ctx.getDefaultCatalog()));
+        String database = ctx.getDatabase();
+        options.put(SCHEMA, 
SessionOptionValueFactory.makeSessionOptionValue(database == null ? "" : 
database));
+        for (List<String> row : VariableMgr.dump(SetType.SESSION, 
ctx.getSessionVariable(), null)) {
+            // A row is name, value, default value, changed.
+            options.put(row.get(0), 
SessionOptionValueFactory.makeSessionOptionValue(row.get(1)));
+        }
+        return options;
+    }
+
+    private static ErrorValue switchCatalog(ConnectContext ctx, 
SessionOptionValue value) {
+        String catalog = value.acceptVisitor(STRING_VALUE);
+        // A session is always in some catalog, so there is no catalog the 
empty value could stand for.
+        if (catalog == null || catalog.isEmpty()) {
+            return ErrorValue.INVALID_VALUE;
+        }
+        // SWITCH checks the name's format before anything else and its error 
code would not reach
+        // here (see runStatement); the check is one of the value alone, so it 
is made here first.
+        try {
+            Util.checkCatalogAllRules(catalog);
+        } catch (AnalysisException e) {
+            LOG.warn("session option {} of Arrow Flight SQL connection {} 
could not be set, catalog name {} "
+                    + "is malformed: {}", CATALOG, ctx.getConnectionId(), 
catalog, e.getMessage());
+            return ErrorValue.INVALID_VALUE;
+        }
+        return runStatement(ctx, CATALOG, "SWITCH " + 
quoteIdentifier(catalog), ErrorCode.ERR_UNKNOWN_CATALOG);
+    }
+
+    private static ErrorValue useDatabase(ConnectContext ctx, 
SessionOptionValue value) {
+        String database = value.acceptVisitor(STRING_VALUE);
+        if (database == null) {
+            return ErrorValue.INVALID_VALUE;
+        }
+        // No database: the state the session started in, which 
GetSessionOptions reports as the
+        // empty string and the empty value asks for back (Flight's way of 
unsetting an option; what
+        // the ADBC driver sends to erase one). No statement leads there -- 
there is no USE of
+        // nothing -- so it is not one the session runs, and there is nothing 
in it to check.
+        if (database.isEmpty()) {
+            ctx.clearDatabase();

Review Comment:
   [P2] Reject schema clears inside active transactions
   
   This direct mutation bypasses the command eligibility checks used by every 
non-empty schema/catalog/variable option. A Flight client can run `USE db; 
BEGIN`, clear `schema` here, and receive success even though `StmtExecutor` 
rejects `USE`, `SWITCH`, and `SET` while `isTxnModel()` is true; subsequent 
unqualified DML loses its database, and the client cannot restore it because a 
non-empty schema correctly goes through the rejected `USE` path until commit or 
rollback. Please make schema erasure share the ordinary state-changing-command 
fence and return an option error without mutation when it is prohibited, with a 
transaction-mode action test for both the empty value and empty string.



##########
fe/fe-core/src/main/java/org/apache/doris/arrowflight/protocol/FlightProtocolAdapter.java:
##########
@@ -356,49 +380,88 @@ public void addDeferredExecutor(StmtExecutor executor) {
         }
     }
 
+    /**
+     * Takes every deferred executor out of the list and finalizes it: the 
next request of the
+     * session ({@link #beginRequest}), which stands in for the end of the 
previous query's DoGet, and
+     * the session's teardown.
+     */
     public void closeDeferredExecutors() {
-        List<StmtExecutor> toClose;
+        List<StmtExecutor> taken;
         synchronized (deferredExecutors) {
             if (deferredExecutors.isEmpty()) {
                 return;
             }
-            toClose = new ArrayList<>(deferredExecutors);
+            taken = new ArrayList<>(deferredExecutors);
             deferredExecutors.clear();
         }
-        for (StmtExecutor deferredExecutor : toClose) {
-            try {
-                deferredExecutor.finalizeArrowFlightQuery();
-            } catch (Throwable t) {
-                LOG.warn("failed to finalize deferred arrow flight executor", 
t);
-            }
-        }
+        finalizeDeferredExecutors(taken);
     }
 
     /**
-     * How long, in seconds, a sleeping connection may keep its deferred 
executors before the
-     * timeout checker finalizes them without killing the connection
-     * (Config.arrow_flight_deferred_query_idle_timeout_second). A Flight 
client that opens a
-     * session per query and never closes it would otherwise pin each deferred 
query's query queue
-     * slot and query registration until wait_timeout (8h by default). The 
bound is never shorter
-     * than the execution timeout the deferred query was run with: the client 
may still be pulling
-     * that query's results from the BE, which still needs the batch split 
source the coordinator
-     * holds. Returns -1 when the bound is disabled or nothing is deferred.
+     * Takes out of the list the deferred executors whose own bound has passed 
by {@code now} and
+     * returns them for the caller to finalize: the timeout checker's half of 
the exactly-once rule
+     * in the class comment. Each executor is judged and removed in the same 
critical section, by
+     * its own deadline -- when its query started plus {@link 
#deferredBoundMs} -- so that of the
+     * executors of one multi-statement request, each with a start and an 
execution timeout of its

Review Comment:
   [P1] Enforce one result for backend-result statements
   
   This treats multiple remote executors in one request as a valid lifecycle 
state, but `finishStatement()` enforces the documented 
one-result/last-statement rule only with `FlightSqlChannel.resultNum()`, which 
never sees results left on backends. Thus `SELECT ...; SELECT ...` appends both 
queries' endpoints and, when their schemas match, the producer advertises them 
as partitions of one Flight result so clients concatenate unrelated rows. 
`SELECT ...; SET ...` instead returns the synthesized SET status and silently 
drops the SELECT endpoints. Track backend-result production as well as 
FE-channel results and reject any result-producing statement that is not the 
sole final result; cover equal/unequal-schema SELECT pairs and SELECT followed 
by SET.



##########
fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java:
##########
@@ -1297,8 +1366,22 @@ private void forwardToMaster() throws Exception {
         }
     }
 
+    /**
+     * Whether this statement's profile is reported, decided the first time it 
is asked and the
+     * same ever after (see {@link #profileEnabled}): the update that 
publishes the profile as
+     * RUNNING and the one that finishes it must agree, whatever the session 
variable says by then.
+     */
+    private boolean reportsProfile() {
+        Boolean decided = profileEnabled;
+        if (decided == null) {
+            decided = context.getSessionVariable().enableProfile() && 
isProfileSafeStmt();

Review Comment:
   [P2] Enable `Profile` from the same effective decision
   
   In a multi-statement Flight request, a later `SELECT /*+ 
SET_VAR(enable_profile=true) */ ...` is parsed up front, but the first 
executor's finally block restores the shared session value before the later 
executor is constructed. That executor therefore creates `Profile(false)`, 
whose `isQueryFinished` starts true; analysis then reapplies its SET_VAR and 
this line freezes `reportsProfile()` as true, yet `Profile.updateSummary()` 
immediately returns for both the RUNNING and final updates. The effectively 
enabled query never appears in profiles. Please construct/enable `Profile` from 
the same per-statement effective decision (or apply the hint before 
construction), and cover actual multi-statement parse/revert/reapply ordering 
in the profile matrix.



-- 
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