github-actions[bot] commented on code in PR #67966:
URL: https://github.com/apache/doris/pull/67966#discussion_r4011202246
##########
fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java:
##########
@@ -1034,15 +1046,28 @@ public long getFlightSqlDeferredExecutorsIdleTimeoutS()
{
? ((FlightProtocolAdapter)
protocolAdapter).getDeferredExecutorsIdleTimeoutS() : -1;
}
+ // When the oldest deferred query started; -1 when the connection has
nothing deferred.
+ public long getFlightSqlDeferredExecutorsStartTimeMs() {
+ return protocolAdapter instanceof FlightProtocolAdapter
+ ? ((FlightProtocolAdapter)
protocolAdapter).getDeferredExecutorsStartTimeMs() : -1;
+ }
+
// Called by the timeout checker for a sleeping connection that is not
past wait_timeout yet.
- private void reapIdleFlightSqlDeferredExecutors(long idleMs) {
+ // The bound counts from when the deferred query started, not from
startTime: the session's
+ // later commands (a session option, a metadata request) move startTime on
without finishing
+ // that query, and must not keep its coordinator alive either.
+ private void reapIdleFlightSqlDeferredExecutors(long now) {
long timeoutS = getFlightSqlDeferredExecutorsIdleTimeoutS();
- if (timeoutS < 0 || idleMs <= timeoutS * 1000L) {
+ if (timeoutS < 0) {
+ return;
+ }
+ long deferredMs = now - getFlightSqlDeferredExecutorsStartTimeMs();
Review Comment:
[P1] Reap each executor against its own frozen deadline
This combines `min(start_i)` with `max(timeout_i)` and then closes the whole
current list. A multi-statement Flight request can retain multiple
remote-result executors, while each `StmtExecutor.executeByNereids()` resets
the start time and each statement can freeze a different `SET_VAR` timeout, so
this aggregate can close a later executor before its own deadline. The three
operations are also separate snapshots: the checker can read old A's timeout,
race with `beginRequest()` clearing A, see start `-1`, and then close newly
added B using that stale decision. Please atomically select/remove exact
expired executors, pairing each start with its own `max(config, execution
timeout)`, then finalize that snapshot outside the lock; cover both unequal
per-statement lifetimes and the checker-versus-`beginRequest` interleaving.
##########
fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java:
##########
@@ -1088,13 +1104,27 @@ public int getDeferredExecTimeoutS() {
return deferredExecTimeoutS;
}
+ // The query id the deferred query runs under; null when the query is not
deferred.
+ public TUniqueId getDeferredQueryId() {
+ return deferredQueryId;
+ }
+
+ // When the deferred query started, in epoch milliseconds; -1 when the
query is not deferred.
+ public long getDeferredStartTimeMs() {
+ return deferredStartTimeMs;
+ }
+
// Keep this query's coordinator alive past GetFlightInfo (see the gate in
executeAndSendResult)
// and hand it to the ConnectContext, which finalizes it later. Records
the execution timeout in
// effect right now: it floors the idle reaper's bound and must be the
value the query actually
- // ran with, not the session value left behind after SET_VAR hints are
reverted.
+ // ran with, not the session value left behind after SET_VAR hints are
reverted. Records the
+ // query id and the start time for the same reason: a statement the
session runs in the meantime
+ // replaces both on the context, and the query is finalized under its own.
void deferForArrowFlight() {
deferredForArrowFlight = true;
deferredExecTimeoutS = context.getExecTimeoutS();
+ deferredQueryId = context.queryId();
Review Comment:
[P2] Freeze the deferred query's profiling state too
Only the ID and start time are frozen here. A profiled query has already
published `updateProfile(false)` when it is deferred, but a later
`SetSessionOptions` request deliberately retains this executor and may set
`enable_profile=false` on the same context. `finalizeQuery()` then calls
`updateProfile(true)`, which rechecks that new value and returns, leaving the
old profile unfinished. With profiling still enabled, it also rebuilds the
final summary from later mutable catalog/database/backend-selection and related
state. Please snapshot the deferred query's profiling decision and query-scoped
summary inputs, or retain the running summary and update only terminal fields,
and test finalization after intervening option changes.
##########
fe/fe-core/src/main/java/org/apache/doris/arrowflight/FlightSessionOptions.java:
##########
@@ -0,0 +1,295 @@
+// 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.
+ *
+ * <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
+ * none has been chosen) 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);
+ 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 || database.isEmpty()) {
Review Comment:
[P2] Let clients clear the selected schema
After selecting a database, this API cannot restore Doris's valid initial
no-database state: Flight's valueless option is the conventional unset request,
and `GetSessionOptions` itself represents that state as `schema = ""`, but both
forms are rejected here. This also breaks the generic ADBC option-erase path
and prevents a GET value from being set back. Please handle schema clear by
restoring `currentDb` to the supported empty state, and cover both a valueless
erase and the empty-string round trip.
--
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]