You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by li...@apache.org on 2017/07/03 07:44:14 UTC

[03/26] kylin git commit: minor, enhance computed column checks

minor, enhance computed column checks


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

Branch: refs/heads/kylin-2.1.x
Commit: d61f867ff9fa170e155310edeb03db6059433f2f
Parents: 69532cd
Author: Hongbin Ma <ma...@apache.org>
Authored: Wed Jun 28 21:13:25 2017 +0800
Committer: Hongbin Ma <ma...@kyligence.io>
Committed: Thu Jun 29 13:51:45 2017 +0800

----------------------------------------------------------------------
 core-metadata/pom.xml                           |  16 +-
 .../apache/kylin/metadata/model/ColumnDesc.java |  15 +-
 .../metadata/model/ComputedColumnDesc.java      |   3 +
 .../kylin/metadata/model/DataModelDesc.java     |  30 ++-
 .../apache/kylin/metadata/model/TblColRef.java  |   2 +-
 .../metadata/model/tool/CalciteParser.java      | 203 +++++++++++++++++++
 .../kylin/model/tool/CalciteParserTest.java     | 111 ++++++++++
 .../model_desc/ci_inner_join_model.json         |   6 +-
 .../model_desc/ci_left_join_model.json          |   6 +-
 .../query/util/ConvertToComputedColumn.java     | 111 ++--------
 .../query/util/ConvertToComputedColumnTest.java |  22 --
 11 files changed, 379 insertions(+), 146 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kylin/blob/d61f867f/core-metadata/pom.xml
----------------------------------------------------------------------
diff --git a/core-metadata/pom.xml b/core-metadata/pom.xml
index f5ab12a..d45a838 100644
--- a/core-metadata/pom.xml
+++ b/core-metadata/pom.xml
@@ -17,7 +17,8 @@
  limitations under the License.
 -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
@@ -60,6 +61,19 @@
             <groupId>com.google.code.findbugs</groupId>
             <artifactId>jsr305</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.kylin</groupId>
+            <artifactId>atopcalcite</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.calcite.avatica</groupId>
+                    <artifactId>avatica-core</artifactId>
+                </exclusion>
+            </exclusions>
+            <!--set to provided on purpose, so that dependant on metadata module will not depend on calcite unnecessarily-->
+            <!--https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html-->
+            <scope>provided</scope>
+        </dependency>
 
         <!-- Compiled -->
         <dependency>

http://git-wip-us.apache.org/repos/asf/kylin/blob/d61f867f/core-metadata/src/main/java/org/apache/kylin/metadata/model/ColumnDesc.java
----------------------------------------------------------------------
diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/model/ColumnDesc.java b/core-metadata/src/main/java/org/apache/kylin/metadata/model/ColumnDesc.java
index 5d15d56..3c84c96 100644
--- a/core-metadata/src/main/java/org/apache/kylin/metadata/model/ColumnDesc.java
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/model/ColumnDesc.java
@@ -19,9 +19,9 @@
 package org.apache.kylin.metadata.model;
 
 import java.io.Serializable;
-import java.util.regex.Pattern;
 
 import org.apache.kylin.metadata.datatype.DataType;
+import org.apache.kylin.metadata.model.tool.CalciteParser;
 
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
@@ -82,7 +82,8 @@ public class ColumnDesc implements Serializable {
         this.index = other.index;
     }
 
-    public ColumnDesc(String id, String name, String datatype, String comment, String dataGen, String index, String computedColumnExpr) {
+    public ColumnDesc(String id, String name, String datatype, String comment, String dataGen, String index,
+            String computedColumnExpr) {
         this.id = id;
         this.name = name;
         this.datatype = datatype;
@@ -193,11 +194,12 @@ public class ColumnDesc implements Serializable {
         return index;
     }
 
-    public String getComputedColumnExpr(String tableAlias, String tableIdentity) {
+    public String getComputedColumnExpr(String tableAlias) {
         Preconditions.checkState(computedColumnExpr != null);
+        Preconditions.checkState(tableAlias != null);
 
-        //http://stackoverflow.com/questions/5054995/how-to-replace-case-insensitive-literal-substrings-in-java
-        return computedColumnExpr.replaceAll("(?i)" + Pattern.quote(tableIdentity), tableAlias);
+        String s = CalciteParser.insertAliasInExpr(computedColumnExpr, tableAlias);
+        return s;
     }
 
     public boolean isComputedColumnn() {
@@ -257,6 +259,7 @@ public class ColumnDesc implements Serializable {
 
     @Override
     public String toString() {
-        return "ColumnDesc{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", datatype='" + datatype + '\'' + ", comment='" + comment + '\'' + '}';
+        return "ColumnDesc{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", datatype='" + datatype + '\''
+                + ", comment='" + comment + '\'' + '}';
     }
 }

http://git-wip-us.apache.org/repos/asf/kylin/blob/d61f867f/core-metadata/src/main/java/org/apache/kylin/metadata/model/ComputedColumnDesc.java
----------------------------------------------------------------------
diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/model/ComputedColumnDesc.java b/core-metadata/src/main/java/org/apache/kylin/metadata/model/ComputedColumnDesc.java
index 013cb8c..540b5fc 100644
--- a/core-metadata/src/main/java/org/apache/kylin/metadata/model/ComputedColumnDesc.java
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/model/ComputedColumnDesc.java
@@ -20,6 +20,7 @@ package org.apache.kylin.metadata.model;
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.base.Preconditions;
+import org.apache.kylin.metadata.model.tool.CalciteParser;
 
 @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
 public class ComputedColumnDesc {
@@ -42,6 +43,8 @@ public class ComputedColumnDesc {
 
         tableIdentity = tableIdentity.toUpperCase();
         columnName = columnName.toUpperCase();
+
+        CalciteParser.ensureNoTableNameExists(expression);
     }
 
     public String getFullName() {

http://git-wip-us.apache.org/repos/asf/kylin/blob/d61f867f/core-metadata/src/main/java/org/apache/kylin/metadata/model/DataModelDesc.java
----------------------------------------------------------------------
diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/model/DataModelDesc.java b/core-metadata/src/main/java/org/apache/kylin/metadata/model/DataModelDesc.java
index 7de955e..91802f7 100644
--- a/core-metadata/src/main/java/org/apache/kylin/metadata/model/DataModelDesc.java
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/model/DataModelDesc.java
@@ -489,17 +489,11 @@ public class DataModelDesc extends RootPersistentEntity {
             }
 
             CCInfo other = ccInfoMap.get(thisCCName);
-            if (other == null || (other.getDataModelDescs().size() == 1 && other.getDataModelDescs().contains(this))) {
-                //check whether two computer columns's definition is the same.
-                for (CCInfo sysCCInfo : ccInfoMap.values()) {
-                    String definition0 = thisCCDesc.getExpression();
-                    String definition1 = sysCCInfo.getComputedColumnDesc().getExpression();
-                    if (isTwoCCDefinitionEquals(definition0, definition1)) {
-                        throw new IllegalStateException(String.format(
-                                "Computed column %s'definition: %s is already defined in other models: %s. Please change another definition, or try to keep consistent definition",
-                                thisCCName, definition0, sysCCInfo.getDataModelDescs()));
-                    }
-                }
+
+            if (other == null) {
+                checkSameCCDefinition(ccInfoMap, thisCCDesc, thisCCName);
+                ccInfoMap.put(thisCCName, new CCInfo(thisCCDesc, Sets.<DataModelDesc> newHashSet(this)));
+            } else if (other.getDataModelDescs().size() == 1 && other.getDataModelDescs().contains(this)) {
                 ccInfoMap.put(thisCCName, new CCInfo(thisCCDesc, Sets.<DataModelDesc> newHashSet(this)));
             } else if (other.getComputedColumnDesc().equals(thisCCDesc)) {
                 other.getDataModelDescs().add(this);
@@ -511,6 +505,20 @@ public class DataModelDesc extends RootPersistentEntity {
         }
     }
 
+    private void checkSameCCDefinition(Map<String, CCInfo> ccInfoMap, ComputedColumnDesc thisCCDesc,
+            String thisCCName) {
+        //check whether two computer columns's definition is the same.
+        for (CCInfo sysCCInfo : ccInfoMap.values()) {
+            String definition0 = thisCCDesc.getExpression();
+            String definition1 = sysCCInfo.getComputedColumnDesc().getExpression();
+            if (isTwoCCDefinitionEquals(definition0, definition1)) {
+                throw new IllegalStateException(String.format(
+                        "Computed column %s's definition: %s is already defined in other models: %s. Please change another definition, or try to keep consistent definition",
+                        thisCCName, definition0, sysCCInfo.getDataModelDescs()));
+            }
+        }
+    }
+
     private boolean isTwoCCDefinitionEquals(String definition0, String definition1) {
         definition0 = definition0.replaceAll("\\s*", "");
         definition1 = definition1.replaceAll("\\s*", "");

http://git-wip-us.apache.org/repos/asf/kylin/blob/d61f867f/core-metadata/src/main/java/org/apache/kylin/metadata/model/TblColRef.java
----------------------------------------------------------------------
diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/model/TblColRef.java b/core-metadata/src/main/java/org/apache/kylin/metadata/model/TblColRef.java
index 5a28e8b..5563856 100644
--- a/core-metadata/src/main/java/org/apache/kylin/metadata/model/TblColRef.java
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/model/TblColRef.java
@@ -158,7 +158,7 @@ public class TblColRef implements Serializable {
         if (!column.isComputedColumnn()) {
             return getIdentity();
         } else {
-            return column.getComputedColumnExpr(getTableAlias(), table.getTableIdentity());
+            return column.getComputedColumnExpr(getTableAlias());
         }
     }
 

http://git-wip-us.apache.org/repos/asf/kylin/blob/d61f867f/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
new file mode 100644
index 0000000..47a90bf
--- /dev/null
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/model/tool/CalciteParser.java
@@ -0,0 +1,203 @@
+/*
+ * 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.tool;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlLiteral;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.parser.SqlParseException;
+import org.apache.calcite.sql.parser.SqlParser;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.util.SqlBasicVisitor;
+import org.apache.calcite.sql.util.SqlVisitor;
+import org.apache.commons.lang3.tuple.Pair;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+public class CalciteParser {
+    public static SqlNode parse(String sql) throws SqlParseException {
+        SqlParser.ConfigBuilder parserBuilder = SqlParser.configBuilder();
+        SqlParser sqlParser = SqlParser.create(sql, parserBuilder.build());
+        return sqlParser.parseQuery();
+    }
+
+    public static SqlNode getOnlySelectNode(String sql) {
+        SqlNodeList selectList = null;
+        try {
+            selectList = ((SqlSelect) CalciteParser.parse(sql)).getSelectList();
+        } catch (SqlParseException e) {
+            throw new RuntimeException("Failed to parse expression \'" + sql
+                    + "\', please make sure the expression is valid and no table name exists in the expression");
+        }
+
+        Preconditions.checkArgument(selectList.size() == 1,
+                "Expression is invalid because size of select list exceeds one");
+
+        return selectList.get(0);
+    }
+
+    public static SqlNode getExpNode(String expr) {
+        return getOnlySelectNode("select " + expr + " from t");
+
+    }
+
+    public static boolean isNodeEqual(SqlNode node0, SqlNode node1) {
+        if (node0 == null) {
+            return node1 == null;
+        } else if (node1 == null) {
+            return false;
+        }
+
+        if (!Objects.equals(node0.getClass().getSimpleName(), node1.getClass().getSimpleName())) {
+            return false;
+        }
+
+        if (node0 instanceof SqlCall) {
+            SqlCall thisNode = (SqlCall) node0;
+            SqlCall thatNode = (SqlCall) node1;
+            if (!thisNode.getOperator().getName().equalsIgnoreCase(thatNode.getOperator().getName())) {
+                return false;
+            }
+            return isNodeEqual(thisNode.getOperandList(), thatNode.getOperandList());
+        }
+        if (node0 instanceof SqlLiteral) {
+            SqlLiteral thisNode = (SqlLiteral) node0;
+            SqlLiteral thatNode = (SqlLiteral) node1;
+            return Objects.equals(thisNode.getValue(), thatNode.getValue());
+        }
+        if (node0 instanceof SqlNodeList) {
+            SqlNodeList thisNode = (SqlNodeList) node0;
+            SqlNodeList thatNode = (SqlNodeList) node1;
+            if (thisNode.getList().size() != thatNode.getList().size()) {
+                return false;
+            }
+            for (int i = 0; i < thisNode.getList().size(); i++) {
+                SqlNode thisChild = thisNode.getList().get(i);
+                final SqlNode thatChild = thatNode.getList().get(i);
+                if (!isNodeEqual(thisChild, thatChild)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        if (node0 instanceof SqlIdentifier) {
+            SqlIdentifier thisNode = (SqlIdentifier) node0;
+            SqlIdentifier thatNode = (SqlIdentifier) node1;
+            // compare ignore table alias.eg: expression like "a.b + a.c + a.d" ,alias a will be ignored when compared
+            String name0 = thisNode.names.get(thisNode.names.size() - 1).replace("\"", "");
+            String name1 = thatNode.names.get(thatNode.names.size() - 1).replace("\"", "");
+            return name0.equalsIgnoreCase(name1);
+        }
+
+        return false;
+    }
+
+    private static boolean isNodeEqual(List<SqlNode> operands0, List<SqlNode> operands1) {
+        if (operands0.size() != operands1.size()) {
+            return false;
+        }
+        for (int i = 0; i < operands0.size(); i++) {
+            if (!isNodeEqual(operands0.get(i), operands1.get(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static void ensureNoTableNameExists(String expr) {
+        SqlNode sqlNode = getExpNode(expr);
+
+        SqlVisitor sqlVisitor = new SqlBasicVisitor() {
+            @Override
+            public Object visit(SqlIdentifier id) {
+                if (id.names.size() > 1) {
+                    throw new IllegalArgumentException("SqlIdentifier " + id + " contains DB/Table name");
+                }
+                return null;
+            }
+        };
+
+        sqlNode.accept(sqlVisitor);
+    }
+
+    public static String insertAliasInExpr(String expr, String alias) {
+        String prefix = "select ";
+        String suffix = " from t";
+        String sql = prefix + expr + suffix;
+        SqlNode sqlNode = getOnlySelectNode(sql);
+
+        final List<SqlIdentifier> sqlIdentifiers = Lists.newArrayList();
+        SqlVisitor sqlVisitor = new SqlBasicVisitor() {
+            @Override
+            public Object visit(SqlIdentifier id) {
+                if (id.names.size() > 1) {
+                    throw new IllegalArgumentException("SqlIdentifier " + id + " contains DB/Table name");
+                }
+                sqlIdentifiers.add(id);
+                return null;
+            }
+        };
+
+        sqlNode.accept(sqlVisitor);
+
+        Collections.sort(sqlIdentifiers, new Comparator<SqlIdentifier>() {
+            @Override
+            public int compare(SqlIdentifier o1, SqlIdentifier o2) {
+                int linegap = o2.getParserPosition().getLineNum() - o1.getParserPosition().getLineNum();
+                if (linegap != 0)
+                    return linegap;
+
+                return o2.getParserPosition().getColumnNum() - o1.getParserPosition().getColumnNum();
+            }
+        });
+
+        for (SqlIdentifier sqlIdentifier : sqlIdentifiers) {
+            Pair<Integer, Integer> replacePos = getReplacePos(sqlIdentifier, sql);
+            int start = replacePos.getLeft();
+            sql = sql.substring(0, start) + alias + "." + sql.substring(start);
+        }
+
+        return sql.substring(prefix.length(), sql.length() - suffix.length());
+    }
+
+    public static Pair<Integer, Integer> getReplacePos(SqlNode node, String... lines) {
+        SqlParserPos pos = node.getParserPosition();
+        int lineStart = pos.getLineNum();
+        int lineEnd = pos.getEndLineNum();
+        int columnStart = pos.getColumnNum() - 1;
+        int columnEnd = pos.getEndColumnNum();
+        //for the case that sql is multi lines
+        for (int i = 0; i < lineStart - 1; i++) {
+            columnStart += lines[i].length() + 1;
+        }
+        for (int i = 0; i < lineEnd - 1; i++) {
+            columnEnd += lines[i].length() + 1;
+        }
+        return Pair.of(columnStart, columnEnd);
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/d61f867f/core-metadata/src/test/java/org/apache/kylin/model/tool/CalciteParserTest.java
----------------------------------------------------------------------
diff --git a/core-metadata/src/test/java/org/apache/kylin/model/tool/CalciteParserTest.java b/core-metadata/src/test/java/org/apache/kylin/model/tool/CalciteParserTest.java
new file mode 100644
index 0000000..acf7b5a
--- /dev/null
+++ b/core-metadata/src/test/java/org/apache/kylin/model/tool/CalciteParserTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.model.tool;
+
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.parser.SqlParseException;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.kylin.metadata.model.tool.CalciteParser;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import com.google.common.base.Preconditions;
+
+public class CalciteParserTest {
+
+    @Rule
+    public final ExpectedException exception = ExpectedException.none();
+
+    @Test
+    public void testNoTableNameExists() throws SqlParseException {
+        String expr1 = "a + b";
+        Assert.assertEquals("x.a + x.b", CalciteParser.insertAliasInExpr(expr1, "x"));
+
+        String expr2 = "a + year(b)";
+        Assert.assertEquals("x.a + year(x.b)", CalciteParser.insertAliasInExpr(expr2, "x"));
+
+        String expr3 = "a + hiveudf(b)";
+        Assert.assertEquals("x.a + hiveudf(x.b)", CalciteParser.insertAliasInExpr(expr3, "x"));
+    }
+
+    @Test
+    public void testTableNameExists1() throws SqlParseException {
+        String expr1 = "a + x.b";
+
+        exception.expect(IllegalArgumentException.class);
+        exception.expectMessage("SqlIdentifier X.B contains DB/Table name");
+        CalciteParser.insertAliasInExpr(expr1, "x");
+    }
+
+    @Test
+    public void testTableNameExists2() throws SqlParseException {
+        String expr1 = "a + year(x.b)";
+
+        exception.expect(IllegalArgumentException.class);
+        exception.expectMessage("SqlIdentifier X.B contains DB/Table name");
+        CalciteParser.insertAliasInExpr(expr1, "x");
+    }
+
+    @Test
+    public void testTableNameExists3() throws SqlParseException {
+        String expr1 = "a + hiveudf(x.b)";
+
+        exception.expect(IllegalArgumentException.class);
+        exception.expectMessage("SqlIdentifier X.B contains DB/Table name");
+        CalciteParser.insertAliasInExpr(expr1, "x");
+    }
+
+    @Test
+    public void testPos() throws SqlParseException {
+        String[] sqls = new String[] { "select \n a \n + \n b \n from t", //
+                "select\na\n+\nb\nfrom t", //
+                "select \r\n a \r\n + \r\n b \r\n from t", //
+                "select\r\na\r\n+\r\nb\r\nfrom t" };
+
+        for (String sql : sqls) {
+            SqlNode parse = ((SqlSelect) CalciteParser.parse(sql)).getSelectList().get(0);
+            String[] lines = sql.split("\n");
+            Pair<Integer, Integer> replacePos = CalciteParser.getReplacePos(parse, lines);
+            String substring = sql.substring(replacePos.getLeft(), replacePos.getRight());
+            Preconditions.checkArgument(substring.startsWith("a"));
+            Preconditions.checkArgument(substring.endsWith("b"));
+        }
+
+    }
+
+    @Test
+    public void testEqual() throws SqlParseException {
+        String sql0 = "select a.a + a.b + a.c from t as a";
+        String sql1 = "select (((a . a +    a.b +    a.c))) from t as a";
+        String sql2 = "select (a + b) + c  from t";
+        String sql3 = "select a.a + (a.b + a.c) from t as a";
+
+        SqlNode sn0 = CalciteParser.getOnlySelectNode(sql0);
+        SqlNode sn1 = CalciteParser.getOnlySelectNode(sql1);
+        SqlNode sn2 = CalciteParser.getOnlySelectNode(sql2);
+        SqlNode sn3 = CalciteParser.getOnlySelectNode(sql3);
+
+        Assert.assertEquals(true, CalciteParser.isNodeEqual(sn0, sn1));
+        Assert.assertEquals(true, CalciteParser.isNodeEqual(sn0, sn2));
+        Assert.assertEquals(false, CalciteParser.isNodeEqual(sn0, sn3));
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/d61f867f/examples/test_case_data/localmeta/model_desc/ci_inner_join_model.json
----------------------------------------------------------------------
diff --git a/examples/test_case_data/localmeta/model_desc/ci_inner_join_model.json b/examples/test_case_data/localmeta/model_desc/ci_inner_join_model.json
index d084ce2..5225cf3 100644
--- a/examples/test_case_data/localmeta/model_desc/ci_inner_join_model.json
+++ b/examples/test_case_data/localmeta/model_desc/ci_inner_join_model.json
@@ -125,21 +125,21 @@
     {
       "tableIdentity": "DEFAULT.TEST_KYLIN_FACT",
       "columnName": "DEAL_AMOUNT",
-      "expression": "DEFAULT.TEST_KYLIN_FACT.PRICE * DEFAULT.TEST_KYLIN_FACT.ITEM_COUNT",
+      "expression": "PRICE * ITEM_COUNT",
       "datatype": "decimal",
       "comment": "deal amount of inner join model"
     },
     {
       "tableIdentity": "DEFAULT.TEST_KYLIN_FACT",
       "columnName": "DEAL_YEAR",
-      "expression": "year(DEFAULT.TEST_KYLIN_FACT.CAL_DT)",
+      "expression": "year(CAL_DT)",
       "datatype": "integer",
       "comment": "the year of the deal of the inner join model"
     },
     {
       "tableIdentity": "DEFAULT.TEST_ACCOUNT",
       "columnName": "COUNTRY_ABBR",
-      "expression": "SUBSTR(DEFAULT.TEST_ACCOUNT.ACCOUNT_COUNTRY,0,1)",
+      "expression": "SUBSTR(ACCOUNT_COUNTRY,0,1)",
       "datatype": "string",
       "comment": "first char of country of the inner join model"
     }

http://git-wip-us.apache.org/repos/asf/kylin/blob/d61f867f/examples/test_case_data/localmeta/model_desc/ci_left_join_model.json
----------------------------------------------------------------------
diff --git a/examples/test_case_data/localmeta/model_desc/ci_left_join_model.json b/examples/test_case_data/localmeta/model_desc/ci_left_join_model.json
index 4a10899..0058425 100644
--- a/examples/test_case_data/localmeta/model_desc/ci_left_join_model.json
+++ b/examples/test_case_data/localmeta/model_desc/ci_left_join_model.json
@@ -125,21 +125,21 @@
     {
       "tableIdentity": "DEFAULT.TEST_KYLIN_FACT",
       "columnName": "DEAL_AMOUNT",
-      "expression": "DEFAULT.TEST_KYLIN_FACT.PRICE * DEFAULT.TEST_KYLIN_FACT.ITEM_COUNT", 
+      "expression": "PRICE * ITEM_COUNT", 
       "datatype": "decimal",
       "comment": "deal amount of left join model"
     },
     {
       "tableIdentity": "DEFAULT.TEST_KYLIN_FACT",
       "columnName": "DEAL_YEAR",
-      "expression": "year(DEFAULT.TEST_KYLIN_FACT.CAL_DT)",
+      "expression": "year(CAL_DT)",
       "datatype": "integer",
       "comment": "the year of the deal of the left join model"
     },
     {
       "tableIdentity": "DEFAULT.TEST_ACCOUNT",
       "columnName": "COUNTRY_ABBR",
-      "expression": "SUBSTR(DEFAULT.TEST_ACCOUNT.ACCOUNT_COUNTRY,0,1)",
+      "expression": "SUBSTR(ACCOUNT_COUNTRY,0,1)",
       "datatype": "string",
       "comment": "first char of country of the left join model"
     }

http://git-wip-us.apache.org/repos/asf/kylin/blob/d61f867f/query/src/main/java/org/apache/kylin/query/util/ConvertToComputedColumn.java
----------------------------------------------------------------------
diff --git a/query/src/main/java/org/apache/kylin/query/util/ConvertToComputedColumn.java b/query/src/main/java/org/apache/kylin/query/util/ConvertToComputedColumn.java
index d8f1220..7f188c1 100644
--- a/query/src/main/java/org/apache/kylin/query/util/ConvertToComputedColumn.java
+++ b/query/src/main/java/org/apache/kylin/query/util/ConvertToComputedColumn.java
@@ -23,7 +23,6 @@ import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 
 import javax.annotation.Nullable;
 
@@ -35,15 +34,14 @@ import org.apache.calcite.sql.SqlIntervalQualifier;
 import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
-import org.apache.calcite.sql.SqlSelect;
 import org.apache.calcite.sql.parser.SqlParseException;
-import org.apache.calcite.sql.parser.SqlParser;
-import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.sql.util.SqlVisitor;
+import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.metadata.MetadataManager;
 import org.apache.kylin.metadata.model.DataModelDesc;
+import org.apache.kylin.metadata.model.tool.CalciteParser;
 import org.apache.kylin.metadata.project.ProjectInstance;
 import org.apache.kylin.metadata.project.ProjectManager;
 import org.slf4j.Logger;
@@ -65,7 +63,11 @@ public class ConvertToComputedColumn implements QueryUtil.IQueryTransformer {
             return sql;
         }
         ImmutableSortedMap<String, String> computedColumns = getSortedComputedColumnWithProject(project);
-        return replaceComputedColumn(sql, computedColumns);
+        String s = replaceComputedColumn(sql, computedColumns);
+        if (!StringUtils.equals(sql, s)) {
+            logger.debug("change sql to " + s);
+        }
+        return s;
     }
 
     static String replaceComputedColumn(String inputSql, ImmutableSortedMap<String, String> computedColumn) {
@@ -88,7 +90,7 @@ public class ConvertToComputedColumn implements QueryUtil.IQueryTransformer {
                 logger.error("Convert to computedColumn Fail,parse sql fail ", e.getMessage());
             }
             for (SqlNode node : matchedNodes) {
-                Pair<Integer, Integer> startEndPos = getReplacePos(lines, node);
+                Pair<Integer, Integer> startEndPos = CalciteParser.getReplacePos(node, lines);
                 int start = startEndPos.getLeft();
                 int end = startEndPos.getRight();
                 //add table alias like t1.column,if exists alias
@@ -104,32 +106,18 @@ public class ConvertToComputedColumn implements QueryUtil.IQueryTransformer {
         return result;
     }
 
-    private static Pair<Integer, Integer> getReplacePos(String[] lines, SqlNode node) {
-        SqlParserPos pos = node.getParserPosition();
-        int lineStart = pos.getLineNum();
-        int columnStart = pos.getColumnNum() - 1;
-        int columnEnd = pos.getEndColumnNum();
-        //for the case that sql is multi lines
-        for (int i = 0; i < lineStart - 1; i++) {
-            int offset = lines[i].length();
-            columnStart += offset + 1;
-            columnEnd += offset + 1;
-        }
-        return Pair.of(columnStart, columnEnd);
-    }
-
     //Return matched node's position and its alias(if exists).If can not find matches, return an empty capacity list
     private static List<SqlNode> getMatchedNodes(String inputSql, String ccExp) throws SqlParseException {
         if (ccExp == null || ccExp.equals("")) {
             return new ArrayList<>();
         }
         ArrayList<SqlNode> toBeReplacedNodes = new ArrayList<>();
-        SqlNode ccNode = getCCExpNode(ccExp);
+        SqlNode ccNode = CalciteParser.getExpNode(ccExp);
         List<SqlNode> inputNodes = getInputTreeNodes(inputSql);
 
         // find whether user input sql's tree node equals computed columns's define expression
         for (SqlNode inputNode : inputNodes) {
-            if (isNodeEqual(inputNode, ccNode)) {
+            if (CalciteParser.isNodeEqual(inputNode, ccNode)) {
                 toBeReplacedNodes.add(inputNode);
             }
         }
@@ -138,85 +126,10 @@ public class ConvertToComputedColumn implements QueryUtil.IQueryTransformer {
 
     private static List<SqlNode> getInputTreeNodes(String inputSql) throws SqlParseException {
         SqlTreeVisitor stv = new SqlTreeVisitor();
-        parse(inputSql).accept(stv);
+        CalciteParser.parse(inputSql).accept(stv);
         return stv.getSqlNodes();
     }
 
-    private static SqlNode getCCExpNode(String ccExp) throws SqlParseException {
-        ccExp = "select " + ccExp + " from t";
-        return ((SqlSelect) parse(ccExp)).getSelectList().get(0);
-    }
-
-    static SqlNode parse(String sql) throws SqlParseException {
-        SqlParser.ConfigBuilder parserBuilder = SqlParser.configBuilder();
-        SqlParser sqlParser = SqlParser.create(sql, parserBuilder.build());
-        return sqlParser.parseQuery();
-    }
-
-    static boolean isNodeEqual(SqlNode node0, SqlNode node1) {
-        if (node0 == null) {
-            return node1 == null;
-        } else if (node1 == null) {
-            return false;
-        }
-
-        if (!Objects.equals(node0.getClass().getSimpleName(), node1.getClass().getSimpleName())) {
-            return false;
-        }
-
-        if (node0 instanceof SqlCall) {
-            SqlCall thisNode = (SqlCall) node0;
-            SqlCall thatNode = (SqlCall) node1;
-            if (!thisNode.getOperator().getName().equalsIgnoreCase(thatNode.getOperator().getName())) {
-                return false;
-            }
-            return isNodeEqual(thisNode.getOperandList(), thatNode.getOperandList());
-        }
-        if (node0 instanceof SqlLiteral) {
-            SqlLiteral thisNode = (SqlLiteral) node0;
-            SqlLiteral thatNode = (SqlLiteral) node1;
-            return Objects.equals(thisNode.getValue(), thatNode.getValue());
-        }
-        if (node0 instanceof SqlNodeList) {
-            SqlNodeList thisNode = (SqlNodeList) node0;
-            SqlNodeList thatNode = (SqlNodeList) node1;
-            if (thisNode.getList().size() != thatNode.getList().size()) {
-                return false;
-            }
-            for (int i = 0; i < thisNode.getList().size(); i++) {
-                SqlNode thisChild = thisNode.getList().get(i);
-                final SqlNode thatChild = thatNode.getList().get(i);
-                if (!isNodeEqual(thisChild, thatChild)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-        if (node0 instanceof SqlIdentifier) {
-            SqlIdentifier thisNode = (SqlIdentifier) node0;
-            SqlIdentifier thatNode = (SqlIdentifier) node1;
-            // compare ignore table alias.eg: expression like "a.b + a.c + a.d" ,alias a will be ignored when compared
-            String name0 = thisNode.names.get(thisNode.names.size() - 1).replace("\"", "");
-            String name1 = thatNode.names.get(thatNode.names.size() - 1).replace("\"", "");
-            return name0.equalsIgnoreCase(name1);
-        }
-
-        logger.error("Convert to computed column fail,failed to compare two nodes,unknown instance type");
-        return false;
-    }
-
-    private static boolean isNodeEqual(List<SqlNode> operands0, List<SqlNode> operands1) {
-        if (operands0.size() != operands1.size()) {
-            return false;
-        }
-        for (int i = 0; i < operands0.size(); i++) {
-            if (!isNodeEqual(operands0.get(i), operands1.get(i))) {
-                return false;
-            }
-        }
-        return true;
-    }
-
     private static String getTableAlias(SqlNode node) {
         if (node instanceof SqlCall) {
             SqlCall call = (SqlCall) node;
@@ -350,4 +263,4 @@ class SqlTreeVisitor implements SqlVisitor<SqlNode> {
     public SqlNode visit(SqlIntervalQualifier intervalQualifier) {
         return null;
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/d61f867f/query/src/test/java/org/apache/kylin/query/util/ConvertToComputedColumnTest.java
----------------------------------------------------------------------
diff --git a/query/src/test/java/org/apache/kylin/query/util/ConvertToComputedColumnTest.java b/query/src/test/java/org/apache/kylin/query/util/ConvertToComputedColumnTest.java
index c3efe8d..f9b8d8b 100644
--- a/query/src/test/java/org/apache/kylin/query/util/ConvertToComputedColumnTest.java
+++ b/query/src/test/java/org/apache/kylin/query/util/ConvertToComputedColumnTest.java
@@ -21,8 +21,6 @@ package org.apache.kylin.query.util;
 import java.util.HashMap;
 import java.util.Map;
 
-import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.sql.SqlSelect;
 import org.apache.calcite.sql.parser.SqlParseException;
 import org.junit.Assert;
 import org.junit.Test;
@@ -30,22 +28,6 @@ import org.junit.Test;
 import com.google.common.collect.ImmutableSortedMap;
 
 public class ConvertToComputedColumnTest {
-    @Test
-    public void testEqual() throws SqlParseException {
-        String sql0 = "select a.a + a.b + a.c from t as a";
-        String sql1 = "select (((a . a +    a.b +    a.c))) from t as a";
-        String sql2 = "select (a + b) + c  from t";
-        String sql3 = "select a.a + (a.b + a.c) from t as a";
-
-        SqlNode sn0 = getSelectNode(sql0);
-        SqlNode sn1 = getSelectNode(sql1);
-        SqlNode sn2 = getSelectNode(sql2);
-        SqlNode sn3 = getSelectNode(sql3);
-
-        Assert.assertEquals(true, ConvertToComputedColumn.isNodeEqual(sn0, sn1));
-        Assert.assertEquals(true, ConvertToComputedColumn.isNodeEqual(sn0, sn2));
-        Assert.assertEquals(false, ConvertToComputedColumn.isNodeEqual(sn0, sn3));
-    }
 
     @Test
     public void testErrorCase() {
@@ -101,10 +83,6 @@ public class ConvertToComputedColumnTest {
 
     }
 
-    private static SqlNode getSelectNode(String sql) throws SqlParseException {
-        return ((SqlSelect) ConvertToComputedColumn.parse(sql)).getSelectList().get(0);
-    }
-
     @Test
     public void testTwoCCHasSameSubExp() {
         String sql0 = "select a + b + c from t order by a + b";