KYLIN-2575 translate computed column back to expression when sending to adhoc
Project: http://git-wip-us.apache.org/repos/asf/kylin/repo Commit: http://git-wip-us.apache.org/repos/asf/kylin/commit/7c381487 Tree: http://git-wip-us.apache.org/repos/asf/kylin/tree/7c381487 Diff: http://git-wip-us.apache.org/repos/asf/kylin/diff/7c381487 Branch: refs/heads/master Commit: 7c381487069d2bcf3adb07b38dc0b0803d13a8b4 Parents: 508fc23 Author: Hongbin Ma <mahong...@apache.org> Authored: Tue May 30 19:14:46 2017 +0800 Committer: hongbin ma <m...@kyligence.io> Committed: Tue May 30 23:47:23 2017 +0800 ---------------------------------------------------------------------- assembly/pom.xml | 6 - .../apache/kylin/metadata/MetadataManager.java | 12 +- .../kylin/metadata/model/DataModelDesc.java | 8 +- .../org/apache/kylin/query/KylinTestBase.java | 33 ++-- pom.xml | 4 + server-base/pom.xml | 12 +- .../apache/kylin/rest/service/QueryService.java | 2 +- .../org/apache/kylin/rest/util/AdHocUtil.java | 154 ++++++++++++++++++- .../apache/kylin/rest/util/AdHocUtilTest.java | 98 ++++++++++++ 9 files changed, 296 insertions(+), 33 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/kylin/blob/7c381487/assembly/pom.xml ---------------------------------------------------------------------- diff --git a/assembly/pom.xml b/assembly/pom.xml index dae7152..0a64dde 100644 --- a/assembly/pom.xml +++ b/assembly/pom.xml @@ -91,12 +91,6 @@ <scope>test</scope> </dependency> <dependency> - <groupId>org.apache.mrunit</groupId> - <artifactId>mrunit</artifactId> - <classifier>hadoop2</classifier> - <scope>test</scope> - </dependency> - <dependency> <groupId>org.apache.hbase</groupId> <artifactId>hbase-common</artifactId> <scope>provided</scope> http://git-wip-us.apache.org/repos/asf/kylin/blob/7c381487/core-metadata/src/main/java/org/apache/kylin/metadata/MetadataManager.java ---------------------------------------------------------------------- diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/MetadataManager.java b/core-metadata/src/main/java/org/apache/kylin/metadata/MetadataManager.java index f8e6832..2a894b9 100644 --- a/core-metadata/src/main/java/org/apache/kylin/metadata/MetadataManager.java +++ b/core-metadata/src/main/java/org/apache/kylin/metadata/MetadataManager.java @@ -116,13 +116,21 @@ public class MetadataManager { private CaseInsensitiveStringCache<ExternalFilterDesc> extFilterMap; public static class CCInfo { - public ComputedColumnDesc computedColumnDesc; - public Set<DataModelDesc> dataModelDescs; + private ComputedColumnDesc computedColumnDesc; + private Set<DataModelDesc> dataModelDescs; public CCInfo(ComputedColumnDesc computedColumnDesc, Set<DataModelDesc> dataModelDescs) { this.computedColumnDesc = computedColumnDesc; this.dataModelDescs = dataModelDescs; } + + public ComputedColumnDesc getComputedColumnDesc() { + return computedColumnDesc; + } + + public Set<DataModelDesc> getDataModelDescs() { + return dataModelDescs; + } } private Map<String, CCInfo> ccInfoMap = Maps.newHashMap();// this is to check any two models won't conflict computed columns http://git-wip-us.apache.org/repos/asf/kylin/blob/7c381487/core-metadata/src/main/java/org/apache/kylin/metadata/model/DataModelDesc.java ---------------------------------------------------------------------- diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/model/DataModelDesc.java b/core-metadata/src/main/java/org/apache/kylin/metadata/model/DataModelDesc.java index e759bdf..f5092a8 100644 --- a/core-metadata/src/main/java/org/apache/kylin/metadata/model/DataModelDesc.java +++ b/core-metadata/src/main/java/org/apache/kylin/metadata/model/DataModelDesc.java @@ -476,13 +476,13 @@ public class DataModelDesc extends RootPersistentEntity { } CCInfo other = ccInfoMap.get(computedColumnDesc.getFullName()); - if (other == null || (other.dataModelDescs.size() == 1 && other.dataModelDescs.contains(this))) { + if (other == null || (other.getDataModelDescs().size() == 1 && other.getDataModelDescs().contains(this))) { ccInfoMap.put(computedColumnDesc.getFullName(), new CCInfo(computedColumnDesc, Sets.<DataModelDesc> newHashSet(this))); - } else if (other.computedColumnDesc.equals(computedColumnDesc)) { - other.dataModelDescs.add(this); + } else if (other.getComputedColumnDesc().equals(computedColumnDesc)) { + other.getDataModelDescs().add(this); } else { throw new IllegalStateException(String.format("Computed column named %s is already defined in other models: %s. Please change another name, or try to keep consistent definition", // - computedColumnDesc.getFullName(), other.dataModelDescs)); + computedColumnDesc.getFullName(), other.getDataModelDescs())); } } } http://git-wip-us.apache.org/repos/asf/kylin/blob/7c381487/kylin-it/src/test/java/org/apache/kylin/query/KylinTestBase.java ---------------------------------------------------------------------- diff --git a/kylin-it/src/test/java/org/apache/kylin/query/KylinTestBase.java b/kylin-it/src/test/java/org/apache/kylin/query/KylinTestBase.java index 42f3a44..0db5388 100644 --- a/kylin-it/src/test/java/org/apache/kylin/query/KylinTestBase.java +++ b/kylin-it/src/test/java/org/apache/kylin/query/KylinTestBase.java @@ -42,15 +42,14 @@ import java.util.Set; import java.util.TreeSet; import java.util.logging.LogManager; -import com.google.common.collect.Lists; import org.apache.commons.lang3.StringUtils; import org.apache.kylin.common.KylinConfig; import org.apache.kylin.common.util.HBaseMetadataTestCase; import org.apache.kylin.common.util.Pair; import org.apache.kylin.metadata.project.ProjectInstance; +import org.apache.kylin.metadata.querymeta.SelectedColumnMeta; import org.apache.kylin.query.relnode.OLAPContext; import org.apache.kylin.query.routing.rules.RemoveBlackoutRealizationsRule; -import org.apache.kylin.metadata.querymeta.SelectedColumnMeta; import org.apache.kylin.rest.util.AdHocUtil; import org.dbunit.DatabaseUnitException; import org.dbunit.database.DatabaseConfig; @@ -70,6 +69,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; import com.google.common.io.Files; /** @@ -110,7 +110,8 @@ public class KylinTestBase { // h2 (BIGINT) public static class TestH2DataTypeFactory extends H2DataTypeFactory { @Override - public DataType createDataType(int sqlType, String sqlTypeName, String tableName, String columnName) throws DataTypeException { + public DataType createDataType(int sqlType, String sqlTypeName, String tableName, String columnName) + throws DataTypeException { if ((columnName.startsWith("COL") || columnName.startsWith("col")) && sqlType == Types.BIGINT) { return DataType.INTEGER; @@ -223,7 +224,8 @@ public class KylinTestBase { // //////////////////////////////////////////////////////////////////////////////////////// // execute - protected ITable executeQuery(IDatabaseConnection dbConn, String queryName, String sql, boolean needSort) throws Exception { + protected ITable executeQuery(IDatabaseConnection dbConn, String queryName, String sql, boolean needSort) + throws Exception { // change join type to match current setting sql = changeJoinType(sql, joinType); @@ -258,9 +260,9 @@ public class KylinTestBase { return output(resultSet, needDisplay); } catch (SQLException sqlException) { - List<List<String>> results = Lists.newArrayList(); + List<List<String>> results = Lists.newArrayList(); List<SelectedColumnMeta> columnMetas = Lists.newArrayList(); - AdHocUtil.doAdHocQuery(sql, results, columnMetas, sqlException); + AdHocUtil.doAdHocQuery(ProjectInstance.DEFAULT_PROJECT_NAME, sql, results, columnMetas, sqlException); return results.size(); } finally { if (resultSet != null) { @@ -280,7 +282,8 @@ public class KylinTestBase { } } - protected ITable executeDynamicQuery(IDatabaseConnection dbConn, String queryName, String sql, List<String> parameters, boolean needSort) throws Exception { + protected ITable executeDynamicQuery(IDatabaseConnection dbConn, String queryName, String sql, + List<String> parameters, boolean needSort) throws Exception { // change join type to match current setting sql = changeJoinType(sql, joinType); @@ -316,7 +319,8 @@ public class KylinTestBase { String[] tokens = StringUtils.split(sql, null);// split white spaces for (int i = 0; i < tokens.length - 1; ++i) { - if ((tokens[i].equalsIgnoreCase("inner") || tokens[i].equalsIgnoreCase("left")) && tokens[i + 1].equalsIgnoreCase("join")) { + if ((tokens[i].equalsIgnoreCase("inner") || tokens[i].equalsIgnoreCase("left")) + && tokens[i + 1].equalsIgnoreCase("join")) { tokens[i] = targetType.toLowerCase(); } } @@ -407,7 +411,8 @@ public class KylinTestBase { } } - protected void execAndCompResultSize(String queryFolder, String[] exclusiveQuerys, boolean needSort) throws Exception { + protected void execAndCompResultSize(String queryFolder, String[] exclusiveQuerys, boolean needSort) + throws Exception { logger.info("---------- test folder: " + queryFolder); Set<String> exclusiveSet = buildExclusiveSet(exclusiveQuerys); @@ -504,7 +509,6 @@ public class KylinTestBase { logger.info("Queries appended with limit: " + appendLimitQueries); } - protected void execAndCompQuery(String queryFolder, String[] exclusiveQuerys, boolean needSort) throws Exception { execAndCompQuery(queryFolder, exclusiveQuerys, needSort, new ICompareQueryTranslator() { @Override @@ -518,7 +522,8 @@ public class KylinTestBase { }); } - protected void execAndCompQuery(String queryFolder, String[] exclusiveQuerys, boolean needSort, ICompareQueryTranslator translator) throws Exception { + protected void execAndCompQuery(String queryFolder, String[] exclusiveQuerys, boolean needSort, + ICompareQueryTranslator translator) throws Exception { logger.info("---------- test folder: " + new File(queryFolder).getAbsolutePath()); Set<String> exclusiveSet = buildExclusiveSet(exclusiveQuerys); @@ -557,7 +562,8 @@ public class KylinTestBase { } } - protected void execAndCompDynamicQuery(String queryFolder, String[] exclusiveQuerys, boolean needSort) throws Exception { + protected void execAndCompDynamicQuery(String queryFolder, String[] exclusiveQuerys, boolean needSort) + throws Exception { logger.info("---------- test folder: " + queryFolder); Set<String> exclusiveSet = buildExclusiveSet(exclusiveQuerys); @@ -683,7 +689,8 @@ public class KylinTestBase { cubeConnection = QueryDataSource.create(ProjectInstance.DEFAULT_PROJECT_NAME, config).getConnection(); //setup h2 - h2Connection = DriverManager.getConnection("jdbc:h2:mem:db" + (h2InstanceCount++) + ";CACHE_SIZE=32072", "sa", ""); + h2Connection = DriverManager.getConnection("jdbc:h2:mem:db" + (h2InstanceCount++) + ";CACHE_SIZE=32072", "sa", + ""); // Load H2 Tables (inner join) H2Database h2DB = new H2Database(h2Connection, config); h2DB.loadAllTables(); http://git-wip-us.apache.org/repos/asf/kylin/blob/7c381487/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 2fcc6fa..f887c8d 100644 --- a/pom.xml +++ b/pom.xml @@ -93,6 +93,8 @@ <h2.version>1.4.192</h2.version> <jetty.version>9.2.20.v20161216</jetty.version> <jamm.version>0.3.1</jamm.version> + <mockito.version>2.7.14</mockito.version> + <!-- Commons --> <commons-lang3.version>3.4</commons-lang3.version> @@ -730,6 +732,8 @@ <artifactId>opensaml</artifactId> <version>${opensaml.version}</version> </dependency> + + <!-- Spring Core --> <dependency> <groupId>org.springframework</groupId> http://git-wip-us.apache.org/repos/asf/kylin/blob/7c381487/server-base/pom.xml ---------------------------------------------------------------------- diff --git a/server-base/pom.xml b/server-base/pom.xml index b165b99..c7247a5 100644 --- a/server-base/pom.xml +++ b/server-base/pom.xml @@ -17,7 +17,8 @@ limitations under the License. --> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> @@ -47,7 +48,7 @@ </exclusion> </exclusions> </dependency> - + <!-- these plug-in modules, should not have API dependencies --> <dependency> <groupId>org.apache.kylin</groupId> @@ -134,6 +135,13 @@ <type>test-jar</type> <scope>test</scope> </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + <!--MRUnit relies on older version of mockito, so cannot manage it globally--> + <version>${mockito.version}</version> + </dependency> <dependency> <groupId>org.apache.tomcat</groupId> http://git-wip-us.apache.org/repos/asf/kylin/blob/7c381487/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java ---------------------------------------------------------------------- diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java b/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java index 5130e55..06f9d80 100644 --- a/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java +++ b/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java @@ -729,7 +729,7 @@ public class QueryService extends BasicService { results.add(oneRow); } } catch (SQLException sqlException) { - isAdHoc = AdHocUtil.doAdHocQuery(correctedSql, results, columnMetas, sqlException); + isAdHoc = AdHocUtil.doAdHocQuery(sqlRequest.getProject(), correctedSql, results, columnMetas, sqlException); } finally { close(resultSet, stat, conn); } http://git-wip-us.apache.org/repos/asf/kylin/blob/7c381487/server-base/src/main/java/org/apache/kylin/rest/util/AdHocUtil.java ---------------------------------------------------------------------- diff --git a/server-base/src/main/java/org/apache/kylin/rest/util/AdHocUtil.java b/server-base/src/main/java/org/apache/kylin/rest/util/AdHocUtil.java index 678e58e..8221790 100644 --- a/server-base/src/main/java/org/apache/kylin/rest/util/AdHocUtil.java +++ b/server-base/src/main/java/org/apache/kylin/rest/util/AdHocUtil.java @@ -18,11 +18,25 @@ package org.apache.kylin.rest.util; +import static org.apache.kylin.metadata.MetadataManager.CCInfo; + import java.sql.SQLException; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.Nullable; +import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.lang3.tuple.Triple; import org.apache.kylin.common.KylinConfig; +import org.apache.kylin.metadata.MetadataManager; +import org.apache.kylin.metadata.model.DataModelDesc; +import org.apache.kylin.metadata.project.ProjectInstance; +import org.apache.kylin.metadata.project.ProjectManager; import org.apache.kylin.metadata.querymeta.SelectedColumnMeta; import org.apache.kylin.query.routing.NoRealizationFoundException; import org.apache.kylin.rest.exception.InternalErrorException; @@ -31,11 +45,19 @@ import org.apache.kylin.storage.adhoc.IAdhocConverter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + public class AdHocUtil { private static final Logger logger = LoggerFactory.getLogger(AdHocUtil.class); - public static boolean doAdHocQuery(String sql, List<List<String>> results, List<SelectedColumnMeta> columnMetas, SQLException sqlException) throws Exception { - boolean isExpectedCause = (ExceptionUtils.getRootCause(sqlException).getClass().equals(NoRealizationFoundException.class)); + public static boolean doAdHocQuery(String project, String sql, List<List<String>> results, + List<SelectedColumnMeta> columnMetas, SQLException sqlException) throws Exception { + + boolean isExpectedCause = (ExceptionUtils.getRootCause(sqlException).getClass() + .equals(NoRealizationFoundException.class)); KylinConfig kylinConfig = KylinConfig.getInstanceFromEnv(); Boolean isAdHoc = false; @@ -62,9 +84,10 @@ public class AdHocUtil { runner.init(); try { - String adhocSql = converter.convert(sql); - if (!sql.equals(adhocSql)) { - logger.info("the original query is converted to {} before delegating to adhoc", adhocSql); + String expandCC = restoreComputedColumnToExpr(sql, project); + String adhocSql = converter.convert(expandCC); + if (!adhocSql.equals(adhocSql)) { + logger.info("before delegating to adhoc, the query is converted to {} ", adhocSql); } runner.executeQuery(adhocSql, results, columnMetas); @@ -78,4 +101,125 @@ public class AdHocUtil { return isAdHoc; } + + private final static Pattern identifierInSqlPattern = Pattern.compile( + //find pattern like "table"."column" or "column" + "((?<![\\p{L}_0-9\\.\\\"])(\\\"[\\p{L}_0-9]+\\\"\\.)?(\\\"[\\p{L}_0-9]+\\\")(?![\\p{L}_0-9\\.\\\"]))" + "|" + //find pattern like table.column or column + + "((?<![\\p{L}_0-9\\.\\\"])([\\p{L}_0-9]+\\.)?([\\p{L}_0-9]+)(?![\\p{L}_0-9\\.\\\"]))"); + + private final static Pattern identifierInExprPattern = Pattern.compile( + // a.b.c + "((?<![\\p{L}_0-9\\.\\\"])([\\p{L}_0-9]+\\.)([\\p{L}_0-9]+\\.)([\\p{L}_0-9]+)(?![\\p{L}_0-9\\.\\\"]))"); + + private final static Pattern endWithAsPattern = Pattern.compile("\\s+as\\s+$", Pattern.CASE_INSENSITIVE); + + public static String restoreComputedColumnToExpr(String beforeSql, String project) { + MetadataManager metadataManager = MetadataManager.getInstance(KylinConfig.getInstanceFromEnv()); + Map<String, CCInfo> ccInfoMap = metadataManager.getCcInfoMap(); + final ProjectInstance projectInstance = ProjectManager.getInstance(KylinConfig.getInstanceFromEnv()) + .getProject(project); + + Iterable<CCInfo> projectCCInfo = Iterables.filter(ccInfoMap.values(), new Predicate<CCInfo>() { + @Override + public boolean apply(@Nullable CCInfo ccInfo) { + return Iterables.any(ccInfo.getDataModelDescs(), new Predicate<DataModelDesc>() { + @Override + public boolean apply(@Nullable DataModelDesc model) { + return projectInstance.containsModel(model.getName()); + } + }); + } + }); + + String afterSql = beforeSql; + for (CCInfo ccInfo : projectCCInfo) { + afterSql = restoreComputedColumnToExpr(afterSql, ccInfo); + } + + if (!StringUtils.equals(beforeSql, afterSql)) { + logger.info("computed column in sql is expanded before sending to adhoc engine: " + afterSql); + } + return afterSql; + } + + static String restoreComputedColumnToExpr(String sql, CCInfo ccInfo) { + + String ccName = ccInfo.getComputedColumnDesc().getColumnName(); + List<Triple<Integer, Integer, String>> replacements = Lists.newArrayList(); + Matcher matcher = identifierInSqlPattern.matcher(sql); + + while (matcher.find()) { + if (matcher.group(1) != null) { //with quote case: "TABLE"."COLUMN" + + String quotedColumnName = matcher.group(3); + Preconditions.checkNotNull(quotedColumnName); + String columnName = StringUtils.strip(quotedColumnName, "\""); + if (!columnName.equalsIgnoreCase(ccName)) { + continue; + } + + if (matcher.group(2) != null) { // table name exist + String quotedTableAlias = StringUtils.strip(matcher.group(2), "."); + String tableAlias = StringUtils.strip(quotedTableAlias, "\""); + replacements.add(Triple.of(matcher.start(1), matcher.end(1), + replaceIdentifierInExpr(ccInfo.getComputedColumnDesc().getExpression(), tableAlias, true))); + } else { //only column + if (endWithAsPattern.matcher(sql.substring(0, matcher.start(1))).find()) { + //select DEAL_AMOUNT as "deal_amount" case + continue; + } + replacements.add(Triple.of(matcher.start(1), matcher.end(1), + replaceIdentifierInExpr(ccInfo.getComputedColumnDesc().getExpression(), null, true))); + } + } else if (matcher.group(4) != null) { //without quote case: table.column or simply column + String columnName = matcher.group(6); + Preconditions.checkNotNull(columnName); + if (!columnName.equalsIgnoreCase(ccName)) { + continue; + } + + if (matcher.group(5) != null) { //table name exist + String tableAlias = StringUtils.strip(matcher.group(5), "."); + replacements.add(Triple.of(matcher.start(4), matcher.end(4), replaceIdentifierInExpr( + ccInfo.getComputedColumnDesc().getExpression(), tableAlias, false))); + + } else { //only column + if (endWithAsPattern.matcher(sql.substring(0, matcher.start(4))).find()) { + //select DEAL_AMOUNT as deal_amount case + continue; + } + replacements.add(Triple.of(matcher.start(4), matcher.end(4), + replaceIdentifierInExpr(ccInfo.getComputedColumnDesc().getExpression(), null, false))); + } + } + } + + Collections.reverse(replacements); + for (Triple<Integer, Integer, String> triple : replacements) { + sql = sql.substring(0, triple.getLeft()) + "(" + triple.getRight() + ")" + + sql.substring(triple.getMiddle()); + } + return sql; + } + + // identifier in expr must be DB.TABLE.COLUMN, all TABLE in expr should be guaranteed to be same + static String replaceIdentifierInExpr(String expr, String tableAlias, boolean quoted) { + List<Triple<Integer, Integer, String>> replacements = Lists.newArrayList(); + Matcher matcher = identifierInExprPattern.matcher(expr); + while (matcher.find()) { + + String t = tableAlias == null ? StringUtils.strip(matcher.group(3), ".") : tableAlias; + String c = matcher.group(4); + + String replacement = quoted ? "\"" + t.toUpperCase() + "\".\"" + c.toUpperCase() + "\"" : t + "." + c; + replacements.add(Triple.of(matcher.start(1), matcher.end(1), replacement)); + } + + Collections.reverse(replacements); + for (Triple<Integer, Integer, String> triple : replacements) { + expr = expr.substring(0, triple.getLeft()) + triple.getRight() + expr.substring(triple.getMiddle()); + } + return expr; + } } http://git-wip-us.apache.org/repos/asf/kylin/blob/7c381487/server-base/src/test/java/org/apache/kylin/rest/util/AdHocUtilTest.java ---------------------------------------------------------------------- diff --git a/server-base/src/test/java/org/apache/kylin/rest/util/AdHocUtilTest.java b/server-base/src/test/java/org/apache/kylin/rest/util/AdHocUtilTest.java new file mode 100644 index 0000000..b93e2d3 --- /dev/null +++ b/server-base/src/test/java/org/apache/kylin/rest/util/AdHocUtilTest.java @@ -0,0 +1,98 @@ +/* + * 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.util; + +import static org.apache.kylin.metadata.MetadataManager.CCInfo; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.kylin.metadata.model.ComputedColumnDesc; +import org.junit.Assert; +import org.junit.Test; + +public class AdHocUtilTest { + + @Test + public void testReplaceIdentifierInExpr() { + { + String ret = AdHocUtil.replaceIdentifierInExpr("a.b.x * a.b.y", null, false); + Assert.assertEquals("b.x * b.y", ret); + } + { + String ret = AdHocUtil.replaceIdentifierInExpr("a_1.b_2.x_3 * a_1.b_2.y_3", null, false); + Assert.assertEquals("b_2.x_3 * b_2.y_3", ret); + } + { + String ret = AdHocUtil.replaceIdentifierInExpr("a.b.x * a.b.y", "c", false); + Assert.assertEquals("c.x * c.y", ret); + } + { + String ret = AdHocUtil.replaceIdentifierInExpr("a.b.x * a.b.y", "c", true); + Assert.assertEquals("\"C\".\"X\" * \"C\".\"Y\"", ret); + } + { + String ret = AdHocUtil.replaceIdentifierInExpr("substr(a.b.x,1,3)>a.b.y", "c", true); + Assert.assertEquals("substr(\"C\".\"X\",1,3)>\"C\".\"Y\"", ret); + } + { + String ret = AdHocUtil.replaceIdentifierInExpr("strcmp(substr(a.b.x,1,3),a.b.y) > 0", "c", true); + Assert.assertEquals("strcmp(substr(\"C\".\"X\",1,3),\"C\".\"Y\") > 0", ret); + } + { + String ret = AdHocUtil.replaceIdentifierInExpr("strcmp(substr(a.b.x,1,3),a.b.y) > 0", null, true); + Assert.assertEquals("strcmp(substr(\"B\".\"X\",1,3),\"B\".\"Y\") > 0", ret); + } + { + String ret = AdHocUtil.replaceIdentifierInExpr("strcmp(substr(a.b.x, 1, 3),a.b.y) > 0", null, false); + Assert.assertEquals("strcmp(substr(b.x, 1, 3),b.y) > 0", ret); + } + } + + @Test + public void testRestoreComputedColumnToExpr() { + + ComputedColumnDesc computedColumnDesc = mock(ComputedColumnDesc.class); + when(computedColumnDesc.getColumnName()).thenReturn("DEAL_AMOUNT"); + when(computedColumnDesc.getExpression()).thenReturn("DB.TABLE.price * DB.TABLE.number"); + + CCInfo ccInfo = mock(CCInfo.class); + when(ccInfo.getComputedColumnDesc()).thenReturn(computedColumnDesc); + + { + String ret = AdHocUtil.restoreComputedColumnToExpr( + "select DEAL_AMOUNT from DB.TABLE group by date order by DEAL_AMOUNT", ccInfo); + Assert.assertEquals( + "select (TABLE.price * TABLE.number) from DB.TABLE group by date order by (TABLE.price * TABLE.number)", + ret); + } + { + String ret = AdHocUtil.restoreComputedColumnToExpr( + "select DEAL_AMOUNT as DEAL_AMOUNT from DB.TABLE group by date order by DEAL_AMOUNT", ccInfo); + Assert.assertEquals( + "select (TABLE.price * TABLE.number) as DEAL_AMOUNT from DB.TABLE group by date order by (TABLE.price * TABLE.number)", + ret); + } + { + String ret = AdHocUtil.restoreComputedColumnToExpr( + "select \"DEAL_AMOUNT\" AS deal_amount from DB.TABLE group by date order by DEAL_AMOUNT", ccInfo); + Assert.assertEquals( + "select (\"TABLE\".\"PRICE\" * \"TABLE\".\"NUMBER\") AS deal_amount from DB.TABLE group by date order by (TABLE.price * TABLE.number)", + ret); + } + } +}