KYLIN-2758 Complete input SQL's default database name when the query is 
entering query pushdown engine



Project: http://git-wip-us.apache.org/repos/asf/kylin/repo
Commit: http://git-wip-us.apache.org/repos/asf/kylin/commit/bc347b7b
Tree: http://git-wip-us.apache.org/repos/asf/kylin/tree/bc347b7b
Diff: http://git-wip-us.apache.org/repos/asf/kylin/diff/bc347b7b

Branch: refs/heads/2.1.x
Commit: bc347b7b213e881b689a813f220390716e9b04b6
Parents: 625e706
Author: Jiatao Tao <245915...@qq.com>
Authored: Fri Jul 28 03:29:54 2017 -0500
Committer: GitHub <nore...@github.com>
Committed: Fri Jul 28 03:29:54 2017 -0500

----------------------------------------------------------------------
 .../metadata/model/tool/CalciteParser.java      |  13 +
 .../org/apache/kylin/query/KylinTestBase.java   |   4 +-
 query/pom.xml                                   |   7 +
 .../apache/kylin/query/util/PushDownUtil.java   | 294 +++++++++++++++++++
 .../kylin/query/util/PushDownUtilTest.java      | 163 ++++++++++
 .../apache/kylin/rest/service/QueryService.java |   4 +-
 .../apache/kylin/rest/util/PushDownUtil.java    | 180 ------------
 .../kylin/rest/util/PushDownUtilTest.java       |  94 ------
 8 files changed, 481 insertions(+), 278 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kylin/blob/bc347b7b/core-metadata/src/main/java/org/apache/kylin/metadata/model/tool/CalciteParser.java
----------------------------------------------------------------------
diff --git 
a/core-metadata/src/main/java/org/apache/kylin/metadata/model/tool/CalciteParser.java
 
b/core-metadata/src/main/java/org/apache/kylin/metadata/model/tool/CalciteParser.java
index 5352708..a48abae 100644
--- 
a/core-metadata/src/main/java/org/apache/kylin/metadata/model/tool/CalciteParser.java
+++ 
b/core-metadata/src/main/java/org/apache/kylin/metadata/model/tool/CalciteParser.java
@@ -68,6 +68,19 @@ public class CalciteParser {
 
     }
 
+    public static SqlNode getFromNode(String sql) {
+        //When the sql have limit clause, calcite will parse it as a SqlOrder 
Object.
+        sql = sql.split("LIMIT")[0];
+        SqlNode fromNode = null;
+        try {
+            fromNode = ((SqlSelect) (CalciteParser.parse(sql))).getFrom();
+        } catch (SqlParseException e) {
+            throw new RuntimeException("Failed to parse expression \'" + sql
+                    + "\', please make sure the expression is valid");
+        }
+        return fromNode;
+    }
+
     public static boolean isNodeEqual(SqlNode node0, SqlNode node1) {
         if (node0 == null) {
             return node1 == null;

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc347b7b/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 e1d4a44..0c6feb3 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
@@ -51,7 +51,7 @@ 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.rest.util.PushDownUtil;
+import org.apache.kylin.query.util.PushDownUtil;
 import org.dbunit.DatabaseUnitException;
 import org.dbunit.database.DatabaseConfig;
 import org.dbunit.database.DatabaseConnection;
@@ -263,7 +263,7 @@ public class KylinTestBase {
         } catch (SQLException sqlException) {
             List<List<String>> results = Lists.newArrayList();
             List<SelectedColumnMeta> columnMetas = Lists.newArrayList();
-            boolean b = 
PushDownUtil.doPushDownQuery(ProjectInstance.DEFAULT_PROJECT_NAME, sql, 
results, columnMetas,
+            boolean b = 
PushDownUtil.doPushDownQuery(ProjectInstance.DEFAULT_PROJECT_NAME, "DEFAULT", 
sql, results, columnMetas,
                     sqlException);
             if (!b) {
                 throw sqlException;

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc347b7b/query/pom.xml
----------------------------------------------------------------------
diff --git a/query/pom.xml b/query/pom.xml
index 941eb8a..24c63f9 100644
--- a/query/pom.xml
+++ b/query/pom.xml
@@ -70,5 +70,12 @@
             <artifactId>junit</artifactId>
             <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>
     </dependencies>
 </project>

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc347b7b/query/src/main/java/org/apache/kylin/query/util/PushDownUtil.java
----------------------------------------------------------------------
diff --git a/query/src/main/java/org/apache/kylin/query/util/PushDownUtil.java 
b/query/src/main/java/org/apache/kylin/query/util/PushDownUtil.java
new file mode 100644
index 0000000..f496626
--- /dev/null
+++ b/query/src/main/java/org/apache/kylin/query/util/PushDownUtil.java
@@ -0,0 +1,294 @@
+/*
+ * 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.query.util;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.calcite.sql.SqlBasicCall;
+import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlDataTypeSpec;
+import org.apache.calcite.sql.SqlDynamicParam;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlIntervalQualifier;
+import org.apache.calcite.sql.SqlJoin;
+import org.apache.calcite.sql.SqlLiteral;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.util.SqlVisitor;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.text.StrBuilder;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.util.ClassUtil;
+import org.apache.kylin.metadata.MetadataManager;
+import org.apache.kylin.metadata.model.ComputedColumnDesc;
+import org.apache.kylin.metadata.model.DataModelDesc;
+import org.apache.kylin.metadata.model.tool.CalciteParser;
+import org.apache.kylin.metadata.querymeta.SelectedColumnMeta;
+import org.apache.kylin.query.routing.NoRealizationFoundException;
+import org.apache.kylin.source.adhocquery.IPushDownConverter;
+import org.apache.kylin.source.adhocquery.IPushDownRunner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+public class PushDownUtil {
+    private static final Logger logger = 
LoggerFactory.getLogger(PushDownUtil.class);
+
+    public static boolean doPushDownQuery(String project, String sql, String 
schema, List<List<String>> results,
+            List<SelectedColumnMeta> columnMetas, SQLException sqlException) 
throws Exception {
+
+        KylinConfig kylinConfig = KylinConfig.getInstanceFromEnv();
+        if (!kylinConfig.isPushDownEnabled()) {
+            return false;
+        }
+
+        Throwable rootCause = ExceptionUtils.getRootCause(sqlException);
+        boolean isExpectedCause = rootCause != null && 
(rootCause.getClass().equals(NoRealizationFoundException.class));
+
+        if (isExpectedCause) {
+            logger.info("Query failed to utilize pre-calculation, routing to 
other engines", sqlException);
+            IPushDownRunner runner = (IPushDownRunner) 
ClassUtil.newInstance(kylinConfig.getPushDownRunnerClassName());
+            IPushDownConverter converter = (IPushDownConverter) ClassUtil
+                    .newInstance(kylinConfig.getPushDownConverterClassName());
+
+            runner.init(kylinConfig);
+
+            logger.debug("Query pushdown runner {}", runner);
+
+            String expandCC = restoreComputedColumnToExpr(sql, project);
+            if (!StringUtils.equals(expandCC, sql)) {
+                logger.info("computed column in sql is expanded to:  " + 
expandCC);
+            }
+            if (schema != null && !schema.equals("DEFAULT")) {
+                expandCC = schemaCompletion(expandCC, schema);
+            }
+            String adhocSql = converter.convert(expandCC);
+            if (!adhocSql.equals(expandCC)) {
+                logger.info("the query is converted to {} according to 
kylin.query.pushdown.converter-class-name",
+                        adhocSql);
+            }
+
+            runner.executeQuery(adhocSql, results, columnMetas);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    static String schemaCompletion(String inputSql, String schema) {
+        if (inputSql == null || inputSql.equals("")) {
+            return "";
+        }
+        SqlNode fromNode = CalciteParser.getFromNode(inputSql);
+
+        // get all table node that don't have schema by visitor pattern
+        FromTablesVisitor ftv = new FromTablesVisitor();
+        fromNode.accept(ftv);
+        List<SqlNode> tablesWithoutSchema = ftv.getTablesWithoutSchema();
+
+        List<Pair<Integer, Integer>> tablesPos = new ArrayList<>();
+        for (SqlNode tables : tablesWithoutSchema) {
+            tablesPos.add(CalciteParser.getReplacePos(tables, inputSql));
+        }
+
+        // make the behind position in the front of the list, so that the 
front position will not be affected when replaced
+        Collections.sort(tablesPos);
+        Collections.reverse(tablesPos);
+
+        StrBuilder afterConvert = new StrBuilder(inputSql);
+        for (Pair<Integer, Integer> pos : tablesPos) {
+            String tableWithSchema = schema + "." + 
inputSql.substring(pos.getLeft(), pos.getRight());
+            afterConvert.replace(pos.getLeft(), pos.getRight(), 
tableWithSchema);
+        }
+        return afterConvert.toString();
+    }
+
+    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) {
+        final MetadataManager metadataManager = 
MetadataManager.getInstance(KylinConfig.getInstanceFromEnv());
+        List<DataModelDesc> dataModelDescs = 
metadataManager.getModels(project);
+
+        String afterSql = beforeSql;
+        for (DataModelDesc dataModelDesc : dataModelDescs) {
+            for (ComputedColumnDesc computedColumnDesc : 
dataModelDesc.getComputedColumnDescs()) {
+                afterSql = restoreComputedColumnToExpr(afterSql, 
computedColumnDesc);
+            }
+        }
+        return afterSql;
+    }
+
+    static String restoreComputedColumnToExpr(String sql, ComputedColumnDesc 
computedColumnDesc) {
+
+        String ccName = computedColumnDesc.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(computedColumnDesc.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(computedColumnDesc.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(computedColumnDesc.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(computedColumnDesc.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;
+    }
+
+    static String replaceIdentifierInExpr(String expr, String tableAlias, 
boolean quoted) {
+        if (tableAlias == null) {
+            return expr;
+        }
+
+        return CalciteParser.insertAliasInExpr(expr, tableAlias);
+    }
+}
+
+/**
+ * Created by jiatao.tao
+ * Get all the tables from "FROM" clause that without schema
+ */
+class FromTablesVisitor implements SqlVisitor<SqlNode> {
+    private List<SqlNode> tables;
+
+    FromTablesVisitor() {
+        this.tables = new ArrayList<>();
+    }
+
+    List<SqlNode> getTablesWithoutSchema() {
+        return tables;
+    }
+
+    @Override
+    public SqlNode visit(SqlNodeList nodeList) {
+        return null;
+    }
+
+    @Override
+    public SqlNode visit(SqlLiteral literal) {
+        return null;
+    }
+
+    @Override
+    public SqlNode visit(SqlCall call) {
+        if (call instanceof SqlBasicCall) {
+            SqlBasicCall node = (SqlBasicCall) call;
+            node.getOperands()[0].accept(this);
+            return null;
+        }
+        if (call instanceof SqlJoin) {
+            SqlJoin node = (SqlJoin) call;
+            node.getLeft().accept(this);
+            node.getRight().accept(this);
+            return null;
+        }
+        for (SqlNode operand : call.getOperandList()) {
+            if (operand != null) {
+                operand.accept(this);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SqlNode visit(SqlIdentifier id) {
+        if (id.names.size() == 1) {
+            tables.add(id);
+        }
+        return null;
+    }
+
+    @Override
+    public SqlNode visit(SqlDataTypeSpec type) {
+        return null;
+    }
+
+    @Override
+    public SqlNode visit(SqlDynamicParam param) {
+        return null;
+    }
+
+    @Override
+    public SqlNode visit(SqlIntervalQualifier intervalQualifier) {
+        return null;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc347b7b/query/src/test/java/org/apache/kylin/query/util/PushDownUtilTest.java
----------------------------------------------------------------------
diff --git 
a/query/src/test/java/org/apache/kylin/query/util/PushDownUtilTest.java 
b/query/src/test/java/org/apache/kylin/query/util/PushDownUtilTest.java
new file mode 100644
index 0000000..e2ccd9e
--- /dev/null
+++ b/query/src/test/java/org/apache/kylin/query/util/PushDownUtilTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.query.util;
+
+import org.apache.kylin.metadata.model.ComputedColumnDesc;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class PushDownUtilTest {
+    @Test
+    public void testSchemaCompletion() {
+        String sql1 = "SELECT a \n"+
+                "FROM a.KYLIN_SALES as KYLIN_SALES\n" +
+                "INNER JOIN \"A\".KYLIN_ACCOUNT as BUYER_ACCOUNT\n" +
+                "ON KYLIN_SALES.BUYER_ID = BUYER_ACCOUNT.ACCOUNT_ID\n" +
+                "INNER JOIN \"KYLIN_COUNTRY\" as BUYER_COUNTRY\n" +
+                "ON BUYER_ACCOUNT.ACCOUNT_COUNTRY = BUYER_COUNTRY.COUNTRY";
+        String sql2 = "select * from DB2.t,DB2.tt,ttt";
+
+        String sql3 = "SELECT t1.week_beg_dt, t1.sum_price, t2.cnt\n" +
+                "FROM (\n" +
+                "  select test_cal_dt.week_beg_dt, sum(price) as sum_price\n" +
+                "  from DB1.\"test_kylin_fact\"\n" +
+                "  inner JOIN test_cal_dt as test_cal_dt\n" +
+                "  ON test_kylin_fact.cal_dt = test_cal_dt.cal_dt\n" +
+                "  inner JOIN test_category_groupings\n" +
+                "  ON test_kylin_fact.leaf_categ_id = 
test_category_groupings.leaf_categ_id AND test_kylin_fact.lstg_site_id = 
test_category_groupings.site_id\n" +
+                "  inner JOIN test_sites as test_sites\n" +
+                "  ON test_kylin_fact.lstg_site_id = test_sites.site_id\n" +
+                "  group by test_cal_dt.week_beg_dt\n" +
+                ") t1\n" +
+                "inner join  (\n" +
+                "  select test_cal_dt.week_beg_dt, count(*) as cnt\n" +
+                "  from DB1.test_kylin_fact\n" +
+                "  inner JOIN test_cal_dt as test_cal_dt\n" +
+                "  ON test_kylin_fact.cal_dt = test_cal_dt.cal_dt\n" +
+                "  inner JOIN test_category_groupings\n" +
+                "  ON test_kylin_fact.leaf_categ_id = 
test_category_groupings.leaf_categ_id AND test_kylin_fact.lstg_site_id = 
test_category_groupings.site_id\n" +
+                "  inner JOIN test_sites as test_sites\n" +
+                "  ON test_kylin_fact.lstg_site_id = test_sites.site_id\n" +
+                "  group by test_cal_dt.week_beg_dt\n" +
+                ") t2\n" +
+                "on t1.week_beg_dt=t2.week_beg_dt";
+
+        String exceptSQL1 = "SELECT a \n" +
+                "FROM a.KYLIN_SALES as KYLIN_SALES\n" +
+                "INNER JOIN \"A\".KYLIN_ACCOUNT as BUYER_ACCOUNT\n" +
+                "ON KYLIN_SALES.BUYER_ID = BUYER_ACCOUNT.ACCOUNT_ID\n" +
+                "INNER JOIN EDW.\"KYLIN_COUNTRY\" as BUYER_COUNTRY\n" +
+                "ON BUYER_ACCOUNT.ACCOUNT_COUNTRY = BUYER_COUNTRY.COUNTRY";
+
+        String exceptSQL2 = "select * from DB2.t,DB2.tt,EDW.ttt";
+
+        String exceptSQL3 = "SELECT t1.week_beg_dt, t1.sum_price, t2.cnt\n" +
+                "FROM (\n" +
+                "  select test_cal_dt.week_beg_dt, sum(price) as sum_price\n" +
+                "  from DB1.\"test_kylin_fact\"\n" +
+                "  inner JOIN EDW.test_cal_dt as test_cal_dt\n" +
+                "  ON test_kylin_fact.cal_dt = test_cal_dt.cal_dt\n" +
+                "  inner JOIN EDW.test_category_groupings\n" +
+                "  ON test_kylin_fact.leaf_categ_id = 
test_category_groupings.leaf_categ_id AND test_kylin_fact.lstg_site_id = 
test_category_groupings.site_id\n" +
+                "  inner JOIN EDW.test_sites as test_sites\n" +
+                "  ON test_kylin_fact.lstg_site_id = test_sites.site_id\n" +
+                "  group by test_cal_dt.week_beg_dt\n" +
+                ") t1\n" +
+                "inner join  (\n" +
+                "  select test_cal_dt.week_beg_dt, count(*) as cnt\n" +
+                "  from DB1.test_kylin_fact\n" +
+                "  inner JOIN EDW.test_cal_dt as test_cal_dt\n" +
+                "  ON test_kylin_fact.cal_dt = test_cal_dt.cal_dt\n" +
+                "  inner JOIN EDW.test_category_groupings\n" +
+                "  ON test_kylin_fact.leaf_categ_id = 
test_category_groupings.leaf_categ_id AND test_kylin_fact.lstg_site_id = 
test_category_groupings.site_id\n" +
+                "  inner JOIN EDW.test_sites as test_sites\n" +
+                "  ON test_kylin_fact.lstg_site_id = test_sites.site_id\n" +
+                "  group by test_cal_dt.week_beg_dt\n" +
+                ") t2\n" +
+                "on t1.week_beg_dt=t2.week_beg_dt";
+        Assert.assertEquals(exceptSQL1, PushDownUtil.schemaCompletion(sql1, 
"EDW"));
+        Assert.assertEquals(exceptSQL2, PushDownUtil.schemaCompletion(sql2, 
"EDW"));
+        Assert.assertEquals(exceptSQL3, PushDownUtil.schemaCompletion(sql3, 
"EDW"));
+    }
+
+    @Test
+    public void testReplaceIdentifierInExpr() {
+        {
+            String ret = PushDownUtil.replaceIdentifierInExpr("x * y", null, 
false);
+            Assert.assertEquals("x * y", ret);
+        }
+        {
+            String ret = PushDownUtil.replaceIdentifierInExpr("x_3 * y_3", 
"b_2", false);
+            Assert.assertEquals("b_2.x_3 * b_2.y_3", ret);
+        }
+        {
+            String ret = 
PushDownUtil.replaceIdentifierInExpr("substr(x,1,3)>y", "c", true);
+            Assert.assertEquals("substr(c.x,1,3)>c.y", ret);
+        }
+        {
+            String ret = 
PushDownUtil.replaceIdentifierInExpr("strcmp(substr(x,1,3),y)", "c", true);
+            Assert.assertEquals("strcmp(substr(c.x,1,3),c.y)", ret);
+        }
+        {
+            String ret = 
PushDownUtil.replaceIdentifierInExpr("strcmp(substr(x,1,3),y)", null, true);
+            Assert.assertEquals("strcmp(substr(x,1,3),y)", ret);
+        }
+        {
+            String ret = 
PushDownUtil.replaceIdentifierInExpr("strcmp(substr(x,1,3),y)", null, false);
+            Assert.assertEquals("strcmp(substr(x,1,3),y)", ret);
+        }
+    }
+
+    @Test
+    public void testRestoreComputedColumnToExpr() {
+
+        ComputedColumnDesc computedColumnDesc = 
Mockito.mock(ComputedColumnDesc.class);
+        
Mockito.when(computedColumnDesc.getColumnName()).thenReturn("DEAL_AMOUNT");
+        Mockito.when(computedColumnDesc.getExpression()).thenReturn("price * 
number");
+        {
+            String ret = PushDownUtil.restoreComputedColumnToExpr(
+                    "select DEAL_AMOUNT from DB.TABLE group by date order by 
DEAL_AMOUNT", computedColumnDesc);
+            Assert.assertEquals("select (price * number) from DB.TABLE group 
by date order by (price * number)", ret);
+        }
+        {
+            String ret = PushDownUtil.restoreComputedColumnToExpr(
+                    "select DEAL_AMOUNT as DEAL_AMOUNT from DB.TABLE group by 
date order by DEAL_AMOUNT",
+                    computedColumnDesc);
+            Assert.assertEquals(
+                    "select (price * number) as DEAL_AMOUNT from DB.TABLE 
group by date order by (price * number)",
+                    ret);
+        }
+        {
+            String ret = PushDownUtil.restoreComputedColumnToExpr(
+                    "select \"DEAL_AMOUNT\" AS deal_amount from DB.TABLE group 
by date order by DEAL_AMOUNT",
+                    computedColumnDesc);
+            Assert.assertEquals(
+                    "select (price * number) AS deal_amount from DB.TABLE 
group by date order by (price * number)",
+                    ret);
+        }
+        {
+            String ret = PushDownUtil.restoreComputedColumnToExpr(
+                    "select x.DEAL_AMOUNT AS deal_amount from DB.TABLE x group 
by date order by x.DEAL_AMOUNT",
+                    computedColumnDesc);
+            Assert.assertEquals(
+                    "select (x.price * x.number) AS deal_amount from DB.TABLE 
x group by date order by (x.price * x.number)",
+                    ret);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc347b7b/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 1c55f6b..ab47caa 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
@@ -94,7 +94,7 @@ import org.apache.kylin.rest.request.PrepareSqlRequest;
 import org.apache.kylin.rest.request.SQLRequest;
 import org.apache.kylin.rest.response.SQLResponse;
 import org.apache.kylin.rest.util.AclUtil;
-import org.apache.kylin.rest.util.PushDownUtil;
+import org.apache.kylin.query.util.PushDownUtil;
 import org.apache.kylin.rest.util.TableauInterceptor;
 import org.apache.kylin.storage.hybrid.HybridInstance;
 import org.slf4j.Logger;
@@ -791,7 +791,7 @@ public class QueryService extends BasicService {
                 results.add(oneRow);
             }
         } catch (SQLException sqlException) {
-            isPushDown = PushDownUtil.doPushDownQuery(sqlRequest.getProject(), 
correctedSql, results, columnMetas,
+            isPushDown = PushDownUtil.doPushDownQuery(sqlRequest.getProject(), 
correctedSql, conn.getSchema(), results, columnMetas,
                     sqlException);
             if (!isPushDown) {
                 throw sqlException;

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc347b7b/server-base/src/main/java/org/apache/kylin/rest/util/PushDownUtil.java
----------------------------------------------------------------------
diff --git 
a/server-base/src/main/java/org/apache/kylin/rest/util/PushDownUtil.java 
b/server-base/src/main/java/org/apache/kylin/rest/util/PushDownUtil.java
deleted file mode 100644
index c21d5c2..0000000
--- a/server-base/src/main/java/org/apache/kylin/rest/util/PushDownUtil.java
+++ /dev/null
@@ -1,180 +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.rest.util;
-
-import java.sql.SQLException;
-import java.util.Collections;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-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.common.util.ClassUtil;
-import org.apache.kylin.metadata.MetadataManager;
-import org.apache.kylin.metadata.model.ComputedColumnDesc;
-import org.apache.kylin.metadata.model.DataModelDesc;
-import org.apache.kylin.metadata.model.tool.CalciteParser;
-import org.apache.kylin.metadata.querymeta.SelectedColumnMeta;
-import org.apache.kylin.query.routing.NoRealizationFoundException;
-import org.apache.kylin.source.adhocquery.IPushDownConverter;
-import org.apache.kylin.source.adhocquery.IPushDownRunner;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Lists;
-
-public class PushDownUtil {
-    private static final Logger logger = 
LoggerFactory.getLogger(PushDownUtil.class);
-
-    public static boolean doPushDownQuery(String project, String sql, 
List<List<String>> results,
-            List<SelectedColumnMeta> columnMetas, SQLException sqlException) 
throws Exception {
-
-        KylinConfig kylinConfig = KylinConfig.getInstanceFromEnv();
-        if (!kylinConfig.isPushDownEnabled()) {
-            return false;
-        }
-
-        Throwable rootCause = ExceptionUtils.getRootCause(sqlException);
-        boolean isExpectedCause = rootCause != null && 
(rootCause.getClass().equals(NoRealizationFoundException.class));
-
-        if (isExpectedCause) {
-            logger.info("Query failed to utilize pre-calculation, routing to 
other engines", sqlException);
-            IPushDownRunner runner = (IPushDownRunner) 
ClassUtil.newInstance(kylinConfig.getPushDownRunnerClassName());
-            IPushDownConverter converter = (IPushDownConverter) ClassUtil
-                    .newInstance(kylinConfig.getPushDownConverterClassName());
-
-            runner.init(kylinConfig);
-
-            logger.debug("Query pushdown runner {}", runner);
-
-            String expandCC = restoreComputedColumnToExpr(sql, project);
-            if (!StringUtils.equals(expandCC, sql)) {
-                logger.info("computed column in sql is expanded to:  " + 
expandCC);
-            }
-            String adhocSql = converter.convert(expandCC);
-            if (!adhocSql.equals(expandCC)) {
-                logger.info("the query is converted to {} according to 
kylin.query.pushdown.converter-class-name",
-                        adhocSql);
-            }
-
-            runner.executeQuery(adhocSql, results, columnMetas);
-
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    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) {
-        final MetadataManager metadataManager = 
MetadataManager.getInstance(KylinConfig.getInstanceFromEnv());
-        List<DataModelDesc> dataModelDescs = 
metadataManager.getModels(project);
-
-        String afterSql = beforeSql;
-        for (DataModelDesc dataModelDesc : dataModelDescs) {
-            for (ComputedColumnDesc computedColumnDesc : 
dataModelDesc.getComputedColumnDescs()) {
-                afterSql = restoreComputedColumnToExpr(afterSql, 
computedColumnDesc);
-            }
-        }
-        return afterSql;
-    }
-
-    static String restoreComputedColumnToExpr(String sql, ComputedColumnDesc 
computedColumnDesc) {
-
-        String ccName = computedColumnDesc.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(computedColumnDesc.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(computedColumnDesc.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(computedColumnDesc.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(computedColumnDesc.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;
-    }
-
-    static String replaceIdentifierInExpr(String expr, String tableAlias, 
boolean quoted) {
-        if (tableAlias == null) {
-            return expr;
-        }
-
-        return CalciteParser.insertAliasInExpr(expr, tableAlias);
-    }
-}

http://git-wip-us.apache.org/repos/asf/kylin/blob/bc347b7b/server-base/src/test/java/org/apache/kylin/rest/util/PushDownUtilTest.java
----------------------------------------------------------------------
diff --git 
a/server-base/src/test/java/org/apache/kylin/rest/util/PushDownUtilTest.java 
b/server-base/src/test/java/org/apache/kylin/rest/util/PushDownUtilTest.java
deleted file mode 100644
index 5302a70..0000000
--- a/server-base/src/test/java/org/apache/kylin/rest/util/PushDownUtilTest.java
+++ /dev/null
@@ -1,94 +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.rest.util;
-
-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 PushDownUtilTest {
-
-    @Test
-    public void testReplaceIdentifierInExpr() {
-        {
-            String ret = PushDownUtil.replaceIdentifierInExpr("x * y", null, 
false);
-            Assert.assertEquals("x * y", ret);
-        }
-        {
-            String ret = PushDownUtil.replaceIdentifierInExpr("x_3 * y_3", 
"b_2", false);
-            Assert.assertEquals("b_2.x_3 * b_2.y_3", ret);
-        }
-        {
-            String ret = 
PushDownUtil.replaceIdentifierInExpr("substr(x,1,3)>y", "c", true);
-            Assert.assertEquals("substr(c.x,1,3)>c.y", ret);
-        }
-        {
-            String ret = 
PushDownUtil.replaceIdentifierInExpr("strcmp(substr(x,1,3),y)", "c", true);
-            Assert.assertEquals("strcmp(substr(c.x,1,3),c.y)", ret);
-        }
-        {
-            String ret = 
PushDownUtil.replaceIdentifierInExpr("strcmp(substr(x,1,3),y)", null, true);
-            Assert.assertEquals("strcmp(substr(x,1,3),y)", ret);
-        }
-        {
-            String ret = 
PushDownUtil.replaceIdentifierInExpr("strcmp(substr(x,1,3),y)", null, false);
-            Assert.assertEquals("strcmp(substr(x,1,3),y)", ret);
-        }
-    }
-
-    @Test
-    public void testRestoreComputedColumnToExpr() {
-
-        ComputedColumnDesc computedColumnDesc = mock(ComputedColumnDesc.class);
-        when(computedColumnDesc.getColumnName()).thenReturn("DEAL_AMOUNT");
-        when(computedColumnDesc.getExpression()).thenReturn("price * number");
-
-        {
-            String ret = PushDownUtil.restoreComputedColumnToExpr(
-                    "select DEAL_AMOUNT from DB.TABLE group by date order by 
DEAL_AMOUNT", computedColumnDesc);
-            Assert.assertEquals("select (price * number) from DB.TABLE group 
by date order by (price * number)", ret);
-        }
-        {
-            String ret = PushDownUtil.restoreComputedColumnToExpr(
-                    "select DEAL_AMOUNT as DEAL_AMOUNT from DB.TABLE group by 
date order by DEAL_AMOUNT",
-                    computedColumnDesc);
-            Assert.assertEquals(
-                    "select (price * number) as DEAL_AMOUNT from DB.TABLE 
group by date order by (price * number)",
-                    ret);
-        }
-        {
-            String ret = PushDownUtil.restoreComputedColumnToExpr(
-                    "select \"DEAL_AMOUNT\" AS deal_amount from DB.TABLE group 
by date order by DEAL_AMOUNT",
-                    computedColumnDesc);
-            Assert.assertEquals(
-                    "select (price * number) AS deal_amount from DB.TABLE 
group by date order by (price * number)",
-                    ret);
-        }
-        {
-            String ret = PushDownUtil.restoreComputedColumnToExpr(
-                    "select x.DEAL_AMOUNT AS deal_amount from DB.TABLE x group 
by date order by x.DEAL_AMOUNT",
-                    computedColumnDesc);
-            Assert.assertEquals(
-                    "select (x.price * x.number) AS deal_amount from DB.TABLE 
x group by date order by (x.price * x.number)",
-                    ret);
-        }
-    }
-}

Reply via email to