This is an automated email from the ASF dual-hosted git repository.

liyang pushed a commit to branch kylin5
in repository https://gitbox.apache.org/repos/asf/kylin.git

commit d7f37675e2de44478da78d5ae7b4b70fd043b13f
Author: haocheni <hao_...@qq.com>
AuthorDate: Fri Sep 22 19:21:38 2023 +0800

    KYLIN-5832 Support queryDetect api
---
 .../java/org/apache/kylin/common/QueryContext.java |   1 +
 .../kylin/rest/controller/NQueryController.java    |   9 +
 .../kylin/rest/request/QueryDetectRequest.java     |  33 +++
 .../kylin/rest/response/QueryDetectResponse.java   | 156 ++++++++++++
 .../apache/kylin/rest/service/QueryService.java    |  37 ++-
 .../kylin/rest/service/QueryServiceTest.java       | 278 ++++++++++++++++++++-
 .../kylin/query/engine/QueryRoutingEngine.java     |   7 +
 .../engine/exec/sparder/SparderQueryPlanExec.java  |   6 +
 .../kylin/query/engine/QueryRoutingEngineTest.java |  34 +++
 9 files changed, 557 insertions(+), 4 deletions(-)

diff --git 
a/src/core-common/src/main/java/org/apache/kylin/common/QueryContext.java 
b/src/core-common/src/main/java/org/apache/kylin/common/QueryContext.java
index 592df87f68..b112591e27 100644
--- a/src/core-common/src/main/java/org/apache/kylin/common/QueryContext.java
+++ b/src/core-common/src/main/java/org/apache/kylin/common/QueryContext.java
@@ -389,6 +389,7 @@ public class QueryContext implements Closeable {
         private boolean isRefused;
         private boolean includeHeader;
         private boolean isVacant;
+        private boolean isQueryDetect;
     }
 
     @Getter
diff --git 
a/src/query-server/src/main/java/org/apache/kylin/rest/controller/NQueryController.java
 
b/src/query-server/src/main/java/org/apache/kylin/rest/controller/NQueryController.java
index 6d69ea475f..1f56073647 100644
--- 
a/src/query-server/src/main/java/org/apache/kylin/rest/controller/NQueryController.java
+++ 
b/src/query-server/src/main/java/org/apache/kylin/rest/controller/NQueryController.java
@@ -73,12 +73,14 @@ import org.apache.kylin.rest.exception.ForbiddenException;
 import org.apache.kylin.rest.exception.InternalErrorException;
 import org.apache.kylin.rest.model.Query;
 import org.apache.kylin.rest.request.PrepareSqlRequest;
+import org.apache.kylin.rest.request.QueryDetectRequest;
 import org.apache.kylin.rest.request.SQLFormatRequest;
 import org.apache.kylin.rest.request.SQLRequest;
 import org.apache.kylin.rest.request.SaveSqlRequest;
 import org.apache.kylin.rest.request.SyncFileSegmentsRequest;
 import org.apache.kylin.rest.response.DataResult;
 import org.apache.kylin.rest.response.EnvelopeResponse;
+import org.apache.kylin.rest.response.QueryDetectResponse;
 import org.apache.kylin.rest.response.QueryHistoryFiltersResponse;
 import org.apache.kylin.rest.response.QueryStatisticsResponse;
 import org.apache.kylin.rest.response.SQLResponse;
@@ -662,6 +664,13 @@ public class NQueryController extends NBasicController {
         return new EnvelopeResponse<>(KylinException.CODE_SUCCESS, 
queryService.format(request.getSqls()), "");
     }
 
+    @ApiOperation(value = "queryDetect", tags = { "QE" })
+    @PostMapping("/detection")
+    public EnvelopeResponse<QueryDetectResponse> queryDetect(@RequestBody 
QueryDetectRequest request) {
+        checkProjectName(request.getProject());
+        return new EnvelopeResponse<>(KylinException.CODE_SUCCESS, 
queryService.queryDetect(request), "");
+    }
+
     private void checkQueryName(String queryName) {
         if (!queryNamePattern.matcher(queryName).matches()) {
             throw new KylinException(INVALID_NAME, 
MsgPicker.getMsg().getInvalidQueryName());
diff --git 
a/src/query-service/src/main/java/org/apache/kylin/rest/request/QueryDetectRequest.java
 
b/src/query-service/src/main/java/org/apache/kylin/rest/request/QueryDetectRequest.java
new file mode 100644
index 0000000000..9ee03f4b3f
--- /dev/null
+++ 
b/src/query-service/src/main/java/org/apache/kylin/rest/request/QueryDetectRequest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.kylin.rest.request;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class QueryDetectRequest {
+    private String sql;
+    private String project;
+    private Integer offset = 0;
+    private Integer limit = 500;
+}
diff --git 
a/src/query-service/src/main/java/org/apache/kylin/rest/response/QueryDetectResponse.java
 
b/src/query-service/src/main/java/org/apache/kylin/rest/response/QueryDetectResponse.java
new file mode 100644
index 0000000000..b6548101d9
--- /dev/null
+++ 
b/src/query-service/src/main/java/org/apache/kylin/rest/response/QueryDetectResponse.java
@@ -0,0 +1,156 @@
+/*
+ * 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.kylin.rest.response;
+
+import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.QueryContext;
+import org.apache.kylin.guava30.shaded.common.collect.Lists;
+import org.apache.kylin.metadata.cube.model.IndexEntity;
+import org.apache.kylin.metadata.cube.model.IndexPlan;
+import org.apache.kylin.metadata.cube.model.LayoutEntity;
+import org.apache.kylin.metadata.cube.model.NIndexPlanManager;
+import org.apache.kylin.metadata.query.NativeQueryRealization;
+import org.apache.kylin.metadata.query.QueryHistory;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@JsonAutoDetect(fieldVisibility = NONE, getterVisibility = NONE, 
isGetterVisibility = NONE, setterVisibility = NONE)
+public class QueryDetectResponse {
+    @JsonProperty("is_exception")
+    private boolean isException = false;
+
+    @JsonProperty("exception_message")
+    private String exceptionMessage;
+
+    @JsonProperty("query_id")
+    private String queryId;
+
+    @JsonProperty("is_push_down")
+    private boolean isPushDown = false;
+
+    @JsonProperty("is_post_aggregation")
+    private boolean isPostAggregation = false;
+
+    @JsonProperty("is_table_index")
+    private boolean isTableIndex = false;
+
+    @JsonProperty("is_base_index")
+    private boolean isBaseIndex = false;
+
+    @JsonProperty("is_cache")
+    private boolean isCache = false;
+
+    @JsonProperty("is_constants")
+    private boolean isConstants = false;
+
+    @JsonProperty("realizations")
+    private List<IndexInfo> realizations = Lists.newArrayList();
+
+    public QueryDetectResponse buildExceptionResponse(SQLResponse sqlResponse) 
{
+        this.isException = true;
+        this.exceptionMessage = sqlResponse.getExceptionMessage();
+        this.queryId = sqlResponse.getQueryId();
+        this.isCache = sqlResponse.isHitExceptionCache();
+        this.realizations = Lists.newArrayList();
+        return this;
+    }
+
+    public QueryDetectResponse buildResponse(String project, SQLResponse 
sqlResponse, QueryContext queryContext) {
+        // build RealizationVO
+        List<QueryDetectResponse.IndexInfo> indexInfoList = 
sqlResponse.getNativeRealizations().stream()
+                .map(realization -> new 
QueryDetectResponse.IndexInfo().buildResponse(realization, project))
+                .collect(Collectors.toList());
+        boolean isConstantQuery = 
QueryHistory.EngineType.CONSTANTS.name().equals(sqlResponse.getEngineType());
+
+        this.isException = sqlResponse.isException;
+        this.exceptionMessage = sqlResponse.getExceptionMessage();
+        this.queryId = sqlResponse.getQueryId();
+        this.isPushDown = sqlResponse.isQueryPushDown();
+        this.isPostAggregation = !sqlResponse.isQueryPushDown() && 
!isConstantQuery
+                && !queryContext.getMetrics().isExactlyMatch();
+        // any realization is TableIndex、baseIndex
+        this.isTableIndex = 
indexInfoList.stream().anyMatch(QueryDetectResponse.IndexInfo::isTableIndex);
+        this.isBaseIndex = 
indexInfoList.stream().anyMatch(QueryDetectResponse.IndexInfo::isBaseIndex);
+        this.isCache = sqlResponse.isStorageCacheUsed();
+        this.isConstants = isConstantQuery;
+        this.realizations = indexInfoList;
+        return this;
+    }
+
+    @Data
+    @AllArgsConstructor
+    @NoArgsConstructor
+    @JsonAutoDetect(fieldVisibility = NONE, getterVisibility = NONE, 
isGetterVisibility = NONE, setterVisibility = NONE)
+    public static class IndexInfo {
+        @JsonProperty("model_id")
+        private String modelId;
+
+        @JsonProperty("model_alias")
+        private String modelAlias;
+
+        @JsonProperty("layout_id")
+        private long layoutId;
+
+        @JsonProperty("index_type")
+        private String indexType;
+
+        @JsonProperty("partial_match_model")
+        private boolean partialMatchModel = false;
+
+        @JsonProperty("valid")
+        private boolean valid = true;
+
+        @JsonProperty("is_table_index")
+        private boolean isTableIndex = false;
+
+        @JsonProperty("is_base_index")
+        private boolean isBaseIndex = false;
+
+        public IndexInfo buildResponse(NativeQueryRealization realization, 
String project) {
+            // calculate isBaseIndex
+            KylinConfig kylinConfig = KylinConfig.getInstanceFromEnv();
+            IndexPlan indexPlan = NIndexPlanManager.getInstance(kylinConfig, 
project)
+                    .getIndexPlan(realization.getModelId());
+            LayoutEntity layoutEntity = indexPlan == null ? null : 
indexPlan.getLayoutEntity(realization.getLayoutId());
+
+            this.modelId = realization.getModelId();
+            this.modelAlias = realization.getModelAlias();
+            this.layoutId = realization.getLayoutId();
+            this.indexType = realization.getIndexType();
+            this.partialMatchModel = realization.isPartialMatchModel();
+            this.valid = realization.isValid();
+            this.isTableIndex = 
IndexEntity.isTableIndex(realization.getLayoutId());
+            this.isBaseIndex = layoutEntity != null && 
layoutEntity.isBaseIndex();
+            return this;
+        }
+    }
+}
diff --git 
a/src/query-service/src/main/java/org/apache/kylin/rest/service/QueryService.java
 
b/src/query-service/src/main/java/org/apache/kylin/rest/service/QueryService.java
index 7b10e1eb67..04bd71ff81 100644
--- 
a/src/query-service/src/main/java/org/apache/kylin/rest/service/QueryService.java
+++ 
b/src/query-service/src/main/java/org/apache/kylin/rest/service/QueryService.java
@@ -136,6 +136,7 @@ import 
org.apache.kylin.query.exception.UserStopQueryException;
 import org.apache.kylin.query.relnode.ContextUtil;
 import org.apache.kylin.query.relnode.OLAPContext;
 import org.apache.kylin.query.util.EscapeDialect;
+import org.apache.kylin.query.util.PrepareSQLUtils;
 import org.apache.kylin.query.util.QueryLimiter;
 import org.apache.kylin.query.util.QueryModelPriorities;
 import org.apache.kylin.query.util.QueryParams;
@@ -150,7 +151,9 @@ import org.apache.kylin.rest.cluster.ClusterManager;
 import org.apache.kylin.rest.config.AppConfig;
 import org.apache.kylin.rest.model.Query;
 import org.apache.kylin.rest.request.PrepareSqlRequest;
+import org.apache.kylin.rest.request.QueryDetectRequest;
 import org.apache.kylin.rest.request.SQLRequest;
+import org.apache.kylin.rest.response.QueryDetectResponse;
 import org.apache.kylin.rest.response.SQLResponse;
 import org.apache.kylin.rest.response.SQLResponseTrace;
 import org.apache.kylin.rest.response.TableMetaCacheResult;
@@ -158,7 +161,6 @@ import 
org.apache.kylin.rest.response.TableMetaCacheResultV2;
 import org.apache.kylin.rest.security.MutableAclRecord;
 import org.apache.kylin.rest.util.AclEvaluate;
 import org.apache.kylin.rest.util.AclPermissionUtil;
-import org.apache.kylin.query.util.PrepareSQLUtils;
 import org.apache.kylin.rest.util.QueryCacheSignatureUtil;
 import org.apache.kylin.rest.util.QueryRequestLimits;
 import org.apache.kylin.rest.util.QueryUtils;
@@ -645,7 +647,8 @@ public class QueryService extends BasicService implements 
CacheSignatureQuerySup
             sqlResponse = QueryUtils.handleTempStatement(sqlRequest, 
kylinConfig);
 
             // search cache
-            if (sqlResponse == null && kylinConfig.isQueryCacheEnabled() && 
!sqlRequest.isForcedToPushDown()) {
+            if (sqlResponse == null && kylinConfig.isQueryCacheEnabled() && 
!sqlRequest.isForcedToPushDown()
+                    && !queryContext.getQueryTagInfo().isAsyncQuery()) {
                 sqlResponse = searchCache(sqlRequest, kylinConfig);
             }
 
@@ -754,6 +757,10 @@ public class QueryService extends BasicService implements 
CacheSignatureQuerySup
     }
 
     private void addToQueryHistory(SQLRequest sqlRequest, SQLResponse 
sqlResponse, String originalSql) {
+        if (QueryContext.current().getQueryTagInfo().isQueryDetect()) {
+            return;
+        }
+
         if (!(QueryContext.current().getQueryTagInfo().isAsyncQuery()
                 && 
NProjectManager.getProjectConfig(sqlRequest.getProject()).isUniqueAsyncQueryYarnQueue()))
 {
             try {
@@ -936,8 +943,10 @@ public class QueryService extends BasicService implements 
CacheSignatureQuerySup
     }
 
     private boolean isQueryCacheEnabled(KylinConfig kylinConfig) {
+        boolean isNotQueryDetect = 
!QueryContext.current().getQueryTagInfo().isQueryDetect();
         return checkCondition(kylinConfig.isQueryCacheEnabled(), "query cache 
disabled in KylinConfig") //
-                && checkCondition(!BackdoorToggles.getDisableCache(), "query 
cache disabled in BackdoorToggles");
+                && checkCondition(!BackdoorToggles.getDisableCache(), "query 
cache disabled in BackdoorToggles")
+                && checkCondition(isNotQueryDetect, "query cache disabled 
because it is query detect");
     }
 
     private boolean isQueryExceptionCacheEnabled(KylinConfig kylinConfig) {
@@ -1168,6 +1177,28 @@ public class QueryService extends BasicService 
implements CacheSignatureQuerySup
         
}).sorted(Comparator.comparingInt(Pair::getFirst)).map(Pair::getSecond).collect(Collectors.toList());
     }
 
+    public QueryDetectResponse queryDetect(QueryDetectRequest 
queryDetectRequest) {
+        try (QueryContext queryContext = QueryContext.current()) {
+            // call query method to collect realizations, etc
+            queryContext.getQueryTagInfo().setQueryDetect(true);
+            String project = queryDetectRequest.getProject();
+            SQLRequest sqlRequest = new SQLRequest();
+            sqlRequest.setSql(queryDetectRequest.getSql());
+            sqlRequest.setProject(project);
+            sqlRequest.setLimit(queryDetectRequest.getLimit());
+            sqlRequest.setOffset(queryDetectRequest.getOffset());
+            SQLResponse sqlResponse = queryWithCache(sqlRequest);
+
+            // build exception response
+            if (sqlResponse.isException()) {
+                return new 
QueryDetectResponse().buildExceptionResponse(sqlResponse);
+            }
+
+            // build normal response
+            return new QueryDetectResponse().buildResponse(project, 
sqlResponse, queryContext);
+        }
+    }
+
     @SuppressWarnings("checkstyle:methodlength")
     private List<TableMetaWithType> doGetMetadataV2(String project, String 
targetModelName) {
         aclEvaluate.checkProjectQueryPermission(project);
diff --git 
a/src/query-service/src/test/java/org/apache/kylin/rest/service/QueryServiceTest.java
 
b/src/query-service/src/test/java/org/apache/kylin/rest/service/QueryServiceTest.java
index 89fead0d99..b9c5ab91e2 100644
--- 
a/src/query-service/src/test/java/org/apache/kylin/rest/service/QueryServiceTest.java
+++ 
b/src/query-service/src/test/java/org/apache/kylin/rest/service/QueryServiceTest.java
@@ -82,11 +82,13 @@ import org.apache.kylin.metadata.acl.AclTCR;
 import org.apache.kylin.metadata.acl.AclTCRManager;
 import org.apache.kylin.metadata.cube.cuboid.NLayoutCandidate;
 import org.apache.kylin.metadata.cube.model.IndexEntity;
+import org.apache.kylin.metadata.cube.model.IndexPlan;
 import org.apache.kylin.metadata.cube.model.LayoutEntity;
 import org.apache.kylin.metadata.cube.model.NDataSegment;
 import org.apache.kylin.metadata.cube.model.NDataflow;
 import org.apache.kylin.metadata.cube.model.NDataflowManager;
 import org.apache.kylin.metadata.cube.model.NDataflowUpdate;
+import org.apache.kylin.metadata.cube.model.NIndexPlanManager;
 import org.apache.kylin.metadata.model.ColumnDesc;
 import org.apache.kylin.metadata.model.ComputedColumnDesc;
 import org.apache.kylin.metadata.model.NDataModel;
@@ -99,6 +101,7 @@ import org.apache.kylin.metadata.project.NProjectManager;
 import org.apache.kylin.metadata.project.ProjectInstance;
 import org.apache.kylin.metadata.query.NativeQueryRealization;
 import org.apache.kylin.metadata.query.QueryHistory;
+import org.apache.kylin.metadata.query.QueryMetrics;
 import org.apache.kylin.metadata.query.QueryMetricsContext;
 import org.apache.kylin.metadata.querymeta.ColumnMeta;
 import org.apache.kylin.metadata.querymeta.ColumnMetaWithType;
@@ -127,7 +130,9 @@ import org.apache.kylin.rest.constant.Constant;
 import org.apache.kylin.rest.exception.InternalErrorException;
 import org.apache.kylin.rest.model.Query;
 import org.apache.kylin.rest.request.PrepareSqlRequest;
+import org.apache.kylin.rest.request.QueryDetectRequest;
 import org.apache.kylin.rest.request.SQLRequest;
+import org.apache.kylin.rest.response.QueryDetectResponse;
 import org.apache.kylin.rest.response.SQLResponse;
 import org.apache.kylin.rest.security.AclEntityFactory;
 import org.apache.kylin.rest.security.AclEntityType;
@@ -169,7 +174,8 @@ import lombok.val;
  * @author xduo
  */
 @RunWith(PowerMockRunner.class)
-@PrepareForTest({ SpringContext.class, UserGroupInformation.class, 
SparkSession.class, QueryService.class })
+@PrepareForTest({ SpringContext.class, UserGroupInformation.class, 
SparkSession.class, QueryService.class,
+        NIndexPlanManager.class, QueryContext.class })
 @PowerMockIgnore({ "javax.management.*" })
 public class QueryServiceTest extends NLocalFileMetadataTestCase {
 
@@ -2743,4 +2749,274 @@ public class QueryServiceTest extends 
NLocalFileMetadataTestCase {
         val queryEntry = result.get();
         
Assert.assertTrue(queryEntry.getPlannerCancelFlag().isCancelRequested());
     }
+
+    @Test
+    public void testQueryDetectException() {
+        QueryDetectRequest queryDetectRequest = new QueryDetectRequest("sql", 
"default", 0, 500);
+        // build sqlRequest
+        SQLRequest sqlRequest = new SQLRequest();
+        sqlRequest.setSql(queryDetectRequest.getSql());
+        String project = queryDetectRequest.getProject();
+        sqlRequest.setLimit(queryDetectRequest.getLimit());
+        sqlRequest.setOffset(queryDetectRequest.getOffset());
+        sqlRequest.setProject(project);
+        // build sqlResponse
+        SQLResponse sqlResponse = new SQLResponse();
+        sqlResponse.setException(true);
+        sqlResponse.setExceptionMessage("exceptionMessage");
+        sqlResponse.setQueryId("queryId");
+        // build excepted queryDetectVO
+        QueryDetectResponse exceptedQueryDetectResponse = new 
QueryDetectResponse().buildExceptionResponse(sqlResponse);
+
+        
Mockito.doReturn(sqlResponse).when(queryService).queryWithCache(sqlRequest);
+
+        QueryDetectResponse queryDetectResponse = 
queryService.queryDetect(queryDetectRequest);
+
+        Assert.assertEquals(exceptedQueryDetectResponse, queryDetectResponse);
+    }
+
+    @Test
+    public void testQueryDetectPushDown() {
+        QueryDetectRequest queryDetectRequest = new QueryDetectRequest("sql", 
"default", 0, 500);
+        // build sqlRequest
+        SQLRequest sqlRequest = new SQLRequest();
+        sqlRequest.setSql(queryDetectRequest.getSql());
+        String project = queryDetectRequest.getProject();
+        sqlRequest.setLimit(queryDetectRequest.getLimit());
+        sqlRequest.setOffset(queryDetectRequest.getOffset());
+        sqlRequest.setProject(project);
+
+        // build sqlResponse
+        SQLResponse sqlResponse = new SQLResponse();
+        sqlResponse.setException(false);
+        sqlResponse.setQueryId("queryId");
+        sqlResponse.setQueryPushDown(true);
+        List<NativeQueryRealization> nativeRealizations = Lists.newArrayList();
+        sqlResponse.setNativeRealizations(nativeRealizations);
+
+        // build excepted queryDetectVO
+        QueryDetectResponse exceptedQueryDetectResponse = new 
QueryDetectResponse().buildResponse(project, sqlResponse,
+                QueryContext.current());
+
+        
Mockito.doReturn(sqlResponse).when(queryService).queryWithCache(sqlRequest);
+
+        QueryDetectResponse queryDetectResponse = 
queryService.queryDetect(queryDetectRequest);
+
+        Assert.assertEquals(exceptedQueryDetectResponse, queryDetectResponse);
+    }
+
+    @Test
+    public void testQueryDetectAggIndex() {
+        QueryDetectRequest queryDetectRequest = new QueryDetectRequest("sql", 
"default", 0, 500);
+        // build sqlRequest
+        SQLRequest sqlRequest = new SQLRequest();
+        sqlRequest.setSql(queryDetectRequest.getSql());
+        String project = queryDetectRequest.getProject();
+        sqlRequest.setLimit(queryDetectRequest.getLimit());
+        sqlRequest.setOffset(queryDetectRequest.getOffset());
+        sqlRequest.setProject(project);
+
+        // build sqlResponse
+        SQLResponse sqlResponse = new SQLResponse();
+        sqlResponse.setException(false);
+        sqlResponse.setQueryId("queryId");
+        sqlResponse.setQueryPushDown(false);
+        NativeQueryRealization realization = new NativeQueryRealization();
+        realization.setModelId("modelId1");
+        realization.setLayoutId(1L);
+        realization.setModelAlias("modelAlias1");
+        realization.setIndexType(QueryMetrics.AGG_INDEX);
+        realization.setPartialMatchModel(false);
+        realization.setValid(true);
+        List<NativeQueryRealization> nativeRealizations = 
Lists.newArrayList(realization);
+        sqlResponse.setNativeRealizations(nativeRealizations);
+
+        // mock NIndexPlanManager and IndexPlan
+        PowerMockito.mockStatic(NIndexPlanManager.class);
+        NIndexPlanManager nIndexPlanManager = 
Mockito.mock(NIndexPlanManager.class);
+        IndexPlan indexPlan = Mockito.mock(IndexPlan.class);
+        LayoutEntity layoutEntity = new LayoutEntity();
+        layoutEntity.setBase(true);
+
+        // mock method return value
+        Mockito.when(NIndexPlanManager.getInstance(Mockito.any(), 
Mockito.any())).thenReturn(nIndexPlanManager);
+        
Mockito.when(nIndexPlanManager.getIndexPlan(realization.getModelId())).thenReturn(indexPlan);
+        
Mockito.when(indexPlan.getLayoutEntity(realization.getLayoutId())).thenReturn(layoutEntity);
+        
Mockito.doReturn(sqlResponse).when(queryService).queryWithCache(sqlRequest);
+
+        // build excepted queryDetectVO
+        QueryDetectResponse exceptedQueryDetectResponse = new 
QueryDetectResponse().buildResponse(project, sqlResponse,
+                QueryContext.current());
+
+        QueryDetectResponse queryDetectResponse = 
queryService.queryDetect(queryDetectRequest);
+
+        Assert.assertEquals(exceptedQueryDetectResponse, queryDetectResponse);
+    }
+
+    @Test
+    public void testQueryDetectWhenIsConstants() {
+        QueryDetectRequest queryDetectRequest = new QueryDetectRequest("sql", 
"default", 0, 500);
+        // build sqlRequest
+        SQLRequest sqlRequest = new SQLRequest();
+        sqlRequest.setSql(queryDetectRequest.getSql());
+        String project = queryDetectRequest.getProject();
+        sqlRequest.setLimit(queryDetectRequest.getLimit());
+        sqlRequest.setOffset(queryDetectRequest.getOffset());
+        sqlRequest.setProject(project);
+
+        // build sqlResponse
+        SQLResponse sqlResponse = new SQLResponse();
+        sqlResponse.setException(false);
+        sqlResponse.setQueryId("queryId");
+        sqlResponse.setQueryPushDown(false);
+        List<NativeQueryRealization> nativeRealizations = Lists.newArrayList();
+        sqlResponse.setNativeRealizations(nativeRealizations);
+        sqlResponse.setEngineType(QueryHistory.EngineType.CONSTANTS.name());
+
+        // mock method return value
+        
Mockito.doReturn(sqlResponse).when(queryService).queryWithCache(sqlRequest);
+
+        // build excepted queryDetectVO
+        QueryDetectResponse exceptedQueryDetectResponse = new 
QueryDetectResponse().buildResponse(project, sqlResponse,
+                QueryContext.current());
+
+        QueryDetectResponse queryDetectResponse = 
queryService.queryDetect(queryDetectRequest);
+
+        Assert.assertEquals(exceptedQueryDetectResponse, queryDetectResponse);
+    }
+
+    @Test
+    public void testQueryDetectWhenIsSnapshot() {
+        QueryDetectRequest queryDetectRequest = new QueryDetectRequest("sql", 
"default", 0, 500);
+        // build sqlRequest
+        SQLRequest sqlRequest = new SQLRequest();
+        sqlRequest.setSql(queryDetectRequest.getSql());
+        String project = queryDetectRequest.getProject();
+        sqlRequest.setLimit(queryDetectRequest.getLimit());
+        sqlRequest.setOffset(queryDetectRequest.getOffset());
+        sqlRequest.setProject(project);
+
+        // build sqlResponse
+        SQLResponse sqlResponse = new SQLResponse();
+        sqlResponse.setException(false);
+        sqlResponse.setQueryId("queryId");
+        sqlResponse.setQueryPushDown(false);
+        NativeQueryRealization realization = new NativeQueryRealization();
+        realization.setModelId("modelId1");
+        realization.setLayoutId(-1L);
+        realization.setModelAlias("modelAlias1");
+        realization.setIndexType(QueryMetrics.AGG_INDEX);
+        realization.setPartialMatchModel(false);
+        realization.setValid(true);
+        List<NativeQueryRealization> nativeRealizations = 
Lists.newArrayList(realization);
+        sqlResponse.setNativeRealizations(nativeRealizations);
+
+        // mock NIndexPlanManager and IndexPlan
+        PowerMockito.mockStatic(NIndexPlanManager.class);
+        NIndexPlanManager nIndexPlanManager = 
Mockito.mock(NIndexPlanManager.class);
+        IndexPlan indexPlan = Mockito.mock(IndexPlan.class);
+
+        // mock method return value
+        Mockito.when(NIndexPlanManager.getInstance(Mockito.any(), 
Mockito.any())).thenReturn(nIndexPlanManager);
+        
Mockito.when(nIndexPlanManager.getIndexPlan(realization.getModelId())).thenReturn(indexPlan);
+        
Mockito.when(indexPlan.getLayoutEntity(realization.getLayoutId())).thenReturn(null);
+        
Mockito.doReturn(sqlResponse).when(queryService).queryWithCache(sqlRequest);
+
+        // build excepted queryDetectVO
+        QueryDetectResponse exceptedQueryDetectResponse = new 
QueryDetectResponse().buildResponse(project, sqlResponse,
+                QueryContext.current());
+
+        QueryDetectResponse queryDetectResponse = 
queryService.queryDetect(queryDetectRequest);
+
+        Assert.assertEquals(exceptedQueryDetectResponse, queryDetectResponse);
+    }
+
+    @Test
+    public void testQueryDetectWhenIndexPlanIsNull() {
+        QueryDetectRequest queryDetectRequest = new QueryDetectRequest("sql", 
"default", 0, 500);
+        // build sqlRequest
+        SQLRequest sqlRequest = new SQLRequest();
+        sqlRequest.setSql(queryDetectRequest.getSql());
+        String project = queryDetectRequest.getProject();
+        sqlRequest.setLimit(queryDetectRequest.getLimit());
+        sqlRequest.setOffset(queryDetectRequest.getOffset());
+        sqlRequest.setProject(project);
+
+        // build sqlResponse
+        SQLResponse sqlResponse = new SQLResponse();
+        sqlResponse.setException(false);
+        sqlResponse.setQueryId("queryId");
+        sqlResponse.setQueryPushDown(false);
+        NativeQueryRealization realization = new NativeQueryRealization();
+        realization.setModelId("modelId1");
+        realization.setLayoutId(-1L);
+        realization.setModelAlias("modelAlias1");
+        realization.setIndexType(QueryMetrics.AGG_INDEX);
+        realization.setPartialMatchModel(false);
+        realization.setValid(true);
+        List<NativeQueryRealization> nativeRealizations = 
Lists.newArrayList(realization);
+        sqlResponse.setNativeRealizations(nativeRealizations);
+
+        // mock NIndexPlanManager and IndexPlan
+        PowerMockito.mockStatic(NIndexPlanManager.class);
+        NIndexPlanManager nIndexPlanManager = 
Mockito.mock(NIndexPlanManager.class);
+
+        // mock method return value
+        Mockito.when(NIndexPlanManager.getInstance(Mockito.any(), 
Mockito.any())).thenReturn(nIndexPlanManager);
+        
Mockito.when(nIndexPlanManager.getIndexPlan(realization.getModelId())).thenReturn(null);
+        
Mockito.doReturn(sqlResponse).when(queryService).queryWithCache(sqlRequest);
+
+        // build excepted queryDetectVO
+        QueryDetectResponse exceptedQueryDetectResponse = new 
QueryDetectResponse().buildResponse(project, sqlResponse,
+                QueryContext.current());
+
+        QueryDetectResponse queryDetectResponse = 
queryService.queryDetect(queryDetectRequest);
+
+        Assert.assertEquals(exceptedQueryDetectResponse, queryDetectResponse);
+    }
+
+    @Test
+    public void testAddToQueryHistoryIsQueryDetect() throws Exception {
+        try (QueryContext queryContext = QueryContext.current()) {
+            queryContext.getQueryTagInfo().setQueryDetect(true);
+            Method method = 
QueryService.class.getDeclaredMethod("addToQueryHistory", SQLRequest.class,
+                    SQLResponse.class, String.class);
+            method.setAccessible(true);
+
+            // check void method is ok
+            method.invoke(queryService, null, null, null);
+        }
+    }
+
+    @Test
+    public void testIsQueryCacheEnabledIsQueryDetect() throws Exception {
+        try (QueryContext queryContext = QueryContext.current()) {
+            queryContext.getQueryTagInfo().setQueryDetect(true);
+            Method method = 
QueryService.class.getDeclaredMethod("isQueryCacheEnabled", KylinConfig.class);
+            method.setAccessible(true);
+            KylinConfig kylinConfig = KylinConfig.getInstanceFromEnv();
+            boolean isQueryCacheEnabled = (boolean) 
method.invoke(queryService, kylinConfig);
+
+            Assert.assertFalse(isQueryCacheEnabled);
+        }
+    }
+
+    @Test
+    public void testNotHitCacheWhenIsAsyncQuery() {
+        try (QueryContext queryContext = QueryContext.current()) {
+            queryContext.getQueryTagInfo().setAsyncQuery(true);
+            String project = "default";
+            String sql = "select * from table";
+            SQLRequest sqlRequest = new SQLRequest();
+            sqlRequest.setProject(project);
+            sqlRequest.setSql(sql);
+
+            queryService.doQueryWithCache(sqlRequest);
+
+            // async query API can not search cache
+            
Assert.assertFalse(queryContext.getQueryTagInfo().isStorageCacheUsed());
+            
Assert.assertFalse(queryContext.getQueryTagInfo().isHitExceptionCache());
+            Assert.assertTrue(queryContext.getQueryTagInfo().isAsyncQuery());
+        }
+    }
 }
diff --git 
a/src/query/src/main/java/org/apache/kylin/query/engine/QueryRoutingEngine.java 
b/src/query/src/main/java/org/apache/kylin/query/engine/QueryRoutingEngine.java
index 0e3f86b399..f1e5cf9054 100644
--- 
a/src/query/src/main/java/org/apache/kylin/query/engine/QueryRoutingEngine.java
+++ 
b/src/query/src/main/java/org/apache/kylin/query/engine/QueryRoutingEngine.java
@@ -54,6 +54,7 @@ import org.apache.kylin.metadata.project.NProjectManager;
 import org.apache.kylin.metadata.query.NativeQueryRealization;
 import org.apache.kylin.metadata.query.StructField;
 import org.apache.kylin.metadata.querymeta.SelectedColumnMeta;
+import org.apache.kylin.metadata.realization.NoRealizationFoundException;
 import 
org.apache.kylin.metadata.realization.NoStreamingRealizationFoundException;
 import org.apache.kylin.query.engine.data.QueryResult;
 import org.apache.kylin.query.exception.BusyQueryException;
@@ -260,6 +261,12 @@ public class QueryRoutingEngine {
     private QueryResult pushDownQuery(SQLException sqlException, QueryParams 
queryParams) throws SQLException {
         QueryContext.current().getMetrics().setOlapCause(sqlException);
         QueryContext.current().getQueryTagInfo().setPushdown(true);
+        if (QueryContext.current().getQueryTagInfo().isQueryDetect()) {
+            if (sqlException.getCause() instanceof 
NoRealizationFoundException) {
+                return new QueryResult();
+            }
+            throw sqlException;
+        }
         PushdownResult result = null;
         try {
             result = tryPushDownSelectQuery(queryParams, sqlException, 
BackdoorToggles.getPrepareOnly());
diff --git 
a/src/query/src/main/java/org/apache/kylin/query/engine/exec/sparder/SparderQueryPlanExec.java
 
b/src/query/src/main/java/org/apache/kylin/query/engine/exec/sparder/SparderQueryPlanExec.java
index 988eca606a..07c18197eb 100644
--- 
a/src/query/src/main/java/org/apache/kylin/query/engine/exec/sparder/SparderQueryPlanExec.java
+++ 
b/src/query/src/main/java/org/apache/kylin/query/engine/exec/sparder/SparderQueryPlanExec.java
@@ -76,6 +76,7 @@ public class SparderQueryPlanExec implements QueryPlanExec {
 
         
QueryContextCutter.selectRealization(QueryContext.current().getProject(), rel,
                 BackdoorToggles.getIsQueryFromAutoModeling());
+
         String msg = "EXECUTION PLAN AFTER (SparderQueryPlanExec) SELECT 
REALIZATION IS SET";
         ContextUtil.dumpCalcitePlan(msg, rel, log);
 
@@ -107,6 +108,11 @@ public class SparderQueryPlanExec implements QueryPlanExec 
{
         // rewrite
         rewrite(rel);
 
+        // query detect
+        if (QueryContext.current().getQueryTagInfo().isQueryDetect()) {
+            return new ExecuteResult(Lists.newArrayList(), 0);
+        }
+
         // submit rel and dataContext to query engine
         return internalCompute(new SparkEngine(), dataContext, 
rel.getInput(0));
     }
diff --git 
a/src/query/src/test/java/org/apache/kylin/query/engine/QueryRoutingEngineTest.java
 
b/src/query/src/test/java/org/apache/kylin/query/engine/QueryRoutingEngineTest.java
index dd50f2388f..fa929a34b2 100644
--- 
a/src/query/src/test/java/org/apache/kylin/query/engine/QueryRoutingEngineTest.java
+++ 
b/src/query/src/test/java/org/apache/kylin/query/engine/QueryRoutingEngineTest.java
@@ -21,6 +21,7 @@ package org.apache.kylin.query.engine;
 import static 
org.apache.kylin.query.engine.QueryRoutingEngine.SPARK_JOB_FAILED;
 import static 
org.apache.kylin.query.engine.QueryRoutingEngine.SPARK_MEM_LIMIT_EXCEEDED;
 
+import java.lang.reflect.Method;
 import java.sql.Date;
 import java.sql.SQLException;
 import java.sql.Time;
@@ -43,6 +44,7 @@ import org.apache.kylin.common.persistence.InMemResourceStore;
 import org.apache.kylin.common.persistence.ResourceStore;
 import org.apache.kylin.common.persistence.transaction.TransactionException;
 import org.apache.kylin.common.util.NLocalFileMetadataTestCase;
+import org.apache.kylin.metadata.realization.NoRealizationFoundException;
 import 
org.apache.kylin.metadata.realization.NoStreamingRealizationFoundException;
 import org.apache.kylin.query.QueryExtension;
 import org.apache.kylin.query.engine.data.QueryResult;
@@ -485,4 +487,36 @@ public class QueryRoutingEngineTest extends 
NLocalFileMetadataTestCase {
             slowQueryDetector.queryEnd();
         }
     }
+
+    @Test
+    public void testPushDownQueryWhenIsQueryDetect() throws Exception {
+        try (QueryContext queryContext = QueryContext.current()) {
+            queryContext.getQueryTagInfo().setQueryDetect(true);
+            Method method = 
QueryRoutingEngine.class.getDeclaredMethod("pushDownQuery", SQLException.class,
+                    QueryParams.class);
+            method.setAccessible(true);
+
+            try {
+                method.invoke(queryRoutingEngine, new SQLException(), null);
+                Assert.fail();
+            } catch (Exception e) {
+                Assert.assertTrue(e.getCause() instanceof SQLException);
+                
Assert.assertTrue(QueryContext.current().getQueryTagInfo().isPushdown());
+            }
+        }
+    }
+
+    @Test
+    public void testPushDownQueryWhenIsQueryDetectAndPushDown() throws 
Exception {
+        try (QueryContext queryContext = QueryContext.current()) {
+            queryContext.getQueryTagInfo().setQueryDetect(true);
+            Method method = 
QueryRoutingEngine.class.getDeclaredMethod("pushDownQuery", SQLException.class,
+                    QueryParams.class);
+            method.setAccessible(true);
+
+            Object result = method.invoke(queryRoutingEngine, new 
SQLException(new NoRealizationFoundException("")),
+                    null);
+            Assert.assertNotNull(result);
+        }
+    }
 }


Reply via email to