diff --git a/docker/thirdparties/docker-compose/clickhouse/clickhouse.yaml.tpl b/docker/thirdparties/docker-compose/clickhouse/clickhouse.yaml.tpl index 3832680dea..6e34c459be 100644 --- a/docker/thirdparties/docker-compose/clickhouse/clickhouse.yaml.tpl +++ b/docker/thirdparties/docker-compose/clickhouse/clickhouse.yaml.tpl @@ -19,7 +19,7 @@ version: "2.1" services: doris--clickhouse: - image: "clickhouse/clickhouse-server:latest" + image: "clickhouse/clickhouse-server:23.3" restart: always environment: CLICKHOUSE_PASSWORD: 123456 diff --git a/docker/thirdparties/docker-compose/clickhouse/init/03-create-table.sql b/docker/thirdparties/docker-compose/clickhouse/init/03-create-table.sql index c13fac5cf9..46d2e6ffc3 100644 --- a/docker/thirdparties/docker-compose/clickhouse/init/03-create-table.sql +++ b/docker/thirdparties/docker-compose/clickhouse/init/03-create-table.sql @@ -131,3 +131,11 @@ CREATE TABLE doris_test.final_test ) ENGINE = ReplacingMergeTree ORDER BY key; + +CREATE TABLE doris_test.ts +( + id Int64, + ts UInt64 +) +ENGINE = MergeTree +ORDER BY id; \ No newline at end of file diff --git a/docker/thirdparties/docker-compose/clickhouse/init/04-insert.sql b/docker/thirdparties/docker-compose/clickhouse/init/04-insert.sql index 69c2ebd25f..c5e83eefe0 100644 --- a/docker/thirdparties/docker-compose/clickhouse/init/04-insert.sql +++ b/docker/thirdparties/docker-compose/clickhouse/init/04-insert.sql @@ -39,3 +39,6 @@ INSERT INTO doris_test.json VALUES ('1','{"a": 1, "b": { "c": 2, "d": [1, 2, 3] INSERT INTO doris_test.final_test Values (1, 'first'); INSERT INTO doris_test.final_test Values (1, 'second'); +INSERT INTO doris_test.ts values (1,1694438743); + + diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/external/jdbc/JdbcFunctionPushDownRule.java b/fe/fe-core/src/main/java/org/apache/doris/planner/external/jdbc/JdbcFunctionPushDownRule.java index bac9d62dbe..d1a44cb700 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/planner/external/jdbc/JdbcFunctionPushDownRule.java +++ b/fe/fe-core/src/main/java/org/apache/doris/planner/external/jdbc/JdbcFunctionPushDownRule.java @@ -17,33 +17,130 @@ package org.apache.doris.planner.external.jdbc; +import org.apache.doris.analysis.Expr; +import org.apache.doris.analysis.FunctionCallExpr; +import org.apache.doris.analysis.FunctionName; import org.apache.doris.thrift.TOdbcTableType; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.Map; import java.util.TreeSet; +import java.util.function.Predicate; public class JdbcFunctionPushDownRule { - private static final TreeSet UNSUPPORTED_MYSQL_FUNCTIONS = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + private static final Logger LOG = LogManager.getLogger(JdbcFunctionPushDownRule.class); + private static final TreeSet MYSQL_UNSUPPORTED_FUNCTIONS = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); static { - UNSUPPORTED_MYSQL_FUNCTIONS.add("date_trunc"); - UNSUPPORTED_MYSQL_FUNCTIONS.add("money_format"); + MYSQL_UNSUPPORTED_FUNCTIONS.add("date_trunc"); + MYSQL_UNSUPPORTED_FUNCTIONS.add("money_format"); } - public static boolean isUnsupportedFunctions(TOdbcTableType tableType, String filter) { - if (tableType.equals(TOdbcTableType.MYSQL)) { - return isMySQLUnsupportedFunctions(filter); - } else { - return false; + private static final TreeSet CLICKHOUSE_SUPPORTED_FUNCTIONS = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + + static { + CLICKHOUSE_SUPPORTED_FUNCTIONS.add("from_unixtime"); + } + + private static boolean isMySQLFunctionUnsupported(String functionName) { + return MYSQL_UNSUPPORTED_FUNCTIONS.contains(functionName.toLowerCase()); + } + + private static boolean isClickHouseFunctionUnsupported(String functionName) { + return !CLICKHOUSE_SUPPORTED_FUNCTIONS.contains(functionName.toLowerCase()); + } + + + private static final Map REPLACE_MYSQL_FUNCTIONS = Maps.newHashMap(); + + static { + REPLACE_MYSQL_FUNCTIONS.put("nvl", "ifnull"); + } + + private static boolean isReplaceMysqlFunctions(String functionName) { + return REPLACE_MYSQL_FUNCTIONS.containsKey(functionName.toLowerCase()); + } + + private static final Map REPLACE_CLICKHOUSE_FUNCTIONS = Maps.newHashMap(); + + static { + REPLACE_CLICKHOUSE_FUNCTIONS.put("from_unixtime", "FROM_UNIXTIME"); + } + + private static boolean isReplaceClickHouseFunctions(String functionName) { + return REPLACE_CLICKHOUSE_FUNCTIONS.containsKey(functionName.toLowerCase()); + } + + public static Expr processFunctions(TOdbcTableType tableType, Expr expr, List errors) { + if (tableType == null || expr == null) { + return expr; } + + Predicate checkFunction; + Predicate replaceFunction; + + if (TOdbcTableType.MYSQL.equals(tableType)) { + replaceFunction = JdbcFunctionPushDownRule::isReplaceMysqlFunctions; + checkFunction = JdbcFunctionPushDownRule::isMySQLFunctionUnsupported; + } else if (TOdbcTableType.CLICKHOUSE.equals(tableType)) { + replaceFunction = JdbcFunctionPushDownRule::isReplaceClickHouseFunctions; + checkFunction = JdbcFunctionPushDownRule::isClickHouseFunctionUnsupported; + } else { + return expr; + } + + return processFunctionsRecursively(expr, checkFunction, replaceFunction, errors, tableType); } - private static boolean isMySQLUnsupportedFunctions(String filter) { - for (String func : UNSUPPORTED_MYSQL_FUNCTIONS) { - if (filter.contains(func)) { - return true; + private static Expr processFunctionsRecursively(Expr expr, Predicate checkFunction, + Predicate replaceFunction, List errors, TOdbcTableType tableType) { + if (expr instanceof FunctionCallExpr) { + FunctionCallExpr functionCallExpr = (FunctionCallExpr) expr; + String func = functionCallExpr.getFnName().getFunction(); + + Preconditions.checkArgument(!func.isEmpty(), "function can not be empty"); + + func = replaceFunctionNameIfNecessary(func, replaceFunction, functionCallExpr, tableType); + + if (!func.isEmpty() && checkFunction.test(func)) { + String errMsg = "Unsupported function: " + func + " in expr: " + expr.toMySql() + + " in JDBC Table Type: " + tableType; + LOG.warn(errMsg); + errors.add(errMsg); } } - return false; + + List children = expr.getChildren(); + for (int i = 0; i < children.size(); i++) { + Expr child = children.get(i); + Expr newChild = processFunctionsRecursively(child, checkFunction, replaceFunction, errors, tableType); + expr.setChild(i, newChild); + } + + return expr; + } + + private static String replaceFunctionNameIfNecessary(String func, Predicate replaceFunction, + FunctionCallExpr functionCallExpr, TOdbcTableType tableType) { + if (replaceFunction.test(func)) { + String newFunc; + if (TOdbcTableType.MYSQL.equals(tableType)) { + newFunc = REPLACE_MYSQL_FUNCTIONS.get(func.toLowerCase()); + } else if (TOdbcTableType.CLICKHOUSE.equals(tableType)) { + newFunc = REPLACE_CLICKHOUSE_FUNCTIONS.get(func); + } else { + newFunc = null; + } + if (newFunc != null) { + functionCallExpr.setFnName(FunctionName.createBuiltinName(newFunc)); + func = functionCallExpr.getFnName().getFunction(); + } + } + return func; } } - diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/external/jdbc/JdbcScanNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/external/jdbc/JdbcScanNode.java index 94b6aeb160..ccba1a165f 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/planner/external/jdbc/JdbcScanNode.java +++ b/fe/fe-core/src/main/java/org/apache/doris/planner/external/jdbc/JdbcScanNode.java @@ -19,6 +19,7 @@ package org.apache.doris.planner.external.jdbc; import org.apache.doris.analysis.Analyzer; import org.apache.doris.analysis.BinaryPredicate; +import org.apache.doris.analysis.BoolLiteral; import org.apache.doris.analysis.DateLiteral; import org.apache.doris.analysis.Expr; import org.apache.doris.analysis.ExprSubstitutionMap; @@ -113,7 +114,7 @@ public class JdbcScanNode extends ExternalScanNode { break; } } - //clean conjusts cause graph sannnode no need conjuncts + // clean conjusts cause graph sannnode no need conjuncts conjuncts = Lists.newArrayList(); } @@ -133,19 +134,32 @@ public class JdbcScanNode extends ExternalScanNode { } ArrayList conjunctsList = Expr.cloneList(conjuncts, sMap); + List errors = Lists.newArrayList(); + List pushDownConjuncts = collectConjunctsToPushDown(conjunctsList, errors); + + for (Expr individualConjunct : pushDownConjuncts) { + String filter = conjunctExprToString(jdbcType, individualConjunct); + filters.add(filter); + conjuncts.remove(individualConjunct); + } + } + + private List collectConjunctsToPushDown(List conjunctsList, List errors) { + List pushDownConjuncts = new ArrayList<>(); for (Expr p : conjunctsList) { if (shouldPushDownConjunct(jdbcType, p)) { - String filter = conjunctExprToString(jdbcType, p); - if (filter.equals("TRUE")) { - filter = "1 = 1"; + List individualConjuncts = p.getConjuncts(); + for (Expr individualConjunct : individualConjuncts) { + Expr newp = JdbcFunctionPushDownRule.processFunctions(jdbcType, individualConjunct, errors); + if (!errors.isEmpty()) { + errors.clear(); + continue; + } + pushDownConjuncts.add(newp); } - if (JdbcFunctionPushDownRule.isUnsupportedFunctions(jdbcType, filter)) { - continue; - } - filters.add(filter); - conjuncts.remove(p); } } + return pushDownConjuncts; } private void createJdbcColumns() { @@ -282,7 +296,7 @@ public class JdbcScanNode extends ExternalScanNode { @Override public int getNumInstances() { return ConnectContext.get().getSessionVariable().getEnablePipelineEngine() - ? ConnectContext.get().getSessionVariable().getParallelExecInstanceNum() : 1; + ? ConnectContext.get().getSessionVariable().getParallelExecInstanceNum() : 1; } @Override @@ -292,17 +306,22 @@ public class JdbcScanNode extends ExternalScanNode { tbl.getId(), -1L); } - // Now some database have different function call like doris, now doris do not - // push down the function call except MYSQL - public static boolean shouldPushDownConjunct(TOdbcTableType tableType, Expr expr) { - if (!tableType.equals(TOdbcTableType.MYSQL)) { - List fnExprList = Lists.newArrayList(); - expr.collect(FunctionCallExpr.class, fnExprList); - if (!fnExprList.isEmpty()) { + private static boolean shouldPushDownConjunct(TOdbcTableType tableType, Expr expr) { + if (containsFunctionCallExpr(expr)) { + if (tableType.equals(TOdbcTableType.MYSQL) || tableType.equals(TOdbcTableType.CLICKHOUSE)) { + return Config.enable_func_pushdown; + } else { return false; } + } else { + return true; } - return Config.enable_func_pushdown; + } + + private static boolean containsFunctionCallExpr(Expr expr) { + List fnExprList = Lists.newArrayList(); + expr.collect(FunctionCallExpr.class, fnExprList); + return !fnExprList.isEmpty(); } public static String conjunctExprToString(TOdbcTableType tableType, Expr expr) { @@ -338,6 +357,11 @@ public class JdbcScanNode extends ExternalScanNode { } } + // only for old planner + if (expr.contains(BoolLiteral.class) && expr.getStringValue().equals("1") && expr.getChildren().isEmpty()) { + return "1 = 1"; + } + return expr.toMySql(); } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/planner/external/odbc/OdbcScanNode.java b/fe/fe-core/src/main/java/org/apache/doris/planner/external/odbc/OdbcScanNode.java index 0f27e9da58..bf4e835e4f 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/planner/external/odbc/OdbcScanNode.java +++ b/fe/fe-core/src/main/java/org/apache/doris/planner/external/odbc/OdbcScanNode.java @@ -20,6 +20,7 @@ package org.apache.doris.planner.external.odbc; import org.apache.doris.analysis.Analyzer; import org.apache.doris.analysis.Expr; import org.apache.doris.analysis.ExprSubstitutionMap; +import org.apache.doris.analysis.FunctionCallExpr; import org.apache.doris.analysis.SlotDescriptor; import org.apache.doris.analysis.SlotRef; import org.apache.doris.analysis.TupleDescriptor; @@ -28,6 +29,7 @@ import org.apache.doris.catalog.Env; import org.apache.doris.catalog.JdbcTable; import org.apache.doris.catalog.OdbcTable; import org.apache.doris.common.AnalysisException; +import org.apache.doris.common.Config; import org.apache.doris.common.UserException; import org.apache.doris.planner.PlanNodeId; import org.apache.doris.planner.external.ExternalScanNode; @@ -181,7 +183,7 @@ public class OdbcScanNode extends ExternalScanNode { } ArrayList odbcConjuncts = Expr.cloneList(conjuncts, sMap); for (Expr p : odbcConjuncts) { - if (JdbcScanNode.shouldPushDownConjunct(odbcType, p)) { + if (shouldPushDownConjunct(odbcType, p)) { String filter = JdbcScanNode.conjunctExprToString(odbcType, p); filters.add(filter); conjuncts.remove(p); @@ -224,4 +226,15 @@ public class OdbcScanNode extends ExternalScanNode { return ConnectContext.get().getSessionVariable().getEnablePipelineEngine() ? ConnectContext.get().getSessionVariable().getParallelExecInstanceNum() : 1; } + + public static boolean shouldPushDownConjunct(TOdbcTableType tableType, Expr expr) { + if (!tableType.equals(TOdbcTableType.MYSQL)) { + List fnExprList = Lists.newArrayList(); + expr.collect(FunctionCallExpr.class, fnExprList); + if (!fnExprList.isEmpty()) { + return false; + } + } + return Config.enable_func_pushdown; + } } diff --git a/regression-test/data/external_table_p0/jdbc/test_clickhouse_jdbc_catalog.out b/regression-test/data/external_table_p0/jdbc/test_clickhouse_jdbc_catalog.out index 0b847974ef..05372e3f0a 100644 Binary files a/regression-test/data/external_table_p0/jdbc/test_clickhouse_jdbc_catalog.out and b/regression-test/data/external_table_p0/jdbc/test_clickhouse_jdbc_catalog.out differ diff --git a/regression-test/data/external_table_p0/jdbc/test_mysql_jdbc_catalog.out b/regression-test/data/external_table_p0/jdbc/test_mysql_jdbc_catalog.out index 5037157a24..d5cc90fa80 100644 --- a/regression-test/data/external_table_p0/jdbc/test_mysql_jdbc_catalog.out +++ b/regression-test/data/external_table_p0/jdbc/test_mysql_jdbc_catalog.out @@ -238,29 +238,6 @@ VIEWS \N 2023-06-17T10:00 --- !filter1 -- -1 6 1 1 2099.18 3 8 1554296.82 68781940.49 d 8 5 0 d a 7 9 - --- !filter2 -- -1 6 1 1 2099.18 3 8 1554296.82 68781940.49 d 8 5 0 d a 7 9 -2 8 9 8 2900.42 1 6 97486621.73 59634489.39 c 3 2 0 a e 7 4 -3 5 7 3 6276.86 8 9 32758730.38 10260499.72 c 8 1 0 d c 9 2 -4 3 7 5 2449.00 6 3 91359059.28 64743145.92 e 7 8 0 b d 8 4 -5 6 4 5 9137.82 2 7 26526675.70 90098303.36 a 6 7 0 d e 4 1 -6 3 6 8 7601.25 4 9 49117098.47 46499188.80 c 3 3 0 c d 4 8 -7 3 2 8 5297.81 9 3 23753694.20 96930000.64 c 7 2 0 b e 1 5 -8 3 6 7 3683.85 5 7 26056250.91 1127755.43 b 7 6 0 d b 4 7 -9 3 9 1 4785.38 1 5 95199488.12 94869703.42 a 4 4 0 c d 2 4 - --- !filter3 -- -1 6 1 1 2099.18 3 8 1554296.82 68781940.49 d 8 5 0 d a 7 9 - --- !date_trunc -- -2023-06-17T10:00 - --- !money_format -- -1 - -- !test_insert1 -- doris1 18 @@ -305,3 +282,26 @@ sys -- !mysql_view -- 10086 4294967295 201 +-- !filter1 -- +1 6 1 1 2099.18 3 8 1554296.82 68781940.49 d 8 5 0 d a 7 9 + +-- !filter2 -- +1 6 1 1 2099.18 3 8 1554296.82 68781940.49 d 8 5 0 d a 7 9 +2 8 9 8 2900.42 1 6 97486621.73 59634489.39 c 3 2 0 a e 7 4 +3 5 7 3 6276.86 8 9 32758730.38 10260499.72 c 8 1 0 d c 9 2 +4 3 7 5 2449.00 6 3 91359059.28 64743145.92 e 7 8 0 b d 8 4 +5 6 4 5 9137.82 2 7 26526675.70 90098303.36 a 6 7 0 d e 4 1 +6 3 6 8 7601.25 4 9 49117098.47 46499188.80 c 3 3 0 c d 4 8 +7 3 2 8 5297.81 9 3 23753694.20 96930000.64 c 7 2 0 b e 1 5 +8 3 6 7 3683.85 5 7 26056250.91 1127755.43 b 7 6 0 d b 4 7 +9 3 9 1 4785.38 1 5 95199488.12 94869703.42 a 4 4 0 c d 2 4 + +-- !filter3 -- +1 6 1 1 2099.18 3 8 1554296.82 68781940.49 d 8 5 0 d a 7 9 + +-- !date_trunc -- +2023-06-17T10:00 + +-- !money_format -- +1 + diff --git a/regression-test/suites/external_table_p0/jdbc/test_clickhouse_jdbc_catalog.groovy b/regression-test/suites/external_table_p0/jdbc/test_clickhouse_jdbc_catalog.groovy index 9c2050ab70..b01f200574 100644 --- a/regression-test/suites/external_table_p0/jdbc/test_clickhouse_jdbc_catalog.groovy +++ b/regression-test/suites/external_table_p0/jdbc/test_clickhouse_jdbc_catalog.groovy @@ -68,6 +68,15 @@ suite("test_clickhouse_jdbc_catalog", "p0,external,clickhouse,external_docker,ex order_qt_final1 """select * from final_test""" sql "set jdbc_clickhouse_query_final = false;" order_qt_final2 """select * from final_test""" + order_qt_func_push """select * from ts where from_unixtime(ts,'yyyyMMdd') >= '2022-01-01';""" + explain { + sql("select * from ts where from_unixtime(ts,'yyyyMMdd') >= '2022-01-01';") + contains """QUERY: SELECT "id", "ts" FROM "doris_test"."ts" WHERE (FROM_UNIXTIME(ts, '%Y%m%d') >= '2022-01-01')""" + } + explain { + sql("select * from ts where nvl(ts,null) >= '2022-01-01';") + contains """QUERY: SELECT "id", "ts" FROM "doris_test"."ts"""" + } sql """ drop catalog if exists ${catalog_name} """ } diff --git a/regression-test/suites/external_table_p0/jdbc/test_mysql_jdbc_catalog.groovy b/regression-test/suites/external_table_p0/jdbc/test_mysql_jdbc_catalog.groovy index cc8f4fd0fe..3717f17bec 100644 --- a/regression-test/suites/external_table_p0/jdbc/test_mysql_jdbc_catalog.groovy +++ b/regression-test/suites/external_table_p0/jdbc/test_mysql_jdbc_catalog.groovy @@ -112,11 +112,6 @@ suite("test_mysql_jdbc_catalog", "p0,external,mysql,external_docker,external_doc order_qt_auto_default_t """insert into ${auto_default_t}(name) values('a'); """ order_qt_dt """select * from ${dt}; """ order_qt_dt_null """select * from ${dt_null} order by 1; """ - order_qt_filter1 """select * from ${ex_tb17} where id = 1; """ - order_qt_filter2 """select * from ${ex_tb17} where 1=1 order by 1; """ - order_qt_filter3 """select * from ${ex_tb17} where id = 1 and 1 = 1; """ - order_qt_date_trunc """ SELECT timestamp0 from dt where DATE_TRUNC(date_sub(timestamp0,INTERVAL 9 HOUR),'hour') > '2011-03-03 17:39:05'; """ - order_qt_money_format """ select k8 from test1 where money_format(k8) = '1.00'; """ // test insert String uuid1 = UUID.randomUUID().toString(); @@ -238,8 +233,60 @@ suite("test_mysql_jdbc_catalog", "p0,external,mysql,external_docker,external_doc "jdbc.driver_url" = "${driver_url}", "jdbc.driver_class" = "com.mysql.cj.jdbc.Driver"); """ - qt_mysql_view """ select * from view_catalog.doris_test.mysql_view order by col_1;""" - sql """ drop catalog if exists view_catalog; """ + qt_mysql_view """ select * from view_catalog.doris_test.mysql_view order by col_1;""" + sql """ drop catalog if exists view_catalog; """ + + sql """ drop catalog if exists mysql_fun_push_catalog """ + sql """ CREATE CATALOG mysql_fun_push_catalog PROPERTIES ( + "type"="jdbc", + "jdbc.user"="root", + "jdbc.password"="123456", + "jdbc.jdbc_url" = "jdbc:mysql://${externalEnvIp}:${mysql_port}/doris_test?useSSL=false", + "jdbc.driver_url" = "${driver_url}", + "jdbc.driver_class" = "com.mysql.cj.jdbc.Driver"); + """ + + sql """switch mysql_fun_push_catalog""" + sql """ use ${ex_db_name}""" + sql """ admin set frontend config ("enable_func_pushdown" = "true"); """ + order_qt_filter1 """select * from ${ex_tb17} where id = 1; """ + order_qt_filter2 """select * from ${ex_tb17} where 1=1 order by 1; """ + order_qt_filter3 """select * from ${ex_tb17} where id = 1 and 1 = 1; """ + order_qt_date_trunc """ SELECT timestamp0 from dt where DATE_TRUNC(date_sub(timestamp0,INTERVAL 9 HOUR),'hour') > '2011-03-03 17:39:05'; """ + order_qt_money_format """ select k8 from test1 where money_format(k8) = '1.00'; """ + explain { + sql("select k8 from test1 where money_format(k8) = '1.00';") + + contains "QUERY: SELECT `k8` FROM `doris_test`.`test1`" + } + explain { + sql ("SELECT timestamp0 from dt where DATE_TRUNC(date_sub(timestamp0,INTERVAL 9 HOUR),'hour') > '2011-03-03 17:39:05';") + + contains "QUERY: SELECT `timestamp0` FROM `doris_test`.`dt`" + } + explain { + sql ("SELECT timestamp0 from dt where DATE_TRUNC(date_sub(timestamp0,INTERVAL 9 HOUR),'hour') > '2011-03-03 17:39:05' and timestamp0 > '2022-01-01';;") + + contains "QUERY: SELECT `timestamp0` FROM `doris_test`.`dt` WHERE (timestamp0 > '2022-01-01 00:00:00')" + } + explain { + sql ("select k6, k8 from test1 where nvl(k6, null) = 1;") + + contains "QUERY: SELECT `k6`, `k8` FROM `doris_test`.`test1` WHERE (ifnull(k6, NULL) = 1)" + } + explain { + sql ("select k6, k8 from test1 where nvl(nvl(k6, null),null) = 1;") + + contains "QUERY: SELECT `k6`, `k8` FROM `doris_test`.`test1` WHERE (ifnull(ifnull(k6, NULL), NULL) = 1)" + } + sql """ admin set frontend config ("enable_func_pushdown" = "false"); """ + explain { + sql ("select k6, k8 from test1 where nvl(k6, null) = 1 and k8 = 1;") + + contains "QUERY: SELECT `k6`, `k8` FROM `doris_test`.`test1` WHERE (k8 = 1)" + } + sql """ admin set frontend config ("enable_func_pushdown" = "true"); """ + sql """ drop catalog if exists mysql_fun_push_catalog; """ } }