This is an automated email from the ASF dual-hosted git repository. xxyu pushed a commit to branch kylin5 in repository https://gitbox.apache.org/repos/asf/kylin.git
commit 5657afc7f2a827419d8b8beda4b294c75f6245e0 Author: Pengfei Zhan <dethr...@gmail.com> AuthorDate: Wed Feb 15 21:35:56 2023 +0800 KYLIN-5523 [FOLLOW UP] fix some function of cc as join key or filter column --- .../org/apache/kylin/common/KylinConfigBase.java | 7 +- .../kylin-backward-compatibility.properties | 1 + .../org/apache/kylin/job/JoinedFlatTableTest.java | 242 -------------- .../kylin/metadata/model/JoinedFlatTable.java | 371 --------------------- .../apache/kylin/rest/service/ModelService.java | 53 ++- .../kylin/rest/service/ModelServiceTest.java | 32 +- .../org/apache/kylin/query/util/PushDownUtil.java | 59 +++- .../apache/kylin/query/util/QueryAliasMatcher.java | 25 +- .../apache/kylin/query/util/CCOnRealModelTest.java | 11 +- .../apache/kylin/query/util/PushDownUtilTest.java | 171 +++++++++- 10 files changed, 282 insertions(+), 690 deletions(-) diff --git a/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java b/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java index 0d4796e6e3..f20dca320b 100644 --- a/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java +++ b/src/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java @@ -2502,6 +2502,7 @@ public abstract class KylinConfigBase implements Serializable { public int getCalciteBindableCacheSize() { return Integer.parseInt(getOptional("kylin.query.calcite.bindable.cache.maxSize", "10")); } + public int getCalciteBindableCacheConcurrencyLevel() { return Integer.parseInt(getOptional("kylin.query.calcite.bindable.cache.concurrencyLevel", "5")); } @@ -3456,7 +3457,7 @@ public abstract class KylinConfigBase implements Serializable { } public boolean skipCheckFlatTable() { - return Boolean.parseBoolean(getOptional("kylin.model.skip-flattable-check", FALSE)); + return Boolean.parseBoolean(getOptional("kylin.model.skip-check-flattable", FALSE)); } public boolean isQueryExceptionCacheEnabled() { @@ -3570,10 +3571,6 @@ public abstract class KylinConfigBase implements Serializable { return Boolean.parseBoolean(getOptional("kylin.table.fast-reload-enabled", TRUE)); } - public boolean isSkipCheckFlatTable() { - return Boolean.parseBoolean(getOptional("kylin.model.skip-check-flattable", FALSE)); - } - public boolean isUnitOfWorkSimulationEnabled() { return Boolean.parseBoolean(getOptional("kylin.env.unitofwork-simulation-enabled", FALSE)); } diff --git a/src/core-common/src/main/resources/kylin-backward-compatibility.properties b/src/core-common/src/main/resources/kylin-backward-compatibility.properties index d14394d8dd..fc239ae6a8 100644 --- a/src/core-common/src/main/resources/kylin-backward-compatibility.properties +++ b/src/core-common/src/main/resources/kylin-backward-compatibility.properties @@ -28,6 +28,7 @@ kylin.realization.providers=kylin.metadata.realization-providers kylin.cube.dimension.customEncodingFactories=kylin.metadata.custom-dimension-encodings kylin.cube.measure.customMeasureType.=kylin.metadata.custom-measure-types. kap.metadata.semi-automatic-mode=kylin.metadata.semi-automatic-mode +kylin.model.skip-flattable-check=kylin.model.skip-check-flattable ### Dictionary ### diff --git a/src/core-job/src/test/java/org/apache/kylin/job/JoinedFlatTableTest.java b/src/core-job/src/test/java/org/apache/kylin/job/JoinedFlatTableTest.java deleted file mode 100644 index 52ab091c31..0000000000 --- a/src/core-job/src/test/java/org/apache/kylin/job/JoinedFlatTableTest.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * 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.job; - -import java.util.List; - -import org.apache.calcite.avatica.util.Quoting; -import org.apache.kylin.common.util.RandomUtil; -import org.apache.kylin.metadata.model.ColumnDesc; -import org.apache.kylin.metadata.model.JoinDesc; -import org.apache.kylin.metadata.model.JoinTableDesc; -import org.apache.kylin.metadata.model.NonEquiJoinCondition; -import org.apache.kylin.metadata.model.TableDesc; -import org.apache.kylin.metadata.model.TableRef; -import org.apache.kylin.metadata.model.TblColRef; -import org.apache.kylin.metadata.model.ComputedColumnDesc; -import org.apache.kylin.metadata.model.JoinedFlatTable; -import org.apache.kylin.metadata.model.NDataModel; -import org.assertj.core.util.Lists; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import com.google.common.collect.ImmutableBiMap; - -public class JoinedFlatTableTest { - - private static final String QUOTE = Quoting.DOUBLE_QUOTE.string; - private NDataModel dataModel = new NDataModel(); - - @Before - public void setUp() { - dataModel.setUuid(RandomUtil.randomUUIDStr()); - - TableDesc lineOrderTableDesc = new TableDesc(); - lineOrderTableDesc.setUuid("665a66d6-08d6-42b8-9be8-d9a0456ee250"); - lineOrderTableDesc.setName("LINEORDER"); - lineOrderTableDesc.setDatabase("SSB"); - lineOrderTableDesc.setSourceType(9); - lineOrderTableDesc.setTableType("MANAGED"); - - ColumnDesc loSuppkeyColDesc = new ColumnDesc(); - loSuppkeyColDesc.setId("1"); - loSuppkeyColDesc.setName("LO_SUPPKEY"); - loSuppkeyColDesc.setDatatype("integer"); - loSuppkeyColDesc.setComment("null"); - loSuppkeyColDesc.setTable(lineOrderTableDesc); - - ColumnDesc loTaxColDesc = new ColumnDesc(); - loTaxColDesc.setId("2"); - loTaxColDesc.setName("LO_TAX"); - loTaxColDesc.setDatatype("bigint"); - loTaxColDesc.setComment("null"); - loTaxColDesc.setTable(lineOrderTableDesc); - - ColumnDesc loRevenueColDesc = new ColumnDesc(); - loRevenueColDesc.setId("3"); - loRevenueColDesc.setName("LO_REVENUE"); - loRevenueColDesc.setDatatype("bigint"); - loRevenueColDesc.setComment("null"); - loRevenueColDesc.setTable(lineOrderTableDesc); - - ColumnDesc cc = new ColumnDesc("4", "PROFIT", "bigint", null, null, null, - "case when `LINEORDER`.`LO_REVENUE` - `LINEORDER`.`LO_TAX` > 0 then 'LINEORDER' else null end"); - cc.setTable(lineOrderTableDesc); - - lineOrderTableDesc.setColumns(new ColumnDesc[] { loSuppkeyColDesc, loTaxColDesc, loRevenueColDesc, cc }); - TableRef lineOrderTableRef = new TableRef(dataModel, "LINEORDER", lineOrderTableDesc, false); - - TableDesc supplierTableDesc = new TableDesc(); - supplierTableDesc.setUuid("719e9bf4-82de-40ec-9454-ab43ff94eef4"); - supplierTableDesc.setName("SUPPLIER"); - supplierTableDesc.setDatabase("SSB"); - supplierTableDesc.setSourceType(9); - supplierTableDesc.setTableType("MANAGED"); - - ColumnDesc sSuppkey = new ColumnDesc(); - sSuppkey.setId("1"); - sSuppkey.setName("S_SUPPKEY"); - sSuppkey.setDatatype("integer"); - sSuppkey.setComment("null"); - sSuppkey.setTable(supplierTableDesc); - - ColumnDesc sCity = new ColumnDesc(); - sCity.setId("2"); - sCity.setName("S_CITY"); - sCity.setDatatype("varchar(4096)"); - sCity.setComment("null"); - sCity.setTable(supplierTableDesc); - - supplierTableDesc.setColumns(new ColumnDesc[] { sSuppkey, sCity }); - TableRef supplierTableRef = new TableRef(dataModel, "SUPPLIER", supplierTableDesc, false); - - ImmutableBiMap.Builder<Integer, TblColRef> effectiveCols = ImmutableBiMap.builder(); - effectiveCols.put(1, lineOrderTableRef.getColumn("LO_SUPPKEY")); - effectiveCols.put(2, lineOrderTableRef.getColumn("LO_REVENUE")); - effectiveCols.put(3, lineOrderTableRef.getColumn("LO_TAX")); - effectiveCols.put(4, lineOrderTableRef.getColumn("PROFIT")); - effectiveCols.put(5, supplierTableRef.getColumn("S_SUPPKEY")); - effectiveCols.put(6, supplierTableRef.getColumn("S_CITY")); - dataModel.setEffectiveCols(effectiveCols.build()); - - dataModel.setRootFactTableName("SSB.LINEORDER"); - dataModel.setRootFactTableRef(lineOrderTableRef); - - JoinDesc joinDesc = new JoinDesc(); - joinDesc.setType("LEFT"); - joinDesc.setPrimaryKey(new String[] { "LINEORDER.LO_SUPPKEY" }); - joinDesc.setForeignKey(new String[] { "SUPPLIER.S_SUPPKEY" }); - joinDesc.setPrimaryTableRef(lineOrderTableRef); - joinDesc.setPrimaryKeyColumns(new TblColRef[] { new TblColRef(lineOrderTableRef, loSuppkeyColDesc) }); - joinDesc.setForeignKeyColumns(new TblColRef[] { new TblColRef(supplierTableRef, sSuppkey) }); - JoinTableDesc supplierJoinTableDesc = new JoinTableDesc(); - supplierJoinTableDesc.setTable("SSB.SUPPLIER"); - supplierJoinTableDesc.setAlias("SUPPLIER"); - supplierJoinTableDesc.setKind(NDataModel.TableKind.LOOKUP); - supplierJoinTableDesc.setTableRef(supplierTableRef); - supplierJoinTableDesc.setJoin(joinDesc); - - dataModel.setJoinTables(Lists.newArrayList(supplierJoinTableDesc)); - dataModel.setFilterCondition("SUPPLIER.S_CITY != 'beijing'"); - ComputedColumnDesc cc1 = new ComputedColumnDesc(); - cc1.setExpression( - "case when \"LINEORDER\".\"LO_REVENUE\" - \"LINEORDER\".\"LO_TAX\" > 0 then 'LINEORDER' else null end"); - cc1.setInnerExpression( - "case when `LINEORDER`.`LO_REVENUE` - `LINEORDER`.`LO_TAX` > 0 then 'LINEORDER' else null end"); - cc1.setColumnName("PROFIT"); - cc1.setDatatype("bigint"); - dataModel.setComputedColumnDescs(Lists.newArrayList(cc1)); - } - - @Test - public void testQuoteIdentifierInSqlExpr() { - String cc = JoinedFlatTable.quoteIdentifierInSqlExpr(dataModel, "LINEORDER.LO_REVENUE-LINEORDER.LO_TAX", QUOTE); - Assert.assertEquals("\"LINEORDER\".\"LO_REVENUE\"-\"LINEORDER\".\"LO_TAX\"", cc); - - String where1 = JoinedFlatTable.quoteIdentifierInSqlExpr(dataModel, "LINEORDER.LO_REVENUE-LINEORDER.LO_TAX>0", - QUOTE); - Assert.assertEquals("\"LINEORDER\".\"LO_REVENUE\"-\"LINEORDER\".\"LO_TAX\">0", where1); - - String where2 = JoinedFlatTable.quoteIdentifierInSqlExpr(dataModel, - "LINEORDER.LO_REVENUE>100 AND LINEORDER.LO_TAX>0", QUOTE); - Assert.assertEquals("\"LINEORDER\".\"LO_REVENUE\">100 AND \"LINEORDER\".\"LO_TAX\">0", where2); - - String where3 = JoinedFlatTable.quoteIdentifierInSqlExpr(dataModel, - "`LINEORDER`.`LO_REVENUE`>100 AND `LINEORDER`.`LO_TAX`>0", QUOTE); - Assert.assertEquals("\"LINEORDER\".\"LO_REVENUE\">100 AND \"LINEORDER\".\"LO_TAX\">0", where3); - } - - @Test - public void testGenerateSelectDataStatement() { - String flatTableSql = JoinedFlatTable.generateSelectDataStatement(dataModel, false); - String expectedSql = "SELECT \n" // - + "\"LINEORDER\".\"LO_SUPPKEY\" as \"LINEORDER_LO_SUPPKEY\",\n" - + "\"LINEORDER\".\"LO_REVENUE\" as \"LINEORDER_LO_REVENUE\",\n" - + "\"LINEORDER\".\"LO_TAX\" as \"LINEORDER_LO_TAX\",\n" - + "\"SUPPLIER\".\"S_SUPPKEY\" as \"SUPPLIER_S_SUPPKEY\",\n" - + "\"SUPPLIER\".\"S_CITY\" as \"SUPPLIER_S_CITY\"\n" // - + "FROM \n" // - + "\"SSB\".\"LINEORDER\" as \"LINEORDER\" \n" // - + "LEFT JOIN \"SSB\".\"SUPPLIER\" as \"SUPPLIER\"\n" // - + "ON \"SUPPLIER\".\"S_SUPPKEY\"=\"LINEORDER\".\"LO_SUPPKEY\"\n" // - + "WHERE \n" // - + "1 = 1\n" // - + " AND (\"SUPPLIER\".\"S_CITY\" != 'beijing')"; - Assert.assertEquals(expectedSql, flatTableSql.trim()); - - NonEquiJoinCondition nonEquiJoinCondition = new NonEquiJoinCondition(); - nonEquiJoinCondition.setExpr("SUPPLIER.S_SUPPKEY <> LINEORDER.LO_SUPPKEY AND LINEORDER.LO_SUPPKEY > 10"); - dataModel.getJoinTables().get(0).getJoin().setNonEquiJoinCondition(nonEquiJoinCondition); - String nonEquiJoinConditionSql = JoinedFlatTable.generateSelectDataStatement(dataModel, false); - Assert.assertTrue(nonEquiJoinConditionSql.contains( - "ON \"SUPPLIER\".\"S_SUPPKEY\" <> \"LINEORDER\".\"LO_SUPPKEY\" AND \"LINEORDER\".\"LO_SUPPKEY\" > 10")); - dataModel.getJoinTables().get(0).getJoin().setNonEquiJoinCondition(null); - } - - @Test - public void testQuoteIdentifier() { - List<String> tablePatterns = JoinedFlatTable.getTableNameOrAliasPatterns("KYLIN_SALES"); - String exprTable = "KYLIN_SALES.PRICE * KYLIN_SALES.COUNT"; - String expectedExprTable = "\"KYLIN_SALES\".PRICE * \"KYLIN_SALES\".COUNT"; - String quotedExprTable = JoinedFlatTable.quoteIdentifier(exprTable, QUOTE, "KYLIN_SALES", tablePatterns); - Assert.assertEquals(expectedExprTable, quotedExprTable); - - exprTable = "`KYLIN_SALES`.PRICE * KYLIN_SALES.COUNT"; - expectedExprTable = "\"KYLIN_SALES\".PRICE * \"KYLIN_SALES\".COUNT"; - quotedExprTable = JoinedFlatTable.quoteIdentifier(exprTable, QUOTE, "KYLIN_SALES", tablePatterns); - Assert.assertEquals(expectedExprTable, quotedExprTable); - - exprTable = "KYLIN_SALES.PRICE AS KYLIN_SALES_PRICE * KYLIN_SALES.COUNT AS KYLIN_SALES_COUNT"; - expectedExprTable = "\"KYLIN_SALES\".PRICE AS KYLIN_SALES_PRICE * \"KYLIN_SALES\".COUNT AS KYLIN_SALES_COUNT"; - quotedExprTable = JoinedFlatTable.quoteIdentifier(exprTable, QUOTE, "KYLIN_SALES", tablePatterns); - Assert.assertEquals(expectedExprTable, quotedExprTable); - - exprTable = "(KYLIN_SALES.PRICE AS KYLIN_SALES_PRICE > 1 and KYLIN_SALES.COUNT AS KYLIN_SALES_COUNT > 50)"; - expectedExprTable = "(\"KYLIN_SALES\".PRICE AS KYLIN_SALES_PRICE > 1 and \"KYLIN_SALES\".COUNT AS KYLIN_SALES_COUNT > 50)"; - quotedExprTable = JoinedFlatTable.quoteIdentifier(exprTable, QUOTE, "KYLIN_SALES", tablePatterns); - Assert.assertEquals(expectedExprTable, quotedExprTable); - - List<String> columnPatterns = JoinedFlatTable.getColumnNameOrAliasPatterns("PRICE"); - String expr = "KYLIN_SALES.PRICE * KYLIN_SALES.COUNT"; - String expectedExpr = "KYLIN_SALES.\"PRICE\" * KYLIN_SALES.COUNT"; - String quotedExpr = JoinedFlatTable.quoteIdentifier(expr, QUOTE, "PRICE", columnPatterns); - Assert.assertEquals(expectedExpr, quotedExpr); - - expr = "KYLIN_SALES.PRICE / KYLIN_SALES.COUNT"; - expectedExpr = "KYLIN_SALES.\"PRICE\" / KYLIN_SALES.COUNT"; - quotedExpr = JoinedFlatTable.quoteIdentifier(expr, QUOTE, "PRICE", columnPatterns); - Assert.assertEquals(expectedExpr, quotedExpr); - - expr = "KYLIN_SALES.PRICE AS KYLIN_SALES_PRICE * KYLIN_SALES.COUNT AS KYLIN_SALES_COUNT"; - expectedExpr = "KYLIN_SALES.\"PRICE\" AS KYLIN_SALES_PRICE * KYLIN_SALES.COUNT AS KYLIN_SALES_COUNT"; - quotedExpr = JoinedFlatTable.quoteIdentifier(expr, QUOTE, "PRICE", columnPatterns); - Assert.assertEquals(expectedExpr, quotedExpr); - - expr = "(PRICE > 1 AND COUNT > 50)"; - expectedExpr = "(\"PRICE\" > 1 AND COUNT > 50)"; - quotedExpr = JoinedFlatTable.quoteIdentifier(expr, QUOTE, "PRICE", columnPatterns); - Assert.assertEquals(expectedExpr, quotedExpr); - - expr = "PRICE>1 and `PRICE` < 15"; - expectedExpr = "\"PRICE\">1 and \"PRICE\" < 15"; - quotedExpr = JoinedFlatTable.quoteIdentifier(expr, QUOTE, "PRICE", columnPatterns); - Assert.assertEquals(expectedExpr, quotedExpr); - } -} diff --git a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/JoinedFlatTable.java b/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/JoinedFlatTable.java deleted file mode 100644 index b222252d63..0000000000 --- a/src/core-metadata/src/main/java/org/apache/kylin/metadata/model/JoinedFlatTable.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * 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.metadata.model; - -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.calcite.avatica.util.Quoting; -import org.apache.commons.lang3.StringUtils; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; - -public class JoinedFlatTable { - - private static final String DATABASE_AND_TABLE = "%s.%s"; - - private static final String QUOTE = Quoting.DOUBLE_QUOTE.string; - private static final String UNDER_LINE = "_"; - private static final String DOT = "."; - private static final Pattern BACK_TICK_DOT_PATTERN = Pattern - .compile("`[^\\f\\n\\r\\t\\v]+?`\\.`[^\\f\\n\\r\\t\\v]+?`"); - - private JoinedFlatTable() { - } - - private static String quote(String identifier) { - return QUOTE + identifier + QUOTE; - } - - private static String colName(TblColRef col) { - return col.getTableAlias() + UNDER_LINE + col.getName(); - } - - private static String quotedTable(TableDesc table) { - if (table.getCaseSensitiveDatabase().equals("null")) { - return quote(table.getCaseSensitiveName().toUpperCase(Locale.ROOT)); - } - return String.format(Locale.ROOT, DATABASE_AND_TABLE, - quote(table.getCaseSensitiveDatabase().toUpperCase(Locale.ROOT)), - quote(table.getCaseSensitiveName().toUpperCase(Locale.ROOT))); - } - - private static String quotedColExpressionInSourceDB(NDataModel modelDesc, TblColRef col) { - Map<String, ComputedColumnDesc> ccMap = Maps.newHashMap(); - modelDesc.getComputedColumnDescs().forEach(cc -> ccMap.putIfAbsent(cc.getColumnName(), cc)); - if (!col.getColumnDesc().isComputedColumn()) { - return quote(col.getTableAlias()) + DOT + quote(col.getName()); - } - return quoteIdentifierInSqlExpr(modelDesc, ccMap.get(col.getName()).getInnerExpression(), QUOTE); - } - - private static String appendEffectiveColumnsStatement(NDataModel modelDesc, List<TblColRef> effectiveColumns, - boolean singleLine, boolean includeCC, Function<TblColRef, String> namingFunction) { - final String sep = getSepBySingleLineTag(singleLine); - - StringBuilder subSql = new StringBuilder(); - if (effectiveColumns.isEmpty()) { - subSql.append("1"); - return subSql.toString(); - } - - effectiveColumns.forEach(col -> { - if (includeCC || !col.getColumnDesc().isComputedColumn()) { - if (subSql.length() > 0) { - subSql.append(",").append(sep); - } - - String colName = namingFunction != null ? namingFunction.apply(col) : colName(col); - subSql.append(quotedColExpressionInSourceDB(modelDesc, col)).append(" as ").append(quote(colName)); - } - }); - - return subSql.toString(); - } - - private static String appendWhereStatement(NDataModel modelDesc, boolean singleLine) { - final String sep = getSepBySingleLineTag(singleLine); - - StringBuilder whereBuilder = new StringBuilder(); - whereBuilder.append("1 = 1").append(sep); - - if (StringUtils.isNotEmpty(modelDesc.getFilterCondition())) { - String quotedFilterCondition = quoteIdentifierInSqlExpr(modelDesc, modelDesc.getFilterCondition(), QUOTE); - whereBuilder.append(" AND (").append(quotedFilterCondition).append(") ").append(sep); // -> filter condition contains special character may cause bug - } - - return whereBuilder.toString(); - } - - private static String appendJoinStatement(NDataModel modelDesc, boolean singleLine) { - final String sep = getSepBySingleLineTag(singleLine); - - StringBuilder subSql = new StringBuilder(); - - Set<TableRef> dimTableCache = new HashSet<>(); - TableRef rootTable = modelDesc.getRootFactTable(); - subSql.append(quotedTable(modelDesc.getRootFactTable().getTableDesc())).append(" as ") - .append(quote(rootTable.getAlias())).append(" ").append(sep); - - for (JoinTableDesc lookupDesc : modelDesc.getJoinTables()) { - JoinDesc join = lookupDesc.getJoin(); - if (checkJoinDesc(join)) { - continue; - } - String joinType = join.getType().toUpperCase(Locale.ROOT); - TableRef dimTable = lookupDesc.getTableRef(); - if (dimTableCache.contains(dimTable)) { - continue; - } - subSql.append(joinType).append(" JOIN ").append(quotedTable(dimTable.getTableDesc())).append(" as ") - .append(quote(dimTable.getAlias())).append(sep); - subSql.append("ON "); - - if (Objects.nonNull(join.getNonEquiJoinCondition())) { - subSql.append(quoteIdentifierInSqlExpr(modelDesc, join.getNonEquiJoinCondition().getExpr(), QUOTE)); - } else { - TblColRef[] pk = join.getPrimaryKeyColumns(); - TblColRef[] fk = join.getForeignKeyColumns(); - if (pk.length != fk.length) { - throw new RuntimeException( - String.format(Locale.ROOT, "Invalid join condition of lookup table: %s", lookupDesc)); - } - - for (int i = 0; i < pk.length; i++) { - if (i > 0) { - subSql.append(" AND "); - } - subSql.append(quotedColExpressionInSourceDB(modelDesc, fk[i])).append("=") - .append(quotedColExpressionInSourceDB(modelDesc, pk[i])); - } - } - subSql.append(sep); - dimTableCache.add(dimTable); - } - return subSql.toString(); - } - - private static String getSepBySingleLineTag(boolean singleLine) { - return singleLine ? " " : "\n"; - } - - public static String generateSelectDataStatement(NDataModel modelDesc, boolean singleLine) { - return generateSelectDataStatement(modelDesc, Lists.newArrayList(modelDesc.getEffectiveCols().values()), - singleLine, false, true, null); - } - - public static String generateSelectDataStatement(NDataModel modelDesc, List<TblColRef> effectiveColumns, - boolean singleLine, boolean includeCC, boolean includeFilter, Function<TblColRef, String> namingFunction) { - final String sep = getSepBySingleLineTag(singleLine); - - StringBuilder sql = new StringBuilder("SELECT ").append(sep); - String columnsStatement = appendEffectiveColumnsStatement(modelDesc, effectiveColumns, singleLine, includeCC, - namingFunction); - sql.append(columnsStatement.endsWith(sep) ? columnsStatement : columnsStatement + sep); - sql.append("FROM ").append(sep); - String joinStatement = appendJoinStatement(modelDesc, singleLine); - sql.append(joinStatement.endsWith(sep) ? joinStatement : joinStatement + sep); - if (includeFilter) { - sql.append("WHERE ").append(sep); - sql.append(appendWhereStatement(modelDesc, singleLine)); - } - return sql.toString(); - } - - private static boolean checkJoinDesc(JoinDesc join) { - return join == null || join.getType().equals(""); - } - - private static String getColumnAlias(String tableName, String columnName, - Map<String, Map<String, String>> tableToColumnsMap) { - Map<String, String> colToAliasMap = getColToColAliasMapInTable(tableName, tableToColumnsMap); - if (!colToAliasMap.containsKey(columnName)) { - return null; - } - return colToAliasMap.get(columnName); - } - - private static boolean columnHasAlias(String tableName, String columnName, - Map<String, Map<String, String>> tableToColumnsMap) { - Map<String, String> colToAliasMap = getColToColAliasMapInTable(tableName, tableToColumnsMap); - return colToAliasMap.containsKey(columnName); - } - - private static Map<String, String> getColToColAliasMapInTable(String tableName, - Map<String, Map<String, String>> tableToColumnsMap) { - if (tableToColumnsMap.containsKey(tableName)) { - return tableToColumnsMap.get(tableName); - } - return Maps.newHashMap(); - } - - private static Set<String> listColumnsInTable(String tableName, - Map<String, Map<String, String>> tableToColumnsMap) { - Map<String, String> colToAliasMap = getColToColAliasMapInTable(tableName, tableToColumnsMap); - return colToAliasMap.keySet(); - } - - @VisibleForTesting - public static String quoteIdentifier(String sqlExpr, String quotation, String identifier, - List<String> identifierPatterns) { - String quotedIdentifier = quotation + identifier.trim() + quotation; - - for (String pattern : identifierPatterns) { - Matcher matcher = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE | Pattern.DOTALL).matcher(sqlExpr); - if (matcher.find()) { - sqlExpr = matcher.replaceAll("$1" + quotedIdentifier + "$3"); - } - } - return sqlExpr; - } - - private static boolean isIdentifierNeedToQuote(String sqlExpr, String identifier, List<String> identifierPatterns) { - if (StringUtils.isBlank(sqlExpr) || StringUtils.isBlank(identifier)) { - return false; - } - - for (String pattern : identifierPatterns) { - if (Pattern.compile(pattern, Pattern.CASE_INSENSITIVE | Pattern.DOTALL).matcher(sqlExpr).find()) { - return true; - } - } - return false; - } - - @VisibleForTesting - public static List<String> getTableNameOrAliasPatterns(String tableName) { - Preconditions.checkNotNull(tableName); - // Pattern must contain these regex groups, and place identifier in sec group ($2) - List<String> patterns = Lists.newArrayList(); - patterns.add("([+\\-*/%&|^=><\\s,(])(" + tableName.trim() + ")(\\.)"); - patterns.add("([+\\-*/%&|^=><\\s,(])(`" + tableName.trim() + "`)(\\.)"); - patterns.add("([\\.\\s])(" + tableName.trim() + ")([,\\s)])"); - patterns.add("([\\.\\s])(`" + tableName.trim() + "`)([,\\s)])"); - patterns.add("(^)(" + tableName.trim() + ")([\\.])"); - patterns.add("(^)(`" + tableName.trim() + "`)([\\.])"); - return patterns; - } - - @VisibleForTesting - public static List<String> getColumnNameOrAliasPatterns(String colName) { - Preconditions.checkNotNull(colName); - // Pattern must contain these regex groups, and place identifier in sec group ($2) - List<String> patterns = Lists.newArrayList(); - patterns.add("([\\.\\s(])(" + colName.trim() + ")([+\\-*/%&|^=><\\s,)]|$)"); - patterns.add("([\\.\\s(])(`" + colName.trim() + "`)([+\\-*/%&|^=><\\s,)]|$)"); - patterns.add("(^)(" + colName.trim() + ")([+\\-*/%&|^=><\\s,)])"); - patterns.add("(^)(`" + colName.trim() + "`)([+\\-*/%&|^=><\\s,)])"); - return patterns; - } - - private static Map<String, Map<String, String>> buildTableToColumnsMap(NDataModel modelDesc) { - Map<String, Map<String, String>> map = Maps.newHashMap(); - Set<TblColRef> colRefs = modelDesc.getEffectiveCols().values(); - for (TblColRef colRef : colRefs) { - String colName = colRef.getName(); - String tableName = colRef.getTableRef().getTableName(); - String colAlias = colRef.getTableAlias() + "_" + colRef.getName(); - if (map.containsKey(tableName)) { - map.get(tableName).put(colName, colAlias); - } else { - Map<String, String> colToAliasMap = Maps.newHashMap(); - colToAliasMap.put(colName, colAlias); - map.put(tableName, colToAliasMap); - } - } - return map; - } - - private static Map<String, String> buildTableToTableAliasMap(NDataModel modelDesc) { - Map<String, String> map = Maps.newHashMap(); - Set<TblColRef> colRefs = modelDesc.getEffectiveCols().values(); - for (TblColRef colRef : colRefs) { - String tableName = colRef.getTableRef().getTableName(); - String alias = colRef.getTableAlias(); - map.put(tableName, alias); - } - return map; - } - - /** - * Used for quote identifiers in Sql Filter Expression & Computed Column Expression for flat table - * @param modelDesc - * @param sqlExpr - * @param quotation - * @return - */ - @VisibleForTesting - public static String quoteIdentifierInSqlExpr(NDataModel modelDesc, String sqlExpr, String quotation) { - if (BACK_TICK_DOT_PATTERN.matcher(sqlExpr).find()) { - return quoteIdentifierInSqlBackTickExpr(sqlExpr, quotation); - } - Map<String, String> tabToAliasMap = buildTableToTableAliasMap(modelDesc); - Map<String, Map<String, String>> tabToColsMap = buildTableToColumnsMap(modelDesc); - - boolean tableMatched = false; - for (Map.Entry<String, String> tableEntry : tabToAliasMap.entrySet()) { - List<String> tabPatterns = getTableNameOrAliasPatterns(tableEntry.getKey()); - if (isIdentifierNeedToQuote(sqlExpr, tableEntry.getKey(), tabPatterns)) { - sqlExpr = quoteIdentifier(sqlExpr, quotation, tableEntry.getKey(), tabPatterns); - tableMatched = true; - } - - String tabAlias = tableEntry.getValue(); - List<String> tabAliasPatterns = getTableNameOrAliasPatterns(tabAlias); - if (isIdentifierNeedToQuote(sqlExpr, tabAlias, tabAliasPatterns)) { - sqlExpr = quoteIdentifier(sqlExpr, quotation, tabAlias, tabAliasPatterns); - tableMatched = true; - } - - if (!tableMatched) { - continue; - } - - Set<String> columns = listColumnsInTable(tableEntry.getKey(), tabToColsMap); - for (String column : columns) { - List<String> colPatterns = getColumnNameOrAliasPatterns(column); - if (isIdentifierNeedToQuote(sqlExpr, column, colPatterns)) { - sqlExpr = quoteIdentifier(sqlExpr, quotation, column, colPatterns); - } - if (columnHasAlias(tableEntry.getKey(), column, tabToColsMap)) { - String colAlias = getColumnAlias(tableEntry.getKey(), column, tabToColsMap); - List<String> colAliasPattern = getColumnNameOrAliasPatterns(colAlias); - if (isIdentifierNeedToQuote(sqlExpr, colAlias, colAliasPattern)) { - sqlExpr = quoteIdentifier(sqlExpr, quotation, colAlias, colPatterns); - } - } - } - - tableMatched = false; //reset - } - return sqlExpr; - } - - private static String quoteIdentifierInSqlBackTickExpr(String sqlExpr, String quotation) { - String result = sqlExpr; - Matcher matcher = BACK_TICK_DOT_PATTERN.matcher(sqlExpr); - while (matcher.find()) { - String target = matcher.group(); - target = target.replace("`", quotation); - result = result.replace(matcher.group(), target); - } - return result; - } - -} diff --git a/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelService.java b/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelService.java index a7dd882b84..de7d4191f4 100644 --- a/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelService.java +++ b/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelService.java @@ -167,7 +167,6 @@ import org.apache.kylin.metadata.model.FusionModelManager; import org.apache.kylin.metadata.model.ISourceAware; import org.apache.kylin.metadata.model.JoinDesc; import org.apache.kylin.metadata.model.JoinTableDesc; -import org.apache.kylin.metadata.model.JoinedFlatTable; import org.apache.kylin.metadata.model.ManagementType; import org.apache.kylin.metadata.model.MeasureDesc; import org.apache.kylin.metadata.model.MultiPartitionDesc; @@ -1292,8 +1291,8 @@ public class ModelService extends AbstractModelService implements TableModelSupp public String getModelSql(String modelId, String project) { aclEvaluate.checkProjectReadPermission(project); - NDataModel modelDesc = getManager(NDataModelManager.class, project).getDataModelDesc(modelId); - return JoinedFlatTable.generateSelectDataStatement(modelDesc, false); + NDataModel model = getManager(NDataModelManager.class, project).getDataModelDesc(modelId); + return PushDownUtil.generateFlatTableSql(model, project, false); } public List<RelatedModelResponse> getRelateModels(String project, String table, String modelId) { @@ -1712,16 +1711,12 @@ public class ModelService extends AbstractModelService implements TableModelSupp } @VisibleForTesting - public void checkFlatTableSql(NDataModel dataModel) { - String project = dataModel.getProject(); - ProjectInstance prjInstance = getManager(NProjectManager.class).getProject(project); - if (KylinConfig.getInstanceFromEnv().isUTEnv() || prjInstance.getConfig().isSkipCheckFlatTable()) { - return; - } - if (getModelConfig(dataModel).skipCheckFlatTable()) { + public void checkFlatTableSql(NDataModel model) { + if (skipCheckFlatTable(model)) { return; } - long rangePartitionTableCount = dataModel.getAllTableRefs().stream() + + long rangePartitionTableCount = model.getAllTableRefs().stream() .filter(p -> p.getTableDesc().isRangePartition()).count(); if (rangePartitionTableCount > 0) { logger.info("Range partitioned tables do not support pushdown, so do not need to perform subsequent logic"); @@ -1729,22 +1724,34 @@ public class ModelService extends AbstractModelService implements TableModelSupp } try { + String project = model.getProject(); + ProjectInstance prjInstance = getManager(NProjectManager.class).getProject(project); if (prjInstance.getSourceType() == ISourceAware.ID_SPARK - && dataModel.getModelType() == NDataModel.ModelType.BATCH) { + && model.getModelType() == NDataModel.ModelType.BATCH) { SparkSession ss = SparderEnv.getSparkSession(); - String flatTableSql = JoinedFlatTable.generateSelectDataStatement(dataModel, false); + String flatTableSql = PushDownUtil.generateFlatTableSql(model, project, false); QueryParams queryParams = new QueryParams(project, flatTableSql, "default", false); queryParams.setKylinConfig(prjInstance.getConfig()); queryParams.setAclInfo(AclPermissionUtil.createAclInfo(project, getCurrentUserGroups())); - String pushdownSql = PushDownUtil.massagePushDownSql(queryParams); - ss.sql(pushdownSql); + ss.sql(PushDownUtil.massagePushDownSql(queryParams)); } } catch (Exception e) { - buildExceptionMessage(dataModel, e); + buildExceptionMessage(model, e); + } + } + + private boolean skipCheckFlatTable(NDataModel model) { + if (KylinConfig.getInstanceFromEnv().isUTEnv()) { + return true; } + IndexPlan indexPlan = getIndexPlan(model.getId(), model.getProject()); + KylinConfig config = indexPlan == null || indexPlan.getConfig() == null + ? NProjectManager.getProjectConfig(model.getProject()) + : indexPlan.getConfig(); + return config.skipCheckFlatTable(); } - private static void buildExceptionMessage(NDataModel dataModel, Exception e) { + private void buildExceptionMessage(NDataModel dataModel, Exception e) { Pattern pattern = Pattern.compile("cannot resolve '(.*?)' given input columns"); Matcher matcher = pattern.matcher(e.getMessage().replace("`", "")); if (matcher.find()) { @@ -1761,14 +1768,6 @@ public class ModelService extends AbstractModelService implements TableModelSupp } } - private KylinConfig getModelConfig(NDataModel dataModel) { - IndexPlan indexPlan = getIndexPlan(dataModel.getId(), dataModel.getProject()); - if (indexPlan == null || indexPlan.getConfig() == null) { - return getManager(NProjectManager.class).getProject(dataModel.getProject()).getConfig(); - } - return indexPlan.getConfig(); - } - private void validatePartitionDateColumn(ModelRequest modelRequest) { if (Objects.nonNull(modelRequest.getPartitionDesc())) { if (StringUtils.isNotEmpty(modelRequest.getPartitionDesc().getPartitionDateColumn())) { @@ -2350,7 +2349,7 @@ public class ModelService extends AbstractModelService implements TableModelSupp } public JobInfoResponse fixSegmentHoles(String project, String modelId, List<SegmentTimeRequest> segmentHoles, - Set<String> ignoredSnapshotTables) throws Exception { + Set<String> ignoredSnapshotTables) throws SQLException { aclEvaluate.checkProjectOperationPermission(project); NDataModel modelDesc = getManager(NDataModelManager.class, project).getDataModelDesc(modelId); checkModelAndIndexManually(project, modelId); @@ -2580,7 +2579,7 @@ public class ModelService extends AbstractModelService implements TableModelSupp boolean buildSegmentOverlapEnable = getIndexPlan(model.getId(), project).getConfig() .isBuildSegmentOverlapEnabled(); - boolean isBuildAllIndexesFinally = batchIndexIds == null || batchIndexIds.size() == 0 + boolean isBuildAllIndexesFinally = CollectionUtils.isEmpty(batchIndexIds) || batchIndexIds.size() == getIndexPlan(model.getId(), project).getAllIndexes().size(); for (NDataSegment existedSegment : segments) { diff --git a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelServiceTest.java b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelServiceTest.java index 5b2926052f..b69b89585b 100644 --- a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelServiceTest.java +++ b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelServiceTest.java @@ -84,8 +84,8 @@ import org.apache.calcite.sql.SqlKind; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.tuple.ImmutablePair; @@ -2203,7 +2203,6 @@ public class ModelServiceTest extends SourceTestCase { request.setEnd("100"); request.setUuid(RandomUtil.randomUUIDStr()); modelService.createModel(request.getProject(), request); - //TODO modelService.updateModelToResourceStore(deserialized, "default"); List<NDataModelResponse> dataModelDescs = modelService.getModels("nmodel_cc_test", "default", true, null, null, "", false); @@ -5161,20 +5160,23 @@ public class ModelServiceTest extends SourceTestCase { public void testBuildExceptionMessage() { NDataModelManager modelManager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), "default"); NDataModel dataModel = modelManager.getDataModelDesc("a8ba3ff1-83bd-4066-ad54-d2fb3d1f0e94"); - { - val testException = new RuntimeException("test"); - Assert.assertThrows("model [test_encoding], Something went wrong. test", KylinException.class, - () -> ReflectionTestUtils.invokeMethod(ModelService.class, "buildExceptionMessage", dataModel, - testException)); - } + String toValidMethodName = "buildExceptionMessage"; + String expectedMsg = "model [test_encoding], Something went wrong. test"; + val testException = new RuntimeException("test"); + Assert.assertThrows(expectedMsg, KylinException.class, + () -> ReflectionTestUtils.invokeMethod(modelService, toValidMethodName, dataModel, testException)); + } - { - val testException = new RuntimeException("cannot resolve 'test' given input columns"); - Assert.assertThrows( - "Can’t save model \"test_encoding\". Please ensure that the used column \"test\" exist in source table \"DEFAULT.TEST_ENCODING\".", - KylinException.class, () -> ReflectionTestUtils.invokeMethod(ModelService.class, - "buildExceptionMessage", dataModel, testException)); - } + @Test + public void testBuildExceptionMessageCausedByResolveProblem() { + NDataModelManager modelManager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), "default"); + NDataModel dataModel = modelManager.getDataModelDesc("a8ba3ff1-83bd-4066-ad54-d2fb3d1f0e94"); + String toValidMethodName = "buildExceptionMessage"; + String expectedMsg = "Can’t save model \"test_encoding\". Please ensure that the used column \"test\" " + + "exist in source table \"DEFAULT.TEST_ENCODING\"."; + val testException = new RuntimeException("cannot resolve 'test' given input columns"); + Assert.assertThrows(expectedMsg, KylinException.class, + () -> ReflectionTestUtils.invokeMethod(modelService, toValidMethodName, dataModel, testException)); } @Test diff --git a/src/query-common/src/main/java/org/apache/kylin/query/util/PushDownUtil.java b/src/query-common/src/main/java/org/apache/kylin/query/util/PushDownUtil.java index 9f3be8de77..b902bfe3f5 100644 --- a/src/query-common/src/main/java/org/apache/kylin/query/util/PushDownUtil.java +++ b/src/query-common/src/main/java/org/apache/kylin/query/util/PushDownUtil.java @@ -90,6 +90,7 @@ public class PushDownUtil { .compile("/\\*\\s*\\+\\s*(?i)MODEL_PRIORITY\\s*\\([\\s\\S]*\\)\\s*\\*/"); public static final String DEFAULT_SCHEMA = "DEFAULT"; private static final String CC_SPLITTER = "'##CC_PUSH_DOWN_TOKEN##'"; + private static final String UNDER_LINE = "_"; private static final ExecutorService asyncExecutor = Executors.newCachedThreadPool(); private static final Map<String, IPushDownConverter> PUSH_DOWN_CONVERTER_MAP = Maps.newConcurrentMap(); @@ -109,11 +110,8 @@ public class PushDownUtil { } public static PushdownResult tryIterQuery(QueryParams queryParams) throws SQLException { - - String sql = queryParams.getSql(); - String project = queryParams.getProject(); - KylinConfig projectConfig = NProjectManager.getProjectConfig(queryParams.getProject()); + queryParams.setKylinConfig(projectConfig); if (!projectConfig.isPushDownEnabled()) { checkPushDownIncapable(queryParams); return null; @@ -130,21 +128,17 @@ public class PushDownUtil { logger.info("Kylin cannot support non-select queries, routing to other engines"); } + // Set a push-down engine for query context. IPushDownRunner runner = (IPushDownRunner) ClassUtil.newInstance(projectConfig.getPushDownRunnerClassName()); - runner.init(projectConfig, project); + runner.init(projectConfig, queryParams.getProject()); logger.debug("Query Pushdown runner {}", runner); - - // set pushdown engine in query context - - // for file source int sourceType = projectConfig.getDefaultSource(); String engine = sourceType == ISourceAware.ID_SPARK && KapConfig.getInstanceFromEnv().isCloud() ? QueryContext.PUSHDOWN_OBJECT_STORAGE : runner.getName(); QueryContext.current().setPushdownEngine(engine); - queryParams.setKylinConfig(projectConfig); - queryParams.setSql(sql); + String sql; try { sql = massagePushDownSql(queryParams); } catch (NoAuthorizedColsError e) { @@ -154,13 +148,12 @@ public class PushDownUtil { QueryContext.currentTrace().startSpan(QueryTrace.PREPARE_AND_SUBMIT_JOB); if (queryParams.isSelect()) { - PushdownResult result = runner.executeQueryToIterator(sql, project); + PushdownResult result = runner.executeQueryToIterator(sql, queryParams.getProject()); if (QueryContext.current().getQueryTagInfo().isAsyncQuery()) { AsyncQueryUtil.saveMetaDataAndFileInfo(QueryContext.current(), result.getColumnMetas()); } return result; } - return PushdownResult.emptyResult(); } @@ -189,6 +182,10 @@ public class PushDownUtil { } public static String massagePushDownSql(QueryParams queryParams) { + if (queryParams.getSql() == null) { + return StringUtils.EMPTY; + } + String sql = queryParams.getSql(); sql = QueryUtil.trimRightSemiColon(sql); sql = SQL_HINT_PATTERN.matcher(sql).replaceAll(""); @@ -225,6 +222,41 @@ public class PushDownUtil { return converters; } + /** + * This method is currently only used for verifying the flat-table generated by the model. + */ + public static String generateFlatTableSql(NDataModel model, String project, boolean singleLine) { + String sep = singleLine ? " " : "\n"; + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("SELECT ").append(sep); + + List<TblColRef> tblColRefs = Lists.newArrayList(model.getEffectiveCols().values()); + if (tblColRefs.isEmpty()) { + sqlBuilder.append("1 ").append(sep); + } else { + String allColStr = tblColRefs.stream() // + .filter(colRef -> !colRef.getColumnDesc().isComputedColumn()) // + .map(colRef -> { + String s = colRef.getTableAlias() + UNDER_LINE + colRef.getName(); + String colName = StringHelper.doubleQuote(s); + return colRef.getDoubleQuoteExp() + " as " + colName + sep; + }).collect(Collectors.joining(", ")); + sqlBuilder.append(allColStr); + } + + sqlBuilder.append("FROM ").append(model.getRootFactTable().getTableDesc().getDoubleQuoteIdentity()); + appendJoinStatement(model, sqlBuilder, singleLine); + + sqlBuilder.append("WHERE ").append(sep); + sqlBuilder.append("1 = 1").append(sep); + if (StringUtils.isNotEmpty(model.getFilterCondition())) { + String filterCondition = massageExpression(model, project, model.getFilterCondition(), null); + sqlBuilder.append(" AND (").append(filterCondition).append(") ").append(sep); + } + + return new EscapeTransformer().transform(sqlBuilder.toString()); + } + public static String expandComputedColumnExp(NDataModel model, String project, String expression) { StringBuilder forCC = new StringBuilder(); forCC.append("select ").append(expression).append(" ,").append(CC_SPLITTER) // @@ -237,6 +269,7 @@ public class PushDownUtil { // massage nested CC for drafted model Map<String, NDataModel> modelMap = Maps.newHashMap(); modelMap.put(model.getUuid(), model); + ccSql = new EscapeTransformer().transform(ccSql); ccSql = RestoreFromComputedColumn.convertWithGivenModels(ccSql, project, DEFAULT_SCHEMA, modelMap); } catch (Exception e) { logger.warn("Failed to massage SQL expression [{}] with input model {}", ccSql, model.getUuid(), e); diff --git a/src/query-common/src/main/java/org/apache/kylin/query/util/QueryAliasMatcher.java b/src/query-common/src/main/java/org/apache/kylin/query/util/QueryAliasMatcher.java index ab4fb87d2e..b9c1b579ea 100644 --- a/src/query-common/src/main/java/org/apache/kylin/query/util/QueryAliasMatcher.java +++ b/src/query-common/src/main/java/org/apache/kylin/query/util/QueryAliasMatcher.java @@ -43,7 +43,6 @@ import org.apache.calcite.sql.util.SqlBasicVisitor; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; import org.apache.kylin.common.KylinConfig; -import org.apache.kylin.common.KylinConfigExt; import org.apache.kylin.common.util.Pair; import org.apache.kylin.common.util.RandomUtil; import org.apache.kylin.metadata.cube.model.NDataflowManager; @@ -72,9 +71,11 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; // match alias in query to alias in model // Not designed to reuse, re-new per query +@Slf4j public class QueryAliasMatcher { static final ColumnRowType MODEL_VIEW_COLUMN_ROW_TYPE = new ColumnRowType(new ArrayList<>()); private static final ColumnRowType SUBQUERY_TAG = new ColumnRowType(null); @@ -196,8 +197,7 @@ public class QueryAliasMatcher { return null; } JoinsGraph joinsGraph = new JoinsGraph(firstTable, joinDescs); - KylinConfigExt projectConfig = NProjectManager.getInstance(KylinConfig.getInstanceFromEnv()).getProject(project) - .getConfig(); + KylinConfig projectConfig = NProjectManager.getProjectConfig(project); if (sqlJoinCapturer.foundJoinOnCC) { // 1st round: dry run without cc expr comparison to collect model alias matching @@ -241,6 +241,7 @@ public class QueryAliasMatcher { } private static class CCJoinEdgeMatcher extends DefaultJoinEdgeMatcher { + private static final EscapeTransformer TRANSFORMER = new EscapeTransformer(); transient QueryAliasMatchInfo matchInfo; boolean compareCCExpr; @@ -263,12 +264,20 @@ public class QueryAliasMatcher { || (!a.isComputedColumn() && b.isComputedColumn())) { return false; } else { - if (!compareCCExpr) + if (!compareCCExpr) { return true; - - SqlNode node1 = CalciteParser.getExpNode(a.getComputedColumnExpr()); - SqlNode node2 = CalciteParser.getExpNode(b.getComputedColumnExpr()); - return ExpressionComparator.isNodeEqual(node1, node2, matchInfo, new AliasDeduceImpl(matchInfo)); + } + try { + SqlNode node1 = CalciteParser.getExpNode(TRANSFORMER.transform(a.getDoubleQuoteInnerExpr())); + SqlNode node2 = CalciteParser.getExpNode(TRANSFORMER.transform(b.getDoubleQuoteInnerExpr())); + return ExpressionComparator.isNodeEqual(node1, node2, matchInfo, new AliasDeduceImpl(matchInfo)); + } catch (Exception e) { + // If this situation occurs, it means that there is an error in the parsing of the computed column. + // Therefore, we can directly assume that these two computed columns are not equal. + log.error("Failed to parse expressions, {} or {}", a.getComputedColumnExpr(), + b.getComputedColumnExpr()); + return false; + } } } } diff --git a/src/query/src/test/java/org/apache/kylin/query/util/CCOnRealModelTest.java b/src/query/src/test/java/org/apache/kylin/query/util/CCOnRealModelTest.java index 1b337e0569..658d266530 100644 --- a/src/query/src/test/java/org/apache/kylin/query/util/CCOnRealModelTest.java +++ b/src/query/src/test/java/org/apache/kylin/query/util/CCOnRealModelTest.java @@ -113,7 +113,7 @@ public class CCOnRealModelTest extends NLocalFileMetadataTestCase { } // ignored for KAP#16258 - @Ignore + @Ignore("historic ignored") @Test public void testSubquery() { { @@ -198,7 +198,6 @@ public class CCOnRealModelTest extends NLocalFileMetadataTestCase { } @Test - @Ignore("Not support CC on Join condition") public void testJoinOnCC() { { String originSql = "select count(*) from TEST_KYLIN_FACT\n" @@ -232,11 +231,9 @@ public class CCOnRealModelTest extends NLocalFileMetadataTestCase { } @Test - public void testNoFrom() throws Exception { - String originSql = "select sum(price * item_count),(SELECT 1 as VERSION) from test_kylin_fact"; - String ccSql = "select sum(TEST_KYLIN_FACT.DEAL_AMOUNT),(SELECT 1 as VERSION) from test_kylin_fact"; - - check(converter, originSql, ccSql); + public void testNoFrom() { + check(converter, "select sum(price * item_count),(SELECT 1 as VERSION) from test_kylin_fact", + "select sum(TEST_KYLIN_FACT.DEAL_AMOUNT),(SELECT 1 as VERSION) from test_kylin_fact"); } @Test diff --git a/src/query/src/test/java/org/apache/kylin/query/util/PushDownUtilTest.java b/src/query/src/test/java/org/apache/kylin/query/util/PushDownUtilTest.java index 0a57502dcf..fdc7dcf28c 100644 --- a/src/query/src/test/java/org/apache/kylin/query/util/PushDownUtilTest.java +++ b/src/query/src/test/java/org/apache/kylin/query/util/PushDownUtilTest.java @@ -18,10 +18,22 @@ package org.apache.kylin.query.util; +import java.util.List; import java.util.Properties; import org.apache.kylin.common.KylinConfig; +import org.apache.kylin.common.exception.KylinException; +import org.apache.kylin.common.exception.QueryErrorCode; +import org.apache.kylin.common.exception.ServerErrorCode; import org.apache.kylin.common.util.NLocalFileMetadataTestCase; +import org.apache.kylin.metadata.model.ComputedColumnDesc; +import org.apache.kylin.metadata.model.JoinDesc; +import org.apache.kylin.metadata.model.JoinTableDesc; +import org.apache.kylin.metadata.model.NDataModel; +import org.apache.kylin.metadata.model.NDataModelManager; +import org.apache.kylin.metadata.model.TableRef; +import org.apache.kylin.metadata.model.TblColRef; +import org.apache.kylin.metadata.project.EnhancedUnitOfWork; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -49,7 +61,8 @@ public class PushDownUtilTest extends NLocalFileMetadataTestCase { PushDownUtil.tryIterQuery(queryParams); Assert.fail(); } catch (Exception e) { - Assert.assertFalse(e instanceof IllegalArgumentException); + Assert.assertTrue(e instanceof KylinException); + Assert.assertEquals(ServerErrorCode.SPARK_FAILURE.toErrorCode(), ((KylinException) e).getErrorCode()); } } @@ -64,7 +77,9 @@ public class PushDownUtilTest extends NLocalFileMetadataTestCase { PushDownUtil.tryIterQuery(queryParams); Assert.fail(); } catch (Exception e) { - Assert.assertFalse(e instanceof IllegalArgumentException); + Assert.assertTrue(e instanceof KylinException); + Assert.assertEquals(QueryErrorCode.INVALID_PARAMETER_PUSH_DOWN.toErrorCode(), + ((KylinException) e).getErrorCode()); } } @@ -133,4 +148,156 @@ public class PushDownUtilTest extends NLocalFileMetadataTestCase { String resSql = "select ab from table where aa = '' and bb = '\\'as\\'n\\''"; Assert.assertEquals(resSql, PushDownUtil.replaceEscapedQuote(sql)); } + + @Test + public void testGenerateFlatTableSql() { + String project = "default"; + NDataModelManager modelManager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), project); + NDataModel model = modelManager.getDataModelDescByAlias("test_bank"); + String expected = "SELECT\n" // + + "\"TEST_BANK_INCOME\".\"COUNTRY\" as \"TEST_BANK_INCOME_COUNTRY\"\n" + + ", \"TEST_BANK_INCOME\".\"INCOME\" as \"TEST_BANK_INCOME_INCOME\"\n" + + ", \"TEST_BANK_INCOME\".\"NAME\" as \"TEST_BANK_INCOME_NAME\"\n" + + ", \"TEST_BANK_INCOME\".\"DT\" as \"TEST_BANK_INCOME_DT\"\n" + + ", \"TEST_BANK_LOCATION\".\"COUNTRY\" as \"TEST_BANK_LOCATION_COUNTRY\"\n" + + ", \"TEST_BANK_LOCATION\".\"OWNER\" as \"TEST_BANK_LOCATION_OWNER\"\n" + + ", \"TEST_BANK_LOCATION\".\"LOCATION\" as \"TEST_BANK_LOCATION_LOCATION\"\n" + + "FROM \"DEFAULT\".\"TEST_BANK_INCOME\"\n" + + "INNER JOIN \"DEFAULT\".\"TEST_BANK_LOCATION\" as \"TEST_BANK_LOCATION\"\n" + + "ON \"TEST_BANK_INCOME\".\"COUNTRY\" = \"TEST_BANK_LOCATION\".\"COUNTRY\"\n" // + + "WHERE\n" // + + "1 = 1"; + Assert.assertEquals(expected, PushDownUtil.generateFlatTableSql(model, project, false)); + } + + @Test + public void testGenerateFlatTableSqlWithCCJoin() { + String project = "default"; + NDataModelManager modelManager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), project); + NDataModel model = modelManager.getDataModelDescByAlias("test_bank"); + updateModelToAddCC(project, model); + // change join condition + EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> { + KylinConfig kylinConfig = KylinConfig.getInstanceFromEnv(); + NDataModelManager modelMgr = NDataModelManager.getInstance(kylinConfig, project); + modelMgr.updateDataModel(model.getUuid(), copyForWrite -> { + List<JoinTableDesc> joinTables = copyForWrite.getJoinTables(); + TableRef rootTableRef = copyForWrite.getRootFactTable(); + TblColRef cc1 = rootTableRef.getColumn("CC1"); + JoinDesc join = joinTables.get(0).getJoin(); + join.setForeignKeyColumns(new TblColRef[] { cc1 }); + join.setForeignKey(new String[] { "TEST_BANK_INCOME.CC1" }); + }); + return null; + }, project); + String expected = "SELECT\n" // + + "\"TEST_BANK_INCOME\".\"COUNTRY\" as \"TEST_BANK_INCOME_COUNTRY\"\n" + + ", \"TEST_BANK_INCOME\".\"INCOME\" as \"TEST_BANK_INCOME_INCOME\"\n" + + ", \"TEST_BANK_INCOME\".\"NAME\" as \"TEST_BANK_INCOME_NAME\"\n" + + ", \"TEST_BANK_INCOME\".\"DT\" as \"TEST_BANK_INCOME_DT\"\n" + + ", \"TEST_BANK_LOCATION\".\"COUNTRY\" as \"TEST_BANK_LOCATION_COUNTRY\"\n" + + ", \"TEST_BANK_LOCATION\".\"OWNER\" as \"TEST_BANK_LOCATION_OWNER\"\n" + + ", \"TEST_BANK_LOCATION\".\"LOCATION\" as \"TEST_BANK_LOCATION_LOCATION\"\n" + + "FROM \"DEFAULT\".\"TEST_BANK_INCOME\"\n" + + "INNER JOIN \"DEFAULT\".\"TEST_BANK_LOCATION\" as \"TEST_BANK_LOCATION\"\n" + + "ON SUBSTRING(\"TEST_BANK_INCOME\".\"COUNTRY\", 0, 4) = \"TEST_BANK_LOCATION\".\"COUNTRY\"\n" + + "WHERE\n" // + + "1 = 1"; + NDataModel updatedModel = modelManager.getDataModelDesc(model.getUuid()); + Assert.assertEquals(expected, PushDownUtil.generateFlatTableSql(updatedModel, project, false)); + + } + + @Test + public void testGenerateFlatTableSqlWithFilterCondition() { + String project = "default"; + NDataModelManager modelManager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), project); + NDataModel model = modelManager.getDataModelDescByAlias("test_bank"); + updateModelToAddCC(project, model); + // change filter condition + EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> { + KylinConfig kylinConfig = KylinConfig.getInstanceFromEnv(); + NDataModelManager modelMgr = NDataModelManager.getInstance(kylinConfig, project); + modelMgr.updateDataModel(model.getUuid(), copyForWrite -> { + copyForWrite.setFilterCondition( + "SUBSTRING(\"TEST_BANK_INCOME\".\"COUNTRY\", 0, 4) = 'china' and cc1 = 'china'"); + }); + return null; + }, project); + String expected = "SELECT\n" // + + "\"TEST_BANK_INCOME\".\"COUNTRY\" as \"TEST_BANK_INCOME_COUNTRY\"\n" + + ", \"TEST_BANK_INCOME\".\"INCOME\" as \"TEST_BANK_INCOME_INCOME\"\n" + + ", \"TEST_BANK_INCOME\".\"NAME\" as \"TEST_BANK_INCOME_NAME\"\n" + + ", \"TEST_BANK_INCOME\".\"DT\" as \"TEST_BANK_INCOME_DT\"\n" + + ", \"TEST_BANK_LOCATION\".\"COUNTRY\" as \"TEST_BANK_LOCATION_COUNTRY\"\n" + + ", \"TEST_BANK_LOCATION\".\"OWNER\" as \"TEST_BANK_LOCATION_OWNER\"\n" + + ", \"TEST_BANK_LOCATION\".\"LOCATION\" as \"TEST_BANK_LOCATION_LOCATION\"\n" + + "FROM \"DEFAULT\".\"TEST_BANK_INCOME\"\n" + + "INNER JOIN \"DEFAULT\".\"TEST_BANK_LOCATION\" as \"TEST_BANK_LOCATION\"\n" + + "ON \"TEST_BANK_INCOME\".\"COUNTRY\" = \"TEST_BANK_LOCATION\".\"COUNTRY\"\n" // + + "WHERE\n" // + + "1 = 1\n" // + + " AND (SUBSTRING(`TEST_BANK_INCOME`.`COUNTRY`, 0, 4) = 'china' and (SUBSTRING(`TEST_BANK_INCOME`.`COUNTRY`, 0, 4)) = 'china')"; + NDataModel updatedModel = modelManager.getDataModelDesc(model.getUuid()); + Assert.assertEquals(expected, PushDownUtil.generateFlatTableSql(updatedModel, project, false)); + } + + @Test + public void testGenerateFlatTableSqlWithSpecialFunctions() { + String project = "default"; + NDataModelManager modelManager = NDataModelManager.getInstance(KylinConfig.getInstanceFromEnv(), project); + NDataModel model = modelManager.getDataModelDescByAlias("test_bank"); + updateModelToAddCC(project, model); + // change filter condition + EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> { + KylinConfig kylinConfig = KylinConfig.getInstanceFromEnv(); + NDataModelManager modelMgr = NDataModelManager.getInstance(kylinConfig, project); + modelMgr.updateDataModel(model.getUuid(), copyForWrite -> { + copyForWrite.setFilterCondition("timestampadd(day, 1, current_date) = '2012-01-01' and cc1 = 'china'"); + }); + return null; + }, project); + String expected = "SELECT\n" // + + "\"TEST_BANK_INCOME\".\"COUNTRY\" as \"TEST_BANK_INCOME_COUNTRY\"\n" + + ", \"TEST_BANK_INCOME\".\"INCOME\" as \"TEST_BANK_INCOME_INCOME\"\n" + + ", \"TEST_BANK_INCOME\".\"NAME\" as \"TEST_BANK_INCOME_NAME\"\n" + + ", \"TEST_BANK_INCOME\".\"DT\" as \"TEST_BANK_INCOME_DT\"\n" + + ", \"TEST_BANK_LOCATION\".\"COUNTRY\" as \"TEST_BANK_LOCATION_COUNTRY\"\n" + + ", \"TEST_BANK_LOCATION\".\"OWNER\" as \"TEST_BANK_LOCATION_OWNER\"\n" + + ", \"TEST_BANK_LOCATION\".\"LOCATION\" as \"TEST_BANK_LOCATION_LOCATION\"\n" + + "FROM \"DEFAULT\".\"TEST_BANK_INCOME\"\n" + + "INNER JOIN \"DEFAULT\".\"TEST_BANK_LOCATION\" as \"TEST_BANK_LOCATION\"\n" + + "ON \"TEST_BANK_INCOME\".\"COUNTRY\" = \"TEST_BANK_LOCATION\".\"COUNTRY\"\n" // + + "WHERE\n" // + + "1 = 1\n" // + + " AND (TIMESTAMPADD(day, 1, current_date) = '2012-01-01' and (SUBSTRING(`TEST_BANK_INCOME`.`COUNTRY`, 0, 4)) = 'china')"; + NDataModel updatedModel = modelManager.getDataModelDesc(model.getUuid()); + Assert.assertEquals(expected, PushDownUtil.generateFlatTableSql(updatedModel, project, false)); + } + + private void updateModelToAddCC(String project, NDataModel model) { + EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> { + KylinConfig kylinConfig = KylinConfig.getInstanceFromEnv(); + NDataModelManager modelMgr = NDataModelManager.getInstance(kylinConfig, project); + modelMgr.updateDataModel(model.getUuid(), copyForWrite -> { + ComputedColumnDesc cc = new ComputedColumnDesc(); + cc.setColumnName("CC1"); + cc.setDatatype("int"); + cc.setExpression("substring(\"TEST_BANK_INCOME\".\"COUNTRY\", 0, 4)"); + cc.setInnerExpression("SUBSTRING(`TEST_BANK_INCOME`.`COUNTRY`, 0, 4)"); + cc.setTableAlias("TEST_BANK_INCOME"); + cc.setTableIdentity(model.getRootFactTableName()); + copyForWrite.getComputedColumnDescs().add(cc); + List<NDataModel.NamedColumn> columns = copyForWrite.getAllNamedColumns(); + int id = columns.size(); + NDataModel.NamedColumn namedColumn = new NDataModel.NamedColumn(); + namedColumn.setName("CC1"); + namedColumn.setId(id); + namedColumn.setAliasDotColumn("TEST_BANK_INCOME.CC1"); + columns.add(namedColumn); + copyForWrite.setAllNamedColumns(columns); + }); + return null; + }, project); + } }