diff --git a/fe/fe-core/src/main/java/com/starrocks/catalog/Function.java b/fe/fe-core/src/main/java/com/starrocks/catalog/Function.java index 903746df60a3b..0db93c43fb48c 100644 --- a/fe/fe-core/src/main/java/com/starrocks/catalog/Function.java +++ b/fe/fe-core/src/main/java/com/starrocks/catalog/Function.java @@ -116,6 +116,13 @@ public enum CompareMode { private boolean isNullable = true; +<<<<<<< HEAD +======= + private Vector> defaultArgExprs; + + private boolean isMetaFunction = false; + +>>>>>>> d42320f9a2 ([Feature] Add function get_query_dump (#48105)) // Only used for serialization protected Function() { } @@ -180,6 +187,7 @@ public Function(Function other) { isPolymorphic = other.isPolymorphic; couldApplyDictOptimize = other.couldApplyDictOptimize; isNullable = other.isNullable; + isMetaFunction = other.isMetaFunction; } public FunctionName getFunctionName() { @@ -261,6 +269,14 @@ public boolean isPolymorphic() { return isPolymorphic; } + public boolean isMetaFunction() { + return isMetaFunction; + } + + public void setMetaFunction(boolean metaFunction) { + isMetaFunction = metaFunction; + } + public long getFunctionId() { return functionId; } diff --git a/fe/fe-core/src/main/java/com/starrocks/http/rest/QueryDumpAction.java b/fe/fe-core/src/main/java/com/starrocks/http/rest/QueryDumpAction.java index 05a7907dbc081..999675ebbf9c1 100644 --- a/fe/fe-core/src/main/java/com/starrocks/http/rest/QueryDumpAction.java +++ b/fe/fe-core/src/main/java/com/starrocks/http/rest/QueryDumpAction.java @@ -3,25 +3,15 @@ package com.starrocks.http.rest; import com.google.common.base.Strings; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.starrocks.catalog.Database; import com.starrocks.catalog.InternalCatalog; import com.starrocks.common.DdlException; +import com.starrocks.common.Pair; import com.starrocks.http.ActionController; import com.starrocks.http.BaseRequest; import com.starrocks.http.BaseResponse; import com.starrocks.http.IllegalArgException; -import com.starrocks.persist.gson.GsonUtils; import com.starrocks.qe.ConnectContext; -import com.starrocks.qe.StmtExecutor; -import com.starrocks.server.GlobalStateMgr; -import com.starrocks.sql.ast.StatementBase; -import com.starrocks.sql.optimizer.dump.DumpInfo; -import com.starrocks.sql.optimizer.dump.QueryDumpDeserializer; -import com.starrocks.sql.optimizer.dump.QueryDumpInfo; -import com.starrocks.sql.optimizer.dump.QueryDumpSerializer; -import com.starrocks.sql.parser.SqlParser; +import com.starrocks.sql.optimizer.dump.QueryDumper; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpResponseStatus; import org.apache.logging.log4j.LogManager; @@ -37,7 +27,10 @@ public class QueryDumpAction extends RestBaseAction { private static final Logger LOG = LogManager.getLogger(QueryDumpAction.class); + + public static final String URL = "/api/query_dump"; private static final String DB = "db"; +<<<<<<< HEAD private static final Gson GSON = new GsonBuilder() .addSerializationExclusionStrategy(new GsonUtils.HiddenAnnotationExclusionStrategy()) .addDeserializationExclusionStrategy(new GsonUtils.HiddenAnnotationExclusionStrategy()) @@ -46,13 +39,16 @@ public class QueryDumpAction extends RestBaseAction { .registerTypeAdapter(QueryDumpInfo.class, new QueryDumpSerializer()) .registerTypeAdapter(QueryDumpInfo.class, new QueryDumpDeserializer()) .create(); +======= + private static final String MOCK = "mock"; +>>>>>>> d42320f9a2 ([Feature] Add function get_query_dump (#48105)) public QueryDumpAction(ActionController controller) { super(controller); } public static void registerAction(ActionController controller) throws IllegalArgException { - controller.registerHandler(HttpMethod.POST, "/api/query_dump", new QueryDumpAction(controller)); + controller.registerHandler(HttpMethod.POST, URL, new QueryDumpAction(controller)); } @Override @@ -60,32 +56,22 @@ public void executeWithoutPassword(BaseRequest request, BaseResponse response) t ConnectContext context = ConnectContext.get(); String catalogDbName = request.getSingleParameter(DB); + String catalogName = ""; + String dbName = ""; if (!Strings.isNullOrEmpty(catalogDbName)) { String[] catalogDbNames = catalogDbName.split("\\."); - String catalogName = InternalCatalog.DEFAULT_INTERNAL_CATALOG_NAME; + catalogName = InternalCatalog.DEFAULT_INTERNAL_CATALOG_NAME; if (catalogDbNames.length == 2) { catalogName = catalogDbNames[0]; } - String dbName = catalogDbNames[catalogDbNames.length - 1]; - context.setCurrentCatalog(catalogName); - Database db = GlobalStateMgr.getCurrentState().getMetadataMgr().getDb(catalogName, dbName); - if (db == null) { - response.getContent().append("Database [" + dbName + "] does not exists"); - sendResult(request, response, HttpResponseStatus.NOT_FOUND); - return; - } - context.setDatabase(db.getFullName()); + dbName = catalogDbNames[catalogDbNames.length - 1]; } context.setIsHTTPQueryDump(true); String query = request.getContent(); - if (Strings.isNullOrEmpty(query)) { - response.getContent().append("not valid parameter"); - sendResult(request, response, HttpResponseStatus.BAD_REQUEST); - return; - } +<<<<<<< HEAD StatementBase parsedStmt; try { parsedStmt = SqlParser.parse(query, context.getSessionVariable()).get(0); @@ -106,5 +92,11 @@ public void executeWithoutPassword(BaseRequest request, BaseResponse response) t response.getContent().append("not use cbo planner, try again."); sendResult(request, response, HttpResponseStatus.BAD_REQUEST); } +======= + Pair statusAndRes = QueryDumper.dumpQuery(catalogName, dbName, query, enableMock); + + response.getContent().append(statusAndRes.second); + sendResult(request, response, statusAndRes.first); +>>>>>>> d42320f9a2 ([Feature] Add function get_query_dump (#48105)) } } diff --git a/fe/fe-core/src/main/java/com/starrocks/sql/optimizer/dump/QueryDumper.java b/fe/fe-core/src/main/java/com/starrocks/sql/optimizer/dump/QueryDumper.java new file mode 100644 index 0000000000000..e504ca27451d0 --- /dev/null +++ b/fe/fe-core/src/main/java/com/starrocks/sql/optimizer/dump/QueryDumper.java @@ -0,0 +1,100 @@ +// Copyright 2021-present StarRocks, Inc. All rights reserved. +// +// Licensed 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 +// +// https://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 com.starrocks.sql.optimizer.dump; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.starrocks.catalog.Database; +import com.starrocks.common.Pair; +import com.starrocks.persist.gson.GsonUtils; +import com.starrocks.qe.ConnectContext; +import com.starrocks.qe.StmtExecutor; +import com.starrocks.server.GlobalStateMgr; +import com.starrocks.sql.ast.StatementBase; +import com.starrocks.sql.parser.SqlParser; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class QueryDumper { + private static final Logger LOG = LogManager.getLogger(QueryDumper.class); + + private static final Gson GSON = new GsonBuilder() + .addSerializationExclusionStrategy(new GsonUtils.HiddenAnnotationExclusionStrategy()) + .addDeserializationExclusionStrategy(new GsonUtils.HiddenAnnotationExclusionStrategy()) + .enableComplexMapKeySerialization() + .disableHtmlEscaping() + .registerTypeAdapter(QueryDumpInfo.class, new QueryDumpSerializer()) + .registerTypeAdapter(QueryDumpInfo.class, new QueryDumpDeserializer()) + .create(); + + public static Pair dumpQuery(String catalogName, String dbName, String query, + boolean enableMock) { + ConnectContext context = ConnectContext.get(); + if (context == null) { + return Pair.create(HttpResponseStatus.BAD_REQUEST, + "There is no ConnectContext for this thread: " + Thread.currentThread().getName()); + } + + final String prevCatalog = context.getCurrentCatalog(); + final String prevDb = context.getDatabase(); + final boolean prevIsHTTPQueryDump = context.isHTTPQueryDump(); + + try { + if (StringUtils.isEmpty(query)) { + return Pair.create(HttpResponseStatus.BAD_REQUEST, "query is empty"); + } + + if (!StringUtils.isEmpty(catalogName)) { + context.setCurrentCatalog(catalogName); + } + + if (!StringUtils.isEmpty(dbName)) { + Database db = GlobalStateMgr.getCurrentState().getMetadataMgr().getDb(catalogName, dbName); + if (db == null) { + return Pair.create(HttpResponseStatus.NOT_FOUND, + String.format("Database [%s.%s] does not exists", catalogName, dbName)); + } + context.setDatabase(db.getFullName()); + } + + context.setIsHTTPQueryDump(true); + + StatementBase parsedStmt; + try { + parsedStmt = SqlParser.parse(query, context.getSessionVariable()).get(0); + StmtExecutor executor = new StmtExecutor(context, parsedStmt); + executor.execute(); + } catch (Exception e) { + LOG.warn("execute query failed. ", e); + return Pair.create(HttpResponseStatus.BAD_REQUEST, "execute query failed. " + e.getMessage()); + } + + DumpInfo dumpInfo = context.getDumpInfo(); + if (dumpInfo != null) { + dumpInfo.setDesensitizedInfo(enableMock); + String dumpStr = GSON.toJson(dumpInfo, QueryDumpInfo.class); + return Pair.create(HttpResponseStatus.OK, dumpStr); + } else { + return Pair.create(HttpResponseStatus.BAD_REQUEST, "not use cbo planner, try again."); + } + } finally { + context.setCurrentCatalog(prevCatalog); + context.setDatabase(prevDb); + context.setIsHTTPQueryDump(prevIsHTTPQueryDump); + } + } +} diff --git a/fe/fe-core/src/main/java/com/starrocks/sql/optimizer/function/MetaFunctions.java b/fe/fe-core/src/main/java/com/starrocks/sql/optimizer/function/MetaFunctions.java new file mode 100644 index 0000000000000..512777d5b9c32 --- /dev/null +++ b/fe/fe-core/src/main/java/com/starrocks/sql/optimizer/function/MetaFunctions.java @@ -0,0 +1,367 @@ +// Copyright 2021-present StarRocks, Inc. All rights reserved. +// +// Licensed 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 +// +// https://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 com.starrocks.sql.optimizer.function; + +import com.google.gson.JsonArray; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.starrocks.analysis.TableName; +import com.starrocks.catalog.Database; +import com.starrocks.catalog.InternalCatalog; +import com.starrocks.catalog.MaterializedView; +import com.starrocks.catalog.MvId; +import com.starrocks.catalog.MvPlanContext; +import com.starrocks.catalog.Table; +import com.starrocks.common.ErrorCode; +import com.starrocks.common.ErrorReport; +import com.starrocks.common.util.concurrent.lock.LockType; +import com.starrocks.common.util.concurrent.lock.Locker; +import com.starrocks.connector.PartitionInfo; +import com.starrocks.connector.PartitionUtil; +import com.starrocks.connector.hive.Partition; +import com.starrocks.memory.MemoryTrackable; +import com.starrocks.memory.MemoryUsageTracker; +import com.starrocks.monitor.unit.ByteSizeValue; +import com.starrocks.privilege.AccessDeniedException; +import com.starrocks.privilege.ObjectType; +import com.starrocks.privilege.PrivilegeType; +import com.starrocks.qe.ConnectContext; +import com.starrocks.scheduler.TaskRunManager; +import com.starrocks.server.GlobalStateMgr; +import com.starrocks.sql.analyzer.Authorizer; +import com.starrocks.sql.optimizer.CachingMvPlanContextBuilder; +import com.starrocks.sql.optimizer.OptExpression; +import com.starrocks.sql.optimizer.dump.QueryDumper; +import com.starrocks.sql.optimizer.operator.scalar.ConstantOperator; +import com.starrocks.sql.optimizer.rewrite.ConstantFunction; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.collections4.SetUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.spark.util.SizeEstimator; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static com.starrocks.catalog.PrimitiveType.BOOLEAN; +import static com.starrocks.catalog.PrimitiveType.VARCHAR; + +/** + * Meta functions can be used to inspect the content of in-memory structures, for debug purpose. + */ +public class MetaFunctions { + + public static Table inspectExternalTable(TableName tableName) { + Table table = GlobalStateMgr.getCurrentState().getMetadataMgr().getTable(tableName) + .orElseThrow(() -> ErrorReport.buildSemanticException(ErrorCode.ERR_BAD_TABLE_ERROR, tableName)); + ConnectContext connectContext = ConnectContext.get(); + try { + Authorizer.checkAnyActionOnTable(connectContext.getCurrentUserIdentity(), + connectContext.getCurrentRoleIds(), + tableName); + } catch (AccessDeniedException e) { + AccessDeniedException.reportAccessDenied( + tableName.getCatalog(), + connectContext.getCurrentUserIdentity(), connectContext.getCurrentRoleIds(), + PrivilegeType.ANY.name(), ObjectType.TABLE.name(), tableName.getTbl()); + } + return table; + } + + public static Pair inspectTable(TableName tableName) { + Database db = GlobalStateMgr.getCurrentState().mayGetDb(tableName.getDb()) + .orElseThrow(() -> ErrorReport.buildSemanticException(ErrorCode.ERR_BAD_DB_ERROR, tableName.getDb())); + Table table = db.tryGetTable(tableName.getTbl()) + .orElseThrow(() -> ErrorReport.buildSemanticException(ErrorCode.ERR_BAD_TABLE_ERROR, tableName)); + ConnectContext connectContext = ConnectContext.get(); + try { + Authorizer.checkAnyActionOnTable( + connectContext.getCurrentUserIdentity(), + connectContext.getCurrentRoleIds(), + tableName); + } catch (AccessDeniedException e) { + AccessDeniedException.reportAccessDenied( + tableName.getCatalog(), + connectContext.getCurrentUserIdentity(), connectContext.getCurrentRoleIds(), + PrivilegeType.ANY.name(), ObjectType.TABLE.name(), tableName.getTbl()); + } + return Pair.of(db, table); + } + + private static void authOperatorPrivilege() { + ConnectContext connectContext = ConnectContext.get(); + try { + Authorizer.checkSystemAction( + connectContext.getCurrentUserIdentity(), + connectContext.getCurrentRoleIds(), + PrivilegeType.OPERATE); + } catch (AccessDeniedException e) { + AccessDeniedException.reportAccessDenied( + InternalCatalog.DEFAULT_INTERNAL_CATALOG_NAME, + connectContext.getCurrentUserIdentity(), connectContext.getCurrentRoleIds(), + PrivilegeType.OPERATE.name(), ObjectType.SYSTEM.name(), null); + } + } + + /** + * Return verbose metadata of a materialized-view + */ + @ConstantFunction(name = "inspect_mv_meta", argTypes = {VARCHAR}, returnType = VARCHAR, isMetaFunction = true) + public static ConstantOperator inspectMvMeta(ConstantOperator mvName) { + TableName tableName = TableName.fromString(mvName.getVarchar()); + Pair dbTable = inspectTable(tableName); + Table table = dbTable.getRight(); + if (!table.isMaterializedView()) { + ErrorReport.reportSemanticException(ErrorCode.ERR_INVALID_PARAMETER, + tableName + " is not materialized view"); + } + Locker locker = new Locker(); + try { + locker.lockDatabase(dbTable.getLeft(), LockType.READ); + MaterializedView mv = (MaterializedView) table; + String meta = mv.inspectMeta(); + return ConstantOperator.createVarchar(meta); + } finally { + locker.unLockDatabase(dbTable.getLeft(), LockType.READ); + } + } + + /** + * Return related materialized-views of a table, in JSON array format + */ + @ConstantFunction(name = "inspect_related_mv", argTypes = {VARCHAR}, returnType = VARCHAR, isMetaFunction = true) + public static ConstantOperator inspectRelatedMv(ConstantOperator name) { + TableName tableName = TableName.fromString(name.getVarchar()); + Optional mayDb; + Table table = inspectExternalTable(tableName); + if (table.isNativeTableOrMaterializedView()) { + mayDb = GlobalStateMgr.getCurrentState().mayGetDb(tableName.getDb()); + } else { + mayDb = Optional.empty(); + } + + Locker locker = new Locker(); + try { + mayDb.ifPresent(database -> locker.lockDatabase(database, LockType.READ)); + + Set relatedMvs = table.getRelatedMaterializedViews(); + JsonArray array = new JsonArray(); + for (MvId mv : SetUtils.emptyIfNull(relatedMvs)) { + String mvName = GlobalStateMgr.getCurrentState().mayGetTable(mv.getDbId(), mv.getId()) + .map(Table::getName) + .orElse(null); + JsonObject obj = new JsonObject(); + obj.add("id", new JsonPrimitive(mv.getId())); + obj.add("name", mvName != null ? new JsonPrimitive(mvName) : JsonNull.INSTANCE); + + array.add(obj); + } + + String json = array.toString(); + return ConstantOperator.createVarchar(json); + } finally { + mayDb.ifPresent(database -> locker.unLockDatabase(database, LockType.READ)); + } + } + + /** + * Return the content in ConnectorTblMetaInfoMgr, which contains mapping information from base table to mv + */ + @ConstantFunction(name = "inspect_mv_relationships", argTypes = {}, returnType = VARCHAR, isMetaFunction = true) + public static ConstantOperator inspectMvRelationships() { + ConnectContext context = ConnectContext.get(); + try { + Authorizer.checkSystemAction(context.getCurrentUserIdentity(), context.getCurrentRoleIds(), + PrivilegeType.OPERATE); + } catch (AccessDeniedException e) { + AccessDeniedException.reportAccessDenied( + "", context.getCurrentUserIdentity(), context.getCurrentRoleIds(), + PrivilegeType.OPERATE.name(), ObjectType.FUNCTION.name(), "inspect_mv_relationships"); + } + + String json = GlobalStateMgr.getCurrentState().getConnectorTblMetaInfoMgr().inspect(); + return ConstantOperator.createVarchar(json); + } + + /** + * Return Hive partition info + */ + @ConstantFunction(name = "inspect_hive_part_info", + argTypes = {VARCHAR}, + returnType = VARCHAR, + isMetaFunction = true) + public static ConstantOperator inspectHivePartInfo(ConstantOperator name) { + TableName tableName = TableName.fromString(name.getVarchar()); + Table table = inspectExternalTable(tableName); + + Map info = PartitionUtil.getPartitionNameWithPartitionInfo(table); + JsonObject obj = new JsonObject(); + for (Map.Entry entry : MapUtils.emptyIfNull(info).entrySet()) { + if (entry.getValue() instanceof Partition) { + Partition part = (Partition) entry.getValue(); + obj.add(entry.getKey(), part.toJson()); + } + } + String json = obj.toString(); + return ConstantOperator.createVarchar(json); + } + + /** + * Return meta data of all pipes in current database + */ + @ConstantFunction(name = "inspect_all_pipes", argTypes = {}, returnType = VARCHAR, isMetaFunction = true) + public static ConstantOperator inspectAllPipes() { + ConnectContext connectContext = ConnectContext.get(); + authOperatorPrivilege(); + String currentDb = connectContext.getDatabase(); + Database db = GlobalStateMgr.getCurrentState().mayGetDb(connectContext.getDatabase()) + .orElseThrow(() -> ErrorReport.buildSemanticException(ErrorCode.ERR_BAD_DB_ERROR, currentDb)); + String json = GlobalStateMgr.getCurrentState().getPipeManager().getPipesOfDb(db.getId()); + return ConstantOperator.createVarchar(json); + } + + /** + * Return all status about the TaskManager + */ + @ConstantFunction(name = "inspect_task_runs", argTypes = {}, returnType = VARCHAR, isMetaFunction = true) + public static ConstantOperator inspectTaskRuns() { + ConnectContext connectContext = ConnectContext.get(); + authOperatorPrivilege(); + TaskRunManager trm = GlobalStateMgr.getCurrentState().getTaskManager().getTaskRunManager(); + return ConstantOperator.createVarchar(trm.inspect()); + } + + @ConstantFunction(name = "inspect_memory", argTypes = {VARCHAR}, returnType = VARCHAR, isMetaFunction = true) + public static ConstantOperator inspectMemory(ConstantOperator moduleName) { + Map statMap = MemoryUsageTracker.REFERENCE.get(moduleName.getVarchar()); + if (statMap == null) { + ErrorReport.reportSemanticException(ErrorCode.ERR_INVALID_PARAMETER, + "Module " + moduleName + " not found."); + } + long estimateSize = 0; + for (Map.Entry statEntry : statMap.entrySet()) { + MemoryTrackable tracker = statEntry.getValue(); + estimateSize += tracker.estimateSize(); + } + + return ConstantOperator.createVarchar(new ByteSizeValue(estimateSize).toString()); + } + + @ConstantFunction(name = "inspect_memory_detail", argTypes = {VARCHAR, VARCHAR}, + returnType = VARCHAR, isMetaFunction = true) + public static ConstantOperator inspectMemoryDetail(ConstantOperator moduleName, ConstantOperator clazzInfo) { + Map statMap = MemoryUsageTracker.REFERENCE.get(moduleName.getVarchar()); + if (statMap == null) { + ErrorReport.reportSemanticException(ErrorCode.ERR_INVALID_PARAMETER, + "Module " + moduleName + " not found."); + } + String classInfo = clazzInfo.getVarchar(); + String clazzName; + String fieldName = null; + if (classInfo.contains(".")) { + clazzName = classInfo.split("\\.")[0]; + fieldName = classInfo.split("\\.")[1]; + } else { + clazzName = classInfo; + } + MemoryTrackable memoryTrackable = statMap.get(clazzName); + if (memoryTrackable == null) { + ErrorReport.reportSemanticException(ErrorCode.ERR_INVALID_PARAMETER, + "In module " + moduleName + " - " + clazzName + " not found."); + } + long estimateSize = 0; + if (fieldName == null) { + estimateSize = memoryTrackable.estimateSize(); + } else { + try { + Field field = memoryTrackable.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + Object object = field.get(memoryTrackable); + estimateSize = SizeEstimator.estimate(object); + } catch (NoSuchFieldException e) { + ErrorReport.reportSemanticException(ErrorCode.ERR_INVALID_PARAMETER, + "In module " + moduleName + " - " + clazzName + " field " + fieldName + " not found."); + } catch (IllegalAccessException e) { + ErrorReport.reportSemanticException(ErrorCode.ERR_INVALID_PARAMETER, + "Get module " + moduleName + " - " + clazzName + " field " + fieldName + " error."); + } + } + + return ConstantOperator.createVarchar(new ByteSizeValue(estimateSize).toString()); + } + + /** + * Return the logical plan of a materialized view with cache + */ + @ConstantFunction(name = "inspect_mv_plan", argTypes = {VARCHAR}, returnType = VARCHAR, isMetaFunction = true) + public static ConstantOperator inspectMvPlan(ConstantOperator mvName) { + return inspectMvPlan(mvName, ConstantOperator.TRUE); + } + + /** + * Return verbose metadata of a materialized view + */ + @ConstantFunction(name = "inspect_mv_plan", argTypes = {VARCHAR, BOOLEAN}, returnType = VARCHAR, isMetaFunction = true) + public static ConstantOperator inspectMvPlan(ConstantOperator mvName, ConstantOperator useCache) { + TableName tableName = TableName.fromString(mvName.getVarchar()); + Pair dbTable = inspectTable(tableName); + Table table = dbTable.getRight(); + if (!table.isMaterializedView()) { + ErrorReport.reportSemanticException(ErrorCode.ERR_INVALID_PARAMETER, + tableName + " is not materialized view"); + } + try { + MaterializedView mv = (MaterializedView) table; + String plans = ""; + List planContexts = + CachingMvPlanContextBuilder.getInstance().getPlanContext(mv, useCache.getBoolean()); + int size = planContexts.size(); + for (int i = 0; i < size; i++) { + MvPlanContext context = planContexts.get(i); + if (context != null) { + OptExpression plan = context.getLogicalPlan(); + String debugString = plan.debugString(); + plans += String.format("plan %d: \n%s\n", i, debugString); + } else { + plans += String.format("plan %d: null\n", i); + } + } + return ConstantOperator.createVarchar(plans); + } catch (Exception e) { + ErrorReport.report(ErrorCode.ERR_UNKNOWN_ERROR, e.getMessage()); + return ConstantOperator.createVarchar("failed"); + } + } + + @ConstantFunction(name = "get_query_dump", argTypes = {VARCHAR, BOOLEAN}, returnType = VARCHAR, isMetaFunction = true) + public static ConstantOperator getQueryDump(ConstantOperator query, ConstantOperator enableMock) { + com.starrocks.common.Pair statusAndRes = + QueryDumper.dumpQuery("", "", query.getVarchar(), enableMock.getBoolean()); + if (statusAndRes.first != HttpResponseStatus.OK) { + ErrorReport.reportSemanticException(ErrorCode.ERR_INVALID_PARAMETER, "get_query_dump: " + statusAndRes.second); + } + return ConstantOperator.createVarchar(statusAndRes.second); + } + + @ConstantFunction(name = "get_query_dump", argTypes = {VARCHAR}, returnType = VARCHAR, isMetaFunction = true) + public static ConstantOperator getQueryDump(ConstantOperator query) { + return getQueryDump(query, ConstantOperator.createBoolean(false)); + } + +} diff --git a/fe/fe-core/src/main/java/com/starrocks/sql/optimizer/rewrite/ScalarOperatorEvaluator.java b/fe/fe-core/src/main/java/com/starrocks/sql/optimizer/rewrite/ScalarOperatorEvaluator.java index c0ffe593c3c79..69c47bac96e91 100644 --- a/fe/fe-core/src/main/java/com/starrocks/sql/optimizer/rewrite/ScalarOperatorEvaluator.java +++ b/fe/fe-core/src/main/java/com/starrocks/sql/optimizer/rewrite/ScalarOperatorEvaluator.java @@ -94,7 +94,10 @@ public Function getMetaFunction(FunctionName name, Type[] args) { if (invoker == null || !invoker.isMetaFunction) { return null; } - return new Function(name, Lists.newArrayList(args), Type.VARCHAR, false); + + Function function = new Function(name, Lists.newArrayList(args), Type.VARCHAR, false); + function.setMetaFunction(true); + return function; } /** diff --git a/fe/fe-core/src/main/java/com/starrocks/sql/optimizer/rewrite/scalar/FoldConstantsRule.java b/fe/fe-core/src/main/java/com/starrocks/sql/optimizer/rewrite/scalar/FoldConstantsRule.java index 1536b70b7ea04..df0d450d6abdf 100644 --- a/fe/fe-core/src/main/java/com/starrocks/sql/optimizer/rewrite/scalar/FoldConstantsRule.java +++ b/fe/fe-core/src/main/java/com/starrocks/sql/optimizer/rewrite/scalar/FoldConstantsRule.java @@ -1,8 +1,25 @@ +<<<<<<< HEAD // This file is licensed under the Elastic License 2.0. Copyright 2021-present, StarRocks Inc. +======= +// Copyright 2021-present StarRocks, Inc. All rights reserved. +// +// Licensed 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 +// +// https://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. +>>>>>>> d42320f9a2 ([Feature] Add function get_query_dump (#48105)) package com.starrocks.sql.optimizer.rewrite.scalar; import com.starrocks.catalog.Type; +import com.starrocks.sql.analyzer.SemanticException; import com.starrocks.sql.optimizer.operator.scalar.BinaryPredicateOperator; import com.starrocks.sql.optimizer.operator.scalar.CallOperator; import com.starrocks.sql.optimizer.operator.scalar.CastOperator; @@ -23,10 +40,24 @@ public class FoldConstantsRule extends BottomUpScalarOperatorRewriteRule { @Override public ScalarOperator visitCall(CallOperator call, ScalarOperatorRewriteContext context) { - if (call.isAggregate() || notAllConstant(call.getChildren())) { + if (call.isAggregate()) { return call; } +<<<<<<< HEAD return ScalarOperatorEvaluator.INSTANCE.evaluation(call); +======= + + if (notAllConstant(call.getChildren())) { + if (call.getFunction() != null && call.getFunction().isMetaFunction()) { + String errMsg = String.format("Meta function %s does not support non-constant arguments", + call.getFunction().getFunctionName()); + throw new SemanticException(errMsg); + } + return call; + } + + return ScalarOperatorEvaluator.INSTANCE.evaluation(call, needMonotonicFunc); +>>>>>>> d42320f9a2 ([Feature] Add function get_query_dump (#48105)) } @Override diff --git a/fe/fe-core/src/test/java/com/starrocks/http/QueryDumpActionTest.java b/fe/fe-core/src/test/java/com/starrocks/http/QueryDumpActionTest.java new file mode 100644 index 0000000000000..fa11f8c4d0617 --- /dev/null +++ b/fe/fe-core/src/test/java/com/starrocks/http/QueryDumpActionTest.java @@ -0,0 +1,136 @@ +// Copyright 2021-present StarRocks, Inc. All rights reserved. +// +// Licensed 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 +// +// https://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 com.starrocks.http; + +import com.starrocks.http.rest.QueryDumpAction; +import com.starrocks.metric.MetricRepo; +import com.starrocks.service.ExecuteEnv; +import io.netty.handler.codec.http.HttpResponseStatus; +import okhttp3.HttpUrl; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.junit.Test; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class QueryDumpActionTest extends StarRocksHttpTestCase { + + @Override + protected void doSetUp() { + MetricRepo.init(); + ExecuteEnv.setup(); + } + + @Test + public void testSuccess() throws Exception { + setUpWithCatalog(); + + try (Response response = postQueryDump(DB_NAME, true, "select * from testTbl")) { + assertThat(response.isSuccessful()).isTrue(); + String body = response.body().string(); + assertThat(body) + .contains("\"statement\":\"SELECT *\\nFROM db_mock_000.tbl_mock_001\"") + .containsPattern("\"column_statistics\":\\{.+\\}") + .containsPattern("\"table_row_count\":\\{.+\\}"); + } + + try (Response response = postQueryDump(DB_NAME, false, "select * from testTbl")) { + assertThat(response.isSuccessful()).isTrue(); + String body = response.body().string(); + assertThat(body) + .containsPattern("\"column_statistics\":\\{.+\\}") + .containsPattern("\"table_row_count\":\\{.+\\}"); + } + + try (Response response = postQueryDump(DB_NAME, null, "select * from testTbl")) { + assertThat(response.isSuccessful()).isTrue(); + String body = response.body().string(); + assertThat(body) + .containsPattern("\"column_statistics\":\\{.+\\}") + .containsPattern("\"table_row_count\":\\{.+\\}"); + } + + try (Response response = postQueryDump("default_catalog." + DB_NAME, false, "select * from testTbl")) { + assertThat(response.isSuccessful()).isTrue(); + String body = response.body().string(); + assertThat(body) + .containsPattern("\"column_statistics\":\\{.+\\}") + .containsPattern("\"table_row_count\":\\{.+\\}"); + } + } + + @Test + public void testInvalidQuery() throws Exception { + setUpWithCatalog(); + + try (Response response = postQueryDump(DB_NAME, false, "")) { + String body = response.body().string(); + assertThat(response.code()).isEqualTo(HttpResponseStatus.BAD_REQUEST.code()); + assertThat(body).contains("query is empty"); + } + + try (Response response = postQueryDump("default_catalog." + DB_NAME, false, "invalid-query")) { + String body = response.body().string(); + assertThat(response.code()).isEqualTo(HttpResponseStatus.BAD_REQUEST.code()); + assertThat(body).contains("execute query failed. Getting syntax error"); + } + } + + @Test + public void testInvalidDatabase() throws Exception { + setUpWithCatalog(); + + try (Response response = postQueryDump("default_catalog.no_db", false, "select * from testTbl")) { + String body = response.body().string(); + assertThat(response.code()).isEqualTo(HttpResponseStatus.NOT_FOUND.code()); + assertThat(body).contains("Database [default_catalog.no_db] does not exists"); + } + try (Response response = postQueryDump("", false, "select * from testTbl")) { + String body = response.body().string(); + assertThat(response.isSuccessful()).isTrue(); + assertThat(body).contains("Getting analyzing error. Detail message: No database selected."); + } + try (Response response = postQueryDump(null, false, "select * from testTbl")) { + String body = response.body().string(); + assertThat(response.isSuccessful()).isTrue(); + assertThat(body).contains("Getting analyzing error. Detail message: No database selected."); + } + } + + private Response postQueryDump(String db, Boolean enableMock, String query) throws IOException { + RequestBody body = RequestBody.create(query, null); + + HttpUrl.Builder builder = HttpUrl.parse(BASE_URL + QueryDumpAction.URL).newBuilder(); + if (db != null) { + builder.addQueryParameter("db", db); + } + if (enableMock != null) { + builder.addQueryParameter("mock", enableMock.toString()); + } + HttpUrl url = builder.build(); + + Request request = new Request.Builder() + .addHeader("Authorization", rootAuth) + .url(url) + .post(body) + .build(); + + return networkClient.newCall(request).execute(); + } + +} diff --git a/fe/fe-core/src/test/java/com/starrocks/http/StarRocksHttpTestCase.java b/fe/fe-core/src/test/java/com/starrocks/http/StarRocksHttpTestCase.java index 3e7cba45a9242..e3d5cfd5a8f6c 100644 --- a/fe/fe-core/src/test/java/com/starrocks/http/StarRocksHttpTestCase.java +++ b/fe/fe-core/src/test/java/com/starrocks/http/StarRocksHttpTestCase.java @@ -21,6 +21,11 @@ package com.starrocks.http; +<<<<<<< HEAD +======= +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +>>>>>>> d42320f9a2 ([Feature] Add function get_query_dump (#48105)) import com.google.common.collect.Lists; import com.starrocks.alter.MaterializedViewHandler; import com.starrocks.alter.SchemaChangeHandler; @@ -37,12 +42,14 @@ import com.starrocks.catalog.PartitionInfo; import com.starrocks.catalog.Replica; import com.starrocks.catalog.SinglePartitionInfo; +import com.starrocks.catalog.TableProperty; import com.starrocks.catalog.TabletInvertedIndex; import com.starrocks.catalog.TabletMeta; import com.starrocks.catalog.Type; import com.starrocks.common.DdlException; import com.starrocks.common.ExceptionChecker.ThrowingRunnable; import com.starrocks.common.jmockit.Deencapsulation; +import com.starrocks.common.util.PropertyAnalyzer; import com.starrocks.load.Load; import com.starrocks.mysql.privilege.Auth; import com.starrocks.persist.EditLog; @@ -259,6 +266,7 @@ private static GlobalStateMgr newDelegateCatalog() { } private static GlobalStateMgr newDelegateGlobalStateMgr() { +<<<<<<< HEAD try { GlobalStateMgr globalStateMgr = Deencapsulation.newInstance(GlobalStateMgr.class); Auth auth = new Auth(); @@ -275,6 +283,20 @@ private static GlobalStateMgr newDelegateGlobalStateMgr() { globalStateMgr.getAuth(); minTimes = 0; result = auth; +======= + GlobalStateMgr globalStateMgr = Deencapsulation.newInstance(GlobalStateMgr.class); + //EasyMock.expect(globalStateMgr.getAuth()).andReturn(starrocksAuth).anyTimes(); + Database db = new Database(testDbId, "testDb"); + OlapTable table = newTable(TABLE_NAME); + table.setTableProperty(new TableProperty(ImmutableMap.of(PropertyAnalyzer.PROPERTIES_REPLICATION_NUM, "1"))); + db.registerTableUnlocked(table); + OlapTable table1 = newTable(TABLE_NAME + 1); + db.registerTableUnlocked(table1); + EsTable esTable = newEsTable(ES_TABLE_NAME); + db.registerTableUnlocked(esTable); + OlapTable newEmptyTable = newEmptyTable("test_empty_table"); + db.registerTableUnlocked(newEmptyTable); +>>>>>>> d42320f9a2 ([Feature] Add function get_query_dump (#48105)) globalStateMgr.getDb(db.getId()); minTimes = 0; @@ -405,6 +427,38 @@ TabletInvertedIndex getCurrentInvertedIndex() { return tabletInvertedIndex; } }; +<<<<<<< HEAD +======= + + new Expectations(globalStateMgr) { + { + globalStateMgr.getNodeMgr(); + minTimes = 0; + result = nodeMgr; + + globalStateMgr.getTabletInvertedIndex(); + minTimes = 0; + result = tabletInvertedIndex; + + globalStateMgr.isLeader(); + minTimes = 0; + result = true; + + globalStateMgr.isSafeMode(); + minTimes = 0; + result = true; + } + }; + + new Expectations(nodeMgr) { + { + nodeMgr.getClusterInfo(); + minTimes = 0; + result = systemInfoService; + } + }; + +>>>>>>> d42320f9a2 ([Feature] Add function get_query_dump (#48105)) assignBackends(); doSetUp(); } diff --git a/fe/fe-core/src/test/java/com/starrocks/sql/optimizer/QueryDumperTest.java b/fe/fe-core/src/test/java/com/starrocks/sql/optimizer/QueryDumperTest.java new file mode 100644 index 0000000000000..76b7e21f8d975 --- /dev/null +++ b/fe/fe-core/src/test/java/com/starrocks/sql/optimizer/QueryDumperTest.java @@ -0,0 +1,127 @@ +// Copyright 2021-present StarRocks, Inc. All rights reserved. +// +// Licensed 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 +// +// https://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 com.starrocks.sql.optimizer; + +import com.starrocks.common.Pair; +import com.starrocks.qe.ConnectContext; +import com.starrocks.sql.optimizer.dump.DumpInfo; +import com.starrocks.sql.optimizer.dump.QueryDumper; +import com.starrocks.sql.plan.PlanTestBase; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class QueryDumperTest extends PlanTestBase { + private DumpInfo prevDumpInfo = null; + + @BeforeClass + public static void beforeClass() throws Exception { + PlanTestBase.beforeClass(); + } + + @Before + public void before() { + prevDumpInfo = connectContext.getDumpInfo(); + connectContext.setDumpInfo(null); + } + + @After + public void after() { + // After dumping query, the connect context should be reset to the previous state. + assertThat(connectContext.isHTTPQueryDump()).isFalse(); + assertThat(connectContext.getCurrentCatalog()).isEqualTo("default_catalog"); + assertThat(connectContext.getDatabase()).isEqualTo("test"); + + connectContext.setDumpInfo(prevDumpInfo); + } + + @Test + public void testDumpQueryInvalidConnectContext() { + ConnectContext context = ConnectContext.get(); + try { + ConnectContext.remove(); + + Pair statusAndRes = QueryDumper.dumpQuery("catalog", "db", "", false); + assertThat(statusAndRes.first).isEqualTo(HttpResponseStatus.BAD_REQUEST); + assertThat(statusAndRes.second).contains("There is no ConnectContext for this thread:"); + + } finally { + context.setThreadLocalInfo(); + } + } + + @Test + public void testDumpQueryInvalidQuery() { + { + + Pair statusAndRes = QueryDumper.dumpQuery("default_catalog", "test", "", false); + assertThat(statusAndRes.first).isEqualTo(HttpResponseStatus.BAD_REQUEST); + assertThat(statusAndRes.second).isEqualTo("query is empty"); + } + + { + Pair statusAndRes = QueryDumper.dumpQuery("default_catalog", "test", "no-query", false); + assertThat(statusAndRes.first).isEqualTo(HttpResponseStatus.BAD_REQUEST); + assertThat(statusAndRes.second).contains("execute query failed. Getting syntax error"); + } + } + + @Test + public void testDumpQueryInvalidCatalog() { + Pair statusAndRes = + QueryDumper.dumpQuery("no-catalog", "test", "select count(v1) from t0", false); + assertThat(statusAndRes.first).isEqualTo(HttpResponseStatus.NOT_FOUND); + assertThat(statusAndRes.second).isEqualTo("Database [no-catalog.test] does not exists"); + } + + @Test + public void testDumpQueryInvalidDatabase() { + Pair statusAndRes = + QueryDumper.dumpQuery("default_catalog", "no-db", "select count(v1) from t0", false); + assertThat(statusAndRes.first).isEqualTo(HttpResponseStatus.NOT_FOUND); + assertThat(statusAndRes.second).isEqualTo("Database [default_catalog.no-db] does not exists"); + } + + @Test + public void testDumpQuerySuccess() { + { + Pair statusAndRes = + QueryDumper.dumpQuery("default_catalog", "test", "select count(v1) from t0", false); + assertThat(statusAndRes.first).isEqualTo(HttpResponseStatus.OK); + // Non-empty column_statistics. + assertThat(statusAndRes.second).containsPattern("\"column_statistics\":\\{.+\\}"); + } + { + Pair statusAndRes = + QueryDumper.dumpQuery("", "", "select count(v1) from t0", false); + assertThat(statusAndRes.first).isEqualTo(HttpResponseStatus.OK); + // Non-empty column_statistics. + assertThat(statusAndRes.second).containsPattern("\"column_statistics\":\\{.+\\}"); + } + { + Pair statusAndRes = + QueryDumper.dumpQuery("default_catalog", "test", "select count(v1) from t0", true); + assertThat(statusAndRes.first).isEqualTo(HttpResponseStatus.OK); + // Non-empty column_statistics. + assertThat(statusAndRes.second) + .containsPattern("\"column_statistics\":\\{.+\\}") + .contains("SELECT count(tbl_mock_001.mock_002)"); + } + } +} diff --git a/fe/fe-core/src/test/java/com/starrocks/sql/plan/ConstantExpressionTest.java b/fe/fe-core/src/test/java/com/starrocks/sql/plan/ConstantExpressionTest.java index 9e07ed77b8006..88044d5795cdb 100644 --- a/fe/fe-core/src/test/java/com/starrocks/sql/plan/ConstantExpressionTest.java +++ b/fe/fe-core/src/test/java/com/starrocks/sql/plan/ConstantExpressionTest.java @@ -17,7 +17,13 @@ package com.starrocks.sql.plan; +<<<<<<< HEAD +======= +import com.starrocks.qe.SqlModeHelper; +import com.starrocks.sql.analyzer.SemanticException; +>>>>>>> d42320f9a2 ([Feature] Add function get_query_dump (#48105)) import com.starrocks.sql.common.StarRocksPlannerException; +import com.starrocks.sql.optimizer.dump.DumpInfo; import com.starrocks.sql.parser.ParsingException; import org.junit.Assert; import org.junit.BeforeClass; @@ -395,4 +401,126 @@ public void testRand() throws Exception { " | : uuid()\n" + " | : uuid()")); } +<<<<<<< HEAD +======= + + @Test + public void testNumericLiteralComparison() throws Exception { + String sql; + String plan; + + final long prevSqlMode = connectContext.getSessionVariable().getSqlMode(); + try { + connectContext.getSessionVariable().setSqlMode(prevSqlMode | SqlModeHelper.MODE_DOUBLE_LITERAL); + + sql = "SELECT percentile_approx(2.25, 0), percentile_approx(2.25, 0.)"; + plan = getFragmentPlan(sql); + assertContains(plan, " 2:Project\n" + + " | : 2: percentile_approx\n" + + " | : clone(2: percentile_approx)\n" + + " | \n" + + " 1:AGGREGATE (update finalize)\n" + + " | output: percentile_approx(2.25, 0.0)\n" + + " | group by: "); + + sql = "SELECT COUNT(CASE WHEN 1 THEN 1 END), COUNT(CASE WHEN TRUE THEN 1 END), " + + "COUNT(CASE WHEN 1.0 THEN 1 END), COUNT(CASE WHEN CAST(1 AS LARGEINT) THEN 1 END)"; + plan = getFragmentPlan(sql); + assertContains(plan, " 2:Project\n" + + " | : 2: count\n" + + " | : clone(2: count)\n" + + " | : clone(2: count)\n" + + " | : clone(2: count)\n" + + " | \n" + + " 1:AGGREGATE (update finalize)\n" + + " | output: count(1)\n" + + " | group by: "); + + sql = "SELECT 1, TRUE, 0, FALSE, 1.1, 1, 1.1, TRUE, FALSE, 0"; + plan = getFragmentPlan(sql); + assertContains(plan, " 1:Project\n" + + " | : 1\n" + + " | : 1.1\n" + + " | : TRUE\n" + + " | : FALSE\n" + + " | : 0"); + + sql = "SELECT TRUE, 1, FALSE, 0, 1.1, 1, 1.1, TRUE, FALSE, 0"; + plan = getFragmentPlan(sql); + assertContains(plan, " 1:Project\n" + + " | : 1\n" + + " | : 1.1\n" + + " | : TRUE\n" + + " | : FALSE\n" + + " | : 0"); + + } finally { + connectContext.getSessionVariable().setSqlMode(prevSqlMode); + } + } + + @Test + public void testGetQueryDump() throws Exception { + DumpInfo prevDumpInfo = connectContext.getDumpInfo(); + + try { + connectContext.setDumpInfo(null); + + // Non-constant arguments. + { + String sql = "SELECT get_query_dump(lower('select count(v1) from t0')) from t0"; + Assert.assertThrows("Meta function get_query_dump does not support non-constant arguments", + SemanticException.class, () -> getFragmentPlan(sql)); + } + + // Success cases. + { + String sql = "SELECT get_query_dump('select count(v1) from t0', false) from t0"; + String plan = getFragmentPlan(sql); + assertContains(plan, "{\"statement\":\"select count(v1) from t0\""); + } + + { + String sql = "SELECT get_query_dump('select count(v1) from t0', true) from t0"; + String plan = getFragmentPlan(sql); + assertContains(plan, "{\"statement\":\"SELECT count(tbl_mock_001.mock_002)..."); + } + + { + String sql = "SELECT get_query_dump('select count(v1) from t0') from t0"; + String plan = getFragmentPlan(sql); + assertContains(plan, "{\"statement\":\"select count(v1) from t0\""); + } + + { + String sql = "SELECT get_query_dump(concat('select count(v1)', ' from t0')) from t0"; + String plan = getFragmentPlan(sql); + assertContains(plan, "{\"statement\":\"select count(v1) from t0\""); + } + + // Failed cases. + { + String sql = "SELECT get_query_dump('') from t0"; + Assert.assertThrows("Invalid parameter get_query_dump: query is empty", + StarRocksPlannerException.class, () -> getFragmentPlan(sql)); + } + { + String sql = "SELECT get_query_dump('not-a-query') from t0"; + Assert.assertThrows("Invalid parameter get_query_dump: execute query failed.", + StarRocksPlannerException.class, () -> getFragmentPlan(sql)); + } + + // Success cases after failed cases. + { + String sql = "SELECT get_query_dump(concat('select count(v1)', ' from t0')) from t0"; + String plan = getFragmentPlan(sql); + assertContains(plan, "{\"statement\":\"select count(v1) from t0\""); + } + + } finally { + connectContext.setDumpInfo(prevDumpInfo); + } + + } +>>>>>>> d42320f9a2 ([Feature] Add function get_query_dump (#48105)) } diff --git a/test/sql/test_function/R/test_get_query_dump b/test/sql/test_function/R/test_get_query_dump new file mode 100644 index 0000000000000..14d36695516a4 --- /dev/null +++ b/test/sql/test_function/R/test_get_query_dump @@ -0,0 +1,106 @@ +-- name: test_get_query_dump +CREATE TABLE __row_util_base ( + k1 bigint NULL +) ENGINE=OLAP +DUPLICATE KEY(`k1`) +DISTRIBUTED BY HASH(`k1`) BUCKETS 32 +PROPERTIES ( + "replication_num" = "1" +); +-- result: +-- !result +insert into __row_util_base select generate_series from TABLE(generate_series(0, 10000 - 1)); +-- result: +-- !result +insert into __row_util_base select * from __row_util_base; -- 20000 +insert into __row_util_base select * from __row_util_base; -- 40000 + +CREATE TABLE __row_util ( + idx bigint NULL +) ENGINE=OLAP +DUPLICATE KEY(`idx`) +DISTRIBUTED BY HASH(`idx`) BUCKETS 32 +PROPERTIES ( + "replication_num" = "1" +); +-- result: +-- !result +insert into __row_util select row_number() over() as idx from __row_util_base; +-- result: +-- !result +CREATE TABLE t1 ( + k1 bigint NULL, + c_int_1_seq bigint NULL, + c_int_2_seq string NULL + +) ENGINE=OLAP +DUPLICATE KEY(`k1`) +DISTRIBUTED BY HASH(`k1`) BUCKETS 32 +PROPERTIES ( + "replication_num" = "1" +); +-- result: +-- !result +insert into t1 +select + idx, + idx, + concat('abc1-', idx) +from __row_util; +-- result: +-- !result +select get_query_dump(''); +-- result: +E: (6013, 'SemanticException: Getting analyzing error. Detail message: Invalid parameter get_query_dump: query is empty.') +-- !result +select get_query_dump('invalid-query'); +-- result: +E: (6013, "SemanticException: Getting analyzing error. Detail message: Invalid parameter get_query_dump: execute query failed. Getting syntax error at line 1, column 0. Detail message: Unexpected input 'invalid', the most similar input is {'INSTALL', 'UNINSTALL', 'KILL', 'LOAD', 'ADMIN', '(', ';'}..") +-- !result +select get_query_dump(lower('select * from t1')); +-- result: +E: (1064, 'Getting analyzing error. Detail message: Meta function get_query_dump does not support non-constant arguments.') +-- !result +with + query_dump as (select get_query_dump('select * from t1') as x), + w1 as ( + select 'statement' as k, length(get_json_string(x, '$.statement')) > 2 as not_empty from query_dump + UNION ALL + select 'table_meta' as k, length(get_json_string(x, '$.table_meta')) > 2 as not_empty from query_dump + UNION ALL + select 'table_row_count' as k, length(get_json_string(x, '$.table_row_count')) > 2 as not_empty from query_dump + UNION ALL + select 'column_statistics' as k, length(get_json_string(x, '$.column_statistics')) > 2 as not_empty from query_dump + ) +select * from w1 order by k; +-- result: +column_statistics 1 +statement 1 +table_meta 1 +table_row_count 1 +-- !result +with + query_dump as (select get_query_dump(concat('select *', ' from t1')) as x), + w1 as ( + select 'statement' as k, length(get_json_string(x, '$.statement')) > 2 as not_empty from query_dump + UNION ALL + select 'table_meta' as k, length(get_json_string(x, '$.table_meta')) > 2 as not_empty from query_dump + UNION ALL + select 'table_row_count' as k, length(get_json_string(x, '$.table_row_count')) > 2 as not_empty from query_dump + UNION ALL + select 'column_statistics' as k, length(get_json_string(x, '$.column_statistics')) > 2 as not_empty from query_dump + ) +select * from w1 order by k; +-- result: +column_statistics 1 +statement 1 +table_meta 1 +table_row_count 1 +-- !result +with + query_dump as (select get_query_dump('select * from t1', true) as x) +select get_json_string(x, '$.statement') from query_dump; +-- result: +SELECT * +FROM db_mock_000.tbl_mock_001 +-- !result \ No newline at end of file diff --git a/test/sql/test_function/T/test_get_query_dump b/test/sql/test_function/T/test_get_query_dump new file mode 100644 index 0000000000000..21978e6f7c18a --- /dev/null +++ b/test/sql/test_function/T/test_get_query_dump @@ -0,0 +1,80 @@ +-- name: test_get_query_dump + +-- Prepare data. +CREATE TABLE __row_util_base ( + k1 bigint NULL +) ENGINE=OLAP +DUPLICATE KEY(`k1`) +DISTRIBUTED BY HASH(`k1`) BUCKETS 32 +PROPERTIES ( + "replication_num" = "1" +); +insert into __row_util_base select generate_series from TABLE(generate_series(0, 10000 - 1)); +insert into __row_util_base select * from __row_util_base; -- 20000 +insert into __row_util_base select * from __row_util_base; -- 40000 + +CREATE TABLE __row_util ( + idx bigint NULL +) ENGINE=OLAP +DUPLICATE KEY(`idx`) +DISTRIBUTED BY HASH(`idx`) BUCKETS 32 +PROPERTIES ( + "replication_num" = "1" +); +insert into __row_util select row_number() over() as idx from __row_util_base; + +CREATE TABLE t1 ( + k1 bigint NULL, + c_int_1_seq bigint NULL, + c_int_2_seq string NULL + +) ENGINE=OLAP +DUPLICATE KEY(`k1`) +DISTRIBUTED BY HASH(`k1`) BUCKETS 32 +PROPERTIES ( + "replication_num" = "1" +); + +insert into t1 +select + idx, + idx, + concat('abc1-', idx) +from __row_util; + +-- Invalid cases. +select get_query_dump(''); +select get_query_dump('invalid-query'); +select get_query_dump(lower('select * from t1')); + +-- Valid cases. +with + query_dump as (select get_query_dump('select * from t1') as x), + w1 as ( + select 'statement' as k, length(get_json_string(x, '$.statement')) > 2 as not_empty from query_dump + UNION ALL + select 'table_meta' as k, length(get_json_string(x, '$.table_meta')) > 2 as not_empty from query_dump + UNION ALL + select 'table_row_count' as k, length(get_json_string(x, '$.table_row_count')) > 2 as not_empty from query_dump + UNION ALL + select 'column_statistics' as k, length(get_json_string(x, '$.column_statistics')) > 2 as not_empty from query_dump + ) +select * from w1 order by k; + + +with + query_dump as (select get_query_dump(concat('select *', ' from t1')) as x), + w1 as ( + select 'statement' as k, length(get_json_string(x, '$.statement')) > 2 as not_empty from query_dump + UNION ALL + select 'table_meta' as k, length(get_json_string(x, '$.table_meta')) > 2 as not_empty from query_dump + UNION ALL + select 'table_row_count' as k, length(get_json_string(x, '$.table_row_count')) > 2 as not_empty from query_dump + UNION ALL + select 'column_statistics' as k, length(get_json_string(x, '$.column_statistics')) > 2 as not_empty from query_dump + ) +select * from w1 order by k; + +with + query_dump as (select get_query_dump('select * from t1', true) as x) +select get_json_string(x, '$.statement') from query_dump;