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 2016/10/24 02:43:03 UTC

[06/10] kylin git commit: KYLIN-2108 refactor massageSql(), disable KeywordDefaultDirtyHack by default

KYLIN-2108 refactor massageSql(), disable KeywordDefaultDirtyHack by default


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

Branch: refs/heads/yang21-cdh5.7
Commit: c9216b180eae37fbffea453028ee9bb8c72bc03c
Parents: cec8b9e
Author: Li Yang <li...@apache.org>
Authored: Thu Oct 20 15:08:14 2016 +0800
Committer: Li Yang <li...@apache.org>
Committed: Thu Oct 20 15:08:28 2016 +0800

----------------------------------------------------------------------
 .../apache/kylin/common/KylinConfigBase.java    |   4 +
 .../test_case_data/localmeta/kylin.properties   |   2 +
 .../apache/kylin/rest/service/QueryService.java |   3 +-
 .../rest/util/KeywordDefaultDirtyHack.java      |  34 +++
 .../org/apache/kylin/rest/util/QueryUtil.java   | 231 ++++++++-----------
 .../kylin/rest/util/TableauInterceptor.java     | 115 +++++++++
 .../apache/kylin/rest/util/QueryUtilTest.java   |  30 ++-
 7 files changed, 274 insertions(+), 145 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kylin/blob/c9216b18/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
----------------------------------------------------------------------
diff --git a/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java b/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
index 79ee084..5d92aef 100644
--- a/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
+++ b/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
@@ -504,6 +504,10 @@ abstract public class KylinConfigBase implements Serializable {
     public boolean getBadQueryPersistentEnabled() {
         return Boolean.parseBoolean(getOptional("kylin.query.badquery.persistent.enable", "true"));
     }
+    
+    public String[] getQueryTransformers() {
+        return getOptionalStringArray("kylin.query.transformers", new String[0]);
+    }
 
     public int getCachedDictMaxEntrySize() {
         return Integer.parseInt(getOptional("kylin.dict.cache.max.entry", "3000"));

http://git-wip-us.apache.org/repos/asf/kylin/blob/c9216b18/examples/test_case_data/localmeta/kylin.properties
----------------------------------------------------------------------
diff --git a/examples/test_case_data/localmeta/kylin.properties b/examples/test_case_data/localmeta/kylin.properties
index 50dd0b6..d727fe8 100644
--- a/examples/test_case_data/localmeta/kylin.properties
+++ b/examples/test_case_data/localmeta/kylin.properties
@@ -75,6 +75,8 @@ kylin.job.yarn.app.rest.check.interval.seconds=10
 
 ### QUERY ###
 
+kylin.query.transformers=org.apache.kylin.rest.util.KeywordDefaultDirtyHack
+
 ### SECURITY ###
 
 # Spring security profile, options: testing, ldap, saml

http://git-wip-us.apache.org/repos/asf/kylin/blob/c9216b18/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 8303cee..918bdf1 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
@@ -68,6 +68,7 @@ import org.apache.kylin.rest.request.SQLRequest;
 import org.apache.kylin.rest.response.SQLResponse;
 import org.apache.kylin.rest.util.QueryUtil;
 import org.apache.kylin.rest.util.Serializer;
+import org.apache.kylin.rest.util.TableauInterceptor;
 import org.apache.kylin.storage.hbase.HBaseConnection;
 import org.apache.kylin.storage.hybrid.HybridInstance;
 import org.slf4j.Logger;
@@ -299,7 +300,7 @@ public class QueryService extends BasicService {
             userInfo += grantedAuthority.getAuthority();
         }
 
-        SQLResponse fakeResponse = QueryUtil.tableauIntercept(sqlRequest.getSql());
+        SQLResponse fakeResponse = TableauInterceptor.tableauIntercept(sqlRequest.getSql());
         if (null != fakeResponse) {
             logger.debug("Return fake response, is exception? " + fakeResponse.getIsException());
             return fakeResponse;

http://git-wip-us.apache.org/repos/asf/kylin/blob/c9216b18/server-base/src/main/java/org/apache/kylin/rest/util/KeywordDefaultDirtyHack.java
----------------------------------------------------------------------
diff --git a/server-base/src/main/java/org/apache/kylin/rest/util/KeywordDefaultDirtyHack.java b/server-base/src/main/java/org/apache/kylin/rest/util/KeywordDefaultDirtyHack.java
new file mode 100644
index 0000000..8d8d971
--- /dev/null
+++ b/server-base/src/main/java/org/apache/kylin/rest/util/KeywordDefaultDirtyHack.java
@@ -0,0 +1,34 @@
+/*
+ * 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 org.apache.kylin.rest.util.QueryUtil.IQueryTransformer;
+
+public class KeywordDefaultDirtyHack implements IQueryTransformer {
+
+    @Override
+    public String transform(String sql) {
+        // KYLIN-2108, DEFAULT is hive default database, but a sql keyword too, needs quote
+        sql = sql.replace("DEFAULT.", "\"DEFAULT\".");
+        sql = sql.replace("default.", "\"default\".");
+
+        return sql;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/c9216b18/server-base/src/main/java/org/apache/kylin/rest/util/QueryUtil.java
----------------------------------------------------------------------
diff --git a/server-base/src/main/java/org/apache/kylin/rest/util/QueryUtil.java b/server-base/src/main/java/org/apache/kylin/rest/util/QueryUtil.java
index cc9d32e..66619fe 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/util/QueryUtil.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/util/QueryUtil.java
@@ -18,87 +18,28 @@
 
 package org.apache.kylin.rest.util;
 
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import org.apache.kylin.rest.model.SelectedColumnMeta;
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.util.ClassUtil;
 import org.apache.kylin.rest.request.SQLRequest;
-import org.apache.kylin.rest.response.SQLResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.Lists;
+
 /**
  */
 public class QueryUtil {
 
     protected static final Logger logger = LoggerFactory.getLogger(QueryUtil.class);
 
-    private static final String S0 = "\\s*";
-    private static final String S1 = "\\s";
-    private static final String SM = "\\s+";
-    private static final Pattern PTN_GROUP_BY = Pattern.compile(S1 + "GROUP" + SM + "BY" + S1, Pattern.CASE_INSENSITIVE);
-    private static final Pattern PTN_HAVING_COUNT_GREATER_THAN_ZERO = Pattern.compile(S1 + "HAVING" + SM + "[(]?" + S0 + "COUNT" + S0 + "[(]" + S0 + "1" + S0 + "[)]" + S0 + ">" + S0 + "0" + S0 + "[)]?", Pattern.CASE_INSENSITIVE);
-    private static final Pattern PTN_SUM_1 = Pattern.compile(S1 + "SUM" + S0 + "[(]" + S0 + "[1]" + S0 + "[)]" + S1, Pattern.CASE_INSENSITIVE);
-    private static final Pattern PTN_INTERVAL = Pattern.compile("interval" + SM + "(floor\\()([\\d\\.]+)(\\))" + SM + "(second|minute|hour|day|month|year)", Pattern.CASE_INSENSITIVE);
-    private static final Pattern PTN_CONCAT = Pattern.compile("concat\\(.+?\\)");//non-greedy
-
-    // private static final Pattern PTN_HAVING_ESCAPE_FUNCTION =
-    // Pattern.compile("\\{fn" + "(" + S0 + ")" + "\\}",
-    // Pattern.CASE_INSENSITIVE);
-    private static final Pattern PTN_HAVING_ESCAPE_FUNCTION = Pattern.compile("\\{fn" + "(.*?)" + "\\}", Pattern.CASE_INSENSITIVE);
-
-    private static String[] tableauTestQueries = new String[] { "SELECT 1", //
-            "CREATE LOCAL TEMPORARY TABLE \"XTableau_B_Connect\" ( \"COL\" INTEGER ) ON COMMIT PRESERVE ROWS", //
-            "DROP TABLE \"XTableau_B_Connect\"", //
-            "SELECT \"COL\" FROM (SELECT 1 AS \"COL\") AS \"SUBQUERY\"", //
-            "SELECT TOP 1 \"COL\" FROM (SELECT 1 AS \"COL\") AS \"CHECKTOP\"", "SELECT \"COL\" FROM (SELECT 1 AS \"COL\") AS \"CHECKTOP\" LIMIT 1", //
-            "SELECT \"SUBCOL\" AS \"COL\"  FROM (   SELECT 1 AS \"SUBCOL\" ) \"SUBQUERY\" GROUP BY 1", "SELECT \"SUBCOL\" AS \"COL\" FROM (   SELECT 1 AS \"SUBCOL\" ) \"SUBQUERY\" GROUP BY 2", "INSERT INTO \"XTableau_C_Connect\" SELECT * FROM (SELECT 1 AS COL) AS CHECKTEMP LIMIT 1", "DROP TABLE \"XTableau_C_Connect\"", "INSERT INTO \"XTableau_B_Connect\" SELECT * FROM (SELECT 1 AS COL) AS CHECKTEMP LIMIT 1" };
-
-    private static SQLResponse temp = new SQLResponse(new LinkedList<SelectedColumnMeta>() {
-        private static final long serialVersionUID = -8086728462624901359L;
-
-        {
-            add(new SelectedColumnMeta(false, false, true, false, 2, true, 11, "COL", "COL", "", "", "", 10, 0, 4, "int4", false, true, false));
-        }
-    }, new LinkedList<List<String>>() {
-        private static final long serialVersionUID = -470083340592928073L;
-
-        {
-            add(new LinkedList<String>() {
-                private static final long serialVersionUID = -3673192785838230054L;
-
-                {
-                    add("1");
-                }
-            });
-        }
-    }, 0, false, null);
-
-    private static SQLResponse[] fakeResponses = new SQLResponse[] { temp, new SQLResponse(null, null, 0, false, null), //
-            new SQLResponse(null, null, 0, false, null), //
-            temp, //
-            new SQLResponse(null, null, 0, true, "near 1 syntax error"), //
-            temp, //
-            new SQLResponse(null, null, 0, true, "group by 1????"), //
-            new SQLResponse(null, null, 0, true, "group by 2????"), //
-            new SQLResponse(null, null, 0, true, "XTableau_C_Connect not exist"), //
-            new SQLResponse(null, null, 0, true, "XTableau_C_Connect not exist"), new SQLResponse(null, null, 0, true, "XTableau_B_Connect not exist"), };
-
-    private static ArrayList<HashSet<String>> tableauTestQueriesInToken = new ArrayList<HashSet<String>>();
-
-    static {
-        for (String q : tableauTestQueries) {
-            HashSet<String> temp = new HashSet<String>();
-            for (String token : q.split("[\r\n\t \\(\\)]")) {
-                temp.add(token);
-            }
-            temp.add("");
-            tableauTestQueriesInToken.add(temp);
-        }
+    private static List<IQueryTransformer> queryTransformers;
+    
+    public interface IQueryTransformer {
+        String transform(String sql);
     }
 
     public static String massageSql(SQLRequest sqlRequest) {
@@ -106,10 +47,6 @@ public class QueryUtil {
         sql = sql.trim();
         sql = sql.replace("\r", " ").replace("\n", System.getProperty("line.separator"));
         
-        // KYLIN-2108, DEFAULT is hive default database, but a Calcite keyword too, needs quote
-        sql = sql.replace("DEFAULT.", "\"DEFAULT\".");
-        sql = sql.replace("default.", "\"default\".");
-
         while (sql.endsWith(";"))
             sql = sql.substring(0, sql.length() - 1);
 
@@ -123,78 +60,103 @@ public class QueryUtil {
             sql += ("\nOFFSET " + offset);
         }
 
-        return healSickSql(sql);
-    }
-
-    // correct sick / invalid SQL
-    private static String healSickSql(String sql) {
-        Matcher m;
-
-        // Case fn{ EXTRACT(...) }
-        // Use non-greedy regrex matching to remove escape functions
-        while (true) {
-            m = PTN_HAVING_ESCAPE_FUNCTION.matcher(sql);
-            if (!m.find())
-                break;
-            sql = sql.substring(0, m.start()) + m.group(1) + sql.substring(m.end());
+        // customizable SQL transformation
+        if (queryTransformers == null) {
+            initQueryTransformers();
         }
-
-        // Case: HAVING COUNT(1)>0 without Group By
-        // Tableau generates: SELECT SUM(1) AS "COL" FROM "VAC_SW" HAVING
-        // COUNT(1)>0
-        m = PTN_HAVING_COUNT_GREATER_THAN_ZERO.matcher(sql);
-        if (m.find() && PTN_GROUP_BY.matcher(sql).find() == false) {
-            sql = sql.substring(0, m.start()) + " " + sql.substring(m.end());
+        for (IQueryTransformer t : queryTransformers) {
+            sql = t.transform(sql);
         }
+        return sql;
+    }
 
-        // Case: SUM(1)
-        // Replace it with COUNT(1)
-        while (true) {
-            m = PTN_SUM_1.matcher(sql);
-            if (!m.find())
-                break;
-            sql = sql.substring(0, m.start()) + " COUNT(1) " + sql.substring(m.end());
+    private static void initQueryTransformers() {
+        List<IQueryTransformer> transformers = Lists.newArrayList();
+        transformers.add(new DefaultQueryTransformer());
+        
+        String[] classes = KylinConfig.getInstanceFromEnv().getQueryTransformers();
+        for (String clz : classes) {
+            try {
+                IQueryTransformer t = (IQueryTransformer) ClassUtil.newInstance(clz);
+                transformers.add(t);
+            } catch (Exception e) {
+                logger.error("Failed to init query transformer", e);
+            }
         }
+        queryTransformers = transformers;
+    }
 
-        // ( date '2001-09-28' + interval floor(1) day ) generated by cognos
-        // calcite only recognizes date '2001-09-28' + interval '1' day
-        while (true) {
-            m = PTN_INTERVAL.matcher(sql);
-            if (!m.find())
-                break;
+    // correct sick / invalid SQL
+    private static class DefaultQueryTransformer implements IQueryTransformer {
+
+        private static final String S0 = "\\s*";
+        private static final String S1 = "\\s";
+        private static final String SM = "\\s+";
+        private static final Pattern PTN_GROUP_BY = Pattern.compile(S1 + "GROUP" + SM + "BY" + S1, Pattern.CASE_INSENSITIVE);
+        private static final Pattern PTN_HAVING_COUNT_GREATER_THAN_ZERO = Pattern.compile(S1 + "HAVING" + SM + "[(]?" + S0 + "COUNT" + S0 + "[(]" + S0 + "1" + S0 + "[)]" + S0 + ">" + S0 + "0" + S0 + "[)]?", Pattern.CASE_INSENSITIVE);
+        private static final Pattern PTN_SUM_1 = Pattern.compile(S1 + "SUM" + S0 + "[(]" + S0 + "[1]" + S0 + "[)]" + S1, Pattern.CASE_INSENSITIVE);
+        private static final Pattern PTN_INTERVAL = Pattern.compile("interval" + SM + "(floor\\()([\\d\\.]+)(\\))" + SM + "(second|minute|hour|day|month|year)", Pattern.CASE_INSENSITIVE);
+        private static final Pattern PTN_CONCAT = Pattern.compile("concat\\(.+?\\)");//non-greedy
+        private static final Pattern PTN_HAVING_ESCAPE_FUNCTION = Pattern.compile("\\{fn" + "(.*?)" + "\\}", Pattern.CASE_INSENSITIVE);
+        
+        @Override
+        public String transform(String sql) {
+            Matcher m;
+
+            // Case fn{ EXTRACT(...) }
+            // Use non-greedy regrex matching to remove escape functions
+            while (true) {
+                m = PTN_HAVING_ESCAPE_FUNCTION.matcher(sql);
+                if (!m.find())
+                    break;
+                sql = sql.substring(0, m.start()) + m.group(1) + sql.substring(m.end());
+            }
 
-            int value = (int) Math.floor(Double.valueOf(m.group(2)));
-            sql = sql.substring(0, m.start(1)) + "'" + value + "'" + sql.substring(m.end(3));
-        }
+            // Case: HAVING COUNT(1)>0 without Group By
+            // Tableau generates: SELECT SUM(1) AS "COL" FROM "VAC_SW" HAVING
+            // COUNT(1)>0
+            m = PTN_HAVING_COUNT_GREATER_THAN_ZERO.matcher(sql);
+            if (m.find() && PTN_GROUP_BY.matcher(sql).find() == false) {
+                sql = sql.substring(0, m.start()) + " " + sql.substring(m.end());
+            }
 
-        //according to https://issues.apache.org/jira/browse/CALCITE-1375,
-        //{fn concat('a','b')} will succeed but concat('a','b') will fail 
-        StringBuilder sb = new StringBuilder();
-        while (true) {
-            m = PTN_CONCAT.matcher(sql);
-            if (!m.find())
-                break;
+            // Case: SUM(1)
+            // Replace it with COUNT(1)
+            while (true) {
+                m = PTN_SUM_1.matcher(sql);
+                if (!m.find())
+                    break;
+                sql = sql.substring(0, m.start()) + " COUNT(1) " + sql.substring(m.end());
+            }
 
-            sb.append(sql.substring(0, m.start()) + "{fn " + m.group(0) + " }");
-            sql = sql.substring(m.end());
-        }
-        String temp = sb.toString() + sql;
-        sql = "".equals(temp) ? sql : temp;
+            // ( date '2001-09-28' + interval floor(1) day ) generated by cognos
+            // calcite only recognizes date '2001-09-28' + interval '1' day
+            while (true) {
+                m = PTN_INTERVAL.matcher(sql);
+                if (!m.find())
+                    break;
 
-        return sql;
-    }
+                int value = (int) Math.floor(Double.valueOf(m.group(2)));
+                sql = sql.substring(0, m.start(1)) + "'" + value + "'" + sql.substring(m.end(3));
+            }
 
-    public static SQLResponse tableauIntercept(String sql) {
+            //according to https://issues.apache.org/jira/browse/CALCITE-1375,
+            //{fn concat('a','b')} will succeed but concat('a','b') will fail 
+            StringBuilder sb = new StringBuilder();
+            while (true) {
+                m = PTN_CONCAT.matcher(sql);
+                if (!m.find())
+                    break;
 
-        String[] tokens = sql.split("[\r\n\t \\(\\)]");
-        for (int i = 0; i < tableauTestQueries.length; ++i) {
-            if (isTokenWiseEqual(tokens, tableauTestQueriesInToken.get(i))) {
-                logger.info("Hit fake response " + i);
-                return fakeResponses[i];
+                sb.append(sql.substring(0, m.start()) + "{fn " + m.group(0) + " }");
+                sql = sql.substring(m.end());
             }
-        }
+            String temp = sb.toString() + sql;
+            sql = "".equals(temp) ? sql : temp;
 
-        return null;
+            return sql;
+        }
+        
     }
 
     public static String makeErrorMsgUserFriendly(Throwable e) {
@@ -230,13 +192,4 @@ public class QueryUtil {
         }
     }
 
-    private static boolean isTokenWiseEqual(String[] tokens, HashSet<String> tokenSet) {
-        for (String token : tokens) {
-            if (!tokenSet.contains(token)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/kylin/blob/c9216b18/server-base/src/main/java/org/apache/kylin/rest/util/TableauInterceptor.java
----------------------------------------------------------------------
diff --git a/server-base/src/main/java/org/apache/kylin/rest/util/TableauInterceptor.java b/server-base/src/main/java/org/apache/kylin/rest/util/TableauInterceptor.java
new file mode 100644
index 0000000..afe91e1
--- /dev/null
+++ b/server-base/src/main/java/org/apache/kylin/rest/util/TableauInterceptor.java
@@ -0,0 +1,115 @@
+/*
+ * 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.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.kylin.rest.model.SelectedColumnMeta;
+import org.apache.kylin.rest.response.SQLResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TableauInterceptor {
+
+    protected static final Logger logger = LoggerFactory.getLogger(TableauInterceptor.class);
+
+    private static String[] tableauTestQueries = new String[] { "SELECT 1", //
+            "CREATE LOCAL TEMPORARY TABLE \"XTableau_B_Connect\" ( \"COL\" INTEGER ) ON COMMIT PRESERVE ROWS", //
+            "DROP TABLE \"XTableau_B_Connect\"", //
+            "SELECT \"COL\" FROM (SELECT 1 AS \"COL\") AS \"SUBQUERY\"", //
+            "SELECT TOP 1 \"COL\" FROM (SELECT 1 AS \"COL\") AS \"CHECKTOP\"", //
+            "SELECT \"COL\" FROM (SELECT 1 AS \"COL\") AS \"CHECKTOP\" LIMIT 1", //
+            "SELECT \"SUBCOL\" AS \"COL\"  FROM (   SELECT 1 AS \"SUBCOL\" ) \"SUBQUERY\" GROUP BY 1", //
+            "SELECT \"SUBCOL\" AS \"COL\" FROM (   SELECT 1 AS \"SUBCOL\" ) \"SUBQUERY\" GROUP BY 2", //
+            "INSERT INTO \"XTableau_C_Connect\" SELECT * FROM (SELECT 1 AS COL) AS CHECKTEMP LIMIT 1", //
+            "DROP TABLE \"XTableau_C_Connect\"", //
+            "INSERT INTO \"XTableau_B_Connect\" SELECT * FROM (SELECT 1 AS COL) AS CHECKTEMP LIMIT 1" };
+
+    private static SQLResponse temp = new SQLResponse(new LinkedList<SelectedColumnMeta>() {
+        private static final long serialVersionUID = -8086728462624901359L;
+
+        {
+            add(new SelectedColumnMeta(false, false, true, false, 2, true, 11, "COL", "COL", "", "", "", 10, 0, 4, "int4", false, true, false));
+        }
+    }, new LinkedList<List<String>>() {
+        private static final long serialVersionUID = -470083340592928073L;
+
+        {
+            add(new LinkedList<String>() {
+                private static final long serialVersionUID = -3673192785838230054L;
+
+                {
+                    add("1");
+                }
+            });
+        }
+    }, 0, false, null);
+
+    private static SQLResponse[] fakeResponses = new SQLResponse[] { temp, //
+            new SQLResponse(null, null, 0, false, null), //
+            new SQLResponse(null, null, 0, false, null), //
+            temp, //
+            new SQLResponse(null, null, 0, true, "near 1 syntax error"), //
+            temp, //
+            new SQLResponse(null, null, 0, true, "group by 1????"), //
+            new SQLResponse(null, null, 0, true, "group by 2????"), //
+            new SQLResponse(null, null, 0, true, "XTableau_C_Connect not exist"), //
+            new SQLResponse(null, null, 0, true, "XTableau_C_Connect not exist"), //
+            new SQLResponse(null, null, 0, true, "XTableau_B_Connect not exist"), };
+
+    private static ArrayList<HashSet<String>> tableauTestQueriesInToken = new ArrayList<HashSet<String>>();
+
+    static {
+        for (String q : tableauTestQueries) {
+            HashSet<String> temp = new HashSet<String>();
+            for (String token : q.split("[\r\n\t \\(\\)]")) {
+                temp.add(token);
+            }
+            temp.add("");
+            tableauTestQueriesInToken.add(temp);
+        }
+    }
+
+    public static SQLResponse tableauIntercept(String sql) {
+
+        String[] tokens = sql.split("[\r\n\t \\(\\)]");
+        for (int i = 0; i < tableauTestQueries.length; ++i) {
+            if (isTokenWiseEqual(tokens, tableauTestQueriesInToken.get(i))) {
+                logger.info("Hit fake response " + i);
+                return fakeResponses[i];
+            }
+        }
+
+        return null;
+    }
+
+    private static boolean isTokenWiseEqual(String[] tokens, HashSet<String> tokenSet) {
+        for (String token : tokens) {
+            if (!tokenSet.contains(token)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/c9216b18/server-base/src/test/java/org/apache/kylin/rest/util/QueryUtilTest.java
----------------------------------------------------------------------
diff --git a/server-base/src/test/java/org/apache/kylin/rest/util/QueryUtilTest.java b/server-base/src/test/java/org/apache/kylin/rest/util/QueryUtilTest.java
index 9305410..c00cd3f 100644
--- a/server-base/src/test/java/org/apache/kylin/rest/util/QueryUtilTest.java
+++ b/server-base/src/test/java/org/apache/kylin/rest/util/QueryUtilTest.java
@@ -18,13 +18,27 @@
 
 package org.apache.kylin.rest.util;
 
+import org.apache.kylin.common.util.LocalFileMetadataTestCase;
 import org.apache.kylin.rest.request.SQLRequest;
+import org.junit.After;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 
-public class QueryUtilTest {
+public class QueryUtilTest extends LocalFileMetadataTestCase {
+    
+    @Before
+    public void setUp() throws Exception {
+        this.createTestMetadata();
+    }
+
+    @After
+    public void after() throws Exception {
+        this.cleanupTestMetadata();
+    }
+    
     @Test
-    public void testHealInterval() {
+    public void testMassageSql() {
         {
             SQLRequest sqlRequest = new SQLRequest();
             sqlRequest.setSql("select ( date '2001-09-28' + interval floor(1.2) day) from test_kylin_fact");
@@ -37,15 +51,21 @@ public class QueryUtilTest {
             String s = QueryUtil.massageSql(sqlRequest);
             Assert.assertEquals("select ( date '2001-09-28' + interval '2' month) from test_kylin_fact group by ( date '2001-09-28' + interval '2' month)", s);
         }
+        {
+            SQLRequest sqlRequest = new SQLRequest();
+            sqlRequest.setSql("select concat(\"TEST_KYLIN_FACT\".\"LSTG_FORMAT_NAME\",\"TEST_KYLIN_FACT\".\"LSTG_FORMAT_NAME\") concat(\"TEST_KYLIN_FACT\".\"LSTG_FORMAT_NAME\",\"TEST_KYLIN_FACT\".\"LSTG_FORMAT_NAME\") ()");
+            String s = QueryUtil.massageSql(sqlRequest);
+            Assert.assertEquals("select {fn concat(\"TEST_KYLIN_FACT\".\"LSTG_FORMAT_NAME\",\"TEST_KYLIN_FACT\".\"LSTG_FORMAT_NAME\") } {fn concat(\"TEST_KYLIN_FACT\".\"LSTG_FORMAT_NAME\",\"TEST_KYLIN_FACT\".\"LSTG_FORMAT_NAME\") } ()", s);
+        }
     }
 
     @Test
-    public void testHealConcat() {
+    public void testKeywordDefaultDirtyHack() {
         {
             SQLRequest sqlRequest = new SQLRequest();
-            sqlRequest.setSql("select concat(\"TEST_KYLIN_FACT\".\"LSTG_FORMAT_NAME\",\"TEST_KYLIN_FACT\".\"LSTG_FORMAT_NAME\") concat(\"TEST_KYLIN_FACT\".\"LSTG_FORMAT_NAME\",\"TEST_KYLIN_FACT\".\"LSTG_FORMAT_NAME\") ()");
+            sqlRequest.setSql("select * from DEFAULT.TEST_KYLIN_FACT");
             String s = QueryUtil.massageSql(sqlRequest);
-            Assert.assertEquals("select {fn concat(\"TEST_KYLIN_FACT\".\"LSTG_FORMAT_NAME\",\"TEST_KYLIN_FACT\".\"LSTG_FORMAT_NAME\") } {fn concat(\"TEST_KYLIN_FACT\".\"LSTG_FORMAT_NAME\",\"TEST_KYLIN_FACT\".\"LSTG_FORMAT_NAME\") } ()", s);
+            Assert.assertEquals("select * from \"DEFAULT\".TEST_KYLIN_FACT", s);
         }
     }
 }