/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is also distributed with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have included with MySQL. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* This file defines the NDB Cluster handler engine_condition_pushdown */ #include "storage/ndb/plugin/ha_ndbcluster_cond.h" #include "my_dbug.h" #include "sql/current_thd.h" #include "sql/item.h" // Item #include "sql/item_cmpfunc.h" // Item_func_like etc. #include "sql/item_func.h" // Item_func #include "storage/ndb/plugin/ha_ndbcluster.h" #include "storage/ndb/plugin/ndb_log.h" #include "storage/ndb/plugin/ndb_thd.h" // Typedefs for long names typedef NdbDictionary::Column NDBCOL; typedef NdbDictionary::Table NDBTAB; typedef enum ndb_item_type { NDB_VALUE = 0, // Qualified more with Item::Type NDB_FIELD = 1, // Qualified from table definition NDB_FUNCTION = 2, // Qualified from Item_func::Functype NDB_END_COND = 3 // End marker for condition group } NDB_ITEM_TYPE; typedef enum ndb_func_type { NDB_EQ_FUNC = 0, NDB_NE_FUNC = 1, NDB_LT_FUNC = 2, NDB_LE_FUNC = 3, NDB_GT_FUNC = 4, NDB_GE_FUNC = 5, NDB_ISNULL_FUNC = 6, NDB_ISNOTNULL_FUNC = 7, NDB_LIKE_FUNC = 8, NDB_NOTLIKE_FUNC = 9, NDB_NOT_FUNC = 10, NDB_COND_AND_FUNC = 11, NDB_COND_OR_FUNC = 12, NDB_UNSUPPORTED_FUNC = 13 } NDB_FUNC_TYPE; typedef union ndb_item_value { const Item *item; // NDB_VALUE struct { // NDB_FIELD Field *field; int column_no; }; struct { // NDB_FUNCTION NDB_FUNC_TYPE func_type; uint arg_count; }; } NDB_ITEM_VALUE; /* Mapping defining the negated and swapped function equivalent - 'not op1 func op2' -> 'op1 neg_func op2' - 'op1 func op2' -> ''op2 swap_func op1' */ struct function_mapping { NDB_FUNC_TYPE func; NDB_FUNC_TYPE neg_func; NDB_FUNC_TYPE swap_func; }; /* Define what functions can be negated in condition pushdown. Note, these HAVE to be in the same order as in definition enum */ static const function_mapping func_map[] = { {NDB_EQ_FUNC, NDB_NE_FUNC, NDB_EQ_FUNC}, {NDB_NE_FUNC, NDB_EQ_FUNC, NDB_NE_FUNC}, {NDB_LT_FUNC, NDB_GE_FUNC, NDB_GT_FUNC}, {NDB_LE_FUNC, NDB_GT_FUNC, NDB_GE_FUNC}, {NDB_GT_FUNC, NDB_LE_FUNC, NDB_LT_FUNC}, {NDB_GE_FUNC, NDB_LT_FUNC, NDB_LE_FUNC}, {NDB_ISNULL_FUNC, NDB_ISNOTNULL_FUNC, NDB_UNSUPPORTED_FUNC}, {NDB_ISNOTNULL_FUNC, NDB_ISNULL_FUNC, NDB_UNSUPPORTED_FUNC}, {NDB_LIKE_FUNC, NDB_NOTLIKE_FUNC, NDB_UNSUPPORTED_FUNC}, {NDB_NOTLIKE_FUNC, NDB_LIKE_FUNC, NDB_UNSUPPORTED_FUNC}, {NDB_NOT_FUNC, NDB_UNSUPPORTED_FUNC, NDB_UNSUPPORTED_FUNC}, {NDB_COND_AND_FUNC, NDB_UNSUPPORTED_FUNC, NDB_UNSUPPORTED_FUNC}, {NDB_COND_OR_FUNC, NDB_UNSUPPORTED_FUNC, NDB_UNSUPPORTED_FUNC}, {NDB_UNSUPPORTED_FUNC, NDB_UNSUPPORTED_FUNC, NDB_UNSUPPORTED_FUNC}}; /* This class is the construction element for serialization of Item tree in condition pushdown. An instance of Ndb_Item represents a constant, table field reference, unary or binary comparison predicate, and start/end of AND/OR. Instances of Ndb_Item are stored in a linked list using the List-template The order of elements produced by iterating this list corresponds to breadth-first traversal of the Item (i.e. expression) tree in prefix order. AND and OR have arbitrary arity, so the end of AND/OR group is marked with Ndb_item with type == NDB_END_COND. NOT items represent negated conditions and generate NAND/NOR groups. */ class Ndb_item { public: Ndb_item(NDB_ITEM_TYPE item_type) : type(item_type) {} // A Ndb_Item where an Item expression defines the value (a const) Ndb_item(const Item *item_value) : type(NDB_VALUE) { value.item = item_value; } // A Ndb_Item refering a Field from 'this' table Ndb_item(Field *field, int column_no) : type(NDB_FIELD) { value.field = field; value.column_no = column_no; } Ndb_item(Item_func::Functype func_type, const Item_func *item_func) : type(NDB_FUNCTION) { value.func_type = item_func_to_ndb_func(func_type); value.arg_count = item_func->argument_count(); } Ndb_item(Item_func::Functype func_type, uint no_args) : type(NDB_FUNCTION) { value.func_type = item_func_to_ndb_func(func_type); value.arg_count = no_args; } ~Ndb_item() {} Field *get_field() const { DBUG_ASSERT(type == NDB_FIELD); return value.field; } int get_field_no() const { DBUG_ASSERT(type == NDB_FIELD); return value.column_no; } NDB_FUNC_TYPE get_func_type() const { DBUG_ASSERT(type == NDB_FUNCTION); return value.func_type; } int get_argument_count() const { DBUG_ASSERT(type == NDB_FUNCTION); return value.arg_count; } uint32 pack_length() const { return get_field()->pack_length(); } const uchar *get_val() const { return get_field()->ptr; } const CHARSET_INFO *get_field_charset() const { const Field *field = get_field(); if (field) return field->charset(); return NULL; } const Item *get_item() const { DBUG_ASSERT(this->type == NDB_VALUE); return value.item; } int save_in_field(const Ndb_item *field_item) const { DBUG_TRACE; Field *field = field_item->get_field(); const Item *item = get_item(); if (unlikely(item == nullptr || field == nullptr)) return -1; my_bitmap_map *old_map = dbug_tmp_use_all_columns(field->table, field->table->write_set); const type_conversion_status status = const_cast(item)->save_in_field(field, false); dbug_tmp_restore_column_map(field->table->write_set, old_map); if (unlikely(status != TYPE_OK)) return -1; return 0; // OK } static NDB_FUNC_TYPE item_func_to_ndb_func(Item_func::Functype fun) { switch (fun) { case (Item_func::EQ_FUNC): { return NDB_EQ_FUNC; } case (Item_func::NE_FUNC): { return NDB_NE_FUNC; } case (Item_func::LT_FUNC): { return NDB_LT_FUNC; } case (Item_func::LE_FUNC): { return NDB_LE_FUNC; } case (Item_func::GT_FUNC): { return NDB_GT_FUNC; } case (Item_func::GE_FUNC): { return NDB_GE_FUNC; } case (Item_func::ISNULL_FUNC): { return NDB_ISNULL_FUNC; } case (Item_func::ISNOTNULL_FUNC): { return NDB_ISNOTNULL_FUNC; } case (Item_func::LIKE_FUNC): { return NDB_LIKE_FUNC; } case (Item_func::NOT_FUNC): { return NDB_NOT_FUNC; } case (Item_func::COND_AND_FUNC): { return NDB_COND_AND_FUNC; } case (Item_func::COND_OR_FUNC): { return NDB_COND_OR_FUNC; } default: { return NDB_UNSUPPORTED_FUNC; } } } static NDB_FUNC_TYPE negate(NDB_FUNC_TYPE fun) { uint i = (uint)fun; DBUG_ASSERT(fun == func_map[i].func); return func_map[i].neg_func; } static NDB_FUNC_TYPE swap(NDB_FUNC_TYPE fun) { uint i = (uint)fun; DBUG_ASSERT(fun == func_map[i].func); return func_map[i].swap_func; } const NDB_ITEM_TYPE type; private: NDB_ITEM_VALUE value; }; /* This class implements look-ahead during the parsing of the item tree. It contains bit masks for expected items, field types and field results. It also contains expected collation. The parse context (Ndb_cond_traverse_context) always contains one expect_stack instance (top of the stack). More expects (deeper look-ahead) can be pushed to the expect_stack to check specific order (currently used for detecting support for LIKE |, but not | LIKE ). */ class Ndb_expect_stack { static const uint MAX_EXPECT_ITEMS = Item::VIEW_FIXER_ITEM + 1; static const uint MAX_EXPECT_FIELD_TYPES = MYSQL_TYPE_GEOMETRY + 1; static const uint MAX_EXPECT_FIELD_RESULTS = DECIMAL_RESULT + 1; public: Ndb_expect_stack() : other_field(nullptr), collation(nullptr), length(0), max_length(0), next(nullptr) { // Allocate type checking bitmaps using fixed size buffers // since max size is known at compile time bitmap_init(&expect_mask, m_expect_buf, MAX_EXPECT_ITEMS, false); bitmap_init(&expect_field_type_mask, m_expect_field_type_buf, MAX_EXPECT_FIELD_TYPES, false); bitmap_init(&expect_field_result_mask, m_expect_field_result_buf, MAX_EXPECT_FIELD_RESULTS, false); } ~Ndb_expect_stack() { if (next) destroy(next); next = NULL; } void push(Ndb_expect_stack *expect_next) { next = expect_next; } void pop() { if (next) { Ndb_expect_stack *expect_next = next; bitmap_copy(&expect_mask, &next->expect_mask); bitmap_copy(&expect_field_type_mask, &next->expect_field_type_mask); bitmap_copy(&expect_field_result_mask, &next->expect_field_result_mask); other_field = next->other_field; collation = next->collation; next = next->next; destroy(expect_next); } } void expect(Item::Type type) { bitmap_set_bit(&expect_mask, (uint)type); } void dont_expect(Item::Type type) { bitmap_clear_bit(&expect_mask, (uint)type); } bool expecting(Item::Type type) { if (unlikely((uint)type > MAX_EXPECT_ITEMS)) { // Unknown type, can't be expected return false; } return bitmap_is_set(&expect_mask, (uint)type); } void expect_nothing() { bitmap_clear_all(&expect_mask); } bool expecting_nothing() { return bitmap_is_clear_all(&expect_mask); } void expect_only(Item::Type type) { expect_nothing(); expect(type); } bool expecting_only(Item::Type type) { return (expecting(type) && bitmap_bits_set(&expect_mask) == 1); } void expect_field_type(enum_field_types type) { bitmap_set_bit(&expect_field_type_mask, (uint)type); } void dont_expect_field_type(enum_field_types type) { bitmap_clear_bit(&expect_field_type_mask, (uint)type); } void expect_all_field_types() { bitmap_set_all(&expect_field_type_mask); } bool expecting_field_type(enum_field_types type) { if (unlikely((uint)type > MAX_EXPECT_FIELD_TYPES)) { // Unknown type, can't be expected return false; } return bitmap_is_set(&expect_field_type_mask, (uint)type); } void expect_only_field_type(enum_field_types type) { bitmap_clear_all(&expect_field_type_mask); expect_field_type(type); } void expect_comparable_field(const Field *field) { other_field = field; } bool expecting_comparable_field(const Field *field) { if (other_field == nullptr) // No Field to be comparable with return true; // Fields need to have equal definition return other_field->eq_def(field); } void expect_field_result(Item_result result) { bitmap_set_bit(&expect_field_result_mask, (uint)result); } bool expecting_field_result(Item_result result) { if (unlikely((uint)result > MAX_EXPECT_FIELD_RESULTS)) { // Unknown result, can't be expected return false; } return bitmap_is_set(&expect_field_result_mask, (uint)result); } void expect_no_field_result() { bitmap_clear_all(&expect_field_result_mask); } bool expecting_no_field_result() { return bitmap_is_clear_all(&expect_field_result_mask); } void expect_collation(const CHARSET_INFO *col) { collation = col; } bool expecting_collation(const CHARSET_INFO *col) { bool matching = (!collation) ? true : (collation == col); collation = NULL; return matching; } void expect_length(Uint32 len) { length = len; } void expect_max_length(Uint32 max) { max_length = max; } bool expecting_length(Uint32 len) { return max_length == 0 || len <= max_length; } bool expecting_max_length(Uint32 max) { return max >= length; } void expect_no_length() { length = max_length = 0; } private: my_bitmap_map m_expect_buf[bitmap_buffer_size(MAX_EXPECT_ITEMS)]; my_bitmap_map m_expect_field_type_buf[bitmap_buffer_size(MAX_EXPECT_FIELD_TYPES)]; my_bitmap_map m_expect_field_result_buf[bitmap_buffer_size(MAX_EXPECT_FIELD_RESULTS)]; MY_BITMAP expect_mask; MY_BITMAP expect_field_type_mask; MY_BITMAP expect_field_result_mask; const Field *other_field; const CHARSET_INFO *collation; Uint32 length; Uint32 max_length; Ndb_expect_stack *next; }; class Ndb_rewrite_context { public: Ndb_rewrite_context(const Item_func *func) : func_item(func), left_hand_item(NULL), count(0) {} ~Ndb_rewrite_context() { if (next) destroy(next); } const Item_func *func_item; const Item *left_hand_item; uint count; Ndb_rewrite_context *next; }; /* This class is used for storing the context when traversing the Item tree. It stores a reference to the table the condition is defined on, the serialized representation being generated, if the condition found is supported, and information what is expected next in the tree inorder for the condition to be supported. */ class Ndb_cond_traverse_context { public: Ndb_cond_traverse_context(TABLE *tab, const NdbDictionary::Table *ndb_tab) : table(tab), ndb_table(ndb_tab), supported(true), skip(0), rewrite_stack(NULL) {} ~Ndb_cond_traverse_context() { if (rewrite_stack) destroy(rewrite_stack); } inline void expect_field_from_table() { expect_stack.expect(Item::FIELD_ITEM); expect_stack.expect_all_field_types(); expect_stack.expect_comparable_field(nullptr); } inline void expect_only_field_from_table() { expect_stack.expect_nothing(); expect_field_from_table(); } inline void expect(Item::Type type) { expect_stack.expect(type); } inline void dont_expect(Item::Type type) { expect_stack.dont_expect(type); } inline bool expecting(Item::Type type) { return expect_stack.expecting(type); } inline void expect_nothing() { expect_stack.expect_nothing(); } inline bool expecting_nothing() { return expect_stack.expecting_nothing(); } inline void expect_only(Item::Type type) { expect_stack.expect_only(type); } inline void expect_field_type(enum_field_types type) { expect_stack.expect_field_type(type); } inline void dont_expect_field_type(enum_field_types type) { expect_stack.dont_expect_field_type(type); } inline void expect_only_field_type(enum_field_types result) { expect_stack.expect_only_field_type(result); } inline void expect_comparable_field(const Field *field) { expect_stack.expect_only_field_type(field->real_type()); expect_stack.expect_comparable_field(field); } inline bool expecting_comparable_field(const Field *field) { return expect_stack.expecting_field_type(field->real_type()) && expect_stack.expecting_comparable_field(field); } inline void expect_field_result(Item_result result) { expect_stack.expect_field_result(result); } inline bool expecting_field_result(Item_result result) { return expect_stack.expecting_field_result(result); } inline void expect_no_field_result() { expect_stack.expect_no_field_result(); } inline bool expecting_no_field_result() { return expect_stack.expecting_no_field_result(); } inline void expect_collation(const CHARSET_INFO *col) { expect_stack.expect_collation(col); } inline bool expecting_collation(const CHARSET_INFO *col) { return expect_stack.expecting_collation(col); } inline void expect_length(Uint32 length) { expect_stack.expect_length(length); } inline void expect_max_length(Uint32 max) { expect_stack.expect_max_length(max); } inline bool expecting_length(Uint32 length) { return expect_stack.expecting_length(length); } inline bool expecting_max_length(Uint32 max) { return expect_stack.expecting_max_length(max); } inline void expect_no_length() { expect_stack.expect_no_length(); } TABLE *const table; const NdbDictionary::Table *const ndb_table; bool supported; List items; Ndb_expect_stack expect_stack; uint skip; Ndb_rewrite_context *rewrite_stack; }; static bool is_supported_temporal_type(enum_field_types type) { switch (type) { case MYSQL_TYPE_TIME: case MYSQL_TYPE_TIME2: case MYSQL_TYPE_DATE: case MYSQL_TYPE_NEWDATE: case MYSQL_TYPE_YEAR: case MYSQL_TYPE_DATETIME: case MYSQL_TYPE_DATETIME2: return true; default: return false; } } /* operand_count() reflect the traverse_cond() operand traversal. Note that traverse_cond() only traverse any operands for FUNC_ITEM and COND_ITEM, which is reflected by operand_count(). */ static uint operand_count(const Item *item) { switch (item->type()) { case Item::FUNC_ITEM: { const Item_func *func_item = static_cast(item); return func_item->argument_count(); } case Item::COND_ITEM: { Item_cond *cond_item = const_cast(static_cast(item)); List *arguments = cond_item->argument_list(); // A COND_ITEM (And/or) is visited both infix and postfix, so need '+1' return arguments->elements + 1; } default: return 0; } } /* Serialize the item tree into a List of Ndb_item objecs for fast generation of NbdScanFilter. Adds information such as position of fields that is not directly available in the Item tree. Also checks if condition is supported. */ static void ndb_serialize_cond(const Item *item, void *arg) { Ndb_cond_traverse_context *context = (Ndb_cond_traverse_context *)arg; DBUG_TRACE; // Check if we are skipping arguments to a function to be evaluated if (context->skip) { DBUG_PRINT("info", ("Skipping argument %d", context->skip)); context->skip--; if (item != nullptr) { context->skip += operand_count(item); } return; } if (context->supported) { Ndb_rewrite_context *rewrite_context = context->rewrite_stack; // Check if we are rewriting some unsupported function call if (rewrite_context) { rewrite_context->count++; if (rewrite_context->count == 1) { // This is the , save it in the rewrite context rewrite_context->left_hand_item = item; } else { // Has already seen the 'left_hand_item', this 'item' is one of // the right hand items in the in/between predicate to be rewritten. Item *cmp_func = nullptr; const Item_func *rewrite_func_item = rewrite_context->func_item; switch (rewrite_func_item->functype()) { case Item_func::BETWEEN: { /* Rewrite BETWEEN AND to >= AND <= */ if (rewrite_context->count == 2) // Lower 'between-limit' { // Lower limit of BETWEEN DBUG_PRINT("info", ("GE_FUNC")); cmp_func = new (*THR_MALLOC) Item_func_ge( const_cast(rewrite_context->left_hand_item), const_cast(item)); } else if (rewrite_context->count == 3) // Upper 'between-limit' { // Upper limit of BETWEEN DBUG_PRINT("info", ("LE_FUNC")); cmp_func = new (*THR_MALLOC) Item_func_le( const_cast(rewrite_context->left_hand_item), const_cast(item)); } else { // Illegal BETWEEN expression DBUG_PRINT("info", ("Illegal BETWEEN expression")); context->supported = false; return; } break; } case Item_func::IN_FUNC: { /* Rewrite IN(, ,..) to = OR = ... */ DBUG_PRINT("info", ("EQ_FUNC")); cmp_func = new (*THR_MALLOC) Item_func_eq( const_cast(rewrite_context->left_hand_item), const_cast(item)); break; } default: // Only BETWEEN/IN can be rewritten. // If we add support for rewrite of others, handling must be added // above DBUG_ASSERT(false); context->supported = false; return; } cmp_func->fix_fields(current_thd, &cmp_func); cmp_func->update_used_tables(); // Traverse and serialize the rewritten predicate context->rewrite_stack = NULL; // Disable rewrite mode context->expect_only(Item::FUNC_ITEM); cmp_func->traverse_cond(&ndb_serialize_cond, context, Item::PREFIX); context->rewrite_stack = rewrite_context; // Re-enable rewrite mode // Possibly terminate the rewrite_context if (context->supported && rewrite_context->count == rewrite_context->func_item->argument_count()) { // Rewrite is done, wrap an END() at the end DBUG_PRINT("info", ("End of rewrite condition group")); context->items.push_back(new (*THR_MALLOC) Ndb_item(NDB_END_COND)); // Pop rewrite stack context->rewrite_stack = rewrite_context->next; rewrite_context->next = NULL; destroy(rewrite_context); } } DBUG_PRINT("info", ("Skip 'item' (to be) handled in rewritten predicate")); context->skip = operand_count(item); return; } else // not in a 'rewrite_context' { const Ndb_item *ndb_item = nullptr; // Check for end of AND/OR expression if (!item) { // End marker for condition group DBUG_PRINT("info", ("End of condition group")); context->expect_no_length(); ndb_item = new (*THR_MALLOC) Ndb_item(NDB_END_COND); } else { bool pop = true; /* Based on which tables being used from an item expression, we might be able to evaluate its value immediately. Generally any tables prior to 'this' table has values known by now, same is true for expressions being entirely 'const'. */ const table_map this_table = context->table->pos_in_table_list->map(); if (!(item->used_tables() & this_table)) { /* Item value can be evaluated right away, and its value used in the condition, instead of the Item-expression. Note that this will also catch the INT_, STRING_, REAL_, DECIMAL_ and VARBIN_ITEM, as well as any CACHE_ITEM and FIELD_ITEM referring 'other' tables. */ #ifndef DBUG_OFF String str; item->print(current_thd, &str, QT_ORDINARY); #endif if (item->is_bool_func()) { // Item is a boolean func, (e.g. an EQ_FUNC) DBUG_ASSERT(item->result_type() == INT_RESULT); DBUG_PRINT("info", ("BOOLEAN 'VALUE' expression: '%s'", str.c_ptr_safe())); ndb_item = new (*THR_MALLOC) Ndb_item(item); // Expect another logical expression context->expect_only(Item::FUNC_ITEM); context->expect(Item::COND_ITEM); } else if (item->type() == Item::VARBIN_ITEM) { // VARBIN_ITEM is special as no similar VARBIN_RESULT type is // defined, so it need to be explicitely handled here. DBUG_PRINT("info", ("VARBIN_ITEM 'VALUE' expression: '%s'", str.c_ptr_safe())); if (context->expecting(Item::VARBIN_ITEM)) { ndb_item = new (*THR_MALLOC) Ndb_item(item); if (context->expecting_no_field_result()) { // We have not seen the field argument referring this table yet context->expect_only_field_from_table(); context->expect_field_result(STRING_RESULT); } else { // Expect another logical expression context->expect_only(Item::FUNC_ITEM); context->expect(Item::COND_ITEM); } } else context->supported = false; } else { // For the INT, REAL, DECIMAL and STRING Item type, we use // the similar result_type() as a 'catch it all' synonym to // handle both an Item and any expression of the specific type. // // Assert that any such Items are of the expected RESULT_ type: DBUG_ASSERT(item->type() != Item::INT_ITEM || item->result_type() == INT_RESULT); DBUG_ASSERT(item->type() != Item::REAL_ITEM || item->result_type() == REAL_RESULT); DBUG_ASSERT(item->type() != Item::DECIMAL_ITEM || item->result_type() == DECIMAL_RESULT); DBUG_ASSERT(item->type() != Item::STRING_ITEM || item->result_type() == STRING_RESULT); switch (item->result_type()) { case INT_RESULT: DBUG_PRINT("info", ("INTEGER 'VALUE' expression: '%s'", str.c_ptr_safe())); if (context->expecting(Item::INT_ITEM)) { ndb_item = new (*THR_MALLOC) Ndb_item(item); if (context->expecting_no_field_result()) { // We have not seen the field argument yet context->expect_only_field_from_table(); context->expect_field_result(INT_RESULT); context->expect_field_result(REAL_RESULT); context->expect_field_result(DECIMAL_RESULT); } else { // Expect another logical expression context->expect_only(Item::FUNC_ITEM); context->expect(Item::COND_ITEM); } } else context->supported = false; break; case REAL_RESULT: DBUG_PRINT("info", ("REAL 'VALUE' expression: '%s'", str.c_ptr_safe())); if (context->expecting(Item::REAL_ITEM)) { ndb_item = new (*THR_MALLOC) Ndb_item(item); if (context->expecting_no_field_result()) { // We have not seen the field argument yet context->expect_only_field_from_table(); context->expect_field_result(REAL_RESULT); } else { // Expect another logical expression context->expect_only(Item::FUNC_ITEM); context->expect(Item::COND_ITEM); } } else context->supported = false; break; case DECIMAL_RESULT: DBUG_PRINT("info", ("DECIMAL 'VALUE' expression: '%s'", str.c_ptr_safe())); if (context->expecting(Item::DECIMAL_ITEM)) { ndb_item = new (*THR_MALLOC) Ndb_item(item); if (context->expecting_no_field_result()) { // We have not seen the field argument yet context->expect_only_field_from_table(); context->expect_field_result(REAL_RESULT); context->expect_field_result(DECIMAL_RESULT); } else { // Expect another logical expression context->expect_only(Item::FUNC_ITEM); context->expect(Item::COND_ITEM); } } else context->supported = false; break; case STRING_RESULT: DBUG_PRINT("info", ("STRING 'VALUE' expression: '%s'", str.c_ptr_safe())); // Check that we do support pushing the item value length if (context->expecting(Item::STRING_ITEM) && context->expecting_length(item->max_length)) { ndb_item = new (*THR_MALLOC) Ndb_item(item); if (context->expecting_no_field_result()) { // We have not seen the field argument yet context->expect_only_field_from_table(); context->expect_field_result(STRING_RESULT); context->expect_collation(item->collation.collation); context->expect_length(item->max_length); } else { // Expect another logical expression context->expect_only(Item::FUNC_ITEM); context->expect(Item::COND_ITEM); context->expect_no_length(); // Check that we are comparing with a field with same // collation if (!context->expecting_collation( item->collation.collation)) { DBUG_PRINT("info", ("Found non-matching collation %s", item->collation.collation->name)); context->supported = false; } } } else context->supported = false; break; default: DBUG_ASSERT(false); context->supported = false; break; } } if (context->supported) { DBUG_ASSERT(ndb_item != nullptr); context->items.push_back(ndb_item); } // Skip any arguments since we will evaluate this expression instead context->skip = operand_count(item); DBUG_PRINT("info", ("Skip until end of arguments marker, operands:%d", context->skip)); return; } switch (item->type()) { case Item::FIELD_ITEM: { const Item_field *field_item = down_cast(item); Field *field = field_item->field; const enum_field_types type = field->real_type(); /* Check whether field is computed at MySQL layer */ if (field->is_virtual_gcol()) { context->supported = false; break; } DBUG_PRINT("info", ("FIELD_ITEM")); DBUG_PRINT("info", ("table %s", field->table->alias)); DBUG_PRINT("info", ("column %s", field->field_name)); DBUG_PRINT("info", ("column length %u", field->field_length)); DBUG_PRINT("info", ("type %d", type)); DBUG_PRINT("info", ("result type %d", field->result_type())); // Check that we are expecting a field with the correct // type, and possibly being 'comparable' with a previous Field. if (context->expecting(Item::FIELD_ITEM) && context->expecting_comparable_field(field) && // Bit fields not yet supported in scan filter type != MYSQL_TYPE_BIT && /* Char(0) field is treated as Bit fields inside NDB Hence not supported in scan filter */ (!(type == MYSQL_TYPE_STRING && field->pack_length() == 0)) && // No BLOB support in scan filter type != MYSQL_TYPE_TINY_BLOB && type != MYSQL_TYPE_MEDIUM_BLOB && type != MYSQL_TYPE_LONG_BLOB && type != MYSQL_TYPE_BLOB && type != MYSQL_TYPE_JSON && type != MYSQL_TYPE_GEOMETRY) { // Found a Field_item of a supported type. and from 'this' table DBUG_ASSERT(context->table == field->table); const NDBCOL *col = context->ndb_table->getColumn(field->field_name); DBUG_ASSERT(col); ndb_item = new (*THR_MALLOC) Ndb_item(field, col->getColumnNo()); /* Check, or set, further expectations for the operand(s). For an operation taking multiple operands, the first operand sets the requirement for the next to be compatible. 'expecting_*_field_result' is used to check if this is the first operand or not: If there are no 'field_result' expectations set yet, this is the first operand, and it is used to set expectations for the next one(s). */ if (!context->expecting_no_field_result()) { // Have some result type expectations to check. // Note that STRING and INT(Year) are always allowed // to be used together with temporal data types. if (!(context->expecting_field_result(field->result_type()) || // Date and year can be written as string or int (is_supported_temporal_type(type) && (context->expecting_field_result(STRING_RESULT) || context->expecting_field_result(INT_RESULT))))) { DBUG_PRINT("info", ("Was not expecting field of result_type %u(%u)", field->result_type(), type)); context->supported = false; break; } // STRING results has to be checked for correct 'length' and // collation, except if it is a result from a temporal data // type. if (field->result_type() == STRING_RESULT && !is_supported_temporal_type(type)) { if (!context->expecting_max_length(field->field_length)) { DBUG_PRINT("info", ("Found non-matching string length %s", field->field_name)); context->supported = false; break; } // Check that field and string constant collations are the // same if (!context->expecting_collation( item->collation.collation)) { DBUG_PRINT("info", ("Found non-matching collation %s", item->collation.collation->name)); context->supported = false; break; } } // Seen expected arguments, expect another logical expression context->expect_only(Item::FUNC_ITEM); context->expect(Item::COND_ITEM); } else // is not 'expecting_field_result' { // This is the first operand, it decides expectations for // the next operand, required to be compatible with this one. if (is_supported_temporal_type(type)) { context->expect_only(Item::STRING_ITEM); context->expect(Item::INT_ITEM); } else { switch (field->result_type()) { case STRING_RESULT: // Expect char string or binary string context->expect_only(Item::STRING_ITEM); context->expect(Item::VARBIN_ITEM); context->expect_collation( field_item->collation.collation); context->expect_max_length(field->field_length); break; case REAL_RESULT: context->expect_only(Item::REAL_ITEM); context->expect(Item::DECIMAL_ITEM); context->expect(Item::INT_ITEM); break; case INT_RESULT: context->expect_only(Item::INT_ITEM); context->expect(Item::VARBIN_ITEM); break; case DECIMAL_RESULT: context->expect_only(Item::DECIMAL_ITEM); context->expect(Item::REAL_ITEM); context->expect(Item::INT_ITEM); break; default: DBUG_ASSERT(false); break; } } const Ndb *ndb = get_thd_ndb(current_thd)->ndb; if (ndbd_support_column_cmp(ndb->getMinDbNodeVersion())) { // Since WL#13120: Two columns may be compared in // NdbScanFilter: // -> Second argument can also be a FIELD_ITEM, referring // another Field from this table. Need to ensure that these // Fields are of identical type, length, precision etc. context->expect(Item::FIELD_ITEM); context->expect_comparable_field(field); } context->expect_field_result(field->result_type()); } } else { DBUG_PRINT("info", ("Was not expecting field of type %u(%u)", field->result_type(), type)); context->supported = false; } break; } case Item::FUNC_ITEM: { // Check that we expect a function here if (!context->expecting(Item::FUNC_ITEM)) { context->supported = false; break; } context->expect_nothing(); context->expect_no_length(); const Item_func *func_item = static_cast(item); switch (func_item->functype()) { case Item_func::EQ_FUNC: { DBUG_PRINT("info", ("EQ_FUNC")); ndb_item = new (*THR_MALLOC) Ndb_item(func_item->functype(), func_item); context->expect(Item::STRING_ITEM); context->expect(Item::INT_ITEM); context->expect(Item::REAL_ITEM); context->expect(Item::DECIMAL_ITEM); context->expect(Item::VARBIN_ITEM); context->expect_field_from_table(); context->expect_no_field_result(); break; } case Item_func::NE_FUNC: { DBUG_PRINT("info", ("NE_FUNC")); ndb_item = new (*THR_MALLOC) Ndb_item(func_item->functype(), func_item); context->expect(Item::STRING_ITEM); context->expect(Item::INT_ITEM); context->expect(Item::REAL_ITEM); context->expect(Item::DECIMAL_ITEM); context->expect(Item::VARBIN_ITEM); context->expect_field_from_table(); context->expect_no_field_result(); break; } case Item_func::LT_FUNC: { DBUG_PRINT("info", ("LT_FUNC")); ndb_item = new (*THR_MALLOC) Ndb_item(func_item->functype(), func_item); context->expect(Item::STRING_ITEM); context->expect(Item::INT_ITEM); context->expect(Item::REAL_ITEM); context->expect(Item::DECIMAL_ITEM); context->expect(Item::VARBIN_ITEM); context->expect_field_from_table(); context->expect_no_field_result(); // Enum can only be compared by equality. context->dont_expect_field_type(MYSQL_TYPE_ENUM); break; } case Item_func::LE_FUNC: { DBUG_PRINT("info", ("LE_FUNC")); ndb_item = new (*THR_MALLOC) Ndb_item(func_item->functype(), func_item); context->expect(Item::STRING_ITEM); context->expect(Item::INT_ITEM); context->expect(Item::REAL_ITEM); context->expect(Item::DECIMAL_ITEM); context->expect(Item::VARBIN_ITEM); context->expect_field_from_table(); context->expect_no_field_result(); // Enum can only be compared by equality. context->dont_expect_field_type(MYSQL_TYPE_ENUM); break; } case Item_func::GE_FUNC: { DBUG_PRINT("info", ("GE_FUNC")); ndb_item = new (*THR_MALLOC) Ndb_item(func_item->functype(), func_item); context->expect(Item::STRING_ITEM); context->expect(Item::INT_ITEM); context->expect(Item::REAL_ITEM); context->expect(Item::DECIMAL_ITEM); context->expect(Item::VARBIN_ITEM); context->expect_field_from_table(); context->expect_no_field_result(); // Enum can only be compared by equality. context->dont_expect_field_type(MYSQL_TYPE_ENUM); break; } case Item_func::GT_FUNC: { DBUG_PRINT("info", ("GT_FUNC")); ndb_item = new (*THR_MALLOC) Ndb_item(func_item->functype(), func_item); context->expect(Item::STRING_ITEM); context->expect(Item::REAL_ITEM); context->expect(Item::DECIMAL_ITEM); context->expect(Item::INT_ITEM); context->expect(Item::VARBIN_ITEM); context->expect_field_from_table(); context->expect_no_field_result(); // Enum can only be compared by equality. context->dont_expect_field_type(MYSQL_TYPE_ENUM); break; } case Item_func::LIKE_FUNC: { Ndb_expect_stack *expect_next = new (*THR_MALLOC) Ndb_expect_stack(); DBUG_PRINT("info", ("LIKE_FUNC")); const Item_func_like *like_func = static_cast(func_item); if (like_func->escape_was_used_in_parsing()) { DBUG_PRINT("info", ("LIKE expressions with ESCAPE not supported")); context->supported = false; } ndb_item = new (*THR_MALLOC) Ndb_item(func_item->functype(), func_item); /* Ndb currently only supports pushing LIKE | we thus push | on the expect stack to catch that we don't support LIKE . */ context->expect_field_from_table(); context->expect_only_field_type(MYSQL_TYPE_STRING); context->expect_field_type(MYSQL_TYPE_VAR_STRING); context->expect_field_type(MYSQL_TYPE_VARCHAR); context->expect_field_result(STRING_RESULT); expect_next->expect(Item::STRING_ITEM); expect_next->expect(Item::FUNC_ITEM); context->expect_stack.push(expect_next); pop = false; break; } case Item_func::ISNULL_FUNC: { DBUG_PRINT("info", ("ISNULL_FUNC")); ndb_item = new (*THR_MALLOC) Ndb_item(func_item->functype(), func_item); context->expect_field_from_table(); context->expect_field_result(STRING_RESULT); context->expect_field_result(REAL_RESULT); context->expect_field_result(INT_RESULT); context->expect_field_result(DECIMAL_RESULT); break; } case Item_func::ISNOTNULL_FUNC: { DBUG_PRINT("info", ("ISNOTNULL_FUNC")); ndb_item = new (*THR_MALLOC) Ndb_item(func_item->functype(), func_item); context->expect_field_from_table(); context->expect_field_result(STRING_RESULT); context->expect_field_result(REAL_RESULT); context->expect_field_result(INT_RESULT); context->expect_field_result(DECIMAL_RESULT); break; } case Item_func::NOT_FUNC: { DBUG_PRINT("info", ("NOT_FUNC")); ndb_item = new (*THR_MALLOC) Ndb_item(func_item->functype(), func_item); context->expect(Item::FUNC_ITEM); context->expect(Item::COND_ITEM); break; } case Item_func::BETWEEN: { DBUG_PRINT("info", ("BETWEEN, rewriting using AND")); const Item_func_between *between_func = static_cast(func_item); Ndb_rewrite_context *rewrite_context = new (*THR_MALLOC) Ndb_rewrite_context(func_item); rewrite_context->next = context->rewrite_stack; context->rewrite_stack = rewrite_context; if (between_func->negated) { DBUG_PRINT("info", ("NOT_FUNC")); context->items.push_back( new (*THR_MALLOC) Ndb_item(Item_func::NOT_FUNC, 1)); } DBUG_PRINT("info", ("COND_AND_FUNC")); ndb_item = new (*THR_MALLOC) Ndb_item( Item_func::COND_AND_FUNC, func_item->argument_count() - 1); // We do not 'expect' anything yet, added later as part of // rewrite, break; } case Item_func::IN_FUNC: { DBUG_PRINT("info", ("IN_FUNC, rewriting using OR")); const Item_func_in *in_func = static_cast(func_item); Ndb_rewrite_context *rewrite_context = new (*THR_MALLOC) Ndb_rewrite_context(func_item); rewrite_context->next = context->rewrite_stack; context->rewrite_stack = rewrite_context; if (in_func->negated) { DBUG_PRINT("info", ("NOT_FUNC")); context->items.push_back( new (*THR_MALLOC) Ndb_item(Item_func::NOT_FUNC, 1)); } DBUG_PRINT("info", ("COND_OR_FUNC")); ndb_item = new (*THR_MALLOC) Ndb_item( Item_func::COND_OR_FUNC, func_item->argument_count() - 1); // We do not 'expect' anything yet, added later as part of // rewrite, break; } default: { DBUG_PRINT("info", ("Found func_item of type %d", func_item->functype())); context->supported = false; } } break; } case Item::COND_ITEM: { const Item_cond *cond_item = static_cast(item); if (context->expecting(Item::COND_ITEM)) { switch (cond_item->functype()) { case Item_func::COND_AND_FUNC: DBUG_PRINT("info", ("COND_AND_FUNC")); ndb_item = new (*THR_MALLOC) Ndb_item(cond_item->functype(), cond_item); break; case Item_func::COND_OR_FUNC: DBUG_PRINT("info", ("COND_OR_FUNC")); ndb_item = new (*THR_MALLOC) Ndb_item(cond_item->functype(), cond_item); break; default: DBUG_PRINT("info", ("COND_ITEM %d", cond_item->functype())); context->supported = false; break; } } else { /* Did not expect condition */ context->supported = false; } break; } case Item::STRING_ITEM: case Item::INT_ITEM: case Item::REAL_ITEM: case Item::VARBIN_ITEM: case Item::DECIMAL_ITEM: case Item::CACHE_ITEM: DBUG_ASSERT(false); // Expression folded under 'used_tables' // Fall through default: DBUG_PRINT("info", ("Found unsupported item of type %d", item->type())); context->supported = false; } if (pop) context->expect_stack.pop(); } if (context->supported) { DBUG_ASSERT(ndb_item != nullptr); context->items.push_back(ndb_item); } } } } ha_ndbcluster_cond::ha_ndbcluster_cond() : m_ndb_cond(), m_scan_filter_code(nullptr), m_unpushed_cond(nullptr) {} ha_ndbcluster_cond::~ha_ndbcluster_cond() { m_ndb_cond.destroy_elements(); } /* Clear the condition stack */ void ha_ndbcluster_cond::cond_clear() { DBUG_TRACE; m_ndb_cond.destroy_elements(); m_scan_filter_code.reset(); m_unpushed_cond = nullptr; } /** Construct the AND conjunction of the pushed- and remainder predicate terms. If the the original condition was either completely pushable, or not pushable at all, it is returned instead of constructing new AND conditions. @param cond Original condition we tried to push @param pushed_list A list of predicate terms to be pushed. @param remainder_list A list of predicate terms not pushable. @param pushed_cond Return the resulting pushed condition. @param remainder_cond Return the unpushable part of 'cond' @return '1' in case of failure, else '0'. */ static int create_and_conditions(Item_cond *cond, List pushed_list, List remainder_list, Item *&pushed_cond, Item *&remainder_cond) { if (remainder_list.is_empty()) { // Entire cond pushed, no remainder pushed_cond = cond; remainder_cond = nullptr; return 0; } if (pushed_list.is_empty()) { // Nothing pushed, entire 'cond' is remainder pushed_cond = nullptr; remainder_cond = cond; return 0; } // Condition was partly pushed, with some remainder if (pushed_list.elements == 1) { // Single boolean term pushed, return it pushed_cond = pushed_list.head(); } else { // Construct an AND'ed condition of pushed boolean terms pushed_cond = new Item_cond_and(pushed_list); if (unlikely(pushed_cond == nullptr)) return 1; pushed_cond->quick_fix_field(); pushed_cond->update_used_tables(); } if (remainder_list.elements == 1) { // A single boolean term as remainder, return it remainder_cond = remainder_list.head(); } else { // Construct a remainder as an AND'ed condition of the boolean terms remainder_cond = new Item_cond_and(remainder_list); if (unlikely(remainder_cond == nullptr)) return 1; remainder_cond->quick_fix_field(); remainder_cond->update_used_tables(); } return 0; } /** Construct the OR conjunction of the pushed- and remainder predicate terms. Note that the handling of partially pushed OR conditions has important differences relative to AND condition: 1) Something has to be pushed from each term in the OR condition (Else the rows matching that term would be missing from the result set) 2) If the OR condition is not completely pushed (there is a remainder), the entire original condition has to be reevaluated on the server side, or in the AND condition containg this OR condition if such exists. @param cond Original condition we tried to push @param pushed_list A list of predicate terms to be pushed. @param remainder_list A list of predicate terms not pushable. @param pushed_cond Return the resulting pushed condition. @param remainder_cond Return the unpushable part of 'cond' @return '1' in case of failure, else '0'. */ static int create_or_conditions(Item_cond *cond, List pushed_list, List remainder_list, Item *&pushed_cond, Item *&remainder_cond) { DBUG_ASSERT(pushed_list.elements == cond->argument_list()->elements); if (remainder_list.is_empty()) { // Entire cond pushed, no remainder pushed_cond = cond; remainder_cond = nullptr; } else { // When condition was partially pushed, we need to reevaluate // original OR-cond on the server side: remainder_cond = cond; // Construct an OR'ed condition of pushed boolean terms pushed_cond = new Item_cond_or(pushed_list); if (unlikely(pushed_cond == nullptr)) return 1; pushed_cond->quick_fix_field(); pushed_cond->update_used_tables(); } return 0; } /** Decompose a condition into AND'ed 'boolean terms'. Add the terms to either the list of 'pushed' or unpushed 'remainder' terms. @param term Condition to try to push down. @param table The Table the conditions may be pushed to. @param ndb_table The Ndb table. @param other_tbls_ok Are other tables allowed to be referred from the condition terms pushed down. @param pushed_cond The (part of) the condition term we may push down to the ndbcluster storage engine. @param remainder The remainder (part of) the condition term still needed to be evaluated by the server. @return a List of Ndb_item objects representing the serialized form of the 'pushed_cond'. */ static List cond_push_boolean_term(Item *term, TABLE *table, const NDBTAB *ndb_table, bool other_tbls_ok, Item *&pushed_cond, Item *&remainder_cond) { DBUG_TRACE; static const List empty_list; if (term->type() == Item::COND_ITEM) { // Build lists of the boolean terms either 'pushed', or being a 'remainder' List pushed_list; List remainder_list; List code; Item_cond *cond = (Item_cond *)term; if (cond->functype() == Item_func::COND_AND_FUNC) { DBUG_PRINT("info", ("COND_AND_FUNC")); List_iterator li(*cond->argument_list()); Item *boolean_term; while ((boolean_term = li++)) { Item *pushed = nullptr, *remainder = nullptr; List code_stub = cond_push_boolean_term( boolean_term, table, ndb_table, other_tbls_ok, pushed, remainder); // Collect all bits we pushed, and its leftovers. if (!code_stub.is_empty()) code.concat(&code_stub); if (pushed != nullptr) pushed_list.push_back(pushed); if (remainder != nullptr) remainder_list.push_back(remainder); } // Transform the list of pushed and the remainder conditions // into its respective AND'ed conditions. if (create_and_conditions(cond, pushed_list, remainder_list, pushed_cond, remainder_cond)) { // Failed, discard pushed conditions and generated code. pushed_cond = nullptr; remainder_cond = cond; code.destroy_elements(); return empty_list; } // Serialized code has to be embedded in an AND-group if (!code.is_empty()) { code.push_front(new (*THR_MALLOC) Ndb_item(Item_func::COND_AND_FUNC, cond)); code.push_back(new (*THR_MALLOC) Ndb_item(NDB_END_COND)); } DBUG_PRINT("info", ("COND_AND_FUNC, end")); } else { DBUG_ASSERT(cond->functype() == Item_func::COND_OR_FUNC); DBUG_PRINT("info", ("COND_OR_FUNC")); List_iterator li(*cond->argument_list()); Item *boolean_term; while ((boolean_term = li++)) { Item *pushed = nullptr, *remainder = nullptr; List code_stub = cond_push_boolean_term( boolean_term, table, ndb_table, other_tbls_ok, pushed, remainder); if (pushed == nullptr) { // Failure of pushing one of the OR-terms fails entire OR'ed cond //(Else the rows matching that term would be missing in result set) // Also see comments in create_or_conditions(). pushed_cond = nullptr; remainder_cond = cond; code.destroy_elements(); return empty_list; } // Collect all bits we pushed, and its leftovers. if (!code_stub.is_empty()) code.concat(&code_stub); if (pushed != nullptr) pushed_list.push_back(pushed); if (remainder != nullptr) remainder_list.push_back(remainder); } // Transform the list of pushed and the remainder conditions // into its respective OR'ed conditions. if (create_or_conditions(cond, pushed_list, remainder_list, pushed_cond, remainder_cond)) { // Failed, discard pushed conditions and generated code. pushed_cond = nullptr; remainder_cond = cond; code.destroy_elements(); return empty_list; } // Serialized code has to be embedded in an OR-group if (!code.is_empty()) { code.push_front(new (*THR_MALLOC) Ndb_item(Item_func::COND_OR_FUNC, cond)); code.push_back(new (*THR_MALLOC) Ndb_item(NDB_END_COND)); } DBUG_PRINT("info", ("COND_OR_FUNC, end")); } return code; } else if (term->type() == Item::FUNC_ITEM) { const Item_func *item_func = static_cast(term); if (item_func->functype() == Item_func::TRIG_COND_FUNC) { const Item_func_trig_cond *func_trig = static_cast(item_func); if (func_trig->get_trig_type() == Item_func_trig_cond::IS_NOT_NULL_COMPL) { DBUG_ASSERT(item_func->argument_count() == 1); Item *cond_arg = item_func->arguments()[0]; Item *remainder = nullptr; List code = cond_push_boolean_term( cond_arg, table, ndb_table, other_tbls_ok, pushed_cond, remainder); if (remainder != nullptr) { item_func->arguments()[0] = remainder; remainder_cond = term; } return code; } } } /* There are some used_tables() 'out of reach', tagged with special *_TABLE_BIT. These can not be referred from a pushed condition. */ const table_map dont_use_tables = INNER_TABLE_BIT | // Condition contain a subquery RAND_TABLE_BIT; // 'non-stable' value if (term->used_tables() & dont_use_tables) { } else if (other_tbls_ok || !(term->used_tables() & ~table->pos_in_table_list->map())) { // Has broken down the condition into predicate terms, or sub conditions, // which either has to be accepted or rejected for pushdown Ndb_cond_traverse_context context(table, ndb_table); context.expect(Item::FUNC_ITEM); context.expect(Item::COND_ITEM); term->traverse_cond(&ndb_serialize_cond, &context, Item::PREFIX); if (context.supported) // 'term' was pushed { pushed_cond = term; remainder_cond = nullptr; DBUG_ASSERT(!context.items.is_empty()); return context.items; } context.items.destroy_elements(); } // Failed to push pushed_cond = nullptr; remainder_cond = term; return empty_list; // Discard any generated Ndb_cond's } /* Push a condition, return any remainder condition */ const Item *ha_ndbcluster_cond::cond_push(const Item *cond, TABLE *table, const NDBTAB *ndb_table, bool other_tbls_ok, Item *&pushed_cond) { DBUG_TRACE; // Build lists of the boolean terms either 'pushed', or being a 'remainder' Item *item = const_cast(cond); Item *remainder = nullptr; List code = cond_push_boolean_term( item, table, ndb_table, other_tbls_ok, pushed_cond, remainder); // Save the serialized representation of the code m_ndb_cond = code; if (pushed_cond != nullptr && !(pushed_cond->used_tables() & ~table->pos_in_table_list->map())) { /** * pushed_cond had no dependencies outside of this 'table'. * Code for pushed condition can be generated now, and reused * for all later API requests to 'table' */ NdbInterpretedCode code(ndb_table); NdbScanFilter filter(&code); const int ret = generate_scan_filter_from_cond(filter); if (unlikely(ret != 0)) { // Failed to 'generate' the pushed code. pushed_cond = nullptr; m_ndb_cond.destroy_elements(); remainder = item; } else { // Success, save the generated code. DBUG_ASSERT(code.getWordsUsed() > 0); m_scan_filter_code.copy(code); } } return remainder; } int ha_ndbcluster_cond::build_scan_filter_predicate( List_iterator &cond, NdbScanFilter *filter, bool negated) const { DBUG_TRACE; const Ndb_item *ndb_item = *cond.ref(); switch (ndb_item->type) { case NDB_FUNCTION: { const Ndb_item *b, *field1, *field2 = nullptr, *value = nullptr; const Ndb_item *a = cond++; if (a == nullptr) break; enum ndb_func_type function_type = (negated) ? Ndb_item::negate(ndb_item->get_func_type()) : ndb_item->get_func_type(); switch (ndb_item->get_argument_count()) { case 1: field1 = (a->type == NDB_FIELD) ? a : NULL; break; case 2: b = cond++; if (b == nullptr) { field1 = nullptr; break; } if (a->type == NDB_FIELD) { field1 = a; if (b->type == NDB_VALUE) value = b; else if (b->type == NDB_FIELD) field2 = b; } else { DBUG_ASSERT(a->type == NDB_VALUE); DBUG_ASSERT(b->type == NDB_FIELD); field1 = b; value = a; } if (value == a) function_type = Ndb_item::swap(function_type); break; default: DBUG_PRINT("info", ("condition had unexpected number of arguments")); return 1; } if (field1 == nullptr) { DBUG_PRINT("info", ("condition missing 'field' argument")); return 1; } if (value != nullptr) { const Item *item = value->get_item(); #ifndef DBUG_OFF if (!item->basic_const_item()) { String expr; String buf, *val = const_cast(item)->val_str(&buf); item->print(current_thd, &expr, QT_ORDINARY); DBUG_PRINT("info", ("Value evaluated to: '%s', expression '%s'", val ? val->c_ptr_safe() : "NULL", expr.c_ptr_safe())); } #endif /* The NdbInterpreter handles a NULL value as being less than any non-NULL value. However, MySQL server (and SQL std spec) specifies that a NULL-value in a comparison predicate should result in an UNKNOWN boolean result, which is 'not TRUE' -> the row being eliminated. Thus, extra checks for both 'field' and 'value' being a NULL-value has to be added to mitigate this semantic difference. */ if (const_cast(item)->is_null()) { /* 'value' known to be a NULL-value. Condition will be 'not TRUE' -> false, independent of the 'field' value. Encapsulate in own group, as only this predicate become 'false', not entire group it is part of. */ if (filter->begin() == -1 || filter->isfalse() == -1 || filter->end() == -1) return 1; return 0; } } const bool field1_maybe_null = field1->get_field()->maybe_null(); const bool field2_maybe_null = field2 && field2->get_field()->maybe_null(); bool added_null_check = false; if (field1_maybe_null || field2_maybe_null) { switch (function_type) { /* The NdbInterpreter handles a NULL value as being less than any non-NULL value. Thus any NULL value columns will evaluate to 'TRUE' (and pass the filter) in the predicate expression: This is not according to how the server expect NULL valued predicates to be evaluated: Any NULL values in a comparison predicate should result in an UNKNOWN boolean result and the row being eliminated. This is mitigated by adding an extra isnotnull-check to eliminate NULL valued rows which otherwise would have passed a ' < ' check in the ScanFilter. */ case NDB_LT_FUNC: case NDB_LE_FUNC: // NdbInterpreter incorrectly compare ' < f2' as 'true' // -> NULL filter f1 case NDB_LIKE_FUNC: case NDB_NOTLIKE_FUNC: // NdbInterpreter incorrectly compare ' [not] like ' as // 'true' // -> NULL filter f1 if (field1_maybe_null) { DBUG_PRINT("info", ("Appending extra field1 ISNOTNULL check")); if (filter->begin(NdbScanFilter::AND) == -1 || filter->isnotnull(field1->get_field_no()) == -1) return 1; added_null_check = true; } break; case NDB_EQ_FUNC: // NdbInterpreter incorrectly compare = as 'true' // -> At least either f1 or f2 need a NULL filter to ensure // not both are NULL. if (!field1_maybe_null) break; // Fall through to check 'field2_maybe_null' case NDB_GE_FUNC: case NDB_GT_FUNC: // NdbInterpreter incorrectly compare f1 > as true -> NULL // filter f2 if (field2_maybe_null) { DBUG_PRINT("info", ("Appending extra field2 ISNOTNULL check")); if (filter->begin(NdbScanFilter::AND) == -1 || filter->isnotnull(field2->get_field_no()) == -1) return 1; added_null_check = true; } break; case NDB_NE_FUNC: // f1 '<>' f2 -> f1 < f2 or f2 < f1: Both f1 and f2 need NULL // filters DBUG_PRINT("info", ("Appending extra field1 & field2 ISNOTNULL check")); if (filter->begin(NdbScanFilter::AND) == -1 || (field1_maybe_null && filter->isnotnull(field1->get_field_no()) == -1) || (field2_maybe_null && filter->isnotnull(field2->get_field_no()) == -1)) return 1; added_null_check = true; break; default: break; } } NdbScanFilter::BinaryCondition cond; switch (function_type) { case NDB_EQ_FUNC: { DBUG_PRINT("info", ("Generating EQ filter")); cond = NdbScanFilter::COND_EQ; break; } case NDB_NE_FUNC: { DBUG_PRINT("info", ("Generating NE filter")); cond = NdbScanFilter::COND_NE; break; } case NDB_LT_FUNC: { DBUG_PRINT("info", ("Generating LT filter")); cond = NdbScanFilter::COND_LT; break; } case NDB_LE_FUNC: { DBUG_PRINT("info", ("Generating LE filter")); cond = NdbScanFilter::COND_LE; break; } case NDB_GE_FUNC: { DBUG_PRINT("info", ("Generating GE filter")); cond = NdbScanFilter::COND_GE; break; } case NDB_GT_FUNC: { DBUG_PRINT("info", ("Generating GT filter")); cond = NdbScanFilter::COND_GT; break; } case NDB_LIKE_FUNC: { DBUG_PRINT("info", ("Generating LIKE filter")); cond = NdbScanFilter::COND_LIKE; break; } case NDB_NOTLIKE_FUNC: { DBUG_PRINT("info", ("Generating NOT LIKE filter")); cond = NdbScanFilter::COND_NOT_LIKE; break; } case NDB_ISNULL_FUNC: { DBUG_PRINT("info", ("Generating ISNULL filter")); if (filter->isnull(field1->get_field_no()) == -1) return 1; return 0; } case NDB_ISNOTNULL_FUNC: { DBUG_PRINT("info", ("Generating ISNOTNULL filter")); if (filter->isnotnull(field1->get_field_no()) == -1) return 1; return 0; } default: DBUG_ASSERT(false); return 1; } if (cond <= NdbScanFilter::COND_NE) { if (value != nullptr) { // Save value in right format for the field type if (unlikely(value->save_in_field(field1) == -1)) return 1; if (filter->cmp(cond, field1->get_field_no(), field1->get_val(), field1->pack_length()) == -1) return 1; } else { DBUG_ASSERT(field2 != nullptr); DBUG_ASSERT(ndbd_support_column_cmp( get_thd_ndb(current_thd)->ndb->getMinDbNodeVersion())); if (filter->cmp(cond, field1->get_field_no(), field2->get_field_no()) == -1) return 1; } } else // [NOT] LIKE { DBUG_ASSERT(cond == NdbScanFilter::COND_LIKE || cond == NdbScanFilter::COND_NOT_LIKE); DBUG_ASSERT(field1 == a && value == b); char buff[MAX_FIELD_WIDTH]; String str(buff, sizeof(buff), field1->get_field_charset()); Item *value_item = const_cast(value->get_item()); const String *pattern = value_item->val_str(&str); if (filter->cmp(cond, field1->get_field_no(), pattern->ptr(), pattern->length()) == -1) return 1; } if (added_null_check && filter->end() == -1) // Local AND group return 1; return 0; } default: break; } DBUG_PRINT("info", ("Found illegal condition")); return 1; } int ha_ndbcluster_cond::build_scan_filter_group( List_iterator &cond, NdbScanFilter *filter, const bool negated) const { uint level = 0; DBUG_TRACE; do { const Ndb_item *ndb_item = cond++; if (ndb_item == nullptr) return 1; switch (ndb_item->type) { case NDB_FUNCTION: { switch (ndb_item->get_func_type()) { case NDB_COND_AND_FUNC: { level++; DBUG_PRINT("info", ("Generating %s group %u", (negated) ? "OR" : "AND", level)); if ((negated) ? filter->begin(NdbScanFilter::OR) : filter->begin(NdbScanFilter::AND) == -1) return 1; break; } case NDB_COND_OR_FUNC: { level++; DBUG_PRINT("info", ("Generating %s group %u", (negated) ? "AND" : "OR", level)); if ((negated) ? filter->begin(NdbScanFilter::AND) : filter->begin(NdbScanFilter::OR) == -1) return 1; break; } case NDB_NOT_FUNC: { DBUG_PRINT("info", ("Generating negated query")); if (build_scan_filter_group(cond, filter, !negated)) return 1; break; } default: if (build_scan_filter_predicate(cond, filter, negated)) return 1; break; } break; } case NDB_VALUE: { // (Boolean-)VALUE known at generate const Item *item = ndb_item->get_item(); #ifndef DBUG_OFF String str; item->print(current_thd, &str, QT_ORDINARY); #endif if (const_cast(item)->is_null()) { // Note that boolean 'unknown' -> 'not true' DBUG_PRINT("info", ("BOOLEAN value 'UNKNOWN', expression '%s'", str.c_ptr_safe())); if (filter->begin(NdbScanFilter::AND) == -1 || filter->isfalse() == -1 || filter->end() == -1) return 1; } else if (const_cast(item)->val_bool() == !negated) { DBUG_PRINT("info", ("BOOLEAN value 'TRUE', expression '%s'", str.c_ptr_safe())); if (filter->begin(NdbScanFilter::OR) == -1 || filter->istrue() == -1 || filter->end() == -1) return 1; } else { DBUG_PRINT("info", ("BOOLEAN value 'FALSE', expression '%s'", str.c_ptr_safe())); if (filter->begin(NdbScanFilter::AND) == -1 || filter->isfalse() == -1 || filter->end() == -1) return 1; } break; } case NDB_END_COND: DBUG_PRINT("info", ("End of group %u", level)); level--; if (filter->end() == -1) return 1; break; default: { DBUG_PRINT("info", ("Illegal scan filter")); DBUG_ASSERT(false); return 1; } } } while (level > 0); return 0; } int ha_ndbcluster_cond::generate_scan_filter_from_cond(NdbScanFilter &filter) { bool need_group = true; DBUG_TRACE; // Determine if we need to wrap an AND group around condition(s) const Ndb_item *ndb_item = m_ndb_cond.head(); if (ndb_item->type == NDB_FUNCTION) { switch (ndb_item->get_func_type()) { case NDB_COND_AND_FUNC: case NDB_COND_OR_FUNC: // A single AND/OR condition has its own AND/OR-group // .. in all other cases we start a AND group now need_group = false; break; default: break; } } if (need_group && filter.begin() == -1) return 1; List_iterator cond(m_ndb_cond); if (build_scan_filter_group(cond, &filter, false)) { DBUG_PRINT("info", ("build_scan_filter_group failed")); const NdbError &err = filter.getNdbError(); if (err.code == NdbScanFilter::FilterTooLarge) { DBUG_PRINT("info", ("%s", err.message)); push_warning(current_thd, Sql_condition::SL_WARNING, err.code, err.message); } return 1; } if (need_group && filter.end() == -1) return 1; return 0; } /* Optimizer sometimes does hash index lookup of a key where some key parts are null. The set of cases where this happens makes no sense but cannot be ignored since optimizer may expect the result to be filtered accordingly. The scan is actually on the table and the index bounds are pushed down. */ int ha_ndbcluster_cond::generate_scan_filter_from_key( NdbScanFilter &filter, const KEY *key_info, const key_range *start_key, const key_range *end_key) { DBUG_TRACE; #ifndef DBUG_OFF { DBUG_PRINT("info", ("key parts:%u length:%u", key_info->user_defined_key_parts, key_info->key_length)); const key_range *keylist[2] = {start_key, end_key}; for (uint j = 0; j <= 1; j++) { char buf[8192]; const key_range *key = keylist[j]; if (key == 0) { sprintf(buf, "key range %u: none", j); } else { sprintf(buf, "key range %u: flag:%u part", j, key->flag); const KEY_PART_INFO *key_part = key_info->key_part; const uchar *ptr = key->key; for (uint i = 0; i < key_info->user_defined_key_parts; i++) { sprintf(buf + strlen(buf), " %u:", i); for (uint k = 0; k < key_part->store_length; k++) { sprintf(buf + strlen(buf), " %02x", ptr[k]); } ptr += key_part->store_length; if (ptr - key->key >= (ptrdiff_t)key->length) { /* key_range has no count of parts so must test byte length. But this is not the place for following assert. */ // DBUG_ASSERT(ptr - key->key == key->length); break; } key_part++; } } DBUG_PRINT("info", ("%s", buf)); } } #endif do { /* Case "x is not null". Seen with index(x) where it becomes range "null < x". Not seen with index(x,y) for any combination of bounds which include "is not null". */ if (start_key != 0 && start_key->flag == HA_READ_AFTER_KEY && end_key == 0 && key_info->user_defined_key_parts == 1) { const KEY_PART_INFO *key_part = key_info->key_part; if (key_part->null_bit != 0) // nullable (must be) { const uchar *ptr = start_key->key; if (ptr[0] != 0) // null (in "null < x") { DBUG_PRINT("info", ("Generating ISNOTNULL filter for nullable %s", key_part->field->field_name)); if (filter.isnotnull(key_part->fieldnr - 1) == -1) return 1; break; } } } /* Case "x is null" in an EQ range. Seen with index(x) for "x is null". Seen with index(x,y) for "x is null and y = 1". Not seen with index(x,y) for "x is null and y is null". Seen only when all key parts are present (but there is no reason to limit the code to this case). */ if (start_key != 0 && start_key->flag == HA_READ_KEY_EXACT && end_key != 0 && end_key->flag == HA_READ_AFTER_KEY && start_key->length == end_key->length && memcmp(start_key->key, end_key->key, start_key->length) == 0) { const KEY_PART_INFO *key_part = key_info->key_part; const uchar *ptr = start_key->key; for (uint i = 0; i < key_info->user_defined_key_parts; i++) { const Field *field = key_part->field; if (key_part->null_bit) // nullable { if (ptr[0] != 0) // null { DBUG_PRINT("info", ("Generating ISNULL filter for nullable %s", field->field_name)); if (filter.isnull(key_part->fieldnr - 1) == -1) return 1; } else { DBUG_PRINT("info", ("Generating EQ filter for nullable %s", field->field_name)); if (filter.cmp(NdbScanFilter::COND_EQ, key_part->fieldnr - 1, ptr + 1, // skip null-indicator byte field->pack_length()) == -1) return 1; } } else { DBUG_PRINT("info", ("Generating EQ filter for non-nullable %s", field->field_name)); if (filter.cmp(NdbScanFilter::COND_EQ, key_part->fieldnr - 1, ptr, field->pack_length()) == -1) return 1; } ptr += key_part->store_length; if (ptr - start_key->key >= (ptrdiff_t)start_key->length) { break; } key_part++; } break; } DBUG_PRINT("info", ("Unknown hash index scan")); // Catch new cases when optimizer changes DBUG_ASSERT(false); } while (0); return 0; } /** In case we failed to 'generate' a scan filter accepted by 'cond_push', or we later choose to ignore it, set_condition() will set the condition to be evaluated by the handler. @param cond The condition to be evaluated by the handler */ void ha_ndbcluster_cond::set_condition(const Item *cond) { m_unpushed_cond = cond; } /** Return the boolean value of a condition previously set by 'set_condition', evaluated on the current row. @return true if the condition is evaluated to true. */ bool ha_ndbcluster_cond::eval_condition() const { return const_cast(m_unpushed_cond)->val_int() == 1; } /** Add any columns referred by 'cond' to the read_set of the table. @param table The table to update the read_set for. @param cond The condition referring columns in 'table' */ void ha_ndbcluster_cond::add_read_set(TABLE *table, const Item *cond) { if (cond != nullptr) { Mark_field mf(table, MARK_COLUMNS_READ); const_cast(cond)->walk(&Item::mark_field_in_map, enum_walk::PREFIX, (uchar *)&mf); } } /* Interface layer between ha_ndbcluster and ha_ndbcluster_cond */ void ha_ndbcluster::generate_scan_filter( NdbInterpretedCode *code, NdbScanOperation::ScanOptions *options) { DBUG_TRACE; if (pushed_cond == nullptr) { DBUG_PRINT("info", ("Empty stack")); return; } if (m_cond.get_interpreter_code().getWordsUsed() > 0) { /** * We had already generated the NdbInterpreterCode for the scan_filter. * Just use what we had. */ if (options != nullptr) { options->interpretedCode = &m_cond.get_interpreter_code(); options->optionsPresent |= NdbScanOperation::ScanOptions::SO_INTERPRETED; } else { code->copy(m_cond.get_interpreter_code()); } return; } // Generate the scan_filter from previously 'serialized' condition code NdbScanFilter filter(code); const int ret = m_cond.generate_scan_filter_from_cond(filter); if (unlikely(ret != 0)) { /** * Failed to generate a scan filter, fallback to let * ha_ndbcluster evaluate the condition. */ m_cond.set_condition(pushed_cond); } else if (options != nullptr) { options->interpretedCode = code; options->optionsPresent |= NdbScanOperation::ScanOptions::SO_INTERPRETED; } } int ha_ndbcluster::generate_scan_filter_with_key( NdbInterpretedCode *code, NdbScanOperation::ScanOptions *options, const KEY *key_info, const key_range *start_key, const key_range *end_key) { DBUG_TRACE; NdbScanFilter filter(code); if (filter.begin(NdbScanFilter::AND) == -1) return 1; // Generate a scanFilter from a prepared pushed conditions if (pushed_cond != nullptr) { /** * Note, that in this case we can not use the pre-generated scan_filter, * as it does not contain the code for the additional 'key'. */ const int ret = m_cond.generate_scan_filter_from_cond(filter); if (unlikely(ret != 0)) { /** * Failed to generate a scan filter, fallback to let * ha_ndbcluster evaluate the condition. */ m_cond.set_condition(pushed_cond); // Discard the failed scanFilter and prepare for 'key' filter.reset(); if (filter.begin(NdbScanFilter::AND) == -1) return 1; } } // Generate a scanFilter from the key definition if (key_info != nullptr) { const int ret = ha_ndbcluster_cond::generate_scan_filter_from_key( filter, key_info, start_key, end_key); if (unlikely(ret != 0)) return ret; } if (filter.end() == -1) return 1; if (options != nullptr) { options->interpretedCode = code; options->optionsPresent |= NdbScanOperation::ScanOptions::SO_INTERPRETED; } return 0; }