/* -*- c-basic-offset: 2 -*- */ /* Copyright(C) 2013-2017 Kouhei Sutou This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ #include "mrn_condition_converter.hpp" #include "mrn_time_converter.hpp" #include "mrn_smart_grn_obj.hpp" // for debug #define MRN_CLASS_NAME "mrn::ConditionConverter" #ifdef MRN_ITEM_HAVE_ITEM_NAME # define MRN_ITEM_FIELD_GET_NAME(item) ((item)->item_name.ptr()) # define MRN_ITEM_FIELD_GET_NAME_LENGTH(item) ((item)->item_name.length()) #else # define MRN_ITEM_FIELD_GET_NAME(item) ((item)->name.str) # define MRN_ITEM_FIELD_GET_NAME_LENGTH(item) ((item)->name.length) #endif namespace mrn { ConditionConverter::ConditionConverter(grn_ctx *ctx, grn_obj *table, bool is_storage_mode) : ctx_(ctx), table_(table), is_storage_mode_(is_storage_mode) { GRN_TEXT_INIT(&column_name_, 0); GRN_VOID_INIT(&value_); } ConditionConverter::~ConditionConverter() { grn_obj_unlink(ctx_, &column_name_); grn_obj_unlink(ctx_, &value_); } bool ConditionConverter::is_convertable(const Item *item) { MRN_DBUG_ENTER_METHOD(); if (!item) { DBUG_RETURN(false); } switch (item->type()) { case Item::COND_ITEM: { const Item_cond *cond_item = reinterpret_cast(item); bool convertable = is_convertable(cond_item); DBUG_RETURN(convertable); } break; case Item::FUNC_ITEM: { const Item_func *func_item = reinterpret_cast(item); bool convertable = is_convertable(func_item); DBUG_RETURN(convertable); } break; default: DBUG_RETURN(false); break; } DBUG_RETURN(false); } bool ConditionConverter::is_convertable(const Item_cond *cond_item) { MRN_DBUG_ENTER_METHOD(); if (!is_storage_mode_) { DBUG_RETURN(false); } if (cond_item->functype() != Item_func::COND_AND_FUNC) { DBUG_RETURN(false); } List *argument_list = const_cast(cond_item)->argument_list(); List_iterator iterator(*argument_list); const Item *sub_item; while ((sub_item = iterator++)) { if (!is_convertable(sub_item)) { DBUG_RETURN(false); } } DBUG_RETURN(true); } bool ConditionConverter::is_convertable(const Item_func *func_item) { MRN_DBUG_ENTER_METHOD(); switch (func_item->functype()) { case Item_func::EQ_FUNC: case Item_func::LT_FUNC: case Item_func::LE_FUNC: case Item_func::GE_FUNC: case Item_func::GT_FUNC: if (!is_storage_mode_) { DBUG_RETURN(false); } { Item **arguments = func_item->arguments(); Item *left_item = arguments[0]; Item *right_item = arguments[1]; if (left_item->type() != Item::FIELD_ITEM) { DBUG_RETURN(false); } if (!right_item->basic_const_item()) { DBUG_RETURN(false); } bool convertable = is_convertable_binary_operation(static_cast(left_item), right_item, func_item->functype()); DBUG_RETURN(convertable); } break; case Item_func::FT_FUNC: DBUG_RETURN(true); break; case Item_func::BETWEEN: if (!is_storage_mode_) { DBUG_RETURN(false); } { Item **arguments = func_item->arguments(); Item *target_item = arguments[0]; Item *min_item = arguments[1]; Item *max_item = arguments[2]; if (target_item->type() != Item::FIELD_ITEM) { DBUG_RETURN(false); } if (!min_item->basic_const_item()) { DBUG_RETURN(false); } if (!max_item->basic_const_item()) { DBUG_RETURN(false); } bool convertable = is_convertable_between(static_cast(target_item), min_item, max_item); DBUG_RETURN(convertable); } default: DBUG_RETURN(false); break; } DBUG_RETURN(true); } bool ConditionConverter::is_convertable_binary_operation( const Item_field *field_item, Item *value_item, Item_func::Functype func_type) { MRN_DBUG_ENTER_METHOD(); bool convertable = false; enum_field_types field_type = field_item->field->real_type(); NormalizedType normalized_type = normalize_field_type(field_type); switch (normalized_type) { case STRING_TYPE: if (value_item->is_of_type(Item::CONST_ITEM, STRING_RESULT) && func_type == Item_func::EQ_FUNC) { convertable = have_index(field_item, GRN_OP_EQUAL); } break; case INT_TYPE: if (field_type == MYSQL_TYPE_ENUM) { convertable = value_item->is_of_type(Item::CONST_ITEM, STRING_RESULT) || value_item->is_of_type(Item::CONST_ITEM, INT_RESULT); } else { convertable = value_item->is_of_type(Item::CONST_ITEM, INT_RESULT); } break; case TIME_TYPE: if (is_valid_time_value(field_item, value_item)) { convertable = have_index(field_item, func_type); } break; case UNSUPPORTED_TYPE: break; } DBUG_RETURN(convertable); } bool ConditionConverter::is_convertable_between(const Item_field *field_item, Item *min_item, Item *max_item) { MRN_DBUG_ENTER_METHOD(); bool convertable = false; enum_field_types field_type = field_item->field->type(); NormalizedType normalized_type = normalize_field_type(field_type); switch (normalized_type) { case STRING_TYPE: if (min_item->is_of_type(Item::CONST_ITEM, STRING_RESULT) && max_item->is_of_type(Item::CONST_ITEM, STRING_RESULT)) { convertable = have_index(field_item, GRN_OP_LESS); } break; case INT_TYPE: if (min_item->is_of_type(Item::CONST_ITEM, INT_RESULT) && max_item->is_of_type(Item::CONST_ITEM, INT_RESULT)) { convertable = have_index(field_item, GRN_OP_LESS); } break; case TIME_TYPE: if (is_valid_time_value(field_item, min_item) && is_valid_time_value(field_item, max_item)) { convertable = have_index(field_item, GRN_OP_LESS); } break; case UNSUPPORTED_TYPE: break; } DBUG_RETURN(convertable); } bool ConditionConverter::is_valid_time_value(const Item_field *field_item, Item *value_item) { MRN_DBUG_ENTER_METHOD(); MYSQL_TIME mysql_time; bool error = get_time_value(field_item, value_item, &mysql_time); DBUG_RETURN(!error); } bool ConditionConverter::get_time_value(const Item_field *field_item, Item *value_item, MYSQL_TIME *mysql_time) { MRN_DBUG_ENTER_METHOD(); bool error; Item *real_value_item = value_item->real_item(); switch (field_item->field->type()) { case MYSQL_TYPE_TIME: { THD *thd= current_thd; error= real_value_item->get_date(thd, mysql_time, Time::Options(thd)); break; } case MYSQL_TYPE_YEAR: mysql_time->year = static_cast(value_item->val_int()); mysql_time->month = 1; mysql_time->day = 1; mysql_time->hour = 0; mysql_time->hour = 0; mysql_time->minute = 0; mysql_time->second_part = 0; mysql_time->neg = false; mysql_time->time_type = MYSQL_TIMESTAMP_DATE; error = false; break; default: { THD *thd= current_thd; Datetime::Options opt(TIME_FUZZY_DATES, thd); error = real_value_item->get_date(thd, mysql_time, opt); break; } } DBUG_RETURN(error); } ConditionConverter::NormalizedType ConditionConverter::normalize_field_type(enum_field_types field_type) { MRN_DBUG_ENTER_METHOD(); NormalizedType type = UNSUPPORTED_TYPE; switch (field_type) { case MYSQL_TYPE_DECIMAL: type = STRING_TYPE; break; case MYSQL_TYPE_TINY: case MYSQL_TYPE_SHORT: case MYSQL_TYPE_LONG: type = INT_TYPE; break; case MYSQL_TYPE_FLOAT: case MYSQL_TYPE_DOUBLE: type = UNSUPPORTED_TYPE; break; case MYSQL_TYPE_NULL: type = UNSUPPORTED_TYPE; break; case MYSQL_TYPE_TIMESTAMP: type = TIME_TYPE; break; case MYSQL_TYPE_LONGLONG: case MYSQL_TYPE_INT24: type = INT_TYPE; break; case MYSQL_TYPE_DATE: case MYSQL_TYPE_TIME: case MYSQL_TYPE_DATETIME: case MYSQL_TYPE_YEAR: case MYSQL_TYPE_NEWDATE: type = TIME_TYPE; break; case MYSQL_TYPE_VARCHAR: type = STRING_TYPE; break; case MYSQL_TYPE_BIT: type = INT_TYPE; break; #ifdef MRN_HAVE_MYSQL_TYPE_TIMESTAMP2 case MYSQL_TYPE_TIMESTAMP2: type = TIME_TYPE; break; #endif #ifdef MRN_HAVE_MYSQL_TYPE_DATETIME2 case MYSQL_TYPE_DATETIME2: type = TIME_TYPE; break; #endif #ifdef MRN_HAVE_MYSQL_TYPE_TIME2 case MYSQL_TYPE_TIME2: type = TIME_TYPE; break; #endif case MYSQL_TYPE_NEWDECIMAL: type = STRING_TYPE; break; case MYSQL_TYPE_ENUM: type = INT_TYPE; break; case MYSQL_TYPE_SET: type = INT_TYPE; break; case MYSQL_TYPE_TINY_BLOB: case MYSQL_TYPE_MEDIUM_BLOB: case MYSQL_TYPE_LONG_BLOB: case MYSQL_TYPE_BLOB: case MYSQL_TYPE_VAR_STRING: case MYSQL_TYPE_STRING: type = STRING_TYPE; break; case MYSQL_TYPE_GEOMETRY: type = UNSUPPORTED_TYPE; break; case MYSQL_TYPE_VARCHAR_COMPRESSED: case MYSQL_TYPE_BLOB_COMPRESSED: DBUG_ASSERT(0); #ifdef MRN_HAVE_MYSQL_TYPE_JSON case MYSQL_TYPE_JSON: type = STRING_TYPE; break; #endif } DBUG_RETURN(type); } bool ConditionConverter::have_index(const Item_field *field_item, grn_operator _operator) { MRN_DBUG_ENTER_METHOD(); grn_obj *column; column = grn_obj_column(ctx_, table_, MRN_ITEM_FIELD_GET_NAME(field_item), MRN_ITEM_FIELD_GET_NAME_LENGTH(field_item)); if (!column) { DBUG_RETURN(false); } mrn::SmartGrnObj smart_column(ctx_, column); int n_indexes = grn_column_index(ctx_, column, _operator, NULL, 0, NULL); bool convertable = (n_indexes > 0); DBUG_RETURN(convertable); } bool ConditionConverter::have_index(const Item_field *field_item, Item_func::Functype func_type) { MRN_DBUG_ENTER_METHOD(); bool have = false; switch (func_type) { case Item_func::EQ_FUNC: have = have_index(field_item, GRN_OP_EQUAL); break; case Item_func::LT_FUNC: have = have_index(field_item, GRN_OP_LESS); break; case Item_func::LE_FUNC: have = have_index(field_item, GRN_OP_LESS_EQUAL); break; case Item_func::GE_FUNC: have = have_index(field_item, GRN_OP_GREATER_EQUAL); break; case Item_func::GT_FUNC: have = have_index(field_item, GRN_OP_GREATER); break; default: break; } DBUG_RETURN(have); } unsigned int ConditionConverter::count_match_against(const Item *item) { MRN_DBUG_ENTER_METHOD(); if (!item) { DBUG_RETURN(0); } switch (item->type()) { case Item::COND_ITEM: if (is_storage_mode_) { Item_cond *cond_item = (Item_cond *)item; if (cond_item->functype() == Item_func::COND_AND_FUNC) { unsigned int n_match_againsts = 0; List_iterator iterator(*((cond_item)->argument_list())); const Item *sub_item; while ((sub_item = iterator++)) { n_match_againsts += count_match_against(sub_item); } DBUG_RETURN(n_match_againsts); } } break; case Item::FUNC_ITEM: { const Item_func *func_item = (const Item_func *)item; switch (func_item->functype()) { case Item_func::FT_FUNC: DBUG_RETURN(1); break; default: break; } } break; default: break; } DBUG_RETURN(0); } void ConditionConverter::convert(const Item *where, grn_obj *expression) { MRN_DBUG_ENTER_METHOD(); if (!where || where->type() != Item::COND_ITEM) { DBUG_VOID_RETURN; } Item_cond *cond_item = (Item_cond *)where; List_iterator iterator(*((cond_item)->argument_list())); const Item *sub_item; while ((sub_item = iterator++)) { switch (sub_item->type()) { case Item::FUNC_ITEM: { const Item_func *func_item = (const Item_func *)sub_item; switch (func_item->functype()) { case Item_func::EQ_FUNC: convert_binary_operation(func_item, expression, GRN_OP_EQUAL); break; case Item_func::LT_FUNC: convert_binary_operation(func_item, expression, GRN_OP_LESS); break; case Item_func::LE_FUNC: convert_binary_operation(func_item, expression, GRN_OP_LESS_EQUAL); break; case Item_func::GE_FUNC: convert_binary_operation(func_item, expression, GRN_OP_GREATER_EQUAL); break; case Item_func::GT_FUNC: convert_binary_operation(func_item, expression, GRN_OP_GREATER); break; case Item_func::BETWEEN: convert_between(func_item, expression); break; default: break; } } break; default: break; } } DBUG_VOID_RETURN; } void ConditionConverter::convert_binary_operation(const Item_func *func_item, grn_obj *expression, grn_operator _operator) { Item **arguments = func_item->arguments(); Item *left_item = arguments[0]; Item *right_item = arguments[1]; if (left_item->type() == Item::FIELD_ITEM) { const Item_field *field_item = static_cast(left_item); append_field_value(field_item, expression); append_const_item(field_item, right_item, expression); grn_expr_append_op(ctx_, expression, _operator, 2); grn_expr_append_op(ctx_, expression, GRN_OP_AND, 2); } } void ConditionConverter::convert_between(const Item_func *func_item, grn_obj *expression) { MRN_DBUG_ENTER_METHOD(); Item **arguments = func_item->arguments(); Item *target_item = arguments[0]; Item *min_item = arguments[1]; Item *max_item = arguments[2]; grn_obj *between_func = grn_ctx_get(ctx_, "between", strlen("between")); grn_expr_append_obj(ctx_, expression, between_func, GRN_OP_PUSH, 1); const Item_field *field_item = static_cast(target_item); append_field_value(field_item, expression); grn_obj include; mrn::SmartGrnObj smart_include(ctx_, &include); GRN_TEXT_INIT(&include, 0); GRN_TEXT_PUTS(ctx_, &include, "include"); append_const_item(field_item, min_item, expression); grn_expr_append_const(ctx_, expression, &include, GRN_OP_PUSH, 1); append_const_item(field_item, max_item, expression); grn_expr_append_const(ctx_, expression, &include, GRN_OP_PUSH, 1); grn_expr_append_op(ctx_, expression, GRN_OP_CALL, 5); grn_expr_append_op(ctx_, expression, GRN_OP_AND, 2); DBUG_VOID_RETURN; } void ConditionConverter::append_field_value(const Item_field *field_item, grn_obj *expression) { MRN_DBUG_ENTER_METHOD(); GRN_BULK_REWIND(&column_name_); GRN_TEXT_PUT(ctx_, &column_name_, MRN_ITEM_FIELD_GET_NAME(field_item), MRN_ITEM_FIELD_GET_NAME_LENGTH(field_item)); grn_expr_append_const(ctx_, expression, &column_name_, GRN_OP_PUSH, 1); grn_expr_append_op(ctx_, expression, GRN_OP_GET_VALUE, 1); DBUG_VOID_RETURN; } void ConditionConverter::append_const_item(const Item_field *field_item, Item *const_item, grn_obj *expression) { MRN_DBUG_ENTER_METHOD(); enum_field_types field_type = field_item->field->real_type(); NormalizedType normalized_type = normalize_field_type(field_type); switch (normalized_type) { case STRING_TYPE: grn_obj_reinit(ctx_, &value_, GRN_DB_TEXT, 0); { String *string; string = const_item->val_str(NULL); GRN_TEXT_SET(ctx_, &value_, string->ptr(), string->length()); } break; case INT_TYPE: grn_obj_reinit(ctx_, &value_, GRN_DB_INT64, 0); if (field_type == MYSQL_TYPE_ENUM) { if (const_item->is_of_type(Item::CONST_ITEM, STRING_RESULT)) { String *string; string = const_item->val_str(NULL); Field_enum *enum_field = static_cast(field_item->field); int enum_value = find_type(string->c_ptr(), enum_field->typelib, FIND_TYPE_BASIC); GRN_INT64_SET(ctx_, &value_, enum_value); } else { GRN_INT64_SET(ctx_, &value_, const_item->val_int()); } } else { GRN_INT64_SET(ctx_, &value_, const_item->val_int()); } break; case TIME_TYPE: grn_obj_reinit(ctx_, &value_, GRN_DB_TIME, 0); { MYSQL_TIME mysql_time; get_time_value(field_item, const_item, &mysql_time); bool truncated = false; TimeConverter time_converter; long long int time = time_converter.mysql_time_to_grn_time(&mysql_time, &truncated); GRN_TIME_SET(ctx_, &value_, time); } break; case UNSUPPORTED_TYPE: // Should not be occurred. DBUG_PRINT("error", ("mroonga: append_const_item: unsupported type: <%d> " "This case should not be occurred.", field_type)); grn_obj_reinit(ctx_, &value_, GRN_DB_VOID, 0); break; } grn_expr_append_const(ctx_, expression, &value_, GRN_OP_PUSH, 1); DBUG_VOID_RETURN; } }