diff options
author | unknown <kostja@bodhi.local> | 2006-10-23 11:51:45 +0400 |
---|---|---|
committer | unknown <kostja@bodhi.local> | 2006-10-23 11:51:45 +0400 |
commit | 07cf5b9bd349f799bc0e81caf79edfbd29fd0512 (patch) | |
tree | 8975cf9bcdde83b55d94ab5299cb5d61a93acb9c /sql | |
parent | 8db4dc3f91dfbe03181e63ed45bdf35a5d65aeb0 (diff) | |
parent | 133e91aa89e05437158946c0a3b7f7d2f27a0f6d (diff) | |
download | mariadb-git-07cf5b9bd349f799bc0e81caf79edfbd29fd0512.tar.gz |
Merge bk-internal.mysql.com:/home/bk/mysql-5.0
into bodhi.local:/opt/local/work/mysql-5.0-runtime-merge
mysql-test/t/func_gconcat.test:
Auto merged
sql/item_func.cc:
Auto merged
sql/sql_base.cc:
Auto merged
sql/sql_insert.cc:
Auto merged
sql/sql_lex.cc:
Auto merged
sql/sql_lex.h:
Auto merged
sql/sql_select.cc:
Auto merged
sql/sql_update.cc:
Auto merged
mysql-test/r/view.result:
Manual merge.
mysql-test/t/view.test:
Manual merge.
Diffstat (limited to 'sql')
-rw-r--r-- | sql/item_buff.cc | 12 | ||||
-rw-r--r-- | sql/item_cmpfunc.cc | 2 | ||||
-rw-r--r-- | sql/item_cmpfunc.h | 1 | ||||
-rw-r--r-- | sql/mysql_priv.h | 8 | ||||
-rw-r--r-- | sql/opt_range.cc | 37 | ||||
-rw-r--r-- | sql/opt_range.h | 2 | ||||
-rw-r--r-- | sql/sql_base.cc | 12 | ||||
-rw-r--r-- | sql/sql_delete.cc | 2 | ||||
-rw-r--r-- | sql/sql_lex.cc | 2 | ||||
-rw-r--r-- | sql/sql_lex.h | 3 | ||||
-rw-r--r-- | sql/sql_rename.cc | 194 | ||||
-rw-r--r-- | sql/sql_select.cc | 267 | ||||
-rw-r--r-- | sql/sql_select.h | 15 | ||||
-rw-r--r-- | sql/sql_table.cc | 50 | ||||
-rw-r--r-- | sql/sql_update.cc | 2 | ||||
-rw-r--r-- | sql/table.cc | 21 | ||||
-rw-r--r-- | sql/unireg.cc | 15 |
17 files changed, 507 insertions, 138 deletions
diff --git a/sql/item_buff.cc b/sql/item_buff.cc index 1661f04a4ae..37f9ca7ce6c 100644 --- a/sql/item_buff.cc +++ b/sql/item_buff.cc @@ -132,11 +132,17 @@ bool Cached_item_decimal::cmp() { my_decimal tmp; my_decimal *ptmp= item->val_decimal(&tmp); - if (null_value != item->null_value || my_decimal_cmp(&value, ptmp)) + if (null_value != item->null_value || + (!item->null_value && my_decimal_cmp(&value, ptmp))) { null_value= item->null_value; - my_decimal2decimal(ptmp, &value); - return TRUE; + /* Save only not null values */ + if (!null_value) + { + my_decimal2decimal(ptmp, &value); + return TRUE; + } + return FALSE; } return FALSE; } diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index 780d70d51dc..9a400d60ae6 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -1078,6 +1078,8 @@ bool Item_func_between::fix_fields(THD *thd, Item **ref) if (Item_func_opt_neg::fix_fields(thd, ref)) return 1; + thd->lex->current_select->between_count++; + /* not_null_tables_cache == union(T1(e),T1(e1),T1(e2)) */ if (pred_level && !negated) return 0; diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index f2c43833bd9..c8439cba303 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -446,6 +446,7 @@ public: negated= !negated; return this; } + bool subst_argument_checker(byte **arg) { return TRUE; } }; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 381f2a989b5..1636b5a31e4 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -141,7 +141,7 @@ MY_LOCALE *my_locale_by_name(const char *name); Feel free to raise this by the smallest amount you can to get the "execution_constants" test to pass. */ -#define STACK_MIN_SIZE 9336 // Abort if less stack during eval. +#define STACK_MIN_SIZE 10788 // Abort if less stack during eval. #define STACK_MIN_SIZE_FOR_OPEN 1024*80 #define STACK_BUFF_ALLOC 256 // For stack overrun checks @@ -659,6 +659,9 @@ int quick_rm_table(enum db_type base,const char *db, const char *table_name); void close_cached_table(THD *thd, TABLE *table); bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list); +bool do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db, + char *new_table_name, char *new_table_alias, + bool skip_error); bool mysql_change_db(THD *thd,const char *name,bool no_access_check); void mysql_parse(THD *thd,char *inBuf,uint length); bool mysql_test_parse_for_slave(THD *thd,char *inBuf,uint length); @@ -722,7 +725,8 @@ bool mysql_xa_recover(THD *thd); bool check_simple_select(); -SORT_FIELD * make_unireg_sortorder(ORDER *order, uint *length); +SORT_FIELD * make_unireg_sortorder(ORDER *order, uint *length, + SORT_FIELD *sortorder); int setup_order(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, List<Item> &fields, List <Item> &all_fields, ORDER *order); int setup_group(THD *thd, Item **ref_pointer_array, TABLE_LIST *tables, diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 737a296e1d7..d68ea0abfe7 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -8372,6 +8372,7 @@ TRP_GROUP_MIN_MAX::make_quick(PARAM *param, bool retrieve_full_rows, quick->quick_prefix_select= NULL; quick->update_key_stat(); + quick->adjust_prefix_ranges(); DBUG_RETURN(quick); } @@ -8602,6 +8603,42 @@ bool QUICK_GROUP_MIN_MAX_SELECT::add_range(SEL_ARG *sel_range) /* + Opens the ranges if there are more conditions in quick_prefix_select than + the ones used for jumping through the prefixes. + + SYNOPSIS + QUICK_GROUP_MIN_MAX_SELECT::adjust_prefix_ranges() + + NOTES + quick_prefix_select is made over the conditions on the whole key. + It defines a number of ranges of length x. + However when jumping through the prefixes we use only the the first + few most significant keyparts in the range key. However if there + are more keyparts to follow the ones we are using we must make the + condition on the key inclusive (because x < "ab" means + x[0] < 'a' OR (x[0] == 'a' AND x[1] < 'b'). + To achive the above we must turn off the NEAR_MIN/NEAR_MAX +*/ +void QUICK_GROUP_MIN_MAX_SELECT::adjust_prefix_ranges () +{ + if (quick_prefix_select && + group_prefix_len < quick_prefix_select->max_used_key_length) + { + DYNAMIC_ARRAY *arr; + uint inx; + + for (inx= 0, arr= &quick_prefix_select->ranges; inx < arr->elements; inx++) + { + QUICK_RANGE *range; + + get_dynamic(arr, (gptr)&range, inx); + range->flag &= ~(NEAR_MIN | NEAR_MAX); + } + } +} + + +/* Determine the total number and length of the keys that will be used for index lookup. diff --git a/sql/opt_range.h b/sql/opt_range.h index 40a95beb894..ddc959f1cdf 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -295,6 +295,7 @@ protected: friend class QUICK_SELECT_DESC; friend class QUICK_INDEX_MERGE_SELECT; friend class QUICK_ROR_INTERSECT_SELECT; + friend class QUICK_GROUP_MIN_MAX_SELECT; DYNAMIC_ARRAY ranges; /* ordered array of range ptrs */ QUICK_RANGE **cur_range; /* current element in ranges */ @@ -643,6 +644,7 @@ public: ~QUICK_GROUP_MIN_MAX_SELECT(); bool add_range(SEL_ARG *sel_range); void update_key_stat(); + void adjust_prefix_ranges(); bool alloc_buffers(); int init(); int reset(); diff --git a/sql/sql_base.cc b/sql/sql_base.cc index ce1011fddf4..0f6715fc078 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -3455,13 +3455,20 @@ find_item_in_list(Item *find, List<Item> &items, uint *counter, const char *field_name=0; const char *table_name=0; bool found_unaliased_non_uniq= 0; + /* + true if the item that we search for is a valid name reference + (and not an item that happens to have a name). + */ + bool is_ref_by_name= 0; uint unaliased_counter; LINT_INIT(unaliased_counter); // Dependent on found_unaliased *unaliased= FALSE; - if (find->type() == Item::FIELD_ITEM || find->type() == Item::REF_ITEM) + is_ref_by_name= (find->type() == Item::FIELD_ITEM || + find->type() == Item::REF_ITEM); + if (is_ref_by_name) { field_name= ((Item_ident*) find)->field_name; table_name= ((Item_ident*) find)->table_name; @@ -3573,7 +3580,7 @@ find_item_in_list(Item *find, List<Item> &items, uint *counter, } } else if (!table_name && (find->eq(item,0) || - find->name && item->name && + is_ref_by_name && find->name && item->name && !my_strcasecmp(system_charset_info, item->name,find->name))) { @@ -4904,6 +4911,7 @@ int setup_conds(THD *thd, TABLE_LIST *tables, TABLE_LIST *leaves, thd->set_query_id=1; select_lex->cond_count= 0; + select_lex->between_count= 0; for (table= tables; table; table= table->next_local) { diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index c95fb5d5973..3e3378542aa 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -167,7 +167,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, MYF(MY_FAE | MY_ZEROFILL)); if (!(sortorder= make_unireg_sortorder((ORDER*) order->first, - &length)) || + &length, NULL)) || (table->sort.found_records = filesort(thd, table, sortorder, length, select, HA_POS_ERROR, &examined_rows)) diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 405f576ac04..23ad36202f6 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1137,7 +1137,7 @@ void st_select_lex::init_query() initialization is checked for failure. */ parent_lex->push_context(&context); - cond_count= with_wild= 0; + cond_count= between_count= with_wild= 0; conds_processed_with_permanent_arena= 0; ref_pointer_array= 0; select_n_having_items= 0; diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 378f968118e..1db62cde5ee 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -530,7 +530,8 @@ public: list during split_sum_func */ uint select_n_having_items; - uint cond_count; /* number of arguments of and/or/xor in where/having */ + uint cond_count; /* number of arguments of and/or/xor in where/having/on */ + uint between_count; /* number of between predicates in where/having/on */ enum_parsing_place parsing_place; /* where we are parsing expression */ bool with_sum_func; /* sum function indicator */ /* diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc index 74951029de9..c87a8696bbc 100644 --- a/sql/sql_rename.cc +++ b/sql/sql_rename.cc @@ -126,94 +126,146 @@ static TABLE_LIST *reverse_table_list(TABLE_LIST *table_list) /* - Rename all tables in list; Return pointer to wrong entry if something goes - wrong. Note that the table_list may be empty! + Rename a single table or a view + + SYNPOSIS + do_rename() + thd Thread handle + ren_table A table/view to be renamed + new_db The database to which the table to be moved to + new_table_name The new table/view name + new_table_alias The new table/view alias + skip_error Whether to skip error + + DESCRIPTION + Rename a single table or a view. + + RETURN + false Ok + true rename failed */ -static TABLE_LIST * -rename_tables(THD *thd, TABLE_LIST *table_list, bool skip_error) +bool +do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db, char *new_table_name, + char *new_table_alias, bool skip_error) { - TABLE_LIST *ren_table,*new_table; + int rc= 1; + char name[FN_REFLEN]; + const char *new_alias, *old_alias; frm_type_enum frm_type; db_type table_type; - DBUG_ENTER("rename_tables"); + DBUG_ENTER("do_rename"); - for (ren_table= table_list; ren_table; ren_table= new_table->next_local) + if (lower_case_table_names == 2) { - int rc= 1; - char name[FN_REFLEN]; - const char *new_alias, *old_alias; - - new_table= ren_table->next_local; - if (lower_case_table_names == 2) - { - old_alias= ren_table->alias; - new_alias= new_table->alias; - } - else - { - old_alias= ren_table->table_name; - new_alias= new_table->table_name; - } - sprintf(name,"%s/%s/%s%s",mysql_data_home, - new_table->db, new_alias, reg_ext); - unpack_filename(name, name); - if (!access(name,F_OK)) - { - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias); - DBUG_RETURN(ren_table); // This can't be skipped - } - sprintf(name,"%s/%s/%s%s",mysql_data_home, - ren_table->db, old_alias, - reg_ext); - unpack_filename(name, name); + old_alias= ren_table->alias; + new_alias= new_table_alias; + } + else + { + old_alias= ren_table->table_name; + new_alias= new_table_name; + } + sprintf(name,"%s/%s/%s%s",mysql_data_home, + new_db, new_alias, reg_ext); + unpack_filename(name, name); + if (!access(name,F_OK)) + { + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias); + DBUG_RETURN(1); // This can't be skipped + } + sprintf(name,"%s/%s/%s%s",mysql_data_home, + ren_table->db, old_alias, + reg_ext); + unpack_filename(name, name); - frm_type= mysql_frm_type(thd, name, &table_type); - switch (frm_type) + frm_type= mysql_frm_type(thd, name, &table_type); + switch (frm_type) + { + case FRMTYPE_TABLE: { - case FRMTYPE_TABLE: + if (table_type == DB_TYPE_UNKNOWN) + my_error(ER_FILE_NOT_FOUND, MYF(0), name, my_errno); + else { - if (table_type == DB_TYPE_UNKNOWN) - my_error(ER_FILE_NOT_FOUND, MYF(0), name, my_errno); - else + if (!(rc= mysql_rename_table(table_type, ren_table->db, old_alias, + new_db, new_alias))) { - if (!(rc= mysql_rename_table(table_type, ren_table->db, old_alias, - new_table->db, new_alias))) + if ((rc= Table_triggers_list::change_table_name(thd, ren_table->db, + old_alias, + new_db, + new_alias))) { - if ((rc= Table_triggers_list::change_table_name(thd, ren_table->db, - old_alias, - new_table->db, - new_alias))) - { - /* - We've succeeded in renaming table's .frm and in updating - corresponding handler data, but have failed to update table's - triggers appropriately. So let us revert operations on .frm - and handler's data and report about failure to rename table. - */ - (void) mysql_rename_table(table_type, new_table->db, new_alias, - ren_table->db, old_alias); - } + /* + We've succeeded in renaming table's .frm and in updating + corresponding handler data, but have failed to update table's + triggers appropriately. So let us revert operations on .frm + and handler's data and report about failure to rename table. + */ + (void) mysql_rename_table(table_type, new_db, new_alias, + ren_table->db, old_alias); } } - break; } - case FRMTYPE_VIEW: - /* change of schema is not allowed */ - if (strcmp(ren_table->db, new_table->db)) - my_error(ER_FORBID_SCHEMA_CHANGE, MYF(0), ren_table->db, - new_table->db); - else - rc= mysql_rename_view(thd, new_alias, ren_table); - break; - default: - DBUG_ASSERT(0); // should never happen - case FRMTYPE_ERROR: - my_error(ER_FILE_NOT_FOUND, MYF(0), name, my_errno); - break; + break; } - if (rc && !skip_error) + case FRMTYPE_VIEW: + /* change of schema is not allowed */ + if (strcmp(ren_table->db, new_db)) + my_error(ER_FORBID_SCHEMA_CHANGE, MYF(0), ren_table->db, + new_db); + else + rc= mysql_rename_view(thd, new_alias, ren_table); + break; + default: + DBUG_ASSERT(0); // should never happen + case FRMTYPE_ERROR: + my_error(ER_FILE_NOT_FOUND, MYF(0), name, my_errno); + break; + } + if (rc && !skip_error) + DBUG_RETURN(1); + + DBUG_RETURN(0); + +} +/* + Rename all tables in list; Return pointer to wrong entry if something goes + wrong. Note that the table_list may be empty! +*/ + +/* + Rename tables/views in the list + + SYNPOSIS + rename_tables() + thd Thread handle + table_list List of tables to rename + skip_error Whether to skip errors + + DESCRIPTION + Take a table/view name from and odd list element and rename it to a + the name taken from list element+1. Note that the table_list may be + empty. + + RETURN + false Ok + true rename failed +*/ + +static TABLE_LIST * +rename_tables(THD *thd, TABLE_LIST *table_list, bool skip_error) +{ + TABLE_LIST *ren_table,*new_table, *tmp_table; + + DBUG_ENTER("rename_tables"); + + for (ren_table= table_list; ren_table; ren_table= new_table->next_local) + { + new_table= ren_table->next_local; + if (do_rename(thd, ren_table, new_table->db, new_table->table_name, + new_table->alias, skip_error)) DBUG_RETURN(ren_table); } DBUG_RETURN(0); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 6d17faf8509..0f0642280ce 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -35,14 +35,17 @@ const char *join_type_str[]={ "UNKNOWN","system","const","eq_ref","ref", "index_merge" }; +struct st_sargable_param; + static void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array); static bool make_join_statistics(JOIN *join, TABLE_LIST *leaves, COND *conds, DYNAMIC_ARRAY *keyuse); static bool update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse, - JOIN_TAB *join_tab, + JOIN_TAB *join_tab, uint tables, COND *conds, COND_EQUAL *cond_equal, - table_map table_map, SELECT_LEX *select_lex); + table_map table_map, SELECT_LEX *select_lex, + st_sargable_param **sargables); static int sort_keyuse(KEYUSE *a,KEYUSE *b); static void set_position(JOIN *join,uint index,JOIN_TAB *table,KEYUSE *key); static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse, @@ -1375,12 +1378,13 @@ JOIN::exec() thd->examined_row_count= 0; DBUG_VOID_RETURN; } - /* - don't reset the found rows count if there're no tables - as FOUND_ROWS() may be called. - */ + /* + Don't reset the found rows count if there're no tables as + FOUND_ROWS() may be called. Never reset the examined row count here. + It must be accumulated from all join iterations of all join parts. + */ if (tables) - thd->limit_found_rows= thd->examined_row_count= 0; + thd->limit_found_rows= 0; if (zero_result_cause) { @@ -1410,7 +1414,8 @@ JOIN::exec() simple_order= simple_group; skip_sort_order= 0; } - if (order && + if (order && + (order != group_list || !(select_options & SELECT_BIG_RESULT)) && (const_tables == tables || ((simple_order || skip_sort_order) && test_if_skip_sort_order(&join_tab[const_tables], order, @@ -1428,6 +1433,12 @@ JOIN::exec() List<Item> *curr_all_fields= &all_fields; List<Item> *curr_fields_list= &fields_list; TABLE *curr_tmp_table= 0; + /* + Initialize examined rows here because the values from all join parts + must be accumulated in examined_row_count. Hence every join + iteration must count from zero. + */ + curr_join->examined_rows= 0; if ((curr_join->select_lex->options & OPTION_SCHEMA_TABLE) && get_schema_tables_result(curr_join)) @@ -1585,6 +1596,7 @@ JOIN::exec() { DBUG_VOID_RETURN; } + sortorder= curr_join->sortorder; } thd->proc_info="Copying to group table"; @@ -1794,6 +1806,7 @@ JOIN::exec() (select_options & OPTION_FOUND_ROWS ? HA_POS_ERROR : unit->select_limit_cnt))) DBUG_VOID_RETURN; + sortorder= curr_join->sortorder; } } /* XXX: When can we have here thd->net.report_error not zero? */ @@ -1834,9 +1847,12 @@ JOIN::exec() Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF); error= do_select(curr_join, curr_fields_list, NULL, procedure); thd->limit_found_rows= curr_join->send_records; - thd->examined_row_count= curr_join->examined_rows; } + /* Accumulate the counts from all join iterations of all join parts. */ + thd->examined_row_count+= curr_join->examined_rows; + DBUG_PRINT("counts", ("thd->examined_row_count: %lu", + (ulong) thd->examined_row_count)); DBUG_VOID_RETURN; } @@ -2052,6 +2068,19 @@ static ha_rows get_quick_record_count(THD *thd, SQL_SELECT *select, DBUG_RETURN(HA_POS_ERROR); /* This shouldn't happend */ } +/* + This structure is used to collect info on potentially sargable + predicates in order to check whether they become sargable after + reading const tables. + We form a bitmap of indexes that can be used for sargable predicates. + Only such indexes are involved in range analysis. +*/ +typedef struct st_sargable_param +{ + Field *field; /* field against which to check sargability */ + Item **arg_value; /* values of potential keys for lookups */ + uint num_values; /* number of values in the above array */ +} SARGABLE_PARAM; /* Calculate the best possible join and initialize the join structure @@ -2074,6 +2103,7 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables, COND *conds, JOIN_TAB *stat,*stat_end,*s,**stat_ref; KEYUSE *keyuse,*start_keyuse; table_map outer_join=0; + SARGABLE_PARAM *sargables= 0; JOIN_TAB *stat_vector[MAX_TABLES+1]; DBUG_ENTER("make_join_statistics"); @@ -2195,7 +2225,7 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables, COND *conds, if (conds || outer_join) if (update_ref_and_keys(join->thd, keyuse_array, stat, join->tables, conds, join->cond_equal, - ~outer_join, join->select_lex)) + ~outer_join, join->select_lex, &sargables)) DBUG_RETURN(1); /* Read tables with 0 or 1 rows (system tables) */ @@ -2345,6 +2375,26 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables, COND *conds, } } while (join->const_table_map & found_ref && ref_changed); + /* + Update info on indexes that can be used for search lookups as + reading const tables may has added new sargable predicates. + */ + if (const_count && sargables) + { + for( ; sargables->field ; sargables++) + { + Field *field= sargables->field; + JOIN_TAB *stat= field->table->reginfo.join_tab; + key_map possible_keys= field->key_start; + possible_keys.intersect(field->table->keys_in_use_for_query); + bool is_const= 1; + for (uint i=0; i< sargables->num_values; i++) + is_const&= sargables->arg_value[i]->const_item(); + if (is_const) + stat[0].const_keys.merge(possible_keys); + } + } + /* Calc how many (possible) matched records in each table */ for (s=stat ; s < stat_end ; s++) @@ -2604,6 +2654,7 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end, eq_func True if we used =, <=> or IS NULL value Value used for comparison with field usable_tables Tables which can be used for key optimization + sargables IN/OUT Array of found sargable candidates NOTES If we are doing a NOT NULL comparison on a NOT NULL field in a outer join @@ -2615,8 +2666,8 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end, static void add_key_field(KEY_FIELD **key_fields,uint and_level, Item_func *cond, - Field *field, bool eq_func, Item **value, uint num_values, - table_map usable_tables) + Field *field, bool eq_func, Item **value, uint num_values, + table_map usable_tables, SARGABLE_PARAM **sargables) { uint exists_optimize= 0; if (!(field->flags & PART_KEY_FLAG)) @@ -2672,6 +2723,19 @@ add_key_field(KEY_FIELD **key_fields,uint and_level, Item_func *cond, is_const&= value[i]->const_item(); if (is_const) stat[0].const_keys.merge(possible_keys); + else if (!eq_func) + { + /* + Save info to be able check whether this predicate can be + considered as sargable for range analisis after reading const tables. + We do not save info about equalities as update_const_equal_items + will take care of updating info on keys from sargable equalities. + */ + (*sargables)--; + (*sargables)->field= field; + (*sargables)->arg_value= value; + (*sargables)->num_values= num_values; + } /* We can't always use indexes when comparing a string index to a number. cmp_type() is checked to allow compare of dates to numbers. @@ -2762,6 +2826,7 @@ add_key_field(KEY_FIELD **key_fields,uint and_level, Item_func *cond, value Value used for comparison with field Is NULL for BETWEEN and IN usable_tables Tables which can be used for key optimization + sargables IN/OUT Array of found sargable candidates NOTES If field items f1 and f2 belong to the same multiple equality and @@ -2775,11 +2840,12 @@ static void add_key_equal_fields(KEY_FIELD **key_fields, uint and_level, Item_func *cond, Item_field *field_item, bool eq_func, Item **val, - uint num_values, table_map usable_tables) + uint num_values, table_map usable_tables, + SARGABLE_PARAM **sargables) { Field *field= field_item->field; add_key_field(key_fields, and_level, cond, field, - eq_func, val, num_values, usable_tables); + eq_func, val, num_values, usable_tables, sargables); Item_equal *item_equal= field_item->item_equal; if (item_equal) { @@ -2794,7 +2860,8 @@ add_key_equal_fields(KEY_FIELD **key_fields, uint and_level, if (!field->eq(item->field)) { add_key_field(key_fields, and_level, cond, item->field, - eq_func, val, num_values, usable_tables); + eq_func, val, num_values, usable_tables, + sargables); } } } @@ -2802,7 +2869,8 @@ add_key_equal_fields(KEY_FIELD **key_fields, uint and_level, static void add_key_fields(KEY_FIELD **key_fields,uint *and_level, - COND *cond, table_map usable_tables) + COND *cond, table_map usable_tables, + SARGABLE_PARAM **sargables) { if (cond->type() == Item_func::COND_ITEM) { @@ -2813,20 +2881,20 @@ add_key_fields(KEY_FIELD **key_fields,uint *and_level, { Item *item; while ((item=li++)) - add_key_fields(key_fields,and_level,item,usable_tables); + add_key_fields(key_fields,and_level,item,usable_tables,sargables); for (; org_key_fields != *key_fields ; org_key_fields++) org_key_fields->level= *and_level; } else { (*and_level)++; - add_key_fields(key_fields,and_level,li++,usable_tables); + add_key_fields(key_fields,and_level,li++,usable_tables,sargables); Item *item; while ((item=li++)) { KEY_FIELD *start_key_fields= *key_fields; (*and_level)++; - add_key_fields(key_fields,and_level,item,usable_tables); + add_key_fields(key_fields,and_level,item,usable_tables,sargables); *key_fields=merge_key_fields(org_key_fields,start_key_fields, *key_fields,++(*and_level)); } @@ -2857,9 +2925,9 @@ add_key_fields(KEY_FIELD **key_fields,uint *and_level, cond_func->argument_count() != 2); add_key_equal_fields(key_fields, *and_level, cond_func, (Item_field*) (cond_func->key_item()->real_item()), - 0, values, + 0, values, cond_func->argument_count()-1, - usable_tables); + usable_tables, sargables); } if (cond_func->functype() == Item_func::BETWEEN) { @@ -2873,7 +2941,8 @@ add_key_fields(KEY_FIELD **key_fields,uint *and_level, { field_item= (Item_field *) (cond_func->arguments()[i]->real_item()); add_key_equal_fields(key_fields, *and_level, cond_func, - field_item, 0, values, 1, usable_tables); + field_item, 0, values, 1, usable_tables, + sargables); } } } @@ -2890,7 +2959,8 @@ add_key_fields(KEY_FIELD **key_fields,uint *and_level, add_key_equal_fields(key_fields, *and_level, cond_func, (Item_field*) (cond_func->arguments()[0])->real_item(), equal_func, - cond_func->arguments()+1, 1, usable_tables); + cond_func->arguments()+1, 1, usable_tables, + sargables); } if (cond_func->arguments()[1]->real_item()->type() == Item::FIELD_ITEM && cond_func->functype() != Item_func::LIKE_FUNC && @@ -2899,7 +2969,8 @@ add_key_fields(KEY_FIELD **key_fields,uint *and_level, add_key_equal_fields(key_fields, *and_level, cond_func, (Item_field*) (cond_func->arguments()[1])->real_item(), equal_func, - cond_func->arguments(),1,usable_tables); + cond_func->arguments(),1,usable_tables, + sargables); } break; } @@ -2914,7 +2985,7 @@ add_key_fields(KEY_FIELD **key_fields,uint *and_level, add_key_equal_fields(key_fields, *and_level, cond_func, (Item_field*) (cond_func->arguments()[0])->real_item(), cond_func->functype() == Item_func::ISNULL_FUNC, - &tmp, 1, usable_tables); + &tmp, 1, usable_tables, sargables); } break; case Item_func::OPTIMIZE_EQUAL: @@ -2932,7 +3003,7 @@ add_key_fields(KEY_FIELD **key_fields,uint *and_level, while ((item= it++)) { add_key_field(key_fields, *and_level, cond_func, item->field, - TRUE, &const_item, 1, usable_tables); + TRUE, &const_item, 1, usable_tables, sargables); } } else @@ -2952,7 +3023,8 @@ add_key_fields(KEY_FIELD **key_fields,uint *and_level, if (!field->eq(item->field)) { add_key_field(key_fields, *and_level, cond_func, field, - TRUE, (Item **) &item, 1, usable_tables); + TRUE, (Item **) &item, 1, usable_tables, + sargables); } } it.rewind(); @@ -3103,6 +3175,7 @@ sort_keyuse(KEYUSE *a,KEYUSE *b) nested_join_table IN Nested join pseudo-table to process end INOUT End of the key field array and_level INOUT And-level + sargables IN/OUT Array of found sargable candidates DESCRIPTION This function populates KEY_FIELD array with entries generated from the @@ -3126,7 +3199,8 @@ sort_keyuse(KEYUSE *a,KEYUSE *b) */ static void add_key_fields_for_nj(TABLE_LIST *nested_join_table, - KEY_FIELD **end, uint *and_level) + KEY_FIELD **end, uint *and_level, + SARGABLE_PARAM **sargables) { List_iterator<TABLE_LIST> li(nested_join_table->nested_join->join_list); table_map tables= 0; @@ -3136,12 +3210,12 @@ static void add_key_fields_for_nj(TABLE_LIST *nested_join_table, while ((table= li++)) { if (table->nested_join) - add_key_fields_for_nj(table, end, and_level); + add_key_fields_for_nj(table, end, and_level, sargables); else if (!table->on_expr) tables |= table->table->map; } - add_key_fields(end, and_level, nested_join_table->on_expr, tables); + add_key_fields(end, and_level, nested_join_table->on_expr, tables, sargables); } @@ -3156,9 +3230,10 @@ static void add_key_fields_for_nj(TABLE_LIST *nested_join_table, tables Number of tables in join cond WHERE condition (note that the function analyzes join_tab[i]->on_expr too) - normal_tables tables not inner w.r.t some outer join (ones for which + normal_tables Tables not inner w.r.t some outer join (ones for which we can make ref access based the WHERE clause) select_lex current SELECT + sargables OUT Array of found sargable candidates RETURN 0 - OK @@ -3167,27 +3242,55 @@ static void add_key_fields_for_nj(TABLE_LIST *nested_join_table, static bool update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, - uint tables, COND *cond, COND_EQUAL *cond_equal, - table_map normal_tables, SELECT_LEX *select_lex) + uint tables, COND *cond, COND_EQUAL *cond_equal, + table_map normal_tables, SELECT_LEX *select_lex, + SARGABLE_PARAM **sargables) { uint and_level,i,found_eq_constant; KEY_FIELD *key_fields, *end, *field; + uint sz; uint m= 1; if (cond_equal && cond_equal->max_members) m= cond_equal->max_members; - - if (!(key_fields=(KEY_FIELD*) - thd->alloc(sizeof(key_fields[0])* - (thd->lex->current_select->cond_count+1)*2*m))) + + /* + We use the same piece of memory to store both KEY_FIELD + and SARGABLE_PARAM structure. + KEY_FIELD values are placed at the beginning this memory + while SARGABLE_PARAM values are put at the end. + All predicates that are used to fill arrays of KEY_FIELD + and SARGABLE_PARAM structures have at most 2 arguments + except BETWEEN predicates that have 3 arguments and + IN predicates. + This any predicate if it's not BETWEEN/IN can be used + directly to fill at most 2 array elements, either of KEY_FIELD + or SARGABLE_PARAM type. For a BETWEEN predicate 3 elements + can be filled as this predicate is considered as + saragable with respect to each of its argument. + An IN predicate can require at most 1 element as currently + it is considered as sargable only for its first argument. + Multiple equality can add elements that are filled after + substitution of field arguments by equal fields. There + can be not more than cond_equal->max_members such substitutions. + */ + sz= max(sizeof(KEY_FIELD),sizeof(SARGABLE_PARAM))* + (((thd->lex->current_select->cond_count+1)*2 + + thd->lex->current_select->between_count)*m+1); + if (!(key_fields=(KEY_FIELD*) thd->alloc(sz))) return TRUE; /* purecov: inspected */ and_level= 0; field= end= key_fields; + *sargables= (SARGABLE_PARAM *) key_fields + + (sz - sizeof((*sargables)[0].field))/sizeof(SARGABLE_PARAM); + /* set a barrier for the array of SARGABLE_PARAM */ + (*sargables)[0].field= 0; + if (my_init_dynamic_array(keyuse,sizeof(KEYUSE),20,64)) return TRUE; if (cond) { - add_key_fields(&end,&and_level,cond,normal_tables); + add_key_fields(&end,&and_level,cond,normal_tables,sargables); for (; field != end ; field++) { add_key_part(keyuse,field); @@ -3210,7 +3313,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, */ if (*join_tab[i].on_expr_ref) add_key_fields(&end,&and_level,*join_tab[i].on_expr_ref, - join_tab[i].table->map); + join_tab[i].table->map,sargables); } /* Process ON conditions for the nested joins */ @@ -3220,7 +3323,7 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, while ((table= li++)) { if (table->nested_join) - add_key_fields_for_nj(table, &end, &and_level); + add_key_fields_for_nj(table, &end, &and_level, sargables); } } @@ -4960,9 +5063,28 @@ make_simple_join(JOIN *join,TABLE *tmp_table) JOIN_TAB *join_tab; DBUG_ENTER("make_simple_join"); - if (!(tableptr=(TABLE**) join->thd->alloc(sizeof(TABLE*))) || - !(join_tab=(JOIN_TAB*) join->thd->alloc(sizeof(JOIN_TAB)))) - DBUG_RETURN(TRUE); + /* + Reuse TABLE * and JOIN_TAB if already allocated by a previous call + to this function through JOIN::exec (may happen for sub-queries). + */ + if (!join->table_reexec) + { + if (!(join->table_reexec= (TABLE**) join->thd->alloc(sizeof(TABLE*)))) + DBUG_RETURN(TRUE); /* purecov: inspected */ + if (join->tmp_join) + join->tmp_join->table_reexec= join->table_reexec; + } + if (!join->join_tab_reexec) + { + if (!(join->join_tab_reexec= + (JOIN_TAB*) join->thd->alloc(sizeof(JOIN_TAB)))) + DBUG_RETURN(TRUE); /* purecov: inspected */ + if (join->tmp_join) + join->tmp_join->join_tab_reexec= join->join_tab_reexec; + } + tableptr= join->table_reexec; + join_tab= join->join_tab_reexec; + join->join_tab=join_tab; join->table=tableptr; tableptr[0]=tmp_table; join->tables=1; @@ -7344,7 +7466,22 @@ static void update_const_equal_items(COND *cond, JOIN_TAB *tab) ((Item_cond*) cond)->functype() == Item_func::MULT_EQUAL_FUNC) { Item_equal *item_equal= (Item_equal *) cond; + bool contained_const= item_equal->get_const() != NULL; item_equal->update_const(); + if (!contained_const && item_equal->get_const()) + { + /* Update keys for range analysis */ + Item_equal_iterator it(*item_equal); + Item_field *item_field; + while ((item_field= it++)) + { + Field *field= item_field->field; + JOIN_TAB *stat= field->table->reginfo.join_tab; + key_map possible_keys= field->key_start; + possible_keys.intersect(field->table->keys_in_use_for_query); + stat[0].const_keys.merge(possible_keys); + } + } } } @@ -10141,6 +10278,8 @@ evaluate_join_record(JOIN *join, JOIN_TAB *join_tab, */ join->examined_rows++; join->thd->row_count++; + DBUG_PRINT("counts", ("join->examined_rows++: %lu", + (ulong) join->examined_rows)); if (found) { @@ -11993,7 +12132,6 @@ static int create_sort_index(THD *thd, JOIN *join, ORDER *order, ha_rows filesort_limit, ha_rows select_limit) { - SORT_FIELD *sortorder; uint length; ha_rows examined_rows; TABLE *table; @@ -12007,11 +12145,18 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, table= tab->table; select= tab->select; - if (test_if_skip_sort_order(tab,order,select_limit,0)) + /* + When there is SQL_BIG_RESULT do not sort using index for GROUP BY, + and thus force sorting on disk. + */ + if ((order != join->group_list || + !(join->select_options & SELECT_BIG_RESULT)) && + test_if_skip_sort_order(tab,order,select_limit,0)) DBUG_RETURN(0); - if (!(sortorder=make_unireg_sortorder(order,&length))) + if (!(join->sortorder= + make_unireg_sortorder(order,&length,join->sortorder))) goto err; /* purecov: inspected */ - /* It's not fatal if the following alloc fails */ + table->sort.io_cache=(IO_CACHE*) my_malloc(sizeof(IO_CACHE), MYF(MY_WME | MY_ZEROFILL)); table->status=0; // May be wrong if quick_select @@ -12056,7 +12201,7 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, if (table->s->tmp_table) table->file->info(HA_STATUS_VARIABLE); // Get record count - table->sort.found_records=filesort(thd, table,sortorder, length, + table->sort.found_records=filesort(thd, table,join->sortorder, length, select, filesort_limit, &examined_rows); tab->records= table->sort.found_records; // For SQL_CALC_ROWS if (select) @@ -12403,7 +12548,8 @@ err: } -SORT_FIELD *make_unireg_sortorder(ORDER *order, uint *length) +SORT_FIELD *make_unireg_sortorder(ORDER *order, uint *length, + SORT_FIELD *sortorder) { uint count; SORT_FIELD *sort,*pos; @@ -12412,7 +12558,9 @@ SORT_FIELD *make_unireg_sortorder(ORDER *order, uint *length) count=0; for (ORDER *tmp = order; tmp; tmp=tmp->next) count++; - pos=sort=(SORT_FIELD*) sql_alloc(sizeof(SORT_FIELD)*(count+1)); + if (!sortorder) + sortorder= (SORT_FIELD*) sql_alloc(sizeof(SORT_FIELD)*(count+1)); + pos=sort=sortorder; if (!pos) return 0; @@ -13525,7 +13673,19 @@ bool JOIN::alloc_func_list() disctinct->group_by optimization */ if (select_distinct) + { group_parts+= fields_list.elements; + /* + If the ORDER clause is specified then it's possible that + it also will be optimized, so reserve space for it too + */ + if (order) + { + ORDER *ord; + for (ord= order; ord; ord= ord->next) + group_parts++; + } + } /* This must use calloc() as rollup_make_fields depends on this */ sum_funcs= (Item_sum**) thd->calloc(sizeof(Item_sum**) * (func_count+1) + @@ -14008,12 +14168,17 @@ bool JOIN::rollup_init() while ((item= it++)) { ORDER *group_tmp; + bool found_in_group= 0; + for (group_tmp= group_list; group_tmp; group_tmp= group_tmp->next) { if (*group_tmp->item == item) + { item->maybe_null= 1; + found_in_group= 1; + } } - if (item->type() == Item::FUNC_ITEM) + if (item->type() == Item::FUNC_ITEM && !found_in_group) { bool changed= FALSE; if (change_group_ref(thd, (Item_func *) item, group_list, &changed)) diff --git a/sql/sql_select.h b/sql/sql_select.h index d5a1bf82bc8..30b8f834ddf 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -282,6 +282,18 @@ public: bool union_part; // this subselect is part of union bool optimized; // flag to avoid double optimization in EXPLAIN + /* + storage for caching buffers allocated during query execution. + These buffers allocations need to be cached as the thread memory pool is + cleared only at the end of the execution of the whole query and not caching + allocations that occur in repetition at execution time will result in + excessive memory usage. + */ + SORT_FIELD *sortorder; // make_unireg_sortorder() + TABLE **table_reexec; // make_simple_join() + JOIN_TAB *join_tab_reexec; // make_simple_join() + /* end of allocation caching storage */ + JOIN(THD *thd_arg, List<Item> &fields_arg, ulonglong select_options_arg, select_result *result_arg) :fields_list(fields_arg) @@ -307,6 +319,9 @@ public: examined_rows= 0; exec_tmp_table1= 0; exec_tmp_table2= 0; + sortorder= 0; + table_reexec= 0; + join_tab_reexec= 0; thd= thd_arg; sum_funcs= sum_funcs2= 0; procedure= 0; diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 1c5be098ae0..f6dbba4aa79 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -3155,9 +3155,10 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, ha_rows copied,deleted; ulonglong next_insert_id; uint db_create_options, used_fields; - enum db_type old_db_type,new_db_type; + enum db_type old_db_type, new_db_type, table_type; bool need_copy_table; bool no_table_reopen= FALSE, varchar= FALSE; + frm_type_enum frm_type; DBUG_ENTER("mysql_alter_table"); thd->proc_info="init"; @@ -3175,6 +3176,51 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, if (alter_info->tablespace_op != NO_TABLESPACE_OP) DBUG_RETURN(mysql_discard_or_import_tablespace(thd,table_list, alter_info->tablespace_op)); + sprintf(new_name_buff,"%s/%s/%s%s",mysql_data_home, db, table_name, reg_ext); + unpack_filename(new_name_buff, new_name_buff); + if (lower_case_table_names != 2) + my_casedn_str(files_charset_info, new_name_buff); + frm_type= mysql_frm_type(thd, new_name_buff, &table_type); + /* Rename a view */ + if (frm_type == FRMTYPE_VIEW && !(alter_info->flags & ~ALTER_RENAME)) + { + /* + Avoid problems with a rename on a table that we have locked or + if the user is trying to to do this in a transcation context + */ + + if (thd->locked_tables || thd->active_transaction()) + { + my_message(ER_LOCK_OR_ACTIVE_TRANSACTION, + ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0)); + DBUG_RETURN(1); + } + + if (wait_if_global_read_lock(thd,0,1)) + DBUG_RETURN(1); + VOID(pthread_mutex_lock(&LOCK_open)); + if (lock_table_names(thd, table_list)) + goto view_err; + + error=0; + if (!do_rename(thd, table_list, new_db, new_name, new_name, 1)) + { + if (mysql_bin_log.is_open()) + { + thd->clear_error(); + Query_log_event qinfo(thd, thd->query, thd->query_length, 0, FALSE); + mysql_bin_log.write(&qinfo); + } + send_ok(thd); + } + + unlock_table_names(thd, table_list, (TABLE_LIST*) 0); + +view_err: + pthread_mutex_unlock(&LOCK_open); + start_waiting_global_read_lock(thd); + DBUG_RETURN(error); + } if (!(table=open_ltable(thd,table_list,TL_WRITE_ALLOW_READ))) DBUG_RETURN(TRUE); @@ -4050,7 +4096,7 @@ copy_data_between_tables(TABLE *from,TABLE *to, if (thd->lex->select_lex.setup_ref_array(thd, order_num) || setup_order(thd, thd->lex->select_lex.ref_pointer_array, &tables, fields, all_fields, order) || - !(sortorder=make_unireg_sortorder(order, &length)) || + !(sortorder=make_unireg_sortorder(order, &length, NULL)) || (from->sort.found_records = filesort(thd, from, sortorder, length, (SQL_SELECT *) 0, HA_POS_ERROR, &examined_rows)) == diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 24577f63124..d431b671f18 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -311,7 +311,7 @@ int mysql_update(THD *thd, table->sort.io_cache = (IO_CACHE *) my_malloc(sizeof(IO_CACHE), MYF(MY_FAE | MY_ZEROFILL)); - if (!(sortorder=make_unireg_sortorder(order, &length)) || + if (!(sortorder=make_unireg_sortorder(order, &length, NULL)) || (table->sort.found_records = filesort(thd, table, sortorder, length, select, limit, &examined_rows)) diff --git a/sql/table.cc b/sql/table.cc index 1886fdb0b82..3a76ba0cc1a 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -468,7 +468,21 @@ int openfrm(THD *thd, const char *name, const char *alias, uint db_stat, count))) goto err; for (count= 0; count < interval->count; count++) - interval->type_lengths[count]= strlen(interval->type_names[count]); + { + char *val= (char*) interval->type_names[count]; + interval->type_lengths[count]= strlen(val); + /* + Replace all ',' symbols with NAMES_SEP_CHAR. + See the comment in unireg.cc, pack_fields() function + for details. + */ + for (uint cnt= 0 ; cnt < interval->type_lengths[count] ; cnt++) + { + char c= val[cnt]; + if (c == ',') + val[cnt]= NAMES_SEP_CHAR; + } + } interval->type_lengths[count]= 0; } } @@ -1963,12 +1977,13 @@ bool st_table_list::prep_where(THD *thd, Item **conds, this expression will not be moved to WHERE condition (i.e. will be clean correctly for PS/SP) */ - tbl->on_expr= and_conds(tbl->on_expr, where); + tbl->on_expr= and_conds(tbl->on_expr, + where->copy_andor_structure(thd)); break; } } if (tbl == 0) - *conds= and_conds(*conds, where); + *conds= and_conds(*conds, where->copy_andor_structure(thd)); if (arena) thd->restore_active_arena(arena, &backup); where_processed= TRUE; diff --git a/sql/unireg.cc b/sql/unireg.cc index 93afd9c9e4e..768a288ca19 100644 --- a/sql/unireg.cc +++ b/sql/unireg.cc @@ -718,6 +718,21 @@ static bool pack_fields(File file, List<create_field> &create_fields, tmp.append(NAMES_SEP_CHAR); for (const char **pos=field->interval->type_names ; *pos ; pos++) { + char *val= (char*) *pos; + uint str_len= strlen(val); + /* + Note, hack: in old frm NAMES_SEP_CHAR is used to separate + names in the interval (ENUM/SET). To allow names to contain + NAMES_SEP_CHAR, we replace it with a comma before writing frm. + Backward conversion is done during frm file opening, + See table.cc, openfrm() function + */ + for (uint cnt= 0 ; cnt < str_len ; cnt++) + { + char c= val[cnt]; + if (c == NAMES_SEP_CHAR) + val[cnt]= ','; + } tmp.append(*pos); tmp.append(NAMES_SEP_CHAR); } |