Gabriel39 commented on code in PR #68631: URL: https://github.com/apache/doris/pull/68631#discussion_r4134432920
########## fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/FlightSqlQuerySchema.java: ########## @@ -0,0 +1,364 @@ +// 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.service.arrowflight; + +import org.apache.doris.analysis.StatementBase; +import org.apache.doris.catalog.AggStateType; +import org.apache.doris.catalog.ArrayType; +import org.apache.doris.catalog.Column; +import org.apache.doris.catalog.MapType; +import org.apache.doris.catalog.PrimitiveType; +import org.apache.doris.catalog.ScalarType; +import org.apache.doris.catalog.StructField; +import org.apache.doris.catalog.StructType; +import org.apache.doris.catalog.Type; +import org.apache.doris.datasource.CatalogIf; +import org.apache.doris.datasource.es.EsExternalCatalog; +import org.apache.doris.datasource.lance.LanceExternalCatalog; +import org.apache.doris.mysql.MysqlCommand; +import org.apache.doris.mysql.privilege.PrivPredicate; +import org.apache.doris.nereids.CascadesContext; +import org.apache.doris.nereids.StatementContext; +import org.apache.doris.nereids.glue.LogicalPlanAdapter; +import org.apache.doris.nereids.parser.NereidsParser; +import org.apache.doris.nereids.parser.SqlDialectHelper; +import org.apache.doris.nereids.rules.rewrite.CheckPrivileges; +import org.apache.doris.nereids.trees.expressions.Slot; +import org.apache.doris.nereids.trees.plans.Plan; +import org.apache.doris.nereids.trees.plans.PrepareCommandPlanner; +import org.apache.doris.nereids.trees.plans.commands.AlterTableCommand; +import org.apache.doris.nereids.trees.plans.commands.Command; +import org.apache.doris.nereids.trees.plans.commands.DeleteFromCommand; +import org.apache.doris.nereids.trees.plans.commands.DescribeCommand; +import org.apache.doris.nereids.trees.plans.commands.ExplainCommand; +import org.apache.doris.nereids.trees.plans.commands.KillCommand; +import org.apache.doris.nereids.trees.plans.commands.ReplayCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowCreateTableCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowDataCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowPartitionsCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowProcCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowPythonPackagesCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowQueryStatsCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowTableCommand; +import org.apache.doris.nereids.trees.plans.commands.TransactionCommand; +import org.apache.doris.nereids.trees.plans.commands.UpdateCommand; +import org.apache.doris.nereids.trees.plans.commands.info.CreateIndexOp; +import org.apache.doris.nereids.trees.plans.commands.info.DropIndexOp; +import org.apache.doris.nereids.trees.plans.commands.insert.BatchInsertIntoTableCommand; +import org.apache.doris.nereids.trees.plans.commands.insert.InsertIntoTVFCommand; +import org.apache.doris.nereids.trees.plans.commands.insert.InsertIntoTableCommand; +import org.apache.doris.nereids.trees.plans.commands.insert.InsertOverwriteTableCommand; +import org.apache.doris.nereids.trees.plans.commands.merge.MergeIntoCommand; +import org.apache.doris.nereids.trees.plans.commands.use.SwitchCommand; +import org.apache.doris.nereids.trees.plans.commands.use.UseCommand; +import org.apache.doris.qe.ConnectContext; +import org.apache.doris.qe.QueryState; +import org.apache.doris.qe.ResultSetMetaData; +import org.apache.doris.qe.SessionVariable; +import org.apache.doris.qe.ShowResultSetMetaData; +import org.apache.doris.qe.StmtExecutor; +import org.apache.doris.qe.VariableMgr; + +import org.apache.arrow.flight.CallStatus; +import org.apache.arrow.util.AutoCloseables; +import org.apache.arrow.vector.types.pojo.ArrowType; +import org.apache.arrow.vector.types.pojo.Field; +import org.apache.arrow.vector.types.pojo.FieldType; +import org.apache.arrow.vector.types.pojo.Schema; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Resolves result metadata without scheduling fragments or evaluating query expressions. */ +final class FlightSqlQuerySchema { + private FlightSqlQuerySchema() { + } + + static Schema analyze(ConnectContext context, String query) throws Exception { + synchronized (context) { + ConnectContext previousThreadContext = ConnectContext.get(); + StatementContext previousStatement = context.getStatementContext(); + SessionVariable previousSession = context.getSessionVariable(); + QueryState previousState = context.getState(); + StmtExecutor previousExecutor = context.getExecutor(); + String previousCatalog = context.getDefaultCatalog(); + String previousDatabase = context.getDatabase(); + List<StatementBase> statements = Collections.emptyList(); + try { + context.setThreadLocalInfo(); + context.setCommand(MysqlCommand.COM_QUERY); + // Parsing SET_VAR hints already mutates session variables. Isolate them even when parsing fails. + context.setSessionVariable(VariableMgr.cloneSessionVariable(previousSession)); + context.setState(new QueryState()); + context.setExecutor(null); + context.setStatementContext(null); + // Match execution's HTTP/plugin conversion before the dialect parser sees the SQL. + String converted = SqlDialectHelper.convertSqlByDialect(query, context.getSessionVariable()); + try { + statements = new NereidsParser().parseSQL(converted, context.getSessionVariable()); + } catch (Exception convertedError) { + if (!context.getSessionVariable().isRetryOriginSqlOnConvertFail() || converted.equals(query)) { + throw convertedError; + } + // Match execution's parse fallback while discarding any failed parser context. + StatementContext failed = context.getStatementContext(); + if (failed != null) { + failed.close(); + context.setStatementContext(null); + } + statements = new NereidsParser().parseSQL(query, context.getSessionVariable()); + } + Map<String, String> scopedDatabases = new HashMap<>(); + if (statements.isEmpty()) { + throw CallStatus.UNIMPLEMENTED.withDescription( + "Schema discovery requires a statement").toRuntimeException(); + } + // JDBC clients commonly prefix their query with USE. Resolve that namespace only within this scope. + for (int i = 0; i < statements.size() - 1; ++i) { + Plan prefix = ((LogicalPlanAdapter) statements.get(i)).getLogicalPlan(); + if (!(prefix instanceof UseCommand) && !(prefix instanceof SwitchCommand)) { + throw CallStatus.UNIMPLEMENTED.withDescription( + "Schema discovery only supports USE or SWITCH before the result statement") + .toRuntimeException(); + } + resolveNamespace(context, prefix, scopedDatabases); + } + LogicalPlanAdapter statement = (LogicalPlanAdapter) statements.get(statements.size() - 1); + StatementContext statementContext = statement.getStatementContext(); + context.setStatementContext(statementContext); + statementContext.setParsedStatement(statement); + if (!statementContext.getPlaceholders().isEmpty()) { + throw CallStatus.UNIMPLEMENTED.withDescription( + "Flight SQL parameter binding is not supported").toRuntimeException(); + } + List<Field> fields = new ArrayList<>(); + Plan plan = statement.getLogicalPlan(); + if (plan instanceof Command) { + resolveNamespace(context, plan, scopedDatabases); + ResultSetMetaData metadata = commandMetadata(context, (Command) plan); + if (metadata == null) { + throw CallStatus.UNIMPLEMENTED.withDescription("Command result metadata is unavailable") + .toRuntimeException(); + } + // FE-local result sets are serialized as nullable strings by FlightSqlChannel. + for (Column column : metadata.getColumns()) { + fields.add(Field.nullable(column.getName(), new ArrowType.Utf8())); + } + if (fields.isEmpty()) { + switch (((Command) plan).stmtType()) { + case SET: + case USE: + case SWITCH: + case CREATE: + case ALTER: + case DROP: + case TRUNCATE: + // Only known no-row command categories have the protocol OK schema. + fields.add(Field.nullable("StatusResult", new ArrowType.Utf8())); + break; + default: + throw CallStatus.UNIMPLEMENTED.withDescription( Review Comment: Fixed in a781cf92ecf. HELP resolves its topic, keyword, or category header from the local help module without reading topic text or producing rows. Unit tests cover all result shapes and compare headers with execution; the Flight RPC regression also checks Prepare/GetSchema/GetFlightInfo/DoGet parity for HELP. ########## fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/DorisFlightSqlProducer.java: ########## @@ -318,18 +327,63 @@ public FlightInfo getFlightInfoStatement(final CommandStatementQuery request, fi @Override public FlightInfo getFlightInfoPreparedStatement(final CommandPreparedStatementQuery command, final CallContext context, final FlightDescriptor descriptor) { - String[] handleParts = command.getPreparedStatementHandle().toStringUtf8().split(":"); - String executedPeerIdentity = handleParts[0]; - String preparedStatementId = handleParts[1]; - ConnectContext connectContext = flightSessionsManager.getConnectContext(executedPeerIdentity); - return executeQueryStatement(executedPeerIdentity, connectContext, - connectContext.getPreparedQuery(preparedStatementId), descriptor); + ConnectContext connection = flightSessionsManager.getConnectContext(context.peerIdentity()); + synchronized (connection) { + return executeQueryStatement(context.peerIdentity(), connection, + preparedQuery(connection, context, command).getLeft(), descriptor); Review Comment: Fixed in a781cf92ecf. After execution replans, compare the actual FlightInfo schema with the prepared schema before returning any ticket. On mismatch, remove the handle, cancel the query, finalize its deferred executors, and clear FE results and endpoints. The new test simulates execution returning a different schema after successful reanalysis and verifies cancellation, cleanup, handle expiry, and no double-finalization for both BE and FE result paths. ########## fe/fe-core/src/main/java/org/apache/doris/qe/ConnectContext.java: ########## @@ -907,15 +924,34 @@ public void resetLoginTime() { this.loginTime = System.currentTimeMillis(); } - public void addPreparedQuery(String preparedStatementId, String preparedQuery) { - preparedQuerys.put(preparedStatementId, preparedQuery); + public synchronized void addPreparedQuery(String preparedStatementId, String preparedQuery) { + addPreparedQuery(preparedStatementId, preparedQuery, null); + } + + public synchronized void addPreparedQuery(String preparedStatementId, String preparedQuery, Schema schema) { + preparedQuerys.put(preparedStatementId, + new PreparedQuery(preparedQuery, getDefaultCatalog(), getDatabase(), schema)); } - public String getPreparedQuery(String preparedStatementId) { - return preparedQuerys.get(preparedStatementId); + public synchronized Schema getPreparedQuerySchema(String preparedStatementId) { + PreparedQuery query = preparedQuerys.get(preparedStatementId); + return query == null ? null : query.schema; + } + + public synchronized String getPreparedQuery(String preparedStatementId) { + PreparedQuery query = preparedQuerys.get(preparedStatementId); + if (query == null) { + return null; + } + // A handle must not execute unqualified SQL in a different namespace than its advertised schema. + if (!Objects.equals(query.catalog, getDefaultCatalog()) || !Objects.equals(query.database, getDatabase())) { Review Comment: Fixed in a781cf92ecf. After successful execution with the advertised schema, rebind this handle to the resulting namespace under the same session lock. A prepared USE or USE-prefixed query can execute repeatedly; unrelated handles still expire on a namespace change. Added FE tests and a real Flight RPC regression executing prepared USE and USE-prefixed SHOW twice, including GetSchema and DoGet after execution. ########## fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/FlightSqlQuerySchema.java: ########## @@ -0,0 +1,364 @@ +// 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.service.arrowflight; + +import org.apache.doris.analysis.StatementBase; +import org.apache.doris.catalog.AggStateType; +import org.apache.doris.catalog.ArrayType; +import org.apache.doris.catalog.Column; +import org.apache.doris.catalog.MapType; +import org.apache.doris.catalog.PrimitiveType; +import org.apache.doris.catalog.ScalarType; +import org.apache.doris.catalog.StructField; +import org.apache.doris.catalog.StructType; +import org.apache.doris.catalog.Type; +import org.apache.doris.datasource.CatalogIf; +import org.apache.doris.datasource.es.EsExternalCatalog; +import org.apache.doris.datasource.lance.LanceExternalCatalog; +import org.apache.doris.mysql.MysqlCommand; +import org.apache.doris.mysql.privilege.PrivPredicate; +import org.apache.doris.nereids.CascadesContext; +import org.apache.doris.nereids.StatementContext; +import org.apache.doris.nereids.glue.LogicalPlanAdapter; +import org.apache.doris.nereids.parser.NereidsParser; +import org.apache.doris.nereids.parser.SqlDialectHelper; +import org.apache.doris.nereids.rules.rewrite.CheckPrivileges; +import org.apache.doris.nereids.trees.expressions.Slot; +import org.apache.doris.nereids.trees.plans.Plan; +import org.apache.doris.nereids.trees.plans.PrepareCommandPlanner; +import org.apache.doris.nereids.trees.plans.commands.AlterTableCommand; +import org.apache.doris.nereids.trees.plans.commands.Command; +import org.apache.doris.nereids.trees.plans.commands.DeleteFromCommand; +import org.apache.doris.nereids.trees.plans.commands.DescribeCommand; +import org.apache.doris.nereids.trees.plans.commands.ExplainCommand; +import org.apache.doris.nereids.trees.plans.commands.KillCommand; +import org.apache.doris.nereids.trees.plans.commands.ReplayCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowCreateTableCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowDataCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowPartitionsCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowProcCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowPythonPackagesCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowQueryStatsCommand; +import org.apache.doris.nereids.trees.plans.commands.ShowTableCommand; +import org.apache.doris.nereids.trees.plans.commands.TransactionCommand; +import org.apache.doris.nereids.trees.plans.commands.UpdateCommand; +import org.apache.doris.nereids.trees.plans.commands.info.CreateIndexOp; +import org.apache.doris.nereids.trees.plans.commands.info.DropIndexOp; +import org.apache.doris.nereids.trees.plans.commands.insert.BatchInsertIntoTableCommand; +import org.apache.doris.nereids.trees.plans.commands.insert.InsertIntoTVFCommand; +import org.apache.doris.nereids.trees.plans.commands.insert.InsertIntoTableCommand; +import org.apache.doris.nereids.trees.plans.commands.insert.InsertOverwriteTableCommand; +import org.apache.doris.nereids.trees.plans.commands.merge.MergeIntoCommand; +import org.apache.doris.nereids.trees.plans.commands.use.SwitchCommand; +import org.apache.doris.nereids.trees.plans.commands.use.UseCommand; +import org.apache.doris.qe.ConnectContext; +import org.apache.doris.qe.QueryState; +import org.apache.doris.qe.ResultSetMetaData; +import org.apache.doris.qe.SessionVariable; +import org.apache.doris.qe.ShowResultSetMetaData; +import org.apache.doris.qe.StmtExecutor; +import org.apache.doris.qe.VariableMgr; + +import org.apache.arrow.flight.CallStatus; +import org.apache.arrow.util.AutoCloseables; +import org.apache.arrow.vector.types.pojo.ArrowType; +import org.apache.arrow.vector.types.pojo.Field; +import org.apache.arrow.vector.types.pojo.FieldType; +import org.apache.arrow.vector.types.pojo.Schema; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Resolves result metadata without scheduling fragments or evaluating query expressions. */ +final class FlightSqlQuerySchema { + private FlightSqlQuerySchema() { + } + + static Schema analyze(ConnectContext context, String query) throws Exception { + synchronized (context) { + ConnectContext previousThreadContext = ConnectContext.get(); + StatementContext previousStatement = context.getStatementContext(); + SessionVariable previousSession = context.getSessionVariable(); + QueryState previousState = context.getState(); + StmtExecutor previousExecutor = context.getExecutor(); + String previousCatalog = context.getDefaultCatalog(); + String previousDatabase = context.getDatabase(); + List<StatementBase> statements = Collections.emptyList(); + try { + context.setThreadLocalInfo(); + context.setCommand(MysqlCommand.COM_QUERY); + // Parsing SET_VAR hints already mutates session variables. Isolate them even when parsing fails. + context.setSessionVariable(VariableMgr.cloneSessionVariable(previousSession)); + context.setState(new QueryState()); + context.setExecutor(null); + context.setStatementContext(null); + // Match execution's HTTP/plugin conversion before the dialect parser sees the SQL. + String converted = SqlDialectHelper.convertSqlByDialect(query, context.getSessionVariable()); + try { + statements = new NereidsParser().parseSQL(converted, context.getSessionVariable()); + } catch (Exception convertedError) { + if (!context.getSessionVariable().isRetryOriginSqlOnConvertFail() || converted.equals(query)) { + throw convertedError; + } + // Match execution's parse fallback while discarding any failed parser context. + StatementContext failed = context.getStatementContext(); + if (failed != null) { + failed.close(); + context.setStatementContext(null); + } + statements = new NereidsParser().parseSQL(query, context.getSessionVariable()); + } + Map<String, String> scopedDatabases = new HashMap<>(); + if (statements.isEmpty()) { + throw CallStatus.UNIMPLEMENTED.withDescription( + "Schema discovery requires a statement").toRuntimeException(); + } + // JDBC clients commonly prefix their query with USE. Resolve that namespace only within this scope. + for (int i = 0; i < statements.size() - 1; ++i) { + Plan prefix = ((LogicalPlanAdapter) statements.get(i)).getLogicalPlan(); + if (!(prefix instanceof UseCommand) && !(prefix instanceof SwitchCommand)) { + throw CallStatus.UNIMPLEMENTED.withDescription( + "Schema discovery only supports USE or SWITCH before the result statement") + .toRuntimeException(); + } + resolveNamespace(context, prefix, scopedDatabases); + } + LogicalPlanAdapter statement = (LogicalPlanAdapter) statements.get(statements.size() - 1); + StatementContext statementContext = statement.getStatementContext(); + context.setStatementContext(statementContext); + statementContext.setParsedStatement(statement); + if (!statementContext.getPlaceholders().isEmpty()) { + throw CallStatus.UNIMPLEMENTED.withDescription( + "Flight SQL parameter binding is not supported").toRuntimeException(); + } + List<Field> fields = new ArrayList<>(); + Plan plan = statement.getLogicalPlan(); + if (plan instanceof Command) { + resolveNamespace(context, plan, scopedDatabases); + ResultSetMetaData metadata = commandMetadata(context, (Command) plan); + if (metadata == null) { + throw CallStatus.UNIMPLEMENTED.withDescription("Command result metadata is unavailable") + .toRuntimeException(); + } + // FE-local result sets are serialized as nullable strings by FlightSqlChannel. + for (Column column : metadata.getColumns()) { + fields.add(Field.nullable(column.getName(), new ArrowType.Utf8())); + } + if (fields.isEmpty()) { + switch (((Command) plan).stmtType()) { + case SET: + case USE: + case SWITCH: + case CREATE: + case ALTER: + case DROP: + case TRUNCATE: + // Only known no-row command categories have the protocol OK schema. + fields.add(Field.nullable("StatusResult", new ArrowType.Utf8())); + break; + default: + throw CallStatus.UNIMPLEMENTED.withDescription( + "Result metadata is unavailable without executing this command") + .toRuntimeException(); + } + } + } else { + PrepareCommandPlanner planner = new PrepareCommandPlanner(statementContext); + planner.plan(statement, context.getSessionVariable().toThrift()); + CascadesContext cascades = planner.getCascadesContext(); + Plan analyzed = cascades.getRewritePlan(); + // PrepareCommandPlanner stops before the rewrite phase that normally checks privileges. + new CheckPrivileges().rewriteRoot(analyzed, cascades.getCurrentJobContext()); + for (Slot slot : analyzed.getOutput()) { + fields.add(field(slot.getName(), slot.getDataType().toCatalogDataType(), slot.nullable(), + true, context.getSessionVariable().getTimeZone())); + } + } + return new Schema(fields); + } finally { + try { + List<AutoCloseable> resources = new ArrayList<>(); + for (StatementBase statement : statements) { + if (statement instanceof LogicalPlanAdapter) { + resources.add(((LogicalPlanAdapter) statement).getStatementContext()); + } + } + // A parser failure can leave a context that was never added to the returned list. + StatementContext current = context.getStatementContext(); + if (current != null && current != previousStatement && !resources.contains(current)) { + resources.add(current); + } + AutoCloseables.close(resources); + } finally { + try { + context.setStatementContext(previousStatement); + context.setSessionVariable(previousSession); + context.setState(previousState); + context.setExecutor(previousExecutor); + if (!previousCatalog.equals(context.getDefaultCatalog()) + || !previousDatabase.equals(context.getDatabase())) { + context.changeDefaultCatalog(previousCatalog); + context.setDatabase(previousDatabase); + } + } finally { + context.setCommand(MysqlCommand.COM_SLEEP); + if (previousThreadContext == null) { + ConnectContext.remove(); + } else { + previousThreadContext.setThreadLocalInfo(); + } + } + } + } + } + } + + private static ResultSetMetaData commandMetadata(ConnectContext context, Command command) throws Exception { + // These getters depend on execution-time state or remote responses. Do not advertise a + // guessed schema, or run the command merely to discover it. + if (command instanceof ShowPythonPackagesCommand || command instanceof DescribeCommand + || command instanceof ShowDataCommand || command instanceof ShowPartitionsCommand + || command instanceof ShowQueryStatsCommand) { + throw CallStatus.UNIMPLEMENTED.withDescription("Command schema requires execution-time metadata") + .toRuntimeException(); + } + if (command instanceof ShowTableCommand) { + ((ShowTableCommand) command).validate(context); + } else if (command instanceof ShowCreateTableCommand) { + return ((ShowCreateTableCommand) command).getMetaData(context); + } else if (command instanceof ShowProcCommand) { + return ((ShowProcCommand) command).getMetaData(context); + } + if (command instanceof ExplainCommand) { + // PLAN PROCESS has no Flight serialization path in StmtExecutor. + if (((ExplainCommand) command).showPlanProcess()) { + throw CallStatus.UNIMPLEMENTED.withDescription("EXPLAIN PLAN PROCESS is not supported over Flight SQL") + .toRuntimeException(); + } + return stringMetadata("Explain String(Nereids Planner)"); + } else if (command instanceof ReplayCommand) { + return stringMetadata("Plan Replayer dump url"); + } else if (command instanceof AlterTableCommand) { + AlterTableCommand alter = (AlterTableCommand) command; + String catalog = alter.getTbl().getCtl(); + // Lance index admission returns a JobId header even for an IF no-op. Do not run + // validation/admission here: those paths can resolve remote tables or allocate IDs. + if (context.getCatalog(catalog == null ? context.getDefaultCatalog() : catalog) + instanceof LanceExternalCatalog && alter.getNereidsOps().stream().anyMatch(op -> + (op instanceof CreateIndexOp && !((CreateIndexOp) op).isAlter()) + || (op instanceof DropIndexOp && !((DropIndexOp) op).isAlter()))) { + return stringMetadata("JobId"); + } + } + ResultSetMetaData metadata = command.getResultSetMetaData(); Review Comment: Fixed in a781cf92ecf. SHOW SNAPSHOT now validates its WHERE predicates before selecting the three-column summary or five-column detail header; the execution path reuses this helper. No repository lookup or snapshot fetch occurs during discovery. Unit tests cover summary/detail headers, invalid timestamp-only filters, and absence of repository access. The Flight RPC regression reproduced the old three-column detail header and passes with the fix. -- 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]
