From fc17cba9340ca7f5b41689e552d2b56c78ad0335 Mon Sep 17 00:00:00 2001 From: qijianliang01 Date: Wed, 14 Jul 2021 13:50:44 +0800 Subject: [PATCH 1/2] [Feature] Support calc constant expr by BE Change-Id: Ifa4e98cfe1db3a9efc5f101639566623778e79c3 --- be/src/runtime/CMakeLists.txt | 1 + be/src/runtime/exec_env.h | 3 + be/src/runtime/exec_env_init.cpp | 3 + be/src/runtime/fold_constant_mgr.cpp | 213 ++++++++++++ be/src/runtime/fold_constant_mgr.h | 57 ++++ be/src/service/internal_service.cpp | 39 +++ be/src/service/internal_service.h | 8 + docs/en/administrator-guide/variables.md | 5 + docs/zh-CN/administrator-guide/variables.md | 5 + .../org/apache/doris/analysis/Analyzer.java | 8 +- .../java/org/apache/doris/analysis/Expr.java | 2 +- .../apache/doris/analysis/GroupByClause.java | 8 + .../doris/analysis/InformationFunction.java | 32 ++ .../org/apache/doris/analysis/InsertStmt.java | 5 + .../org/apache/doris/analysis/QueryStmt.java | 58 ++++ .../org/apache/doris/analysis/SelectStmt.java | 188 +++++++++++ .../doris/analysis/SetOperationStmt.java | 35 ++ .../apache/doris/analysis/StatementBase.java | 10 + .../doris/analysis/SysVariableDesc.java | 17 + .../apache/doris/catalog/PrimitiveType.java | 10 + .../org/apache/doris/qe/ConnectProcessor.java | 3 + .../org/apache/doris/qe/SessionVariable.java | 8 + .../org/apache/doris/qe/StmtExecutor.java | 9 +- .../apache/doris/rewrite/ExprRewriter.java | 24 ++ .../doris/rewrite/FoldConstantsRule.java | 303 ++++++++++++++++++ .../doris/rpc/BackendServiceClient.java | 4 + .../apache/doris/rpc/BackendServiceProxy.java | 15 + .../apache/doris/analysis/QueryStmtTest.java | 280 ++++++++++++++++ gensrc/proto/internal_service.proto | 20 ++ gensrc/proto/palo_internal_service.proto | 1 + gensrc/thrift/FrontendService.thrift | 1 + gensrc/thrift/PaloInternalService.thrift | 9 + 32 files changed, 1380 insertions(+), 4 deletions(-) create mode 100644 be/src/runtime/fold_constant_mgr.cpp create mode 100644 be/src/runtime/fold_constant_mgr.h create mode 100644 fe/fe-core/src/test/java/org/apache/doris/analysis/QueryStmtTest.java diff --git a/be/src/runtime/CMakeLists.txt b/be/src/runtime/CMakeLists.txt index 0fffb5a9645c24..26a4f2d9b33e11 100644 --- a/be/src/runtime/CMakeLists.txt +++ b/be/src/runtime/CMakeLists.txt @@ -107,6 +107,7 @@ set(RUNTIME_FILES mysql_result_writer.cpp memory/system_allocator.cpp memory/chunk_allocator.cpp + fold_constant_mgr.cpp cache/result_node.cpp cache/result_cache.cpp odbc_table_sink.cpp diff --git a/be/src/runtime/exec_env.h b/be/src/runtime/exec_env.h index 2fcf901124ad91..91e8e4c3444d1e 100644 --- a/be/src/runtime/exec_env.h +++ b/be/src/runtime/exec_env.h @@ -55,6 +55,7 @@ class RoutineLoadTaskExecutor; class SmallFileMgr; class FileBlockManager; class PluginMgr; +class FoldConstantMgr; class BackendServiceClient; class FrontendServiceClient; @@ -128,6 +129,7 @@ class ExecEnv { LoadChannelMgr* load_channel_mgr() { return _load_channel_mgr; } LoadStreamMgr* load_stream_mgr() { return _load_stream_mgr; } SmallFileMgr* small_file_mgr() { return _small_file_mgr; } + FoldConstantMgr* fold_constant_mgr() { return _fold_constant_mgr; } const std::vector& store_paths() const { return _store_paths; } void set_store_paths(const std::vector& paths) { _store_paths = paths; } @@ -179,6 +181,7 @@ class ExecEnv { LoadPathMgr* _load_path_mgr = nullptr; DiskIoMgr* _disk_io_mgr = nullptr; TmpFileMgr* _tmp_file_mgr = nullptr; + FoldConstantMgr* _fold_constant_mgr = nullptr; BfdParser* _bfd_parser = nullptr; BrokerMgr* _broker_mgr = nullptr; diff --git a/be/src/runtime/exec_env_init.cpp b/be/src/runtime/exec_env_init.cpp index 63095d57c879d4..04e3ee1ea574b8 100644 --- a/be/src/runtime/exec_env_init.cpp +++ b/be/src/runtime/exec_env_init.cpp @@ -38,6 +38,7 @@ #include "runtime/etl_job_mgr.h" #include "runtime/exec_env.h" #include "runtime/external_scan_context_mgr.h" +#include "runtime/fold_constant_mgr.h" #include "runtime/fragment_mgr.h" #include "runtime/heartbeat_flags.h" #include "runtime/load_channel_mgr.h" @@ -98,6 +99,7 @@ Status ExecEnv::_init(const std::vector& store_paths) { _fragment_mgr = new FragmentMgr(this); _result_cache = new ResultCache(config::query_cache_max_size_mb, config::query_cache_elasticity_size_mb); + _fold_constant_mgr = new FoldConstantMgr(this); _master_info = new TMasterInfo(); _etl_job_mgr = new EtlJobMgr(this); _load_path_mgr = new LoadPathMgr(this); @@ -250,6 +252,7 @@ void ExecEnv::_destroy() { SAFE_DELETE(_etl_job_mgr); SAFE_DELETE(_master_info); SAFE_DELETE(_fragment_mgr); + SAFE_DELETE(_fold_constant_mgr); SAFE_DELETE(_cgroups_mgr); SAFE_DELETE(_etl_thread_pool); SAFE_DELETE(_thread_pool); diff --git a/be/src/runtime/fold_constant_mgr.cpp b/be/src/runtime/fold_constant_mgr.cpp new file mode 100644 index 00000000000000..4d94f1d3ce6286 --- /dev/null +++ b/be/src/runtime/fold_constant_mgr.cpp @@ -0,0 +1,213 @@ +// 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. + +#include +#include + +#include "runtime/fold_constant_mgr.h" +#include "runtime/tuple_row.h" +#include "runtime/exec_env.h" +#include "runtime/runtime_state.h" +#include "runtime/mem_tracker.h" +#include "exprs/expr_context.h" +#include "exprs/expr.h" +#include "common/object_pool.h" +#include "common/status.h" + +#include "gen_cpp/internal_service.pb.h" +#include "gen_cpp/PaloInternalService_types.h" + +using std::string; +using std::map; + +namespace doris { + +TUniqueId FoldConstantMgr::_dummy_id; + +FoldConstantMgr::FoldConstantMgr(ExecEnv* exec_env) + : _exec_env(exec_env), _pool(){ + +} + +Status FoldConstantMgr::fold_constant_expr( + const TFoldConstantParams& params, PConstantExprResult* response) { + auto expr_map = params.expr_map; + auto expr_result_map = response->mutable_expr_result_map(); + + TQueryGlobals query_globals = params.query_globals; + + // init + Status status = init(query_globals); + if (UNLIKELY(!status.ok())) { + LOG(WARNING) << "Failed to init mem trackers, msg: " << status.get_error_msg(); + return status; + } + + for (auto m : expr_map) { + PExprResultMap pexpr_result_map; + for (auto n : m.second) { + ExprContext* ctx = nullptr; + TExpr& texpr = n.second; + // create expr tree from TExpr + RETURN_IF_ERROR(Expr::create_expr_tree(&_pool, texpr, &ctx)); + // prepare and open context + status = prepare_and_open(ctx); + if (UNLIKELY(!status.ok())) { + LOG(WARNING) << "Failed to init mem trackers, msg: " << status.get_error_msg(); + return status; + } + + TupleRow* row = nullptr; + // calc expr + void* src = ctx->get_value(row); + PrimitiveType root_type = ctx->root()->type().type; + // covert to thrift type + TPrimitiveType::type t_type = doris::to_thrift(root_type); + + // collect result + PExprResult expr_result; + string result; + if (src == nullptr) { + expr_result.set_success(false); + } else { + expr_result.set_success(true); + result = get_result(src, ctx->root()->type().type); + } + + expr_result.set_content(result); + expr_result.mutable_type()->set_type(t_type); + + pexpr_result_map.mutable_map()->insert({n.first, expr_result}); + + // close context expr + ctx->close(_runtime_state.get()); + } + + expr_result_map->insert({m.first, pexpr_result_map}); + } + + return Status::OK(); + +} + +Status FoldConstantMgr::init(TQueryGlobals query_globals) { + // init runtime state, runtime profile + TPlanFragmentExecParams params; + params.fragment_instance_id = FoldConstantMgr::_dummy_id; + params.query_id = FoldConstantMgr::_dummy_id; + TExecPlanFragmentParams fragment_params; + fragment_params.params = params; + fragment_params.protocol_version = PaloInternalServiceVersion::V1; + TQueryOptions query_options; + _runtime_state.reset(new RuntimeState(fragment_params.params, query_options, query_globals, + ExecEnv::GetInstance())); + DescriptorTbl* desc_tbl = NULL; + TDescriptorTable* t_desc_tbl = new TDescriptorTable(); + Status status = DescriptorTbl::create(_runtime_state->obj_pool(), *t_desc_tbl, &desc_tbl); + if (UNLIKELY(!status.ok())) { + LOG(WARNING) << "Failed to create descriptor table, msg: " << status.get_error_msg(); + return Status::Uninitialized(status.get_error_msg()); + } + _runtime_state->set_desc_tbl(desc_tbl); + status = _runtime_state->init_mem_trackers(FoldConstantMgr::_dummy_id); + if (UNLIKELY(!status.ok())) { + LOG(WARNING) << "Failed to init mem trackers, msg: " << status.get_error_msg(); + return Status::Uninitialized(status.get_error_msg()); + } + + _runtime_profile = _runtime_state->runtime_profile(); + _runtime_profile->set_name("FoldConstantExpr"); + _mem_tracker = MemTracker::CreateTracker(-1, "FoldConstantExpr", _runtime_state->instance_mem_tracker()); + _mem_pool.reset(new MemPool(_mem_tracker.get())); + + return Status::OK(); +} + +Status FoldConstantMgr::prepare_and_open(ExprContext* ctx) { + RowDescriptor* desc = new RowDescriptor(); + ctx -> prepare(_runtime_state.get(), *desc, _mem_tracker); + return ctx -> open(_runtime_state.get()); +} + +string FoldConstantMgr::get_result(void* src, PrimitiveType slot_type){ + switch (slot_type) { + case TYPE_NULL: { + return NULL; + } + case TYPE_BOOLEAN: { + bool val = *reinterpret_cast(src); + return val ? "true" : "false"; + } + case TYPE_TINYINT: { + int8_t val = *reinterpret_cast(src); + string s; + s.push_back(val); + return s; + } + case TYPE_SMALLINT: { + int16_t val = *reinterpret_cast(src); + return std::to_string(val); + } + case TYPE_INT: { + int32_t val = *reinterpret_cast(src); + return std::to_string(val); + } + case TYPE_BIGINT: { + int64_t val = *reinterpret_cast(src); + return std::to_string(val); + } + case TYPE_LARGEINT: { + char buf[48]; + int len = 48; + char* v = LargeIntValue::to_string(*reinterpret_cast<__int128*>(src), buf, &len); + return std::string(v, len); + } + case TYPE_FLOAT: { + float val = *reinterpret_cast(src); + return std::to_string(val); + } + case TYPE_TIME: + case TYPE_DOUBLE: { + double val = *reinterpret_cast(src); + return std::to_string(val); + } + case TYPE_CHAR: + case TYPE_VARCHAR: + case TYPE_HLL: + case TYPE_OBJECT: { + return (reinterpret_cast(src))->debug_string(); + } + case TYPE_DATE: + case TYPE_DATETIME: { + const DateTimeValue date_value = *reinterpret_cast(src); + char str[MAX_DTVALUE_STR_LEN]; + date_value.to_string(str); + return str; + } + case TYPE_DECIMALV2: { + return reinterpret_cast(src)->to_string(); + } + default: + DCHECK(false) << "Type not implemented: " << slot_type; + return NULL; + } + return NULL; +} + + +} + diff --git a/be/src/runtime/fold_constant_mgr.h b/be/src/runtime/fold_constant_mgr.h new file mode 100644 index 00000000000000..353dda9ee0325a --- /dev/null +++ b/be/src/runtime/fold_constant_mgr.h @@ -0,0 +1,57 @@ +// 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. + +#include "runtime/tuple_row.h" +#include "util/runtime_profile.h" +#include "exprs/expr_context.h" +#include "exprs/expr.h" +#include "common/object_pool.h" +#include "common/status.h" +#include "runtime/exec_env.h" +#include "gen_cpp/internal_service.pb.h" +#include "gen_cpp/PaloInternalService_types.h" + +namespace doris { + +class TFoldConstantParams; +class TExpr; +class TQueryGlobals; + +// This class used to fold constant expr from fe +class FoldConstantMgr { +public: + FoldConstantMgr(ExecEnv* exec_env); + // fold constant expr + Status fold_constant_expr(const TFoldConstantParams& params, PConstantExprResult* response); + // init runtime_state and mem_tracker + Status init(TQueryGlobals query_globals); + // prepare expr + Status prepare_and_open(ExprContext* ctx); + + std::string get_result(void* src, PrimitiveType slot_type); + +private: + std::unique_ptr _runtime_state; + std::shared_ptr _mem_tracker; + RuntimeProfile* _runtime_profile; + std::unique_ptr _mem_pool; + ExecEnv* _exec_env; + ObjectPool _pool; + static TUniqueId _dummy_id; +}; +} + diff --git a/be/src/service/internal_service.cpp b/be/src/service/internal_service.cpp index 1141d7d6d416a4..a24f35695a85be 100644 --- a/be/src/service/internal_service.cpp +++ b/be/src/service/internal_service.cpp @@ -23,10 +23,12 @@ #include "runtime/buffer_control_block.h" #include "runtime/data_stream_mgr.h" #include "runtime/exec_env.h" +#include "runtime/fold_constant_mgr.h" #include "runtime/fragment_mgr.h" #include "runtime/load_channel_mgr.h" #include "runtime/result_buffer_mgr.h" #include "runtime/routine_load/routine_load_task_executor.h" +#include "runtime/runtime_state.h" #include "service/brpc.h" #include "util/thrift_util.h" #include "util/uid_util.h" @@ -285,6 +287,43 @@ void PInternalServiceImpl::apply_filter(::google::protobuf::RpcController* co } st.to_protobuf(response->mutable_status()); } + +template +void PInternalServiceImpl::fold_constant_expr( + google::protobuf::RpcController* cntl_base, + const PConstantExprRequest* request, + PConstantExprResult* response, + google::protobuf::Closure* done) { + + brpc::ClosureGuard closure_guard(done); + brpc::Controller* cntl = static_cast(cntl_base); + + Status st = Status::OK(); + if (request->has_request()) { + st = _fold_constant_expr(request->request(), response); + } else { + // TODO(yangzhengguo) this is just for compatible with old version, this should be removed in the release 0.15 + st = _fold_constant_expr(cntl->request_attachment().to_string(), response); + } + if (!st.ok()) { + LOG(WARNING) << "exec fold constant expr failed, errmsg=" << st.get_error_msg(); + } + st.to_protobuf(response->mutable_status()); +} + +template +Status PInternalServiceImpl::_fold_constant_expr(const std::string& ser_request, + PConstantExprResult* response) { + + TFoldConstantParams t_request; + { + const uint8_t* buf = (const uint8_t*)ser_request.data(); + uint32_t len = ser_request.size(); + RETURN_IF_ERROR(deserialize_thrift_msg(buf, &len, false, &t_request)); + } + FoldConstantMgr mgr(_exec_env); + return mgr.fold_constant_expr(t_request, response); +} template class PInternalServiceImpl; template class PInternalServiceImpl; diff --git a/be/src/service/internal_service.h b/be/src/service/internal_service.h index 247f985eef78fa..b51680db9345bd 100644 --- a/be/src/service/internal_service.h +++ b/be/src/service/internal_service.h @@ -87,14 +87,22 @@ class PInternalServiceImpl : public T { const ::doris::PMergeFilterRequest* request, ::doris::PMergeFilterResponse* response, ::google::protobuf::Closure* done) override; + void apply_filter(::google::protobuf::RpcController* controller, const ::doris::PPublishFilterRequest* request, ::doris::PPublishFilterResponse* response, ::google::protobuf::Closure* done) override; + void fold_constant_expr(google::protobuf::RpcController* controller, + const PConstantExprRequest* request, + PConstantExprResult* response, + google::protobuf::Closure* done) override; + private: Status _exec_plan_fragment(const std::string& s_request); + Status _fold_constant_expr(const std::string& ser_request, PConstantExprResult* response); + private: ExecEnv* _exec_env; PriorityThreadPool _tablet_worker_pool; diff --git a/docs/en/administrator-guide/variables.md b/docs/en/administrator-guide/variables.md index 1379a2b062e40d..38b6030795e09d 100644 --- a/docs/en/administrator-guide/variables.md +++ b/docs/en/administrator-guide/variables.md @@ -75,6 +75,7 @@ Variables that support both session-level and global-level setting include: * `parallel_exchange_instance_num` * `allow_partition_column_nullable` * `insert_visible_timeout_ms` +* `enable_fold_constant_by_be` Variables that support only global-level setting include: @@ -392,3 +393,7 @@ Note that the comment must start with /*+ and can only follow the SELECT. * `extract_wide_range_expr` Used to control whether turn on the 'Wide Common Factors' rule. The value has two: true or false. On by default. + +* `enable_fold_constant_by_be` + + Used to control the calculation method of constant folding. The default is `false`, that is, calculation is performed in `FE`; if it is set to `true`, it will be calculated by `BE` through `RPC` request. diff --git a/docs/zh-CN/administrator-guide/variables.md b/docs/zh-CN/administrator-guide/variables.md index 441cb6fd6ed76c..a4d05de5ebd738 100644 --- a/docs/zh-CN/administrator-guide/variables.md +++ b/docs/zh-CN/administrator-guide/variables.md @@ -73,6 +73,7 @@ SET GLOBAL exec_mem_limit = 137438953472 * `batch_size` * `allow_partition_column_nullable` * `insert_visible_timeout_ms` +* `enable_fold_constant_by_be` 只支持全局生效的变量包括: @@ -387,3 +388,7 @@ SELECT /*+ SET_VAR(query_timeout = 1, enable_partition_cache=true) */ sleep(3); * `extract_wide_range_expr` 用于控制是否开启 「宽泛公因式提取」的优化。取值有两种:true 和 false 。默认情况下开启。 + +* `enable_fold_constant_by_be` + + 用于控制常量折叠的计算方式。默认是 `false`,即在 `FE` 进行计算;若设置为 `true`,则通过 `RPC` 请求经 `BE` 计算。 diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java index deb84137b9e33b..ffd7e372be0c7b 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/Analyzer.java @@ -851,7 +851,6 @@ private void registerConstantConjunct(TupleId id, Expr e) { /** * register expr id - * * @param expr */ void registerExprId(Expr expr) { @@ -1626,6 +1625,13 @@ public boolean safeIsEnableJoinReorderBasedCost() { } return globalState.context.getSessionVariable().isEnableJoinReorderBasedCost(); } + + public boolean safeIsEnableFoldConstantByBe() { + if (globalState.context == null) { + return false; + } + return globalState.context.getSessionVariable().isEnableFoldConstantByBe(); + } /** * Returns true if predicate 'e' can be correctly evaluated by a tree materializing diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/Expr.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/Expr.java index f27a3f107f1961..dc46c2f0a2fef6 100755 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/Expr.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/Expr.java @@ -282,7 +282,7 @@ public ExprId getId() { return id; } - protected void setId(ExprId id) { + public void setId(ExprId id) { this.id = id; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/GroupByClause.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/GroupByClause.java index 9ce4744a5bf1b8..14d1804e71c338 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/GroupByClause.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/GroupByClause.java @@ -118,6 +118,10 @@ public List getOriGroupingExprs() { return oriGroupingExprs; } + public void setOriGroupingExprs(ArrayList list) { + oriGroupingExprs = list; + } + public ArrayList getGroupingExprs() { if (!exprGenerated) { try { @@ -130,6 +134,10 @@ public ArrayList getGroupingExprs() { return groupingExprs; } + public void setGroupingExpr(ArrayList list) { + groupingExprs = list; + } + // generate grouping exprs from group by, grouping sets, cube, rollup clause public void genGroupingExprs() throws AnalysisException { if (exprGenerated) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/InformationFunction.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/InformationFunction.java index d1f2fc77620162..a9fd4a3f6e97b0 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/InformationFunction.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/InformationFunction.java @@ -46,6 +46,12 @@ public String getStrValue() { return strValue; } + public String getIntValue() { + return String.valueOf(intValue); + } + + public String getFuncType() {return funcType; } + @Override public Expr clone() { return new InformationFunction(this); @@ -79,4 +85,30 @@ protected void toThrift(TExprNode msg) { public String toSqlImpl() { return funcType + "()"; } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof InformationFunction)) { + return false; + } + + if (!funcType.equals(((InformationFunction) obj).getFuncType())) { + return false; + } + + if (type.equals(Type.VARCHAR)) { + if (!strValue.equals(((InformationFunction) obj).getStrValue())) { + return false; + } + } else if (type.equals(Type.BIGINT)) { + if (intValue != Integer.parseInt(((InformationFunction) obj).getIntValue())) { + return false; + } + } + return true; + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/InsertStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/InsertStmt.java index bd6f0acfcaa129..37af5c953c331c 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/InsertStmt.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/InsertStmt.java @@ -214,6 +214,11 @@ public void setQueryStmt(QueryStmt queryStmt) { this.queryStmt = queryStmt; } + @Override + public void foldConstant(ExprRewriter rewriter) throws AnalysisException { + Preconditions.checkState(isAnalyzed()); + queryStmt.foldConstant(rewriter); + } @Override public void rewriteExprs(ExprRewriter rewriter) throws AnalysisException { diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/QueryStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/QueryStmt.java index 3460cd513c6a9c..9aefc07e2d3773 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/QueryStmt.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/QueryStmt.java @@ -29,6 +29,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; @@ -435,6 +436,63 @@ public void getWithClauseTableRefs(List tblRefs, Set parentVie } } + /** + * collect all exprs of a QueryStmt to a map + * @param exprMap + */ + public void collectExprs(Map exprMap) {} + + /** + * put all rewritten exprs back to the ori QueryStmt + * @param rewrittenExprMap + */ + public void putBackExprs(Map rewrittenExprMap) {} + + + @Override + public void foldConstant(ExprRewriter rewriter) throws AnalysisException { + Preconditions.checkState(isAnalyzed()); + Map exprMap = new HashMap<>(); + collectExprs(exprMap); + rewriter.rewriteConstant(exprMap, analyzer); + if (rewriter.changed()) { + putBackExprs(exprMap); + } + + } + + + /** + * register expr_id of expr and its children, if not set + * @param expr + */ + public void registerExprId(Expr expr) { + if (expr.getId() == null) { + analyzer.registerExprId(expr); + } + for (Expr child : expr.getChildren()) { + registerExprId(child); + } + } + + /** + * check whether expr and it's children contain alias + * @param expr expr to be checked + * @return true if contains, otherwise false + */ + public boolean containAlias(Expr expr) { + for (Expr child : expr.getChildren()) { + if (containAlias(child)) { + return true; + } + } + + if (null != aliasSMap.get(expr)) { + return true; + } + return false; + } + // get tables used by this query. // Set parentViewNameSet contain parent stmt view name // to make sure query like "with tmp as (select * from db1.table1) " + diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/SelectStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/SelectStmt.java index da372839625a6d..f1bbce18b771f7 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/SelectStmt.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/SelectStmt.java @@ -1320,6 +1320,194 @@ public void rewriteExprs(ExprRewriter rewriter) throws AnalysisException { } } + @Override + public void collectExprs(Map exprMap) { + // subquery + List subqueryExprs = Lists.newArrayList(); + + // select clause + for (SelectListItem item : selectList.getItems()) { + if (item.isStar()) { + continue; + } + // register expr id + registerExprId(item.getExpr()); + + exprMap.put(item.getExpr().getId().toString(), item.getExpr()); + + // equal subquery in select list + if (item.getExpr().contains(Predicates.instanceOf(Subquery.class))) { + item.getExpr().collect(Subquery.class, subqueryExprs); + } + } + + // from clause + for (TableRef ref : fromClause_) { + Preconditions.checkState(ref.isAnalyzed); + if (ref.onClause != null) { + registerExprId(ref.onClause); + exprMap.put(ref.onClause.getId().toString(), ref.onClause); + } + } + + if (whereClause != null) { + registerExprId(whereClause); + exprMap.put(whereClause.getId().toString(), whereClause); + whereClause.collect(Subquery.class, subqueryExprs); + + } + if (havingClause != null) { + registerExprId(havingClause); + exprMap.put(havingClause.getId().toString(), havingClause); + havingClauseAfterAnaylzed.collect(Subquery.class, subqueryExprs); + } + for (Subquery subquery : subqueryExprs) { + registerExprId(subquery); + subquery.getStatement().collectExprs(exprMap); + } + if (groupByClause != null) { + ArrayList groupingExprs = groupByClause.getGroupingExprs(); + if (groupingExprs != null) { + for (Expr expr : groupingExprs) { + if (containAlias(expr)) { + continue; + } + registerExprId(expr); + exprMap.put(expr.getId().toString(), expr); + } + } + List oriGroupingExprs = groupByClause.getOriGroupingExprs(); + if (oriGroupingExprs != null) { + for (Expr expr : oriGroupingExprs) { + /* + * Suppose there is a query statement: + * + * ``` + * select + * i_item_sk as b + * from item + * group by b + * order by b desc + * ``` + * + * where `b` is an alias for `i_item_sk`. + * + * When analyze is done, it becomes + * + * ``` + * SELECT + * `i_item_sk` + * FROM `item` + * GROUP BY `b` + * ORDER BY `b` DESC + * ``` + * Aliases information of groupBy and orderBy clauses is recorded in `QueryStmt.aliasSMap`. + * The select clause has it's own alias info in `SelectListItem.alias`. + * + * Aliases expr in the `group by` and `order by` clauses are not analyzed, i.e. `Expr.isAnalyzed=false` + * Subsequent constant folding will analyze the unanalyzed Expr before collecting the constant + * expressions, preventing the `INVALID_TYPE` expr from being sent to BE. + * + * But when analyzing the alias, the meta information corresponding to the slot cannot be found + * in the catalog, an error will be reported. + * + * So the alias needs to be removed here. + * + */ + if (containAlias(expr)) { + continue; + } + registerExprId(expr); + exprMap.put(expr.getId().toString(), expr); + } + } + } + if (orderByElements != null) { + for (OrderByElement orderByElem : orderByElements) { + // same as above + if (containAlias(orderByElem.getExpr())) { + continue; + } + registerExprId(orderByElem.getExpr()); + exprMap.put(orderByElem.getExpr().getId().toString(), orderByElem.getExpr()); + } + } + } + + @Override + public void putBackExprs(Map rewrittenExprMap) { + // subquery + List subqueryExprs = Lists.newArrayList(); + for (SelectListItem item : selectList.getItems()) { + if (item.isStar()) { + continue; + } + item.setExpr(rewrittenExprMap.get(item.getExpr().getId().toString())); + // equal subquery in select list + if (item.getExpr().contains(Predicates.instanceOf(Subquery.class))) { + item.getExpr().collect(Subquery.class, subqueryExprs); + } + } + + // from clause + for (TableRef ref : fromClause_) { + if (ref.onClause != null) { + ref.setOnClause(rewrittenExprMap.get(ref.onClause.getId().toString())); + } + } + + if (whereClause != null) { + setWhereClause(rewrittenExprMap.get(whereClause.getId().toString())); + whereClause.collect(Subquery.class, subqueryExprs); + } + if (havingClause != null) { + havingClause = rewrittenExprMap.get(havingClause.getId().toString()); + havingClauseAfterAnaylzed.collect(Subquery.class, subqueryExprs); + } + + for (Subquery subquery : subqueryExprs) { + subquery.getStatement().putBackExprs(rewrittenExprMap); + } + + if (groupByClause != null) { + ArrayList groupingExprs = groupByClause.getGroupingExprs(); + if (groupingExprs != null) { + ArrayList newGroupingExpr = new ArrayList<>(); + for (Expr expr : groupingExprs) { + if (expr.getId() == null) { + newGroupingExpr.add(expr); + } else { + newGroupingExpr.add(rewrittenExprMap.get(expr.getId().toString())); + } + } + groupByClause.setGroupingExpr(newGroupingExpr); + + } + List oriGroupingExprs = groupByClause.getOriGroupingExprs(); + if (oriGroupingExprs != null) { + ArrayList newOriGroupingExprs = new ArrayList<>(); + for (Expr expr : oriGroupingExprs) { + if (expr.getId() == null) { + newOriGroupingExprs.add(expr); + } else { + newOriGroupingExprs.add(rewrittenExprMap.get(expr.getId().toString())); + } + } + groupByClause.setOriGroupingExprs(newOriGroupingExprs); + } + } + if (orderByElements != null) { + for (OrderByElement orderByElem : orderByElements) { + Expr expr = orderByElem.getExpr(); + if (expr.getId() == null) { + orderByElem.setExpr(expr); + } else { + orderByElem.setExpr(rewrittenExprMap.get(expr.getId().toString())); + } + } + } + } + private void rewriteSelectList(ExprRewriter rewriter) throws AnalysisException { for (SelectListItem item : selectList.getItems()) { if (item.getExpr() instanceof CaseExpr && item.getExpr().contains(Predicates.instanceOf(Subquery.class))) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/SetOperationStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/SetOperationStmt.java index 194cc1e30292d4..bf97d31e6f13b9 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/SetOperationStmt.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/SetOperationStmt.java @@ -525,6 +525,41 @@ public void materializeRequiredSlots(Analyzer analyzer) throws AnalysisException } } + @Override + public void collectExprs(Map exprMap) { + for (SetOperand op : operands) { + op.getQueryStmt().collectExprs(exprMap); + } + if (orderByElements != null) { + for (OrderByElement orderByElement : orderByElements) { + Expr expr = orderByElement.getExpr(); + // see SelectStmt.collectExprs comments + if (containAlias(expr)) { + continue; + } + registerExprId(expr); + exprMap.put(expr.getId().toString(), expr); + } + } + } + + @Override + public void putBackExprs(Map rewrittenExprMap) { + for (SetOperand op : operands) { + op.getQueryStmt().putBackExprs(rewrittenExprMap); + } + if (orderByElements != null) { + for (OrderByElement orderByElement : orderByElements) { + Expr expr = orderByElement.getExpr(); + if (expr.getId() == null) { + orderByElement.setExpr(expr); + } else { + orderByElement.setExpr(rewrittenExprMap.get(expr.getId().toString())); + } + } + } + } + @Override public void rewriteExprs(ExprRewriter rewriter) throws AnalysisException { for (SetOperand op: operands) op.getQueryStmt().rewriteExprs(rewriter); diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/StatementBase.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/StatementBase.java index 79047ac111731d..da78614b280030 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/StatementBase.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/StatementBase.java @@ -171,6 +171,16 @@ public void rewriteExprs(ExprRewriter rewriter) throws AnalysisException { "rewriteExprs() not implemented for this stmt: " + getClass().getSimpleName()); } + /** + * fold constant exprs in statement + * @throws AnalysisException + * @param rewriter + */ + public void foldConstant(ExprRewriter rewriter) throws AnalysisException { + throw new IllegalStateException( + "foldConstant() not implemented for this stmt: " + getClass().getSimpleName()); + } + public String getClusterName() { return clusterName; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/SysVariableDesc.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/SysVariableDesc.java index c000baf6ca57b6..dd49dccd0e34bc 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/SysVariableDesc.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/SysVariableDesc.java @@ -175,4 +175,21 @@ public String toSqlImpl() { public String toString() { return toSql(); } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof SysVariableDesc)) { + return false; + } + if (!name.equals(((SysVariableDesc) obj).getName())) { + return false; + } + if (!setType.equals(((SysVariableDesc) obj).getSetType())) { + return false; + } + return literalExpr.equals(((SysVariableDesc) obj).getLiteralExpr()); + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/PrimitiveType.java b/fe/fe-core/src/main/java/org/apache/doris/catalog/PrimitiveType.java index 15082c4dc515e1..989f7a02f91dd0 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/catalog/PrimitiveType.java +++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/PrimitiveType.java @@ -538,6 +538,16 @@ public static PrimitiveType fromThrift(TPrimitiveType tPrimitiveType) { return FLOAT; case DOUBLE: return DOUBLE; + case DATE: + return DATE; + case DATETIME: + return DATETIME; + case BINARY: + return BINARY; + case DECIMALV2: + return DECIMALV2; + case TIME: + return TIME; case VARCHAR: return VARCHAR; case CHAR: diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java index 727559ae11da98..869c9885c6d96f 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java @@ -414,6 +414,9 @@ public TMasterOpResult proxyExecute(TMasterOpRequest request) { UserIdentity currentUserIdentity = UserIdentity.fromThrift(request.getCurrentUserIdent()); ctx.setCurrentUserIdentity(currentUserIdentity); } + if (request.isFoldConstantByBe()) { + ctx.getSessionVariable().setEnableFoldConstantByBe(request.foldConstantByBe); + } if (request.isSetSessionVariables()) { ctx.getSessionVariable().setForwardedSessionVariables(request.getSessionVariables()); diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java index 19bedaacfdc5f9..9818ab13facc6a 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java @@ -80,6 +80,7 @@ public class SessionVariable implements Serializable, Writable { public static final String ENABLE_EXCHANGE_NODE_PARALLEL_MERGE = "enable_exchange_node_parallel_merge"; public static final String PREFER_JOIN_METHOD = "prefer_join_method"; + public static final String ENABLE_FOLD_CONSTANT_BY_BE = "enable_fold_constant_by_be"; public static final String ENABLE_ODBC_TRANSCATION = "enable_odbc_transcation"; public static final String ENABLE_SQL_CACHE = "enable_sql_cache"; public static final String ENABLE_PARTITION_CACHE = "enable_partition_cache"; @@ -271,6 +272,9 @@ public class SessionVariable implements Serializable, Writable { @VariableMgr.VarAttr(name = PREFER_JOIN_METHOD) public String preferJoinMethod = "broadcast"; + @VariableMgr.VarAttr(name = ENABLE_FOLD_CONSTANT_BY_BE) + private boolean enableFoldConstantByBe = false; + /* * the parallel exec instance num for one Fragment in one BE * 1 means disable this feature @@ -517,6 +521,10 @@ public void setPreferJoinMethod(String preferJoinMethod) { this.preferJoinMethod = preferJoinMethod; } + public boolean isEnableFoldConstantByBe() { return enableFoldConstantByBe; } + + public void setEnableFoldConstantByBe(boolean foldConstantByBe) {this.enableFoldConstantByBe = foldConstantByBe; } + public int getParallelExecInstanceNum() { return parallelExecInstanceNum; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java b/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java index 7ec62223c000b1..f9b0ca94712d74 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java +++ b/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java @@ -544,12 +544,17 @@ private void parse() throws AnalysisException, DdlException { private void analyzeAndGenerateQueryPlan(TQueryOptions tQueryOptions) throws UserException { parsedStmt.analyze(analyzer); if (parsedStmt instanceof QueryStmt || parsedStmt instanceof InsertStmt) { + ExprRewriter rewriter = analyzer.getExprRewriter(); + rewriter.reset(); + if (context.getSessionVariable().isEnableFoldConstantByBe()) { + // fold constant expr + parsedStmt.foldConstant(rewriter); + + } // Apply expr and subquery rewrites. ExplainOptions explainOptions = parsedStmt.getExplainOptions(); boolean reAnalyze = false; - ExprRewriter rewriter = analyzer.getExprRewriter(); - rewriter.reset(); parsedStmt.rewriteExprs(rewriter); reAnalyze = rewriter.changed(); if (analyzer.containSubquery()) { diff --git a/fe/fe-core/src/main/java/org/apache/doris/rewrite/ExprRewriter.java b/fe/fe-core/src/main/java/org/apache/doris/rewrite/ExprRewriter.java index 1217f41a33e274..269f12adcfdca5 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/rewrite/ExprRewriter.java +++ b/fe/fe-core/src/main/java/org/apache/doris/rewrite/ExprRewriter.java @@ -24,6 +24,7 @@ import com.google.common.collect.Lists; import java.util.List; +import java.util.Map; /** * Helper class that drives the transformation of Exprs according to a given list of @@ -68,6 +69,10 @@ public Expr rewrite(Expr expr, Analyzer analyzer) throws AnalysisException { do { oldNumChanges = numChanges_; for (ExprRewriteRule rule: rules_) { + // when foldConstantByBe is on, fold all constant expr by BE instead of applying FoldConstantsRule in FE. + if (rule instanceof FoldConstantsRule && analyzer.safeIsEnableFoldConstantByBe()) { + continue; + } rewrittenExpr = applyRuleRepeatedly(rewrittenExpr, rule, analyzer); } } while (oldNumChanges != numChanges_); @@ -86,6 +91,25 @@ private Expr applyRuleOnce(Expr expr, ExprRewriteRule rule, Analyzer analyzer) t return rewrittenExpr; } + /** + * FoldConstantsRule rewrite + */ + public void rewriteConstant(Map exprMap, Analyzer analyzer) throws AnalysisException { + if (exprMap.isEmpty()) { + return; + } + boolean changed = false; + // rewrite constant expr + for (ExprRewriteRule rule : rules_) { + if (rule instanceof FoldConstantsRule) { + changed = ((FoldConstantsRule) rule).apply(exprMap, analyzer, changed); + } + } + if (changed) { + ++numChanges_; + } + } + /** * Applies 'rule' on the Expr tree rooted at 'expr' until there are no more changes. * Returns the transformed Expr or 'expr' if there were no changes. diff --git a/fe/fe-core/src/main/java/org/apache/doris/rewrite/FoldConstantsRule.java b/fe/fe-core/src/main/java/org/apache/doris/rewrite/FoldConstantsRule.java index f697150400669e..6042ad8bf04725 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/rewrite/FoldConstantsRule.java +++ b/fe/fe-core/src/main/java/org/apache/doris/rewrite/FoldConstantsRule.java @@ -22,11 +22,43 @@ import org.apache.doris.analysis.CaseExpr; import org.apache.doris.analysis.CastExpr; import org.apache.doris.analysis.Expr; +import org.apache.doris.analysis.InformationFunction; +import org.apache.doris.analysis.LiteralExpr; import org.apache.doris.analysis.NullLiteral; +import org.apache.doris.analysis.SysVariableDesc; +import org.apache.doris.catalog.Catalog; +import org.apache.doris.catalog.PrimitiveType; +import org.apache.doris.catalog.Type; import org.apache.doris.common.AnalysisException; +import org.apache.doris.common.LoadException; +import org.apache.doris.common.util.TimeUtils; +import org.apache.doris.proto.InternalService; +import org.apache.doris.qe.ConnectContext; +import org.apache.doris.qe.VariableMgr; +import org.apache.doris.rpc.BackendServiceProxy; +import org.apache.doris.system.Backend; +import org.apache.doris.thrift.TExpr; +import org.apache.doris.thrift.TFoldConstantParams; +import org.apache.doris.thrift.TNetworkAddress; +import org.apache.doris.thrift.TPrimitiveType; +import org.apache.doris.thrift.TQueryGlobals; + +import com.google.common.base.Preconditions; +import com.google.common.base.Predicates; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + /** * This rule replaces a constant Expr with its equivalent LiteralExpr by evaluating the * Expr in the BE. Exprs that are already LiteralExprs are not changed. @@ -44,6 +76,7 @@ */ public class FoldConstantsRule implements ExprRewriteRule { private static final Logger LOG = LogManager.getLogger(FoldConstantsRule.class); + private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static ExprRewriteRule INSTANCE = new FoldConstantsRule(); @@ -86,5 +119,275 @@ public Expr apply(Expr expr, Analyzer analyzer) throws AnalysisException { } return expr.getResultValue(); } + + /** + * fold constant expr by BE + * SysVariableDesc and InformationFunction need handling specially + * @param exprMap + * @param analyzer + * @return + * @throws AnalysisException + */ + public boolean apply(Map exprMap, Analyzer analyzer, boolean changed) + throws AnalysisException { + // root_expr_id_string: + // child_expr_id_string : texpr + // child_expr_id_string : texpr + Map> paramMap = new HashMap<>(); + Map allConstMap = new HashMap<>(); + // map to collect SysVariableDesc + Map> sysVarsMap = new HashMap<>(); + // map to collect InformationFunction + Map> infoFnsMap = new HashMap<>(); + for (Map.Entry entry : exprMap.entrySet()){ + Map constMap = new HashMap<>(); + Map oriConstMap = new HashMap<>(); + Map sysVarMap = new HashMap<>(); + Map infoFnMap = new HashMap<>(); + getConstExpr(entry.getValue(), constMap, oriConstMap, analyzer, sysVarMap, infoFnMap); + + if (!constMap.isEmpty()) { + paramMap.put(entry.getKey(), constMap); + allConstMap.putAll(oriConstMap); + } + if (!sysVarMap.isEmpty()) { + sysVarsMap.put(entry.getKey(), sysVarMap); + } + if (!infoFnMap.isEmpty()) { + infoFnsMap.put(entry.getKey(), infoFnMap); + } + } + + if (!sysVarsMap.isEmpty()) { + putBackConstExpr(exprMap, sysVarsMap); + changed = true; + } + + if (!infoFnsMap.isEmpty()) { + putBackConstExpr(exprMap, infoFnsMap); + changed = true; + } + + if (!paramMap.isEmpty()) { + Map> resultMap = calcConstExpr(paramMap, allConstMap, analyzer.getContext()); + + if (!resultMap.isEmpty()) { + putBackConstExpr(exprMap, resultMap); + changed = true; + + } + + } + return changed; + } + + /** + * get all constant children expr from a expr + * @param expr + * @param constExprMap + * @param analyzer + * @throws AnalysisException + */ + private void getConstExpr(Expr expr, Map constExprMap, Map oriConstMap, + Analyzer analyzer, Map sysVarMap, Map infoFnMap) + throws AnalysisException { + // Analyze constant exprs, if necessary. Note that the 'expr' may become non-constant + // after analysis (e.g., aggregate functions). + if (!expr.isAnalyzed()) { + expr.analyze(analyzer); + } + if (expr.isConstant()) { + // Do not constant fold cast(null as dataType) because we cannot preserve the + // cast-to-types and that can lead to query failures, e.g., CTAS + if (expr instanceof CastExpr) { + CastExpr castExpr = (CastExpr) expr; + if (castExpr.getChild(0) instanceof NullLiteral) { + return; + } + } + // skip literal expr + if (expr instanceof LiteralExpr) { + return; + } + // collect sysVariableDesc expr + if (expr.contains(Predicates.instanceOf(SysVariableDesc.class))) { + getSysVarDescExpr(expr, sysVarMap); + return; + } + // collect InformationFunction + if (expr.contains(Predicates.instanceOf(InformationFunction.class))) { + getInfoFnExpr(expr, infoFnMap); + return; + } + constExprMap.put(expr.getId().toString(),expr.treeToThrift()); + oriConstMap.put(expr.getId().toString(), expr); + } else { + recursiveGetChildrenConstExpr(expr, constExprMap, oriConstMap, analyzer, sysVarMap, infoFnMap); + + } + } + + private void recursiveGetChildrenConstExpr(Expr expr, Map constExprMap, Map oriConstMap, + Analyzer analyzer, Map sysVarMap, + Map infoFnMap)throws AnalysisException { + for (int i = 0; i < expr.getChildren().size(); i++) { + final Expr child = expr.getChildren().get(i); + getConstExpr(child, constExprMap, oriConstMap, analyzer, sysVarMap, infoFnMap); + } + + } + + private void getSysVarDescExpr(Expr expr, Map sysVarMap) { + if (expr instanceof SysVariableDesc) { + Expr literalExpr = ((SysVariableDesc) expr).getLiteralExpr(); + if (literalExpr == null) { + try { + VariableMgr.fillValue(ConnectContext.get().getSessionVariable(), (SysVariableDesc) expr); + literalExpr = ((SysVariableDesc) expr).getLiteralExpr(); + } catch (AnalysisException e) { + LOG.warn("failed to get session variable value: " + ((SysVariableDesc) expr).getName()); + } + } + sysVarMap.put(expr.getId().toString(), literalExpr); + } else { + for (Expr child : expr.getChildren()) { + getSysVarDescExpr(child, sysVarMap); + } + } + } + + private void getInfoFnExpr(Expr expr, Map infoFnMap) { + if (expr instanceof InformationFunction) { + Type type = expr.getType(); + LiteralExpr literalExpr = null; + try { + String str = null; + if (type.equals(Type.VARCHAR)) { + str = ((InformationFunction) expr).getStrValue(); + } else if (type.equals(Type.BIGINT)) { + str = ((InformationFunction) expr).getIntValue(); + } + Preconditions.checkNotNull(str); + literalExpr = LiteralExpr.create(str, type); + infoFnMap.put(expr.getId().toString(), literalExpr); + } catch (AnalysisException e) { + LOG.warn("failed to get const expr value from InformationFunction: {}", e.getMessage()); + } + + } else { + for (Expr child : expr.getChildren()) { + getInfoFnExpr(child, infoFnMap); + } + } + } + + /** + * put all rewritten expr back to ori expr map + * @param exprMap + * @param resultMap + */ + private void putBackConstExpr(Map exprMap, Map> resultMap) { + for (Map.Entry> entry : resultMap.entrySet()) { + Expr rewrittenExpr = putBackConstExpr(exprMap.get(entry.getKey()), entry.getValue()); + exprMap.put(entry.getKey(), rewrittenExpr); + } + } + + private Expr putBackConstExpr(Expr expr, Map resultMap) { + for (Map.Entry entry : resultMap.entrySet()) { + if (entry.getValue() instanceof LiteralExpr) { + expr = replaceExpr(expr, entry.getKey(), (LiteralExpr) entry.getValue()); + + } + } + return expr; + } + + /** + * find and replace constant child expr of a expr by literal expr + * @param expr + * @param key + * @param literalExpr + * @return + */ + private Expr replaceExpr(Expr expr, String key, LiteralExpr literalExpr) { + if (expr.getId().toString().equals(key)) { + return literalExpr; + } + // ATTN: make sure the child order of expr keep unchanged + for (int i = 0; i < expr.getChildren().size(); i++) { + Expr child = expr.getChild(i); + if (literalExpr.equals(replaceExpr(child, key, literalExpr))) { + literalExpr.setId(child.getId()); + expr.setChild(i, literalExpr); + break; + } + } + return expr; + } + + /** + * calc all constant exprs by BE + * @param map + * @param context + * @return + */ + private Map> calcConstExpr(Map> map, + Map allConstMap, + ConnectContext context) { + TNetworkAddress brpcAddress = null; + Map> resultMap = new HashMap<>(); + try { + List backendIds = Catalog.getCurrentSystemInfo().getBackendIds(true); + if (backendIds.isEmpty()) { + throw new LoadException("Failed to get all partitions. No alive backends"); + } + Collections.shuffle(backendIds); + Backend be = Catalog.getCurrentSystemInfo().getBackend(backendIds.get(0)); + brpcAddress = new TNetworkAddress(be.getHost(), be.getBrpcPort()); + + TQueryGlobals queryGlobals = new TQueryGlobals(); + queryGlobals.setNowString(DATE_FORMAT.format(new Date())); + queryGlobals.setTimestampMs(new Date().getTime()); + queryGlobals.setTimeZone(TimeUtils.DEFAULT_TIME_ZONE); + if (context.getSessionVariable().getTimeZone().equals("CST")) { + queryGlobals.setTimeZone(TimeUtils.DEFAULT_TIME_ZONE); + } else { + queryGlobals.setTimeZone(context.getSessionVariable().getTimeZone()); + } + + TFoldConstantParams tParams = new TFoldConstantParams(map, queryGlobals); + + Future future = BackendServiceProxy.getInstance().foldConstantExpr(brpcAddress, tParams); + InternalService.PConstantExprResult result = future.get(5, TimeUnit.SECONDS); + + if (result.getStatus().getStatusCode() == 0) { + for (Map.Entry entry : result.getExprResultMapMap().entrySet()) { + Map tmp = new HashMap<>(); + for (Map.Entry entry1 : entry.getValue().getMapMap().entrySet()) { + TPrimitiveType type = TPrimitiveType.findByValue(entry1.getValue().getType().getType()); + Expr retExpr = null; + if (entry1.getValue().getSuccess()) { + retExpr = LiteralExpr.create(entry1.getValue().getContent(), + Type.fromPrimitiveType(PrimitiveType.fromThrift(type))); + } else { + retExpr = allConstMap.get(entry1.getKey()); + } + tmp.put(entry1.getKey(), retExpr); + } + if (!tmp.isEmpty()) { + resultMap.put(entry.getKey(), tmp); + } + } + + } else { + LOG.warn("failed to get const expr value from be: {}", result.getStatus().getErrorMsgsList()); + } + } catch (Exception e) { + LOG.warn("failed to get const expr value from be: {}", e.getMessage()); + } + return resultMap; + + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/rpc/BackendServiceClient.java b/fe/fe-core/src/main/java/org/apache/doris/rpc/BackendServiceClient.java index d9871c237ff9af..ffd7e25b97d40a 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/rpc/BackendServiceClient.java +++ b/fe/fe-core/src/main/java/org/apache/doris/rpc/BackendServiceClient.java @@ -76,4 +76,8 @@ public Future clearCache(InternalService.PClearC public Future getInfo(InternalService.PProxyRequest request) { return stub.getInfo(request); } + + public Future foldConstantExpr(InternalService.PConstantExprRequest request) { + return stub.foldConstantExpr(request); + } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/rpc/BackendServiceProxy.java b/fe/fe-core/src/main/java/org/apache/doris/rpc/BackendServiceProxy.java index b488dfde13327c..40f2897520a7b1 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/rpc/BackendServiceProxy.java +++ b/fe/fe-core/src/main/java/org/apache/doris/rpc/BackendServiceProxy.java @@ -20,6 +20,7 @@ import org.apache.doris.proto.InternalService; import org.apache.doris.proto.Types; import org.apache.doris.thrift.TExecPlanFragmentParams; +import org.apache.doris.thrift.TFoldConstantParams; import org.apache.doris.thrift.TNetworkAddress; import org.apache.doris.thrift.TUniqueId; @@ -167,4 +168,18 @@ public Future getInfo( throw new RpcException(address.hostname, e.getMessage()); } } + + public Future foldConstantExpr( + TNetworkAddress address, TFoldConstantParams tParams) throws RpcException, TException { + final InternalService.PConstantExprRequest pRequest = InternalService.PConstantExprRequest.newBuilder() + .setRequest(ByteString.copyFrom(new TSerializer().serialize(tParams))).build(); + + try { + final BackendServiceClient client = getProxy(address); + return client.foldConstantExpr(pRequest); + } catch (Throwable e) { + LOG.warn("failed to fold constant expr, address={}:{}", address.getHostname(), address.getPort(), e); + throw new RpcException(address.hostname, e.getMessage()); + } + } } diff --git a/fe/fe-core/src/test/java/org/apache/doris/analysis/QueryStmtTest.java b/fe/fe-core/src/test/java/org/apache/doris/analysis/QueryStmtTest.java new file mode 100644 index 00000000000000..ccdb16ba043a07 --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/analysis/QueryStmtTest.java @@ -0,0 +1,280 @@ +// 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.doris.analysis; + +import com.google.common.collect.Lists; +import org.apache.doris.catalog.Type; +import org.apache.doris.common.Config; +import org.apache.doris.common.UserException; +import org.apache.doris.qe.ConnectContext; +import org.apache.doris.utframe.DorisAssert; +import org.apache.doris.utframe.UtFrameUtils; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class QueryStmtTest { + private static String runningDir = "fe/mocked/DemoTest/" + UUID.randomUUID().toString() + "/"; + private static DorisAssert dorisAssert; + + @AfterClass + public static void tearDown() throws Exception { + UtFrameUtils.cleanDorisFeDir(runningDir); + } + + @BeforeClass + public static void setUp() throws Exception { + Config.enable_batch_delete_by_default = true; + UtFrameUtils.createMinDorisCluster(runningDir); + String createTblStmtStr = "create table db1.tbl1(k1 varchar(32), k2 varchar(32), k3 varchar(32), k4 int) " + + "AGGREGATE KEY(k1, k2,k3,k4) distributed by hash(k1) buckets 3 properties('replication_num' = '1');"; + String createBaseAllStmtStr = "create table db1.baseall(k1 int, k2 varchar(32)) distributed by hash(k1) " + + "buckets 3 properties('replication_num' = '1');"; + String tbl1 = "CREATE TABLE db1.table1 (\n" + + " `siteid` int(11) NULL DEFAULT \"10\" COMMENT \"\",\n" + + " `citycode` smallint(6) NULL COMMENT \"\",\n" + + " `username` varchar(32) NULL DEFAULT \"\" COMMENT \"\",\n" + + " `pv` bigint(20) NULL DEFAULT \"0\" COMMENT \"\"\n" + + ") ENGINE=OLAP\n" + + "UNIQUE KEY(`siteid`, `citycode`, `username`)\n" + + "COMMENT \"OLAP\"\n" + + "DISTRIBUTED BY HASH(`siteid`) BUCKETS 10\n" + + "PROPERTIES (\n" + + "\"replication_num\" = \"1\",\n" + + "\"in_memory\" = \"false\",\n" + + "\"storage_format\" = \"V2\"\n" + + ")"; + dorisAssert = new DorisAssert(); + dorisAssert.withDatabase("db1").useDatabase("db1"); + dorisAssert.withTable(createTblStmtStr) + .withTable(createBaseAllStmtStr) + .withTable(tbl1); + } + + @Test + public void testCollectExprs() throws Exception { + ConnectContext ctx = UtFrameUtils.createDefaultCtx(); + String sql = "SELECT CASE\n" + + " WHEN (\n" + + " SELECT COUNT(*) / 2\n" + + " FROM db1.tbl1\n" + + " ) > k4 THEN (\n" + + " SELECT AVG(k4)\n" + + " FROM db1.tbl1\n" + + " )\n" + + " ELSE (\n" + + " SELECT SUM(k4)\n" + + " FROM db1.tbl1\n" + + " )\n" + + " END AS kk4\n" + + "FROM db1.tbl1;"; + QueryStmt stmt = (QueryStmt) UtFrameUtils.parseAndAnalyzeStmt(sql, ctx); + Map exprsMap = new HashMap<>(); + stmt.collectExprs(exprsMap); + Assert.assertEquals(4, exprsMap.size()); + + sql = "SELECT username\n" + + "FROM db1.table1\n" + + "WHERE siteid in\n" + + " (SELECT abs(5+abs(0))+1)\n" + + "UNION\n" + + "SELECT CASE\n" + + " WHEN\n" + + " (SELECT count(*)+abs(8)\n" + + " FROM db1.table1\n" + + " WHERE username='helen')>1 THEN 888\n" + + " ELSE 999\n" + + " END AS ccc\n" + + "FROM\n" + + " (SELECT curdate()) a;"; + stmt = (QueryStmt) UtFrameUtils.parseAndAnalyzeStmt(sql, ctx); + exprsMap.clear(); + stmt.collectExprs(exprsMap); + Assert.assertEquals(6, exprsMap.size()); + + sql = "select\n" + + " avg(t1.k4)\n" + + "from\n" + + " db1.tbl1 t1,\n" + + " db1.tbl1 t2,\n" + + " db1.tbl1 t3,\n" + + " db1.tbl1 t4,\n" + + " db1.tbl1 t5,\n" + + " db1.tbl1 t6\n" + + "where\n" + + " t2.k1 = t1.k1\n" + + " and t1.k2 = t6.k2\n" + + " and t6.k4 = 2001\n" + + " and(\n" + + " (\n" + + " t1.k2 = t4.k2\n" + + " and t3.k3 = t1.k3\n" + + " and t3.k1 = 'D'\n" + + " and t4.k3 = '2 yr Degree'\n" + + " and t1.k4 between 100.00\n" + + " and 150.00\n" + + " and t4.k4 = 3\n" + + " )\n" + + " or (\n" + + " t1.k2 = t4.k2\n" + + " and t3.k3 = t1.k3\n" + + " and t3.k1 = 'S'\n" + + " and t4.k3 = 'Secondary'\n" + + " and t1.k4 between 50.00\n" + + " and 100.00\n" + + " and t4.k4 = 1\n" + + " )\n" + + " or (\n" + + " t1.k2 = t4.k2\n" + + " and t3.k3 = t1.k3\n" + + " and t3.k1 = 'W'\n" + + " and t4.k3 = 'Advanced Degree'\n" + + " and t1.k4 between 150.00\n" + + " and 200.00\n" + + " and t4.k4 = 1\n" + + " )\n" + + " )\n" + + " and(\n" + + " (\n" + + " t1.k1 = t5.k1\n" + + " and t5.k2 = 'United States'\n" + + " and t5.k3 in ('CO', 'IL', 'MN')\n" + + " and t1.k4 between 100\n" + + " and 200\n" + + " )\n" + + " or (\n" + + " t1.k1 = t5.k1\n" + + " and t5.k2 = 'United States'\n" + + " and t5.k3 in ('OH', 'MT', 'NM')\n" + + " and t1.k4 between 150\n" + + " and 300\n" + + " )\n" + + " or (\n" + + " t1.k1 = t5.k1\n" + + " and t5.k2 = 'United States'\n" + + " and t5.k3 in ('TX', 'MO', 'MI')\n" + + " and t1.k4 between 50 and 250\n" + + " )\n" + + " );"; + stmt = (QueryStmt) UtFrameUtils.parseAndAnalyzeStmt(sql, ctx); + exprsMap.clear(); + stmt.collectExprs(exprsMap); + Assert.assertEquals(2, exprsMap.size()); + + sql = "SELECT k1 FROM db1.baseall GROUP BY k1 HAVING EXISTS(SELECT k4 FROM db1.tbl1 GROUP BY k4 " + + "HAVING SUM(k4) = k4);"; + stmt = (QueryStmt) UtFrameUtils.parseAndAnalyzeStmt(sql, ctx); + exprsMap.clear(); + stmt.collectExprs(exprsMap); + Assert.assertEquals(4, exprsMap.size()); + } + + @Test + public void testPutBackExprs() throws Exception { + ConnectContext ctx = UtFrameUtils.createDefaultCtx(); + String sql = "SELECT username, @@license, @@time_zone\n" + + "FROM db1.table1\n" + + "WHERE siteid in\n" + + " (SELECT abs(5+abs(0))+1)\n" + + "UNION\n" + + "SELECT CASE\n" + + " WHEN\n" + + " (SELECT count(*)+abs(8)\n" + + " FROM db1.table1\n" + + " WHERE username='helen')>1 THEN 888\n" + + " ELSE 999\n" + + " END AS ccc, @@language, @@storage_engine\n" + + "FROM\n" + + " (SELECT curdate()) a;"; + StatementBase stmt = UtFrameUtils.parseAndAnalyzeStmt(sql, ctx); + stmt.foldConstant(new Analyzer(ctx.getCatalog(), ctx).getExprRewriter()); + + // reAnalyze + reAnalyze(stmt, ctx); + Assert.assertTrue(stmt.toSql().contains("Apache License, Version 2.0")); + Assert.assertTrue(stmt.toSql().contains("/palo/share/english/")); + + // test sysVariableDescs + sql = "SELECT\n" + + " avg(t1.k4)\n" + + "FROM\n" + + " db1.tbl1 t1,\n" + + " db1.tbl1 t2\n" + + "WHERE\n" + + "(\n" + + " t2.k2 = 'United States'\n" + + " AND t2.k3 in (@@license, @@version)\n" + + ")\n" + + "OR (\n" + + " t2.k2 = @@language\n" + + ")"; + stmt = UtFrameUtils.parseAndAnalyzeStmt(sql, ctx); + stmt.foldConstant(new Analyzer(ctx.getCatalog(), ctx).getExprRewriter()); + // reAnalyze + reAnalyze(stmt, ctx); + Assert.assertTrue(stmt.toSql().contains("Apache License, Version 2.0")); + Assert.assertTrue(stmt.toSql().contains("/palo/share/english/")); + + // test informationFunctions + sql = "SELECT\n" + + " avg(t1.k4)\n" + + "FROM\n" + + " db1.tbl1 t1,\n" + + " db1.tbl1 t2\n" + + "WHERE\n" + + "(\n" + + " t2.k2 = 'United States'\n" + + " AND t2.k1 in (USER(), CURRENT_USER(), SCHEMA())\n" + + ")\n" + + "OR (\n" + + " t2.k2 = CONNECTION_ID()\n" + + ")"; + stmt = UtFrameUtils.parseAndAnalyzeStmt(sql, ctx); + stmt.foldConstant(new Analyzer(ctx.getCatalog(), ctx).getExprRewriter()); + // reAnalyze + reAnalyze(stmt, ctx); + Assert.assertTrue(stmt.toSql().contains("root'@'%")); + Assert.assertTrue(stmt.toSql().contains("root'@'127.0.0.1'")); + + } + + private void reAnalyze(StatementBase stmt, ConnectContext ctx) throws UserException { + // reAnalyze + List origResultTypes = Lists.newArrayList(); + for (Expr e: stmt.getResultExprs()) { + origResultTypes.add(e.getType()); + } + List origColLabels = + Lists.newArrayList(stmt.getColLabels()); + + // query re-analyze + stmt.reset(); + // Re-analyze the stmt with a new analyzer. + stmt.analyze(new Analyzer(ctx.getCatalog(), ctx)); + + // Restore the original result types and column labels. + stmt.castResultExprs(origResultTypes); + stmt.setColLabels(origColLabels); + } +} diff --git a/gensrc/proto/internal_service.proto b/gensrc/proto/internal_service.proto index 95b1be4713c670..d900073a155fec 100644 --- a/gensrc/proto/internal_service.proto +++ b/gensrc/proto/internal_service.proto @@ -325,6 +325,25 @@ message PPublishFilterResponse { required PStatus status = 1; }; +message PExprResult { + required PScalarType type = 1; + required string content = 2; + required bool success = 3; +}; + +message PExprResultMap { + map map = 1; +}; + +message PConstantExprRequest { + optional bytes request = 1; +}; + +message PConstantExprResult { + required PStatus status = 1; + map expr_result_map = 2; +}; + // NOTE(zc): If you want to add new method here, // you MUST add same method to palo_internal_service.proto service PBackendService { @@ -341,5 +360,6 @@ service PBackendService { rpc clear_cache(PClearCacheRequest) returns (PCacheResponse); rpc merge_filter(PMergeFilterRequest) returns (PMergeFilterResponse); rpc apply_filter(PPublishFilterRequest) returns (PPublishFilterResponse); + rpc fold_constant_expr(PConstantExprRequest) returns (PConstantExprResult); }; diff --git a/gensrc/proto/palo_internal_service.proto b/gensrc/proto/palo_internal_service.proto index 592fe585d7574d..8439f4a2412ce3 100644 --- a/gensrc/proto/palo_internal_service.proto +++ b/gensrc/proto/palo_internal_service.proto @@ -40,4 +40,5 @@ service PInternalService { rpc clear_cache(doris.PClearCacheRequest) returns (doris.PCacheResponse); rpc merge_filter(doris.PMergeFilterRequest) returns (doris.PMergeFilterResponse); rpc apply_filter(doris.PPublishFilterRequest) returns (doris.PPublishFilterResponse); + rpc fold_constant_expr(doris.PConstantExprRequest) returns (doris.PConstantExprResult); }; diff --git a/gensrc/thrift/FrontendService.thrift b/gensrc/thrift/FrontendService.thrift index df87a5e17cebeb..0f8b42021919a9 100644 --- a/gensrc/thrift/FrontendService.thrift +++ b/gensrc/thrift/FrontendService.thrift @@ -451,6 +451,7 @@ struct TMasterOpRequest { 17: optional Types.TUniqueId query_id // when this is a query, we translate this query id to master 18: optional i64 insert_visible_timeout_ms // deprecated, move into session_variables 19: optional map session_variables + 20: optional bool foldConstantByBe } struct TColumnDefinition { diff --git a/gensrc/thrift/PaloInternalService.thrift b/gensrc/thrift/PaloInternalService.thrift index b2f0c89a1cdbe5..ae64ef5bf2c2a0 100644 --- a/gensrc/thrift/PaloInternalService.thrift +++ b/gensrc/thrift/PaloInternalService.thrift @@ -319,6 +319,15 @@ struct TCancelPlanFragmentResult { 1: optional Status.TStatus status } +// fold constant expr +struct TExprMap { + 1: required map expr_map +} + +struct TFoldConstantParams { + 1: required map> expr_map + 2: required TQueryGlobals query_globals +} // TransmitData struct TTransmitDataParams { From e6e316f82b7293401c8dc30e331e44194e8f4e66 Mon Sep 17 00:00:00 2001 From: qijianliang01 Date: Wed, 14 Jul 2021 19:37:45 +0800 Subject: [PATCH 2/2] fix ut Change-Id: Idb2ba55ad77cb3182df03a12e7b61698591e44a1 --- .../test/java/org/apache/doris/analysis/QueryStmtTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fe/fe-core/src/test/java/org/apache/doris/analysis/QueryStmtTest.java b/fe/fe-core/src/test/java/org/apache/doris/analysis/QueryStmtTest.java index ccdb16ba043a07..e76f24edcf91e5 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/analysis/QueryStmtTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/analysis/QueryStmtTest.java @@ -254,8 +254,8 @@ public void testPutBackExprs() throws Exception { stmt.foldConstant(new Analyzer(ctx.getCatalog(), ctx).getExprRewriter()); // reAnalyze reAnalyze(stmt, ctx); - Assert.assertTrue(stmt.toSql().contains("root'@'%")); - Assert.assertTrue(stmt.toSql().contains("root'@'127.0.0.1'")); + Assert.assertTrue(stmt.toSql().contains("root''@''%")); + Assert.assertTrue(stmt.toSql().contains("root''@''127.0.0.1")); }