diff options
author | Michael Widenius <monty@askmonty.org> | 2014-01-24 14:50:18 +0200 |
---|---|---|
committer | Michael Widenius <monty@askmonty.org> | 2014-01-24 14:50:18 +0200 |
commit | d15b3386db8de5d2562c78a6e02d404757c787f9 (patch) | |
tree | 90af5285a2a75da244248076f5a9bc3418f295de | |
parent | 7335c6f2a4319f431f22b79f664881ca265026b0 (diff) | |
download | mariadb-git-d15b3386db8de5d2562c78a6e02d404757c787f9.tar.gz |
Fix for MDEV-5531: double call procedure in one session - hard shutdown the server
Main fix was to not cache derivied tables as they may be temporary tables that are deleted before the next query.
This was a bit tricky as Item_field::fix_fields depended on cached_tables to be set to resolve some columns.
mysql-test/r/sp-bugs.result:
Added test case
mysql-test/t/sp-bugs.test:
Added test case
sql/item.cc:
Fixed fix_outer_field to handle case where found field did not have in cached_table
Idea is that if cached_table is not avaliable, use from_field->table->pos_in_table_list instead
sql/records.cc:
Also accept INTERNAL_TMP_TABLE for memmap
sql/sql_base.cc:
More DBUG_PRINT
Fixed that setup_natural_join_row_types() is not run twice.
Original code modified context->first_name_resolution_table also for second executions.
This was wrong as this could give wrong results if some joins had been optimized away between calls.
sql/sql_derived.cc:
Mark derived tables as internal temporary tables (INTERNAL_TMP_TABLE), not as NON_TRANSACTIONAL_TMP_TABLE.
This is more correct as the tables are not visible by the end user.
sql/sql_insert.cc:
Reset pos_in_table_list before calling fix_fields.
One of the consequences of the change of not caching all generated tables in Item_ident is that
pos_in_table_list needs to be correct in calls to fix_fields.
sql/sql_lex.cc:
More DBUG_PRINT
sql/sql_parse.cc:
Don't cache derivied tables as they may be temporary tables that are deleted before the next query
sql/sql_select.cc:
Reset table_vector. This was required as some code checked the vector to see if temporary tables had already been created.
sql/table.cc:
Mark tables with field translations as cacheable (as these will not disapper between stmt executions.
-rw-r--r-- | mysql-test/r/sp-bugs.result | 46 | ||||
-rw-r--r-- | mysql-test/t/sp-bugs.test | 57 | ||||
-rw-r--r-- | sql/item.cc | 54 | ||||
-rw-r--r-- | sql/records.cc | 3 | ||||
-rw-r--r-- | sql/sql_base.cc | 60 | ||||
-rw-r--r-- | sql/sql_derived.cc | 2 | ||||
-rw-r--r-- | sql/sql_insert.cc | 3 | ||||
-rw-r--r-- | sql/sql_lex.cc | 10 | ||||
-rw-r--r-- | sql/sql_parse.cc | 7 | ||||
-rw-r--r-- | sql/sql_select.cc | 8 | ||||
-rw-r--r-- | sql/table.cc | 9 |
11 files changed, 213 insertions, 46 deletions
diff --git a/mysql-test/r/sp-bugs.result b/mysql-test/r/sp-bugs.result index 9d9deaebcc3..2442adc46b3 100644 --- a/mysql-test/r/sp-bugs.result +++ b/mysql-test/r/sp-bugs.result @@ -222,3 +222,49 @@ testf_bug11763507 DROP PROCEDURE testp_bug11763507; DROP FUNCTION testf_bug11763507; #END OF BUG#11763507 test. +# +# MDEV-5531 double call procedure in one session +# +CREATE TABLE `t1` ( +`id` int(10) unsigned NOT NULL AUTO_INCREMENT, +`create_ts` int(10) unsigned DEFAULT '0', +PRIMARY KEY (`id`) +) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8; +CREATE PROCEDURE test_5531 (IN step TINYINT(1)) +BEGIN +DECLARE counts INT DEFAULT 0; +DECLARE cur1 CURSOR FOR +SELECT ct.id +FROM (SELECT NULL) AS z +JOIN ( +SELECT id +FROM `t1` + LIMIT 10 +) AS ct +JOIN (SELECT NULL) AS x ON( +EXISTS( +SELECT 1 +FROM `t1` + WHERE id=ct.id +LIMIT 1 +) +); +IF step=1 THEN +TRUNCATE t1; +REPEAT +INSERT INTO `t1` + (create_ts) VALUES +(UNIX_TIMESTAMP()); +SET counts=counts+1; +UNTIL counts>150 END REPEAT; +SET max_sp_recursion_depth=1; +CALL test_5531(2); +SET max_sp_recursion_depth=2; +CALL test_5531(2); +ELSEIF step=2 THEN +OPEN cur1; CLOSE cur1; +END IF; +END $$ +CALL test_5531(1); +DROP PROCEDURE test_5531; +DROP TABLE t1; diff --git a/mysql-test/t/sp-bugs.test b/mysql-test/t/sp-bugs.test index 3ab1689e8b2..67924996423 100644 --- a/mysql-test/t/sp-bugs.test +++ b/mysql-test/t/sp-bugs.test @@ -228,3 +228,60 @@ DROP PROCEDURE testp_bug11763507; DROP FUNCTION testf_bug11763507; --echo #END OF BUG#11763507 test. + +--echo # +--echo # MDEV-5531 double call procedure in one session +--echo # + +CREATE TABLE `t1` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `create_ts` int(10) unsigned DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8; + +DELIMITER $$; + +CREATE PROCEDURE test_5531 (IN step TINYINT(1)) +BEGIN + DECLARE counts INT DEFAULT 0; + DECLARE cur1 CURSOR FOR + + SELECT ct.id + FROM (SELECT NULL) AS z + JOIN ( + SELECT id + FROM `t1` + LIMIT 10 + ) AS ct + JOIN (SELECT NULL) AS x ON( + EXISTS( + SELECT 1 + FROM `t1` + WHERE id=ct.id + LIMIT 1 + ) + ); + + IF step=1 THEN + TRUNCATE t1; + REPEAT + INSERT INTO `t1` + (create_ts) VALUES + (UNIX_TIMESTAMP()); + + SET counts=counts+1; + UNTIL counts>150 END REPEAT; + + SET max_sp_recursion_depth=1; + + CALL test_5531(2); + SET max_sp_recursion_depth=2; + CALL test_5531(2); + ELSEIF step=2 THEN + OPEN cur1; CLOSE cur1; + END IF; +END $$ +DELIMITER ;$$ +CALL test_5531(1); +DROP PROCEDURE test_5531; +DROP TABLE t1; diff --git a/sql/item.cc b/sql/item.cc index bfe065ceced..7b1ca3c0849 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -4700,6 +4700,12 @@ bool is_outer_table(TABLE_LIST *table, SELECT_LEX *select) /** Resolve the name of an outer select column reference. + @param[in] thd current thread + @param[in,out] from_field found field reference or (Field*)not_found_field + @param[in,out] reference view column if this item was resolved to a + view column + + @description The method resolves the column reference represented by 'this' as a column present in outer selects that contain current select. @@ -4709,10 +4715,16 @@ bool is_outer_table(TABLE_LIST *table, SELECT_LEX *select) current select as dependent. The found reference of field should be provided in 'from_field'. - @param[in] thd current thread - @param[in,out] from_field found field reference or (Field*)not_found_field - @param[in,out] reference view column if this item was resolved to a - view column + The cache is critical for prepared statements of type: + + SELECT a FROM (SELECT a FROM test.t1) AS s1 NATURAL JOIN t2 AS s2; + + This is internally converted to a join similar to + + SELECT a FROM t1 AS s1,t2 AS s2 WHERE t2.a=t1.a; + + Without the cache, we would on re-prepare not know if 'a' did match + s1.a or s2.a. @note This is the inner loop of Item_field::fix_fields: @@ -4742,7 +4754,12 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) enum_parsing_place place= NO_MATTER; bool field_found= (*from_field != not_found_field); bool upward_lookup= FALSE; + TABLE_LIST *table_list; + /* Calulate the TABLE_LIST for the table */ + table_list= (cached_table ? cached_table : + field_found && (*from_field) != view_ref_found ? + (*from_field)->table->pos_in_table_list : 0); /* If there are outer contexts (outer selects, but current select is not derived table or view) try to resolve this reference in the @@ -4761,6 +4778,15 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) if (current_sel->master_unit()->first_select()->linkage != DERIVED_TABLE_TYPE) outer_context= context->outer_context; + + /* + This assert is to ensure we have an outer contex when *from_field + is set. + If this would not be the case, we would assert in mark_as_dependent + as last_checked_countex == context + */ + DBUG_ASSERT(outer_context || !*from_field || + *from_field == not_found_field); for (; outer_context; outer_context= outer_context->outer_context) @@ -4777,7 +4803,7 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) to find_field_in_tables(). Only need to find appropriate context. */ if (field_found && outer_context->select_lex != - cached_table->select_lex) + table_list->select_lex) continue; /* In case of a view, find_field_in_tables() writes the pointer to @@ -4976,9 +5002,9 @@ Item_field::fix_outer_field(THD *thd, Field **from_field, Item **reference) if (last_checked_context->select_lex->having_fix_field) { Item_ref *rf; - rf= new Item_ref(context, - (cached_table->db[0] ? cached_table->db : 0), - (char*) cached_table->alias, (char*) field_name); + rf= new Item_ref(context, (*from_field)->table->s->db.str, + (*from_field)->table->alias.c_ptr(), + (char*) field_name); if (!rf) return -1; thd->change_item_tree(reference, rf); @@ -5049,6 +5075,7 @@ bool Item_field::fix_fields(THD *thd, Item **reference) if (!field) // If field is not checked { + TABLE_LIST *table_list; /* In case of view, find_field_in_tables() write pointer to view field expression to 'reference', i.e. it substitute that expression instead @@ -5134,11 +5161,14 @@ bool Item_field::fix_fields(THD *thd, Item **reference) else if (!from_field) goto error; - if (!outer_fixed && cached_table && cached_table->select_lex && + table_list= (cached_table ? cached_table : + from_field != view_ref_found ? + from_field->table->pos_in_table_list : 0); + if (!outer_fixed && table_list && table_list->select_lex && context->select_lex && - cached_table->select_lex != context->select_lex && - !context->select_lex->is_merged_child_of(cached_table->select_lex) && - is_outer_table(cached_table, context->select_lex)) + table_list->select_lex != context->select_lex && + !context->select_lex->is_merged_child_of(table_list->select_lex) && + is_outer_table(table_list, context->select_lex)) { int ret; if ((ret= fix_outer_field(thd, &from_field, reference)) < 0) diff --git a/sql/records.cc b/sql/records.cc index 2f74030d5c4..aca950d7435 100644 --- a/sql/records.cc +++ b/sql/records.cc @@ -189,7 +189,8 @@ bool init_read_record(READ_RECORD *info,THD *thd, TABLE *table, info->table=table; info->forms= &info->table; /* Only one table */ - if (table->s->tmp_table == NON_TRANSACTIONAL_TMP_TABLE && + if ((table->s->tmp_table == INTERNAL_TMP_TABLE || + table->s->tmp_table == NON_TRANSACTIONAL_TMP_TABLE) && !table->sort.addon_field) (void) table->file->extra(HA_EXTRA_MMAP); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index f689d96b4cf..84e5ee49fc4 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -235,6 +235,7 @@ static void check_unused(THD *thd) uint count= 0, open_files= 0, idx= 0; TABLE *cur_link, *start_link, *entry; TABLE_SHARE *share; + DBUG_ENTER("check_unused"); if ((start_link=cur_link=unused_tables)) { @@ -243,7 +244,7 @@ static void check_unused(THD *thd) if (cur_link != cur_link->next->prev || cur_link != cur_link->prev->next) { DBUG_PRINT("error",("Unused_links aren't linked properly")); /* purecov: inspected */ - return; /* purecov: inspected */ + DBUG_VOID_RETURN; /* purecov: inspected */ } } while (count++ < table_cache_count && (cur_link=cur_link->next) != start_link); @@ -291,6 +292,7 @@ static void check_unused(THD *thd) DBUG_PRINT("error",("Unused_links doesn't match open_cache: diff: %d", /* purecov: inspected */ count)); /* purecov: inspected */ } + DBUG_VOID_RETURN; } #else #define check_unused(A) @@ -3103,6 +3105,7 @@ retry_share: MDL_deadlock_handler mdl_deadlock_handler(ot_ctx); bool wait_result; + DBUG_PRINT("info", ("old version of table share found")); release_table_share(share); mysql_mutex_unlock(&LOCK_open); @@ -3127,6 +3130,7 @@ retry_share: and try to reopen them. Note: refresh_version is currently changed only during FLUSH TABLES. */ + DBUG_PRINT("info", ("share version differs between tables")); release_table_share(share); mysql_mutex_unlock(&LOCK_open); (void)ot_ctx->request_backoff_action(Open_table_context::OT_REOPEN_TABLES, @@ -3139,12 +3143,17 @@ retry_share: { table= share->free_tables.front(); table_def_use_table(thd, table); - /* We need to release share as we have EXTRA reference to it in our hands. */ + /* + We need to release share as we have EXTRA reference to it in our hands. + */ + DBUG_PRINT("info", ("release temporarily acquired table share")); release_table_share(share); } else { - /* We have too many TABLE instances around let us try to get rid of them. */ + /* + We have too many TABLE instances around let us try to get rid of them. + */ while (table_cache_count > table_cache_size && unused_tables) free_cache_entry(unused_tables); @@ -3217,6 +3226,7 @@ err_unlock: release_table_share(share); mysql_mutex_unlock(&LOCK_open); + DBUG_PRINT("exit", ("failed")); DBUG_RETURN(TRUE); } @@ -6973,7 +6983,7 @@ find_field_in_tables(THD *thd, Item_ident *item, */ if (db) return cur_field; - + if (found) { if (report_error == REPORT_ALL_ERRORS || @@ -6988,7 +6998,7 @@ find_field_in_tables(THD *thd, Item_ident *item, if (found) return found; - + /* If the field was qualified and there were no tables to search, issue an error that an unknown table was given. The situation is detected @@ -7919,6 +7929,7 @@ err: TRUE Error FALSE OK */ + static bool setup_natural_join_row_types(THD *thd, List<TABLE_LIST> *from_clause, Name_resolution_context *context) @@ -7928,6 +7939,18 @@ static bool setup_natural_join_row_types(THD *thd, if (from_clause->elements == 0) DBUG_RETURN(false); /* We come here in the case of UNIONs. */ + /* + Do not redo work if already done: + 1) for stored procedures, + 2) for multitable update after lock failure and table reopening. + */ + if (!context->select_lex->first_natural_join_processing) + { + DBUG_PRINT("info", ("using cached store_top_level_join_columns")); + DBUG_RETURN(false); + } + context->select_lex->first_natural_join_processing= false; + List_iterator_fast<TABLE_LIST> table_ref_it(*from_clause); TABLE_LIST *table_ref; /* Current table reference. */ /* Table reference to the left of the current. */ @@ -7944,22 +7967,15 @@ static bool setup_natural_join_row_types(THD *thd, left_neighbor= table_ref_it++; } while (left_neighbor && left_neighbor->sj_subq_pred); - /* - Do not redo work if already done: - 1) for stored procedures, - 2) for multitable update after lock failure and table reopening. - */ - if (context->select_lex->first_natural_join_processing) + + if (store_top_level_join_columns(thd, table_ref, + left_neighbor, right_neighbor)) + DBUG_RETURN(true); + if (left_neighbor) { - if (store_top_level_join_columns(thd, table_ref, - left_neighbor, right_neighbor)) - DBUG_RETURN(true); - if (left_neighbor) - { - TABLE_LIST *first_leaf_on_the_right; - first_leaf_on_the_right= table_ref->first_leaf_for_name_resolution(); - left_neighbor->next_name_resolution_table= first_leaf_on_the_right; - } + TABLE_LIST *first_leaf_on_the_right; + first_leaf_on_the_right= table_ref->first_leaf_for_name_resolution(); + left_neighbor->next_name_resolution_table= first_leaf_on_the_right; } right_neighbor= table_ref; } @@ -7973,8 +7989,6 @@ static bool setup_natural_join_row_types(THD *thd, DBUG_ASSERT(right_neighbor); context->first_name_resolution_table= right_neighbor->first_leaf_for_name_resolution(); - context->select_lex->first_natural_join_processing= false; - DBUG_RETURN (false); } @@ -8576,7 +8590,7 @@ insert_fields(THD *thd, Name_resolution_context *context, const char *db_name, if (!(item= field_iterator.create_item(thd))) DBUG_RETURN(TRUE); -// DBUG_ASSERT(item->fixed); + /* cache the table for the Item_fields inserted by expanding stars */ if (item->type() == Item::FIELD_ITEM && tables->cacheable_table) ((Item_field *)item)->cached_table= tables; diff --git a/sql/sql_derived.cc b/sql/sql_derived.cc index accc3012e9e..b7425b188a0 100644 --- a/sql/sql_derived.cc +++ b/sql/sql_derived.cc @@ -716,7 +716,7 @@ exit: { TABLE *table= derived->table; table->derived_select_number= first_select->select_number; - table->s->tmp_table= NON_TRANSACTIONAL_TMP_TABLE; + table->s->tmp_table= INTERNAL_TMP_TABLE; #ifndef NO_EMBEDDED_ACCESS_CHECKS if (derived->referencing_view) table->grant= derived->grant; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 306d5c8d306..10e943458bd 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -2386,6 +2386,9 @@ TABLE *Delayed_insert::get_local_table(THD* client_thd) bitmap= (uchar*) (field + share->fields + 1); copy->record[0]= (bitmap + share->column_bitmap_size*3); memcpy((char*) copy->record[0], (char*) table->record[0], share->reclength); + /* Ensure we don't use the table list of the original table */ + copy->pos_in_table_list= 0; + /* Make a copy of all fields. The copied fields need to point into the copied record. This is done diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 665d5b2724f..fa1bd5a96fa 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -2127,14 +2127,15 @@ void st_select_lex_unit::exclude_tree() this to 'last' as dependent SYNOPSIS - last - pointer to last st_select_lex struct, before wich all + last - pointer to last st_select_lex struct, before which all st_select_lex have to be marked as dependent NOTE 'last' should be reachable from this st_select_lex_node */ -bool st_select_lex::mark_as_dependent(THD *thd, st_select_lex *last, Item *dependency) +bool st_select_lex::mark_as_dependent(THD *thd, st_select_lex *last, + Item *dependency) { DBUG_ASSERT(this != last); @@ -2325,7 +2326,10 @@ bool st_select_lex::setup_ref_array(THD *thd, uint order_group_num) If we need a bigger array, we must allocate a new one. */ if (ref_pointer_array_size >= n_elems) + { + DBUG_PRINT("info", ("reusing old ref_array")); return false; + } } ref_pointer_array= static_cast<Item**>(arena->alloc(sizeof(Item*) * n_elems)); if (ref_pointer_array != NULL) @@ -3328,6 +3332,7 @@ static void fix_prepare_info_in_table_list(THD *thd, TABLE_LIST *tbl) void st_select_lex::fix_prepare_information(THD *thd, Item **conds, Item **having_conds) { + DBUG_ENTER("st_select_lex::fix_prepare_information"); if (!thd->stmt_arena->is_conventional() && first_execution) { first_execution= 0; @@ -3356,6 +3361,7 @@ void st_select_lex::fix_prepare_information(THD *thd, Item **conds, } fix_prepare_info_in_table_list(thd, table_list.first); } + DBUG_VOID_RETURN; } diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 8ebdd3861e7..20fba8e49ae 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1957,6 +1957,7 @@ mysql_execute_command(THD *thd) bool have_table_map_for_update= FALSE; #endif DBUG_ENTER("mysql_execute_command"); + #ifdef WITH_PARTITION_STORAGE_ENGINE thd->work_part_info= 0; #endif @@ -6128,7 +6129,11 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->schema_table= schema_table; } ptr->select_lex= lex->current_select; - ptr->cacheable_table= 1; + /* + We can't cache internal temporary tables between prepares as the + table may be deleted before next exection. + */ + ptr->cacheable_table= !table->is_derived_table(); ptr->index_hints= index_hints_arg; ptr->option= option ? option->str : 0; /* check that used name is unique */ diff --git a/sql/sql_select.cc b/sql/sql_select.cc index dfbf5e144e5..9247485278d 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -3180,7 +3180,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, stat_ref=(JOIN_TAB**) join->thd->alloc(sizeof(JOIN_TAB*)* (MAX_TABLES + table_count + 1)); stat_vector= stat_ref + MAX_TABLES; - table_vector=(TABLE**) join->thd->alloc(sizeof(TABLE*)*(table_count*2)); + table_vector=(TABLE**) join->thd->calloc(sizeof(TABLE*)*(table_count*2)); join->positions= new (join->thd->mem_root) POSITION[(table_count+1)]; /* best_positions is ok to allocate with alloc() as we copy things to it with @@ -14802,7 +14802,8 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, save_sum_fields|= param->precomputed_group_by; DBUG_ENTER("create_tmp_table"); DBUG_PRINT("enter", - ("distinct: %d save_sum_fields: %d rows_limit: %lu group: %d", + ("table_alias: '%s' distinct: %d save_sum_fields: %d " + "rows_limit: %lu group: %d", table_alias, (int) distinct, (int) save_sum_fields, (ulong) rows_limit,test(group))); @@ -16258,7 +16259,8 @@ free_tmp_table(THD *thd, TABLE *entry) MEM_ROOT own_root= entry->mem_root; const char *save_proc_info; DBUG_ENTER("free_tmp_table"); - DBUG_PRINT("enter",("table: %s",entry->alias.c_ptr())); + DBUG_PRINT("enter",("table: %s alias: %s",entry->s->table_name.str, + entry->alias.c_ptr())); save_proc_info=thd->proc_info; thd_proc_info(thd, "removing tmp table"); diff --git a/sql/table.cc b/sql/table.cc index b0be0791cd6..932ca953c40 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -4089,6 +4089,7 @@ bool TABLE_LIST::create_field_translation(THD *thd) uint field_count= 0; Query_arena *arena= thd->stmt_arena, backup; bool res= FALSE; + DBUG_ENTER("TABLE_LIST::create_field_translation"); if (thd->stmt_arena->is_conventional() || thd->stmt_arena->is_stmt_prepare_or_first_sp_execute()) @@ -4109,7 +4110,7 @@ bool TABLE_LIST::create_field_translation(THD *thd) if (field_translation) { /* - Update items in the field translation aftet view have been prepared. + Update items in the field translation after view have been prepared. It's needed because some items in the select list, like IN subselects, might be substituted for optimized ones. */ @@ -4122,7 +4123,7 @@ bool TABLE_LIST::create_field_translation(THD *thd) field_translation_updated= TRUE; } - return FALSE; + DBUG_RETURN(FALSE); } if (arena->is_conventional()) @@ -4148,12 +4149,14 @@ bool TABLE_LIST::create_field_translation(THD *thd) } field_translation= transl; field_translation_end= transl + field_count; + /* It's safe to cache this table for prepared statements */ + cacheable_table= 1; exit: if (arena) thd->restore_active_arena(arena, &backup); - return res; + DBUG_RETURN(res); } |