2207 lines
82 KiB
C++
2207 lines
82 KiB
C++
/*
|
|
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 *>(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
|
|
<field> LIKE <string>|<func>, but not <string>|<func> LIKE <field>).
|
|
*/
|
|
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<const Ndb_item> 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<const Item_func *>(item);
|
|
return func_item->argument_count();
|
|
}
|
|
case Item::COND_ITEM: {
|
|
Item_cond *cond_item =
|
|
const_cast<Item_cond *>(static_cast<const Item_cond *>(item));
|
|
List<Item> *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 <left_hand_item>, 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 <left_hand_item> BETWEEN <item1> AND <item2>
|
|
to <left_hand_item> >= <item1> AND
|
|
<left_hand_item> <= <item2>
|
|
*/
|
|
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<Item *>(rewrite_context->left_hand_item),
|
|
const_cast<Item *>(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<Item *>(rewrite_context->left_hand_item),
|
|
const_cast<Item *>(item));
|
|
} else {
|
|
// Illegal BETWEEN expression
|
|
DBUG_PRINT("info", ("Illegal BETWEEN expression"));
|
|
context->supported = false;
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case Item_func::IN_FUNC: {
|
|
/*
|
|
Rewrite <left_hand_item> IN(<item1>, <item2>,..)
|
|
to <left_hand_item> = <item1> OR
|
|
<left_hand_item> = <item2> ...
|
|
*/
|
|
DBUG_PRINT("info", ("EQ_FUNC"));
|
|
cmp_func = new (*THR_MALLOC) Item_func_eq(
|
|
const_cast<Item *>(rewrite_context->left_hand_item),
|
|
const_cast<Item *>(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<const Item_field *>(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<const Item_func *>(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<const Item_func_like *>(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
|
|
<field> LIKE <string> | <func>
|
|
we thus push <string> | <func>
|
|
on the expect stack to catch that we
|
|
don't support <string> LIKE <field>.
|
|
*/
|
|
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<const Item_func_between *>(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<const Item_func_in *>(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<const Item_cond *>(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<Item> pushed_list,
|
|
List<Item> 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<Item> pushed_list,
|
|
List<Item> 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<const Ndb_item> 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<const Ndb_item> empty_list;
|
|
|
|
if (term->type() == Item::COND_ITEM) {
|
|
// Build lists of the boolean terms either 'pushed', or being a 'remainder'
|
|
List<Item> pushed_list;
|
|
List<Item> remainder_list;
|
|
List<const Ndb_item> code;
|
|
|
|
Item_cond *cond = (Item_cond *)term;
|
|
if (cond->functype() == Item_func::COND_AND_FUNC) {
|
|
DBUG_PRINT("info", ("COND_AND_FUNC"));
|
|
|
|
List_iterator<Item> li(*cond->argument_list());
|
|
Item *boolean_term;
|
|
while ((boolean_term = li++)) {
|
|
Item *pushed = nullptr, *remainder = nullptr;
|
|
List<const Ndb_item> 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<Item> li(*cond->argument_list());
|
|
Item *boolean_term;
|
|
while ((boolean_term = li++)) {
|
|
Item *pushed = nullptr, *remainder = nullptr;
|
|
List<const Ndb_item> 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<const Item_func *>(term);
|
|
if (item_func->functype() == Item_func::TRIG_COND_FUNC) {
|
|
const Item_func_trig_cond *func_trig =
|
|
static_cast<const Item_func_trig_cond *>(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<const Ndb_item> 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<Item *>(cond);
|
|
Item *remainder = nullptr;
|
|
List<const Ndb_item> 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<const Ndb_item> &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 *>(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 *>(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:
|
|
<column> </ <= / <> <non-NULL value>
|
|
|
|
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 '<NULL> < <any value>' check in the ScanFilter.
|
|
*/
|
|
case NDB_LT_FUNC:
|
|
case NDB_LE_FUNC:
|
|
// NdbInterpreter incorrectly compare '<NULL> < f2' as 'true'
|
|
// -> NULL filter f1
|
|
|
|
case NDB_LIKE_FUNC:
|
|
case NDB_NOTLIKE_FUNC:
|
|
// NdbInterpreter incorrectly compare '<NULL> [not] like <value>' 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 <NULL> = <NULL> 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 > <NULL> 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<Item *>(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<const Ndb_item> &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 *>(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 *>(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<const Ndb_item> 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<Item *>(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<Item *>(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;
|
|
}
|