From e6bff9a606691c068cc173bc25f8539517e46925 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 27 Dec 2005 15:04:35 +0300 Subject: [PATCH 1/7] WL#2985 "Partition pruning", postreview fixes: Small code fixes and better comments mysql-test/r/partition.result: Added testcase for BUG#15819 mysql-test/t/partition.test: Added testcase for BUG#15819 sql/item.h: WL#2985 "Partition pruning", postreview fixes: better comments sql/item_timefunc.cc: WL#2985 "Partition pruning", postreview fixes: better comments sql/opt_range.cc: WL#2985 "Partition pruning", postreview fixes: - better comments, local function renames - Made SEL_ARG::is_singlepoint() to correctly handle NULL edge values. - fix uninitialized variable access: s/res |=/res =/ sql/sql_class.cc: WL#2985 "Partition pruning", postreview fixes: Set correct max. length of "partitions" column in EXPLAIN output sql/sql_lex.h: WL#2985 "Partition pruning", postreview fixes: better comments sql/sql_partition.cc: WL#2985 "Partition pruning", postreview fixes: better comments --- mysql-test/r/partition.result | 7 ++ mysql-test/t/partition.test | 11 ++++ sql/item.h | 11 +++- sql/item_timefunc.cc | 30 +++++++++ sql/opt_range.cc | 121 ++++++++++++++++++++++++++-------- sql/sql_class.cc | 5 +- sql/sql_lex.h | 2 +- sql/sql_partition.cc | 10 ++- 8 files changed, 163 insertions(+), 34 deletions(-) diff --git a/mysql-test/r/partition.result b/mysql-test/r/partition.result index 58f02681682..13e6ddda205 100644 --- a/mysql-test/r/partition.result +++ b/mysql-test/r/partition.result @@ -148,3 +148,10 @@ t1 CREATE TABLE `t1` ( `b` int(11) default NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1 PARTITION BY RANGE (a) (PARTITION x1 VALUES LESS THAN (6) ENGINE = MyISAM, PARTITION x3 VALUES LESS THAN (8) ENGINE = MyISAM, PARTITION x4 VALUES LESS THAN (10) ENGINE = MyISAM, PARTITION x5 VALUES LESS THAN (12) ENGINE = MyISAM, PARTITION x6 VALUES LESS THAN (14) ENGINE = MyISAM, PARTITION x7 VALUES LESS THAN (16) ENGINE = MyISAM, PARTITION x8 VALUES LESS THAN (18) ENGINE = MyISAM, PARTITION x9 VALUES LESS THAN (20) ENGINE = MyISAM) drop table t1; +create table t1 (a int not null, b int not null) partition by LIST (a+b) ( +partition p0 values in (12), +partition p1 values in (14) +); +insert into t1 values (10,1); +ERROR HY000: Table has no partition for value 11 +drop table t1; diff --git a/mysql-test/t/partition.test b/mysql-test/t/partition.test index 8b1c3f58071..609e24023d7 100644 --- a/mysql-test/t/partition.test +++ b/mysql-test/t/partition.test @@ -203,3 +203,14 @@ ALTER TABLE t1 REORGANISE PARTITION x0,x1,x2 INTO (PARTITION x1 VALUES LESS THAN (6)); show create table t1; drop table t1; + +# Testcase for BUG#15819 +create table t1 (a int not null, b int not null) partition by LIST (a+b) ( + partition p0 values in (12), + partition p1 values in (14) +); +--error 1500 +insert into t1 values (10,1); + +drop table t1; + diff --git a/sql/item.h b/sql/item.h index c240733bd04..ec643dcf0c8 100644 --- a/sql/item.h +++ b/sql/item.h @@ -381,13 +381,20 @@ public: put values of field_i into table record buffer; return item->val_int(); } + + NOTE + At the moment function monotonicity is not well defined (and so may be + incorrect) for Item trees with parameters/return types that are different + from INT_RESULT, may be NULL, or are unsigned. + It will be possible to address this issue once the related partitioning bugs + (BUG#16002, BUG#15447, BUG#13436) are fixed. */ typedef enum monotonicity_info { NON_MONOTONIC, /* none of the below holds */ - MONOTONIC_INCREASING, /* F() is unary and "x < y" => "F(x) < F(y)" */ - MONOTONIC_STRICT_INCREASING /* F() is unary and "x < y" => "F(x) <= F(y)" */ + MONOTONIC_INCREASING, /* F() is unary and (x < y) => (F(x) <= F(y)) */ + MONOTONIC_STRICT_INCREASING /* F() is unary and (x < y) => (F(x) < F(y)) */ } enum_monotonicity_info; /*************************************************************************/ diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 3560a74ddb2..c14a55a6358 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -885,6 +885,21 @@ longlong Item_func_to_days::val_int() return (longlong) calc_daynr(ltime.year,ltime.month,ltime.day); } + +/* + Get information about this Item tree monotonicity + + SYNOPSIS + Item_func_to_days::get_monotonicity_info() + + DESCRIPTION + Get information about monotonicity of the function represented by this item + tree. + + RETURN + See enum_monotonicity_info. +*/ + enum_monotonicity_info Item_func_to_days::get_monotonicity_info() const { if (args[0]->type() == Item::FIELD_ITEM) @@ -1080,6 +1095,21 @@ longlong Item_func_year::val_int() return (longlong) ltime.year; } + +/* + Get information about this Item tree monotonicity + + SYNOPSIS + Item_func_to_days::get_monotonicity_info() + + DESCRIPTION + Get information about monotonicity of the function represented by this item + tree. + + RETURN + See enum_monotonicity_info. +*/ + enum_monotonicity_info Item_func_year::get_monotonicity_info() const { if (args[0]->type() == Item::FIELD_ITEM && diff --git a/sql/opt_range.cc b/sql/opt_range.cc index f090fac2ecf..f3dc96eb868 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -313,11 +313,46 @@ public: } SEL_ARG *clone_tree(); - /* Return TRUE if this represents "keypartK = const" or "keypartK IS NULL" */ + + /* + Check if this SEL_ARG object represents a single-point interval + + SYNOPSIS + is_singlepoint() + + DESCRIPTION + Check if this SEL_ARG object (not tree) represents a single-point + interval, i.e. if it represents a "keypart = const" or + "keypart IS NULL". + + RETURN + TRUE This SEL_ARG object represents a singlepoint interval + FALSE Otherwise + */ + bool is_singlepoint() { - return !min_flag && !max_flag && - !field->key_cmp((byte*) min_value, (byte*)max_value); + /* + Check for NEAR_MIN ("strictly less") and NO_MIN_RANGE (-inf < field) + flags, and the same for right edge. + */ + if (min_flag || max_flag) + return FALSE; + byte *min_val= min_value; + byte *max_val= min_value; + + if (maybe_null) + { + /* First byte is a NULL value indicator */ + if (*min_val != *max_val) + return FALSE; + + if (*min_val) + return TRUE; /* This "x IS NULL" */ + min_val++; + max_val++; + } + return !field->key_cmp(min_val, max_val); } }; @@ -2110,7 +2145,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, Putting it all together, partitioning module works as follows: prune_partitions() { - call create_partition_index_descrition(); + call create_partition_index_description(); call get_mm_tree(); // invoke the RangeAnalysisModule @@ -2229,7 +2264,7 @@ typedef struct st_part_prune_param part_num_to_partition_id_func part_num_to_part_id; } PART_PRUNE_PARAM; -static bool create_partition_index_descrition(PART_PRUNE_PARAM *prune_par); +static bool create_partition_index_description(PART_PRUNE_PARAM *prune_par); static int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree); static int find_used_partitions_imerge(PART_PRUNE_PARAM *ppar, SEL_IMERGE *imerge); @@ -2243,7 +2278,7 @@ static uint32 part_num_to_part_id_range(PART_PRUNE_PARAM* prune_par, static void print_partitioning_index(KEY_PART *parts, KEY_PART *parts_end); static void dbug_print_field(Field *field); static void dbug_print_segment_range(SEL_ARG *arg, KEY_PART *part); -static void dbug_print_onepoint_range(SEL_ARG **start, uint num); +static void dbug_print_singlepoint_range(SEL_ARG **start, uint num); #endif @@ -2297,7 +2332,7 @@ bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond) range_par->mem_root= &alloc; range_par->old_root= thd->mem_root; - if (create_partition_index_descrition(&prune_param)) + if (create_partition_index_description(&prune_param)) { mark_all_partitions_as_used(part_info); free_root(&alloc,MYF(0)); // Return memory & allocator @@ -2335,9 +2370,10 @@ bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond) if (tree->type != SEL_TREE::KEY && tree->type != SEL_TREE::KEY_SMALLER) goto all_used; - + if (tree->merges.is_empty()) { + /* Range analysis has produced a single list of intervals. */ prune_param.arg_stack_end= prune_param.arg_stack; prune_param.cur_part_fields= 0; prune_param.cur_subpart_fields= 0; @@ -2352,14 +2388,30 @@ bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond) { if (tree->merges.elements == 1) { - if (-1 == (res |= find_used_partitions_imerge(&prune_param, - tree->merges.head()))) + /* + Range analysis has produced a "merge" of several intervals lists, a + SEL_TREE that represents an expression in form + sel_imerge = (tree1 OR tree2 OR ... OR treeN) + that cannot be reduced to one tree. This can only happen when + partitioning index has several keyparts and the condition is OR of + conditions that refer to different key parts. For example, we'll get + here for "partitioning_field=const1 OR subpartitioning_field=const2" + */ + if (-1 == (res= find_used_partitions_imerge(&prune_param, + tree->merges.head()))) goto all_used; } else { - if (-1 == (res |= find_used_partitions_imerge_list(&prune_param, - tree->merges))) + /* + Range analysis has produced a list of several imerges, i.e. a + structure that represents a condition in form + imerge_list= (sel_imerge1 AND sel_imerge2 AND ... AND sel_imergeN) + This is produced for complicated WHERE clauses that range analyzer + can't really analyze properly. + */ + if (-1 == (res= find_used_partitions_imerge_list(&prune_param, + tree->merges))) goto all_used; } } @@ -2384,12 +2436,19 @@ end: /* - Store key image to table record + Store field key image to table record SYNOPSIS - field Field which key image should be stored. - ptr Field value in key format. - len Length of the value, in bytes. + store_key_image_to_rec() + field Field which key image should be stored + ptr Field value in key format + len Length of the value, in bytes + + DESCRIPTION + Copy the field value from its key image to the table record. The source + is the value in key image format, occupying len bytes in buffer pointed + by ptr. The destination is table record, in "field value in table record" + format. */ static void store_key_image_to_rec(Field *field, char *ptr, uint len) @@ -2414,8 +2473,12 @@ static void store_key_image_to_rec(Field *field, char *ptr, uint len) SYNOPSIS store_selargs_to_rec() ppar Partition pruning context - start Array SEL_ARG* for which the minimum values should be stored + start Array of SEL_ARG* for which the minimum values should be stored num Number of elements in the array + + DESCRIPTION + For each SEL_ARG* interval in the specified array, store the left edge + field value (sel_arg->min, key image format) into the table record. */ static void store_selargs_to_rec(PART_PRUNE_PARAM *ppar, SEL_ARG **start, @@ -2569,7 +2632,7 @@ int find_used_partitions_imerge(PART_PRUNE_PARAM *ppar, SEL_IMERGE *imerge) DESCRIPTION This function - * recursively walks the SEL_ARG* tree, collecting partitioning + * recursively walks the SEL_ARG* tree collecting partitioning "intervals"; * finds the partitions one needs to use to get rows in these intervals; * marks these partitions as used. @@ -2578,9 +2641,9 @@ int find_used_partitions_imerge(PART_PRUNE_PARAM *ppar, SEL_IMERGE *imerge) A partition pruning "interval" is equivalent to condition in one of the forms: - "partition_field1=const1 AND ... partition_fieldN=constN" (1) - "subpartition_field1=const1 AND ... subpartition_fieldN=constN" (2) - "(1) AND (2)" (3) + "partition_field1=const1 AND ... AND partition_fieldN=constN" (1) + "subpartition_field1=const1 AND ... AND subpartition_fieldN=constN" (2) + "(1) AND (2)" (3) In (1) and (2) all [sub]partitioning fields must be used, and "x=const" includes "x IS NULL". @@ -2591,7 +2654,7 @@ int find_used_partitions_imerge(PART_PRUNE_PARAM *ppar, SEL_IMERGE *imerge) then the following is also an interval: - " const1 OP1 single_partition_field OR const2" (4) + " const1 OP1 single_partition_field OP2 const2" (4) where OP1 and OP2 are '<' OR '<=', and const_i can be +/- inf. Everything else is not a partition pruning "interval". @@ -2695,7 +2758,7 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree) fields. Save all constN constants into table record buffer. */ store_selargs_to_rec(ppar, ppar->arg_stack, ppar->part_fields); - DBUG_EXECUTE("info", dbug_print_onepoint_range(ppar->arg_stack, + DBUG_EXECUTE("info", dbug_print_singlepoint_range(ppar->arg_stack, ppar->part_fields);); uint32 part_id; /* then find in which partition the {const1, ...,constN} tuple goes */ @@ -2725,7 +2788,7 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree) */ store_selargs_to_rec(ppar, ppar->arg_stack_end - ppar->subpart_fields, ppar->subpart_fields); - DBUG_EXECUTE("info", dbug_print_onepoint_range(ppar->arg_stack_end - + DBUG_EXECUTE("info", dbug_print_singlepoint_range(ppar->arg_stack_end- ppar->subpart_fields, ppar->subpart_fields);); /* Find the subpartition (it's HASH/KEY so we always have one) */ @@ -2852,7 +2915,7 @@ static bool fields_ok_for_partition_index(Field **pfield) struct SYNOPSIS - create_partition_index_descrition() + create_partition_index_description() prune_par INOUT Partition pruning context DESCRIPTION @@ -2869,7 +2932,7 @@ static bool fields_ok_for_partition_index(Field **pfield) FALSE OK */ -static bool create_partition_index_descrition(PART_PRUNE_PARAM *ppar) +static bool create_partition_index_description(PART_PRUNE_PARAM *ppar) { RANGE_OPT_PARAM *range_par= &(ppar->range_param); partition_info *part_info= ppar->part_info; @@ -3056,7 +3119,7 @@ static void dbug_print_segment_range(SEL_ARG *arg, KEY_PART *part) Print a singlepoint multi-keypart range interval to debug trace SYNOPSIS - dbug_print_onepoint_range() + dbug_print_singlepoint_range() start Array of SEL_ARG* ptrs representing conditions on key parts num Number of elements in the array. @@ -3065,9 +3128,9 @@ static void dbug_print_segment_range(SEL_ARG *arg, KEY_PART *part) interval to debug trace. */ -static void dbug_print_onepoint_range(SEL_ARG **start, uint num) +static void dbug_print_singlepoint_range(SEL_ARG **start, uint num) { - DBUG_ENTER("dbug_print_onepoint_range"); + DBUG_ENTER("dbug_print_singlepoint_range"); DBUG_LOCK_FILE; SEL_ARG **end= start + num; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 7c6f61d6edc..c51a01fc215 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -758,7 +758,10 @@ int THD::send_explain_fields(select_result *result) #ifdef WITH_PARTITION_STORAGE_ENGINE if (lex->describe & DESCRIBE_PARTITIONS) { - field_list.push_back(item= new Item_empty_string("partitions", 10, cs)); + /* Maximum length of string that make_used_partitions_str() can produce */ + item= new Item_empty_string("partitions", MAX_PARTITIONS * (1 + FN_LEN), + cs); + field_list.push_back(item); item->maybe_null= 1; } #endif diff --git a/sql/sql_lex.h b/sql/sql_lex.h index eb2be2691b3..b01af13201d 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -104,7 +104,7 @@ enum enum_sql_command { #define DESCRIBE_NORMAL 1 #define DESCRIBE_EXTENDED 2 /* - This is not #ifdef'ed because we want "EXPLAIN PARTITIONS ..." to produce + This is not within #ifdef because we want "EXPLAIN PARTITIONS ..." to produce additional "partitions" column even if partitioning is not compiled in. */ #define DESCRIBE_PARTITIONS 4 diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 5d071c5591c..498cd4e20e8 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -3467,11 +3467,19 @@ void set_key_field_ptr(KEY *key_info, const byte *new_buf, /* - Fill the string comma-separated line of used partitions names + Return comma-separated list of used partitions in the provided given string + SYNOPSIS make_used_partitions_str() part_info IN Partitioning info parts_str OUT The string to fill + + DESCRIPTION + Generate a list of used partitions (from bits in part_info->used_partitions + bitmap), asd store it into the provided String object. + + NOTE + The produced string must not be longer then MAX_PARTITIONS * (1 + FN_LEN). */ void make_used_partitions_str(partition_info *part_info, String *parts_str) From 78d1abbaf9c016843c0880edc60d084f5079d9e2 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 29 Dec 2005 09:32:46 +0300 Subject: [PATCH 2/7] WL#2985 "Partition Pruning": post-review fixes: Better comments mysql-test/r/partition_pruning.result: WL#2985 "Partition Pruning": more tests mysql-test/t/partition_pruning.test: WL#2985 "Partition Pruning": more tests sql/handler.h: WL#2985 "Partition Pruning": post-review fixes: comments --- mysql-test/r/partition_pruning.result | 15 +++++++++++++ mysql-test/t/partition_pruning.test | 15 ++++++++++++- sql/handler.h | 4 ++-- sql/opt_range.cc | 31 +++++++++++++++------------ 4 files changed, 48 insertions(+), 17 deletions(-) diff --git a/mysql-test/r/partition_pruning.result b/mysql-test/r/partition_pruning.result index a503515e4f1..ef431b2c00e 100644 --- a/mysql-test/r/partition_pruning.result +++ b/mysql-test/r/partition_pruning.result @@ -259,3 +259,18 @@ explain partitions select * from t1 where a is not null; id select_type table partitions type possible_keys key key_len ref rows Extra 1 SIMPLE t1 p0,p1 ALL NULL NULL NULL NULL 2 Using where drop table t1; +create table t1 (a int not null, b int not null, key(a), key(b)) +partition by hash(a) partitions 4; +insert into t1 values (1,1),(2,2),(3,3),(4,4); +explain partitions +select * from t1 X, t1 Y +where X.b = Y.b and (X.a=1 or X.a=2) and (Y.a=2 or Y.a=3); +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE X p1,p2 ALL a,b NULL NULL NULL 4 Using where +1 SIMPLE Y p2,p3 ref a,b b 4 test.X.b 2 Using where +explain partitions +select * from t1 X, t1 Y where X.a = Y.a and (X.a=1 or X.a=2); +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE X p1,p2 ALL a NULL NULL NULL 4 Using where +1 SIMPLE Y p1,p2 ref a a 4 test.X.a 2 +drop table t1; diff --git a/mysql-test/t/partition_pruning.test b/mysql-test/t/partition_pruning.test index 521ebaef35f..0d6bd344159 100644 --- a/mysql-test/t/partition_pruning.test +++ b/mysql-test/t/partition_pruning.test @@ -230,9 +230,22 @@ create table t1 (a int) partition by hash(a) partitions 2; insert into t1 values (1),(2); explain partitions select * from t1 where a is null; -# this selects both +# this uses both partitions explain partitions select * from t1 where a is not null; drop table t1; +# Join tests +create table t1 (a int not null, b int not null, key(a), key(b)) + partition by hash(a) partitions 4; +insert into t1 values (1,1),(2,2),(3,3),(4,4); + +explain partitions +select * from t1 X, t1 Y +where X.b = Y.b and (X.a=1 or X.a=2) and (Y.a=2 or Y.a=3); + +explain partitions +select * from t1 X, t1 Y where X.a = Y.a and (X.a=1 or X.a=2); + +drop table t1; # No tests for NULLs in RANGE(monotonic_expr()) - they depend on BUG#15447 # being fixed. diff --git a/sql/handler.h b/sql/handler.h index eff4ecdc4d2..9c67bb8a1f4 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -567,9 +567,9 @@ public: */ get_subpart_id_func get_subpartition_id; - /* NULL-terminated list of fields used in partitioned expression */ + /* NULL-terminated array of fields used in partitioned expression */ Field **part_field_array; - /* NULL-terminated list of fields used in subpartitioned expression */ + /* NULL-terminated array of fields used in subpartitioned expression */ Field **subpart_field_array; /* diff --git a/sql/opt_range.cc b/sql/opt_range.cc index f3dc96eb868..a2fdea1bf6e 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -2246,11 +2246,11 @@ typedef struct st_part_prune_param /*************************************************************** Following fields are used to store an 'iterator' that can be - used to obtain a set of used artitions. + used to obtain a set of used partitions. **************************************************************/ /* Start and end+1 partition "numbers". They can have two meanings depending - depending of the value of part_num_to_part_id: + of the value of part_num_to_part_id: part_num_to_part_id_range - numbers are partition ids part_num_to_part_id_list - numbers are indexes in part_info->list_array */ @@ -2536,7 +2536,7 @@ static uint32 part_num_to_part_id_list(PART_PRUNE_PARAM* ppar, uint32 num) List represents "imerge1 AND imerge2 AND ...". The set of used partitions is an intersection of used partitions sets for imerge_{i}. - We accumulate this intersection a separate bitmap. + We accumulate this intersection in a separate bitmap. RETURN See find_used_partitions() @@ -2554,7 +2554,7 @@ static int find_used_partitions_imerge_list(PART_PRUNE_PARAM *ppar, bitmap_bytes))) { /* - Fallback, process just first SEL_IMERGE. This can leave us with more + Fallback, process just the first SEL_IMERGE. This can leave us with more partitions marked as used then actually needed. */ return find_used_partitions_imerge(ppar, merges.head()); @@ -2623,20 +2623,20 @@ int find_used_partitions_imerge(PART_PRUNE_PARAM *ppar, SEL_IMERGE *imerge) /* - Recursively walk the SEL_ARG tree, find/mark partitions that need to be used + Collect partitioning ranges for the SEL_ARG tree and mark partitions as used SYNOPSIS find_used_partitions() ppar Partition pruning context. - key_tree Intervals tree to perform pruning for. + key_tree SEL_ARG range tree to perform pruning for DESCRIPTION This function - * recursively walks the SEL_ARG* tree collecting partitioning - "intervals"; - * finds the partitions one needs to use to get rows in these intervals; - * marks these partitions as used. - + * recursively walks the SEL_ARG* tree collecting partitioning "intervals" + * finds the partitions one needs to use to get rows in these intervals + * marks these partitions as used + + NOTES WHAT IS CONSIDERED TO BE "INTERVALS" A partition pruning "interval" is equivalent to condition in one of the forms: @@ -2687,7 +2687,7 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree) { /* Partitioning is done by RANGE|INTERVAL(monotonic_expr(fieldX)), and - we got "const1 < fieldX < const2" interval. + we got "const1 CMP fieldX CMP const2" interval */ DBUG_EXECUTE("info", dbug_print_segment_range(key_tree, ppar->range_param. @@ -2761,7 +2761,7 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree) DBUG_EXECUTE("info", dbug_print_singlepoint_range(ppar->arg_stack, ppar->part_fields);); uint32 part_id; - /* then find in which partition the {const1, ...,constN} tuple goes */ + /* Find in which partition the {const1, ...,constN} tuple goes */ if (ppar->get_top_partition_id_func(ppar->part_info, &part_id)) { res= 0; /* No satisfying partitions */ @@ -2843,7 +2843,10 @@ process_next_key_part: if (set_full_part_if_bad_ret) { - /* Restore the "used partition iterator" to its default */ + /* + Restore the "used partitions iterator" to the default setting that + specifies iteration over all partitions. + */ ppar->part_num_to_part_id= part_num_to_part_id_range; ppar->start_part_num= 0; ppar->end_part_num= ppar->part_info->no_parts; From dc2a6e226d4d31ba976061538c4b469ab8596a2b Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 4 Jan 2006 11:09:01 +0300 Subject: [PATCH 3/7] =?UTF-8?q?WL#2985=20"Partition=20Pruning":=20-=20post?= =?UTF-8?q?-...-post=20review=20fixes=20-=20Added=20"integer=20range=20wal?= =?UTF-8?q?king"=20that=20allows=20to=20do=20partition=20pruning=20for=20"?= =?UTF-8?q?a=20<=3D=3F=20t.field=20<=3D=3F=20b"=20=20=20by=20finding=20use?= =?UTF-8?q?d=20partitions=20for=20a,=20a+1,=20a+2,=20...,=20b-1,=20b.?= mysql-test/r/partition_pruning.result: WL#2985 "Partition Pruning": tests for "integer range walking" mysql-test/t/partition.test: WL#2985 "Partition Pruning": post-review fixes mysql-test/t/partition_pruning.test: WL#2985 "Partition Pruning": tests for "integer range walking" sql/handler.h: WL#2985 "Partition Pruning": "integer range walking": - class partition_info now has pointers to "partitioning interval analysis" functions - added "partition set iterator" definitions. sql/opt_range.cc: WL#2985 "Partition Pruning": "integer range walking": - Switched to use "partitioning interval analysis" functions - Fixed two problems in find_used_partitions() that occur on complicated WHERE clauses. sql/sql_partition.cc: WL#2985 "Partition Pruning": "integer range walking": - Added "partitioning interval analysis" functions: get_part_iter_for_interval_via_mapping, get_part_iter_for_interval_via_walking, - Added appropriate partition-set-iterator implementations - Added a function to set up Partitioning Interval Analysis-related fields in partition_info. sql/sql_select.cc: WL#2985 "Partition pruning": added comments. --- mysql-test/r/partition_pruning.result | 30 ++ mysql-test/t/partition.test | 2 +- mysql-test/t/partition_pruning.test | 23 ++ sql/handler.h | 160 ++++++++- sql/opt_range.cc | 279 ++++++---------- sql/sql_partition.cc | 456 +++++++++++++++++++++++++- sql/sql_select.cc | 5 + 7 files changed, 769 insertions(+), 186 deletions(-) diff --git a/mysql-test/r/partition_pruning.result b/mysql-test/r/partition_pruning.result index ef431b2c00e..6f210a76778 100644 --- a/mysql-test/r/partition_pruning.result +++ b/mysql-test/r/partition_pruning.result @@ -274,3 +274,33 @@ id select_type table partitions type possible_keys key key_len ref rows Extra 1 SIMPLE X p1,p2 ALL a NULL NULL NULL 4 Using where 1 SIMPLE Y p1,p2 ref a a 4 test.X.a 2 drop table t1; +create table t1 (a int) partition by hash(a) partitions 20; +insert into t1 values (1),(2),(3); +explain partitions select * from t1 where a > 1 and a < 3; +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE t1 p2 ALL NULL NULL NULL NULL 3 Using where +explain partitions select * from t1 where a >= 1 and a < 3; +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE t1 p1,p2 ALL NULL NULL NULL NULL 3 Using where +explain partitions select * from t1 where a > 1 and a <= 3; +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE t1 p2,p3 ALL NULL NULL NULL NULL 3 Using where +explain partitions select * from t1 where a >= 1 and a <= 3; +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE t1 p1,p2,p3 ALL NULL NULL NULL NULL 3 Using where +drop table t1; +create table t1 (a int, b int) +partition by list(a) subpartition by hash(b) subpartitions 20 +( +partition p0 values in (0), +partition p1 values in (1), +partition p2 values in (2), +partition p3 values in (3) +); +insert into t1 values (1,1),(2,2),(3,3); +explain partitions select * from t1 where b > 1 and b < 3; +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE t1 p0_sp2,p1_sp2,p2_sp2,p3_sp2 ALL NULL NULL NULL NULL 3 Using where +explain partitions select * from t1 where b > 1 and b < 3 and (a =1 or a =2); +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE t1 p1_sp2,p2_sp2 ALL NULL NULL NULL NULL 3 Using where diff --git a/mysql-test/t/partition.test b/mysql-test/t/partition.test index 609e24023d7..7591f8d77e5 100644 --- a/mysql-test/t/partition.test +++ b/mysql-test/t/partition.test @@ -209,7 +209,7 @@ create table t1 (a int not null, b int not null) partition by LIST (a+b) ( partition p0 values in (12), partition p1 values in (14) ); ---error 1500 +--error ER_NO_PARTITION_FOR_GIVEN_VALUE insert into t1 values (10,1); drop table t1; diff --git a/mysql-test/t/partition_pruning.test b/mysql-test/t/partition_pruning.test index 0d6bd344159..b45e6cf0d76 100644 --- a/mysql-test/t/partition_pruning.test +++ b/mysql-test/t/partition_pruning.test @@ -247,5 +247,28 @@ explain partitions select * from t1 X, t1 Y where X.a = Y.a and (X.a=1 or X.a=2); drop table t1; + +# Tests for "short ranges" +create table t1 (a int) partition by hash(a) partitions 20; +insert into t1 values (1),(2),(3); +explain partitions select * from t1 where a > 1 and a < 3; +explain partitions select * from t1 where a >= 1 and a < 3; +explain partitions select * from t1 where a > 1 and a <= 3; +explain partitions select * from t1 where a >= 1 and a <= 3; +drop table t1; + +create table t1 (a int, b int) + partition by list(a) subpartition by hash(b) subpartitions 20 +( + partition p0 values in (0), + partition p1 values in (1), + partition p2 values in (2), + partition p3 values in (3) +); +insert into t1 values (1,1),(2,2),(3,3); + +explain partitions select * from t1 where b > 1 and b < 3; +explain partitions select * from t1 where b > 1 and b < 3 and (a =1 or a =2); + # No tests for NULLs in RANGE(monotonic_expr()) - they depend on BUG#15447 # being fixed. diff --git a/sql/handler.h b/sql/handler.h index 9c67bb8a1f4..931527ecedd 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -474,6 +474,8 @@ typedef struct { uint32 end_part; bool use_bit_array; } part_id_range; + + /** * An enum and a struct to handle partitioning and subpartitioning. */ @@ -537,7 +539,109 @@ typedef bool (*get_part_id_func)(partition_info *part_info, uint32 *part_id); typedef uint32 (*get_subpart_id_func)(partition_info *part_info); -class partition_info :public Sql_alloc { + +struct st_partition_iter; +#define NOT_A_PARTITION_ID ((uint32)-1) + +/* + A "Get next" function for partition iterator. + SYNOPSIS + partition_iter_func() + part_iter Partition iterator, you call only "iter.get_next(&iter)" + + RETURN + NOT_A_PARTITION_ID if there are no more partitions. + [sub]partition_id of the next partition +*/ + +typedef uint32 (*partition_iter_func)(st_partition_iter* part_iter); + + +/* + Partition set iterator. Used to enumerate a set of [sub]partitions + obtained in partition interval analysis (see get_partitions_in_range_iter). + + For the user, the only meaningful field is get_next, which may be used as + follows: + part_iterator.get_next(&part_iterator); + + Initialization is done by any of the following calls: + - get_partitions_in_range_iter-type function call + - init_single_partition_iterator() + - init_all_partitions_iterator() + Cleanup is not needed. +*/ + +typedef struct st_partition_iter +{ + partition_iter_func get_next; + + union { + struct { + uint32 start_part_num; + uint32 end_part_num; + }; + struct { + longlong start_val; + longlong end_val; + }; + bool null_returned; + }; + partition_info *part_info; +} PARTITION_ITERATOR; + + +/* + Get an iterator for set of partitions that match given field-space interval + + SYNOPSIS + get_partitions_in_range_iter() + part_info Partitioning info + is_subpart + min_val Left edge, field value in opt_range_key format. + max_val Right edge, field value in opt_range_key format. + flags Some combination of NEAR_MIN, NEAR_MAX, NO_MIN_RANGE, + NO_MAX_RANGE. + part_iter Iterator structure to be initialized + + DESCRIPTION + Functions with this signature are used to perform "Partitioning Interval + Analysis". This analysis is applicable for any type of [sub]partitioning + by some function of a single fieldX. The idea is as follows: + Given an interval "const1 <=? fieldX <=? const2", find a set of partitions + that may contain records with value of fieldX within the given interval. + + The min_val, max_val and flags parameters specify the interval. + The set of partitions is returned by initializing an iterator in *part_iter + + NOTES + There are currently two functions of this type: + - get_part_iter_for_interval_via_walking + - get_part_iter_for_interval_via_mapping + + RETURN + 0 - No matching partitions, iterator not initialized + 1 - Some partitions would match, iterator intialized for traversing them + -1 - All partitions would match, iterator not initialized +*/ + +typedef int (*get_partitions_in_range_iter)(partition_info *part_info, + bool is_subpart, + byte *min_val, byte *max_val, + uint flags, + PARTITION_ITERATOR *part_iter); + + +/* Initialize the iterator to return a single partition with given part_id */ +inline void init_single_partition_iterator(uint32 part_id, + PARTITION_ITERATOR *part_iter); + +/* Initialize the iterator to enumerate all partitions */ +inline void init_all_partitions_iterator(partition_info *part_info, + PARTITION_ITERATOR *part_iter); + +class partition_info : public Sql_alloc +{ public: /* * Here comes a set of definitions needed for partitioned table handlers. @@ -566,7 +670,7 @@ public: same in all subpartitions */ get_subpart_id_func get_subpartition_id; - + /* NULL-terminated array of fields used in partitioned expression */ Field **part_field_array; /* NULL-terminated array of fields used in subpartitioned expression */ @@ -598,6 +702,39 @@ public: longlong *range_int_array; LIST_PART_ENTRY *list_array; }; + + /******************************************** + * INTERVAL ANALYSIS + ********************************************/ + /* + Partitioning interval analysis function for partitioning, or NULL if + interval analysis is not supported for this kind of partitioning. + */ + get_partitions_in_range_iter get_part_iter_for_interval; + /* + Partitioning interval analysis function for subpartitioning, or NULL if + interval analysis is not supported for this kind of partitioning. + */ + get_partitions_in_range_iter get_subpart_iter_for_interval; + + /* + Valid iff + get_part_iter_for_interval=get_part_iter_for_interval_via_walking: + controls how we'll process "field < C" and "field > C" intervals. + If the partitioning function F is strictly increasing, then for any x, y + "x < y" => "F(x) < F(y)" (*), i.e. when we get interval "field < C" + we can perform partition pruning on the equivalent "F(field) < F(C)". + + If the partitioning function not strictly increasing (it is simply + increasing), then instead of (*) we get "x < y" => "F(x) <= F(y)" + i.e. for interval "field < C" we can perform partition pruning for + "F(field) <= F(C)". + */ + bool range_analysis_include_bounds; + /******************************************** + * INTERVAL ANALYSIS ENDS + ********************************************/ + char* part_info_string; char *part_func_string; @@ -681,6 +818,25 @@ public: #ifdef WITH_PARTITION_STORAGE_ENGINE +uint32 get_next_partition_id_range(struct st_partition_iter* part_iter); + +inline void init_single_partition_iterator(uint32 part_id, + PARTITION_ITERATOR *part_iter) +{ + part_iter->start_part_num= part_id; + part_iter->end_part_num= part_id+1; + part_iter->get_next= get_next_partition_id_range; +} + +inline +void init_all_partitions_iterator(partition_info *part_info, + PARTITION_ITERATOR *part_iter) +{ + part_iter->start_part_num= 0; + part_iter->end_part_num= part_info->no_parts; + part_iter->get_next= get_next_partition_id_range; +} + /* Answers the question if subpartitioning is used for a certain table SYNOPSIS diff --git a/sql/opt_range.cc b/sql/opt_range.cc index a2fdea1bf6e..331261c26cd 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -2070,7 +2070,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, } /**************************************************************************** - * Partition pruning starts + * Partition pruning module ****************************************************************************/ #ifdef WITH_PARTITION_STORAGE_ENGINE @@ -2159,10 +2159,6 @@ struct st_part_prune_param; struct st_part_opt_info; typedef void (*mark_full_part_func)(partition_info*, uint32); -typedef uint32 (*part_num_to_partition_id_func)(struct st_part_prune_param*, - uint32); -typedef uint32 (*get_endpoint_func)(partition_info*, bool left_endpoint, - bool include_endpoint); /* Partition pruning operation context @@ -2170,7 +2166,7 @@ typedef uint32 (*get_endpoint_func)(partition_info*, bool left_endpoint, typedef struct st_part_prune_param { RANGE_OPT_PARAM range_param; /* Range analyzer parameters */ - + /*************************************************************** Following fields are filled in based solely on partitioning definition and not modified after that: @@ -2199,32 +2195,6 @@ typedef struct st_part_prune_param int last_part_partno; int last_subpart_partno; /* Same as above for supartitioning */ - /* - Function to be used to analyze non-singlepoint intervals (Can be pointer - to one of two functions - for RANGE and for LIST types). NULL means - partitioning type and/or expression doesn't allow non-singlepoint interval - analysis. - See get_list_array_idx_for_endpoint (or get_range_...) for description of - what the function does. - */ - get_endpoint_func get_endpoint; - - /* Maximum possible value that can be returned by get_endpoint function */ - uint32 max_endpoint_val; - - /* - For RANGE partitioning, part_num_to_part_id_range, for LIST partitioning, - part_num_to_part_id_list. Just to avoid the if-else clutter. - */ - part_num_to_partition_id_func endpoints_walk_func; - - /* - If true, process "key < const" as "part_func(key) < part_func(const)", - otherwise as "part_func(key) <= part_func(const)". Same for '>' and '>='. - This is defined iff get_endpoint != NULL. - */ - bool force_include_bounds; - /* is_part_keypart[i] == test(keypart #i in partitioning index is a member used in partitioning) @@ -2243,25 +2213,12 @@ typedef struct st_part_prune_param uint cur_part_fields; /* Same as cur_part_fields, but for subpartitioning */ uint cur_subpart_fields; - - /*************************************************************** - Following fields are used to store an 'iterator' that can be - used to obtain a set of used partitions. - **************************************************************/ - /* - Start and end+1 partition "numbers". They can have two meanings depending - of the value of part_num_to_part_id: - part_num_to_part_id_range - numbers are partition ids - part_num_to_part_id_list - numbers are indexes in part_info->list_array - */ - uint32 start_part_num; - uint32 end_part_num; - /* - A function that should be used to convert two above "partition numbers" - to partition_ids. - */ - part_num_to_partition_id_func part_num_to_part_id; + /* Iterator to be used to obtain the "current" set of used partitions */ + PARTITION_ITERATOR part_iter; + + /* Initialized bitmap of no_subparts size */ + MY_BITMAP subparts_bitmap; } PART_PRUNE_PARAM; static bool create_partition_index_description(PART_PRUNE_PARAM *prune_par); @@ -2377,9 +2334,7 @@ bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond) prune_param.arg_stack_end= prune_param.arg_stack; prune_param.cur_part_fields= 0; prune_param.cur_subpart_fields= 0; - prune_param.part_num_to_part_id= part_num_to_part_id_range; - prune_param.start_part_num= 0; - prune_param.end_part_num= prune_param.part_info->no_parts; + init_all_partitions_iterator(part_info, &prune_param.part_iter); if (!tree->keys[0] || (-1 == (res= find_used_partitions(&prune_param, tree->keys[0])))) goto all_used; @@ -2451,7 +2406,7 @@ end: format. */ -static void store_key_image_to_rec(Field *field, char *ptr, uint len) +void store_key_image_to_rec(Field *field, char *ptr, uint len) { /* Do the same as print_key() does */ if (field->real_maybe_null()) @@ -2512,19 +2467,6 @@ static void mark_full_partition_used_with_parts(partition_info *part_info, bitmap_set_bit(&part_info->used_partitions, start); } -/* See comment in PART_PRUNE_PARAM::part_num_to_part_id about what this is */ -static uint32 part_num_to_part_id_range(PART_PRUNE_PARAM* ppar, uint32 num) -{ - return num; -} - -/* See comment in PART_PRUNE_PARAM::part_num_to_part_id about what this is */ -static uint32 part_num_to_part_id_list(PART_PRUNE_PARAM* ppar, uint32 num) -{ - return ppar->part_info->list_array[num].partition_id; -} - - /* Find the set of used partitions for List SYNOPSIS @@ -2612,9 +2554,7 @@ int find_used_partitions_imerge(PART_PRUNE_PARAM *ppar, SEL_IMERGE *imerge) ppar->arg_stack_end= ppar->arg_stack; ppar->cur_part_fields= 0; ppar->cur_subpart_fields= 0; - ppar->part_num_to_part_id= part_num_to_part_id_range; - ppar->start_part_num= 0; - ppar->end_part_num= ppar->part_info->no_parts; + init_all_partitions_iterator(ppar->part_info, &ppar->part_iter); if (-1 == (res |= find_used_partitions(ppar, (*ptree)->keys[0]))) return -1; } @@ -2683,58 +2623,29 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree) if (key_tree->type == SEL_ARG::KEY_RANGE) { - if (partno == 0 && (NULL != ppar->get_endpoint)) + if (partno == 0 && (NULL != ppar->part_info->get_part_iter_for_interval)) { /* Partitioning is done by RANGE|INTERVAL(monotonic_expr(fieldX)), and - we got "const1 CMP fieldX CMP const2" interval + we got "const1 CMP fieldX CMP const2" interval <-- psergey-todo: change */ DBUG_EXECUTE("info", dbug_print_segment_range(key_tree, ppar->range_param. key_parts);); - /* Find minimum */ - if (key_tree->min_flag & NO_MIN_RANGE) - ppar->start_part_num= 0; - else + res= ppar->part_info-> + get_part_iter_for_interval(ppar->part_info, + FALSE, + key_tree->min_value, + key_tree->max_value, + key_tree->min_flag | key_tree->max_flag, + &ppar->part_iter); + if (!res) + goto go_right; /* res=0 --> no satisfying partitions */ + if (res == -1) { - /* - Store the interval edge in the record buffer, and call the - function that maps the edge in table-field space to an edge - in ordered-set-of-partitions (for RANGE partitioning) or - indexes-in-ordered-array-of-list-constants (for LIST) space. - */ - store_key_image_to_rec(key_tree->field, key_tree->min_value, - ppar->range_param.key_parts[0].length); - bool include_endp= ppar->force_include_bounds || - !test(key_tree->min_flag & NEAR_MIN); - ppar->start_part_num= ppar->get_endpoint(ppar->part_info, 1, - include_endp); - if (ppar->start_part_num == ppar->max_endpoint_val) - { - res= 0; /* No satisfying partitions */ - goto pop_and_go_right; - } + //get a full range iterator + init_all_partitions_iterator(ppar->part_info, &ppar->part_iter); } - - /* Find maximum, do the same as above but for right interval bound */ - if (key_tree->max_flag & NO_MAX_RANGE) - ppar->end_part_num= ppar->max_endpoint_val; - else - { - store_key_image_to_rec(key_tree->field, key_tree->max_value, - ppar->range_param.key_parts[0].length); - bool include_endp= ppar->force_include_bounds || - !test(key_tree->max_flag & NEAR_MAX); - ppar->end_part_num= ppar->get_endpoint(ppar->part_info, 0, - include_endp); - if (ppar->start_part_num == ppar->end_part_num) - { - res= 0; /* No satisfying partitions */ - goto pop_and_go_right; - } - } - ppar->part_num_to_part_id= ppar->endpoints_walk_func; - /* Save our intent to mark full partition as used if we will not be able to obtain further limits on subpartitions @@ -2743,6 +2654,42 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree) goto process_next_key_part; } + if (partno == ppar->last_subpart_partno && + (NULL != ppar->part_info->get_subpart_iter_for_interval)) + { + PARTITION_ITERATOR subpart_iter; + DBUG_EXECUTE("info", dbug_print_segment_range(key_tree, + ppar->range_param. + key_parts);); + res= ppar->part_info-> + get_subpart_iter_for_interval(ppar->part_info, + TRUE, + key_tree->min_value, + key_tree->max_value, + key_tree->min_flag | key_tree->max_flag, + &subpart_iter); + DBUG_ASSERT(res); /* We can't get "no satisfying subpartitions" */ + if (res == -1) + return -1; /* all subpartitions satisfy */ + + uint32 subpart_id; + bitmap_clear_all(&ppar->subparts_bitmap); + while ((subpart_id= subpart_iter.get_next(&subpart_iter)) != NOT_A_PARTITION_ID) + bitmap_set_bit(&ppar->subparts_bitmap, subpart_id); + + /* Mark each partition as used in each subpartition. */ + uint32 part_id; + while ((part_id= ppar->part_iter.get_next(&ppar->part_iter)) != + NOT_A_PARTITION_ID) + { + for (uint i= 0; i < ppar->part_info->no_subparts; i++) + if (bitmap_is_set(&ppar->subparts_bitmap, i)) + bitmap_set_bit(&ppar->part_info->used_partitions, + part_id * ppar->part_info->no_subparts + i); + } + goto go_right; + } + if (key_tree->is_singlepoint()) { pushed= TRUE; @@ -2768,9 +2715,7 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree) goto pop_and_go_right; } /* Rembember the limit we got - single partition #part_id */ - ppar->part_num_to_part_id= part_num_to_part_id_range; - ppar->start_part_num= part_id; - ppar->end_part_num= part_id + 1; + init_single_partition_iterator(part_id, &ppar->part_iter); /* If there are no subpartitions/we fail to get any limit for them, @@ -2780,7 +2725,8 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree) goto process_next_key_part; } - if (partno == ppar->last_subpart_partno) + if (partno == ppar->last_subpart_partno && + ppar->cur_subpart_fields == ppar->subpart_fields) { /* Ok, we've got "fieldN<=>constN"-type SEL_ARGs for all subpartitioning @@ -2796,12 +2742,12 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree) uint32 subpart_id= part_info->get_subpartition_id(part_info); /* Mark this partition as used in each subpartition. */ - for (uint32 num= ppar->start_part_num; num != ppar->end_part_num; - num++) + uint32 part_id; + while ((part_id= ppar->part_iter.get_next(&ppar->part_iter)) != + NOT_A_PARTITION_ID) { bitmap_set_bit(&part_info->used_partitions, - ppar->part_num_to_part_id(ppar, num) * - part_info->no_subparts + subpart_id); + part_id * part_info->no_subparts + subpart_id); } res= 1; /* Some partitions were marked as used */ goto pop_and_go_right; @@ -2822,34 +2768,28 @@ int find_used_partitions(PART_PRUNE_PARAM *ppar, SEL_ARG *key_tree) process_next_key_part: if (key_tree->next_key_part) res= find_used_partitions(ppar, key_tree->next_key_part); - else + else res= -1; - - if (res == -1) /* Got "full range" for key_tree->next_key_part call */ - { - if (set_full_part_if_bad_ret) - { - for (uint32 num= ppar->start_part_num; num != ppar->end_part_num; - num++) - { - ppar->mark_full_partition_used(ppar->part_info, - ppar->part_num_to_part_id(ppar, num)); - } - res= 1; - } - else - return -1; - } - + if (set_full_part_if_bad_ret) { + if (res == -1) + { + /* Got "full range" for subpartitioning fields */ + uint32 part_id; + bool found= FALSE; + while ((part_id= ppar->part_iter.get_next(&ppar->part_iter)) != NOT_A_PARTITION_ID) + { + ppar->mark_full_partition_used(ppar->part_info, part_id); + found= TRUE; + } + res= test(found); + } /* Restore the "used partitions iterator" to the default setting that specifies iteration over all partitions. */ - ppar->part_num_to_part_id= part_num_to_part_id_range; - ppar->start_part_num= 0; - ppar->end_part_num= ppar->part_info->no_parts; + init_all_partitions_iterator(ppar->part_info, &ppar->part_iter); } if (pushed) @@ -2860,7 +2800,10 @@ pop_and_go_right: ppar->cur_part_fields-= ppar->is_part_keypart[partno]; ppar->cur_subpart_fields-= ppar->is_subpart_keypart[partno]; } - + + if (res == -1) + return -1; +go_right: if (key_tree->right != &null_element) { if (-1 == (right_res= find_used_partitions(ppar,key_tree->right))) @@ -2967,38 +2910,6 @@ static bool create_partition_index_description(PART_PRUNE_PARAM *ppar) ppar->get_top_partition_id_func= part_info->get_partition_id; } - enum_monotonicity_info minfo; - ppar->get_endpoint= NULL; - if (part_info->part_expr && - (minfo= part_info->part_expr->get_monotonicity_info()) != NON_MONOTONIC) - { - /* - ppar->force_include_bounds controls how we'll process "field < C" and - "field > C" intervals. - If the partitioning function F is strictly increasing, then for any x, y - "x < y" => "F(x) < F(y)" (*), i.e. when we get interval "field < C" - we can perform partition pruning on the equivalent "F(field) < F(C)". - - If the partitioning function not strictly increasing (it is simply - increasing), then instead of (*) we get "x < y" => "F(x) <= F(y)" - i.e. for interval "field < C" we can perform partition pruning for - "F(field) <= F(C)". - */ - ppar->force_include_bounds= test(minfo == MONOTONIC_INCREASING); - if (part_info->part_type == RANGE_PARTITION) - { - ppar->get_endpoint= get_partition_id_range_for_endpoint; - ppar->endpoints_walk_func= part_num_to_part_id_range; - ppar->max_endpoint_val= part_info->no_parts; - } - else if (part_info->part_type == LIST_PARTITION) - { - ppar->get_endpoint= get_list_array_idx_for_endpoint; - ppar->endpoints_walk_func= part_num_to_part_id_list; - ppar->max_endpoint_val= part_info->no_list_values; - } - } - KEY_PART *key_part; MEM_ROOT *alloc= range_par->mem_root; if (!total_parts || @@ -3011,11 +2922,19 @@ static bool create_partition_index_description(PART_PRUNE_PARAM *ppar) !(ppar->is_subpart_keypart= (my_bool*)alloc_root(alloc, sizeof(my_bool)* total_parts))) return TRUE; - + + if (ppar->subpart_fields) + { + uint32 *buf; + uint32 bufsize= bitmap_buffer_size(ppar->part_info->no_subparts); + if (!(buf= (uint32*)alloc_root(alloc, bufsize))) + return TRUE; + bitmap_init(&ppar->subparts_bitmap, buf, ppar->part_info->no_subparts, FALSE); + } range_par->key_parts= key_part; Field **field= (ppar->part_fields)? part_info->part_field_array : part_info->subpart_field_array; - bool subpart_fields= FALSE; + bool in_subpart_fields= FALSE; for (uint part= 0; part < total_parts; part++, key_part++) { key_part->key= 0; @@ -3036,13 +2955,13 @@ static bool create_partition_index_description(PART_PRUNE_PARAM *ppar) key_part->image_type = Field::itRAW; /* We don't set key_parts->null_bit as it will not be used */ - ppar->is_part_keypart[part]= !subpart_fields; - ppar->is_subpart_keypart[part]= subpart_fields; + ppar->is_part_keypart[part]= !in_subpart_fields; + ppar->is_subpart_keypart[part]= in_subpart_fields; if (!*(++field)) { field= part_info->subpart_field_array; - subpart_fields= TRUE; + in_subpart_fields= TRUE; } } range_par->key_parts_end= key_part; diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 498cd4e20e8..5e859761ace 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -91,6 +91,21 @@ uint32 get_partition_id_linear_hash_sub(partition_info *part_info); uint32 get_partition_id_linear_key_sub(partition_info *part_info); #endif +static uint32 get_next_partition_via_walking(PARTITION_ITERATOR*); +static uint32 get_next_subpartition_via_walking(PARTITION_ITERATOR*); +uint32 get_next_partition_id_range(PARTITION_ITERATOR* part_iter); +uint32 get_next_partition_id_list(PARTITION_ITERATOR* part_iter); +int get_part_iter_for_interval_via_mapping(partition_info *part_info, + bool is_subpart, + byte *min_value, byte *max_value, + uint flags, + PARTITION_ITERATOR *part_iter); +int get_part_iter_for_interval_via_walking(partition_info *part_info, + bool is_subpart, + byte *min_value, byte *max_value, + uint flags, + PARTITION_ITERATOR *part_iter); +static void set_up_range_analysis_info(partition_info *part_info); /* A routine used by the parser to decide whether we are specifying a full @@ -1603,8 +1618,8 @@ static void set_up_partition_func_pointers(partition_info *part_info) } } } - - + + /* For linear hashing we need a mask which is on the form 2**n - 1 where 2**n >= no_parts. Thus if no_parts is 6 then mask is 2**3 - 1 = 8 - 1 = 7. @@ -1811,6 +1826,7 @@ bool fix_partition_func(THD *thd, const char *name, TABLE *table) check_range_capable_PF(table); set_up_partition_key_maps(table, part_info); set_up_partition_func_pointers(part_info); + set_up_range_analysis_info(part_info); result= FALSE; end: thd->set_query_id= save_set_query_id; @@ -3489,7 +3505,7 @@ void make_used_partitions_str(partition_info *part_info, String *parts_str) uint partition_id= 0; List_iterator it(part_info->partitions); - if (part_info->subpart_type != NOT_A_PARTITION) + if (is_sub_partitioned(part_info)) { partition_element *head_pe; while ((head_pe= it++)) @@ -3529,3 +3545,437 @@ void make_used_partitions_str(partition_info *part_info, String *parts_str) } } + +/**************************************************************************** + * Partition interval analysis support + ***************************************************************************/ + +/* + Setup partition_info::* members related to partitioning range analysis + + SYNOPSIS + set_up_partition_func_pointers() + part_info Partitioning info structure + + DESCRIPTION + Assuming that passed partition_info structure already has correct values + for members that specify [sub]partitioning type, table fields, and + functions, set up partition_info::* members that are related to + Partitioning Interval Analysis (see get_partitions_in_range_iter for its + definition) + + IMPLEMENTATION + There are two available interval analyzer functions: + (1) get_part_iter_for_interval_via_mapping + (2) get_part_iter_for_interval_via_walking + + They both have limited applicability: + (1) is applicable for "PARTITION BY (func(t.field))", where + func is a monotonic function. + + (2) is applicable for + "[SUB]PARTITION BY (any_func(t.integer_field))" + + If both are applicable, (1) is preferred over (2). + + This function sets part_info::get_part_iter_for_interval according to + this criteria, and also sets some auxilary fields that the function + uses. +*/ + +static void set_up_range_analysis_info(partition_info *part_info) +{ + enum_monotonicity_info minfo; + + /* Set the catch-all default */ + part_info->get_part_iter_for_interval= NULL; + part_info->get_subpart_iter_for_interval= NULL; + + /* + Check if get_part_iter_for_interval_via_mapping() can be used for + partitioning + */ + switch (part_info->part_type) { + case RANGE_PARTITION: + case LIST_PARTITION: + minfo= part_info->part_expr->get_monotonicity_info(); + if (minfo != NON_MONOTONIC) + { + part_info->range_analysis_include_bounds= + test(minfo == MONOTONIC_INCREASING); + part_info->get_part_iter_for_interval= + get_part_iter_for_interval_via_mapping; + goto setup_subparts; + } + default: + ; + } + + /* + Check get_part_iter_for_interval_via_walking() can be used for + partitioning + */ + if (part_info->no_part_fields == 1) + { + Field *field= part_info->part_field_array[0]; + switch (field->type()) { + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_LONGLONG: + part_info->get_part_iter_for_interval= + get_part_iter_for_interval_via_walking; + break; + default: + ; + } + } + +setup_subparts: + /* + Check get_part_iter_for_interval_via_walking() can be used for + subpartitioning + */ + if (part_info->no_subpart_fields == 1) + { + Field *field= part_info->subpart_field_array[0]; + switch (field->type()) { + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_LONGLONG: + part_info->get_subpart_iter_for_interval= + get_part_iter_for_interval_via_walking; + break; + default: + ; + } + } +} + + +typedef uint32 (*get_endpoint_func)(partition_info*, bool left_endpoint, + bool include_endpoint); + +/* + Partitioning Interval Analysis: Initialize the iterator for "mapping" case + + SYNOPSIS + get_part_iter_for_interval_via_mapping() + part_info Partition info + is_subpart TRUE - act for subpartitioning + FALSE - act for partitioning + min_value minimum field value, in opt_range key format. + max_value minimum field value, in opt_range key format. + flags Some combination of NEAR_MIN, NEAR_MAX, NO_MIN_RANGE, + NO_MAX_RANGE. + part_iter Iterator structure to be initialized + + DESCRIPTION + Initialize partition set iterator to walk over the interval in + ordered-list-of-partitions (for RANGE partitioning) or + ordered-list-of-list-constants (for LIST partitioning) space. + + IMPLEMENTATION + This function is applied when partitioning is done by + (ascending_func(t.field)), and we can map an interval in + t.field space into a sub-array of partition_info::range_int_array or + partition_info::list_array (see get_partition_id_range_for_endpoint, + get_list_array_idx_for_endpoint for details). + + The function performs this interval mapping, and sets the iterator to + traverse the sub-array and return appropriate partitions. + + RETURN + 0 - No matching partitions (iterator not initialized) + 1 - Ok, iterator intialized for traversal of matching partitions. + -1 - All partitions would match (iterator not initialized) +*/ + +int get_part_iter_for_interval_via_mapping(partition_info *part_info, + bool is_subpart, + byte *min_value, byte *max_value, + uint flags, + PARTITION_ITERATOR *part_iter) +{ + DBUG_ASSERT(!is_subpart); + Field *field= part_info->part_field_array[0]; + uint32 max_endpoint_val; + get_endpoint_func get_endpoint; + uint field_len= field->pack_length_in_rec(); + + if (part_info->part_type == RANGE_PARTITION) + { + get_endpoint= get_partition_id_range_for_endpoint; + max_endpoint_val= part_info->no_parts; + part_iter->get_next= get_next_partition_id_range; + } + else if (part_info->part_type == LIST_PARTITION) + { + get_endpoint= get_list_array_idx_for_endpoint; + max_endpoint_val= part_info->no_list_values; + part_iter->get_next= get_next_partition_id_list; + part_iter->part_info= part_info; + } + else + DBUG_ASSERT(0); + + /* Find minimum */ + if (flags & NO_MIN_RANGE) + part_iter->start_part_num= 0; + else + { + /* + Store the interval edge in the record buffer, and call the + function that maps the edge in table-field space to an edge + in ordered-set-of-partitions (for RANGE partitioning) or + index-in-ordered-array-of-list-constants (for LIST) space. + */ + store_key_image_to_rec(field, min_value, field_len); + bool include_endp= part_info->range_analysis_include_bounds || + !test(flags & NEAR_MIN); + part_iter->start_part_num= get_endpoint(part_info, 1, include_endp); + if (part_iter->start_part_num == max_endpoint_val) + return 0; /* No partitions */ + } + + /* Find maximum, do the same as above but for right interval bound */ + if (flags & NO_MAX_RANGE) + part_iter->end_part_num= max_endpoint_val; + else + { + store_key_image_to_rec(field, max_value, field_len); + bool include_endp= part_info->range_analysis_include_bounds || + !test(flags & NEAR_MAX); + part_iter->end_part_num= get_endpoint(part_info, 0, include_endp); + if (part_iter->start_part_num == part_iter->end_part_num) + return 0; /* No partitions */ + } + return 1; /* Ok, iterator initialized */ +} + + +/* See get_part_iter_for_interval_via_walking for definition of what this is */ +#define MAX_RANGE_TO_WALK 10 + + +/* + Partitioning Interval Analysis: Initialize iterator to walk integer interval + + SYNOPSIS + get_part_iter_for_interval_via_walking() + part_info Partition info + is_subpart TRUE - act for subpartitioning + FALSE - act for partitioning + min_value minimum field value, in opt_range key format. + max_value minimum field value, in opt_range key format. + flags Some combination of NEAR_MIN, NEAR_MAX, NO_MIN_RANGE, + NO_MAX_RANGE. + part_iter Iterator structure to be initialized + + DESCRIPTION + Initialize partition set iterator to walk over interval in integer field + space. That is, for "const1 <=? t.field <=? const2" interval, initialize + the iterator to do this: + get partition id for t.field = const1, return it + get partition id for t.field = const1+1, return it + ... t.field = const1+2, ... + ... ... ... + ... t.field = const2 ... + + IMPLEMENTATION + See get_partitions_in_range_iter for general description of interval + analysis. We support walking over the following intervals: + "t.field IS NULL" + "c1 <=? t.field <=? c2", where c1 and c2 are finite. + Intervals with +inf/-inf, and [NULL, c1] interval can be processed but + that is more tricky and I don't have time to do it right now. + + Additionally we have these requirements: + * number of values in the interval must be less then number of + [sub]partitions, and + * Number of values in the interval must be less then MAX_RANGE_TO_WALK. + + The rationale behind these requirements is that if they are not met + we're likely to hit most of the partitions and traversing the interval + will only add overhead. So it's better return "all partitions used" in + this case. + + RETURN + 0 - No matching partitions, iterator not initialized + 1 - Some partitions would match, iterator intialized for traversing them + -1 - All partitions would match, iterator not initialized +*/ + +int get_part_iter_for_interval_via_walking(partition_info *part_info, + bool is_subpart, + byte *min_value, byte *max_value, + uint flags, + PARTITION_ITERATOR *part_iter) +{ + Field *field; + uint total_parts; + partition_iter_func get_next_func; + if (is_subpart) + { + field= part_info->subpart_field_array[0]; + total_parts= part_info->no_subparts; + get_next_func= get_next_subpartition_via_walking; + } + else + { + field= part_info->part_field_array[0]; + total_parts= part_info->no_parts; + get_next_func= get_next_partition_via_walking; + } + + /* Handle the "t.field IS NULL" interval, it is a special case */ + if (field->real_maybe_null() && !(flags & (NO_MIN_RANGE | NO_MAX_RANGE)) && + *min_value && *max_value) + { + /* + We don't have a part_iter->get_next() function that would find which + partition "t.field IS NULL" belongs to, so find partition that contains + NULL right here, and return an iterator over singleton set. + */ + uint32 part_id; + field->set_null(); + if (is_subpart) + { + part_id= part_info->get_subpartition_id(part_info); + init_single_partition_iterator(part_id, part_iter); + return 1; /* Ok, iterator initialized */ + } + else + { + if (!part_info->get_partition_id(part_info, &part_id)) + { + init_single_partition_iterator(part_id, part_iter); + return 1; /* Ok, iterator initialized */ + } + } + return 0; /* No partitions match */ + } + + if (flags & (NO_MIN_RANGE | NO_MAX_RANGE)) + return -1; /* Can't handle this interval, have to use all partitions */ + + /* Get integers for left and right interval bound */ + longlong a, b; + uint len= field->pack_length_in_rec(); + store_key_image_to_rec(field, min_value, len); + a= field->val_int(); + + store_key_image_to_rec(field, max_value, len); + b= field->val_int(); + + a += test(flags & NEAR_MIN); + b += test(!(flags & NEAR_MAX)); + uint n_values= b - a; + + if (n_values > total_parts || n_values > MAX_RANGE_TO_WALK) + return -1; + + part_iter->start_val= a; + part_iter->end_val= b; + part_iter->part_info= part_info; + part_iter->get_next= get_next_func; + return 1; +} + + +/* + PARTITION_ITERATOR::get_next implementation: enumerate partitions in range + + SYNOPSIS + get_next_partition_id_list() + part_iter Partition set iterator structure + + DESCRIPTION + This is implementation of PARTITION_ITERATOR::get_next() that returns + [sub]partition ids in [min_partition_id, max_partition_id] range. + + RETURN + partition id + NOT_A_PARTITION_ID if there are no more partitions +*/ + +uint32 get_next_partition_id_range(PARTITION_ITERATOR* part_iter) +{ + if (part_iter->start_part_num == part_iter->end_part_num) + return NOT_A_PARTITION_ID; + else + return part_iter->start_part_num++; +} + + +/* + PARTITION_ITERATOR::get_next implementation for LIST partitioning + + SYNOPSIS + get_next_partition_id_list() + part_iter Partition set iterator structure + + DESCRIPTION + This is special implementation of PARTITION_ITERATOR::get_next() for + LIST partitioning: it enumerates partition ids in + part_info->list_array[i] where i runs over [min_idx, max_idx] interval. + + RETURN + partition id + NOT_A_PARTITION_ID if there are no more partitions +*/ + +uint32 get_next_partition_id_list(PARTITION_ITERATOR *part_iter) +{ + if (part_iter->start_part_num == part_iter->end_part_num) + return NOT_A_PARTITION_ID; + else + return part_iter->part_info->list_array[part_iter-> + start_part_num++].partition_id; +} + + +/* + PARTITION_ITERATOR::get_next implementation: walk over integer interval + + SYNOPSIS + get_next_partition_via_walking() + part_iter Partitioning iterator + + DESCRIPTION + + RETURN + partition id + NOT_A_PARTITION_ID if there are no more partitioning. +*/ + +static uint32 get_next_partition_via_walking(PARTITION_ITERATOR *part_iter) +{ + uint32 part_id; + Field *field= part_iter->part_info->part_field_array[0]; + while (part_iter->start_val != part_iter->end_val) + { + field->store(part_iter->start_val, FALSE); + part_iter->start_val++; + if (!part_iter->part_info->get_partition_id(part_iter->part_info, + &part_id)) + return part_id; + } + return NOT_A_PARTITION_ID; +} + + +/* Same as get_next_partition_via_walking, but for subpartitions */ + +static uint32 get_next_subpartition_via_walking(PARTITION_ITERATOR *part_iter) +{ + uint32 part_id; + Field *field= part_iter->part_info->subpart_field_array[0]; + if (part_iter->start_val == part_iter->end_val) + return NOT_A_PARTITION_ID; + field->store(part_iter->start_val, FALSE); + part_iter->start_val++; + return part_iter->part_info->get_subpartition_id(part_iter->part_info); +} + diff --git a/sql/sql_select.cc b/sql/sql_select.cc index d5a38b13a0e..2a32a6d354f 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -638,6 +638,11 @@ JOIN::optimize() TABLE_LIST *tbl; for (tbl= select_lex->leaf_tables; tbl; tbl= tbl->next_leaf) { + /* + If tbl->embedding!=NULL that means that this table is in the inner + part of the nested outer join, and we can't do partition pruning + (TODO: check if this limitation can be lifted) + */ if (!tbl->embedding) { Item *prune_cond= tbl->on_expr? tbl->on_expr : conds; From 833a5e2daa65069d0866fc4783052ed7a777e782 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 4 Jan 2006 11:11:27 +0300 Subject: [PATCH 4/7] WL#2985 "Partition pruning": added "integer range walking", small change forgotten in previous cset --- sql/opt_range.h | 1 + 1 file changed, 1 insertion(+) diff --git a/sql/opt_range.h b/sql/opt_range.h index 3cd348ba9af..a27b3607d73 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -721,6 +721,7 @@ uint get_index_for_order(TABLE *table, ORDER *order, ha_rows limit); #ifdef WITH_PARTITION_STORAGE_ENGINE bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond); +void store_key_image_to_rec(Field *field, char *ptr, uint len); #endif #endif From 300408112992c21e0ff0f46fc098c8bf4f30662d Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 4 Jan 2006 11:16:34 +0300 Subject: [PATCH 5/7] Fix compiler warning --- sql/sql_update.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/sql_update.cc b/sql/sql_update.cc index bd001cd9a06..9f14736cf54 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1416,7 +1416,7 @@ bool multi_update::send_data(List ¬_used_values) memcpy((char*) tmp_table->field[0]->ptr, (char*) table->file->ref, table->file->ref_length); /* Write row, ignoring duplicated updates to a row */ - if (error= tmp_table->file->ha_write_row(tmp_table->record[0])) + if ((error= tmp_table->file->ha_write_row(tmp_table->record[0]))) { if (error != HA_ERR_FOUND_DUPP_KEY && error != HA_ERR_FOUND_DUPP_UNIQUE && From bcd56c6d34640af6c6307db052f59d06551e5b48 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 5 Jan 2006 03:12:51 +0300 Subject: [PATCH 6/7] WL#2985 "Partition pruning": Make compilable without partitioning engine. --- sql/sql_partition.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 5e859761ace..ef25184191f 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -3481,7 +3481,7 @@ void set_key_field_ptr(KEY *key_info, const byte *new_buf, DBUG_VOID_RETURN; } - +#ifdef WITH_PARTITION_STORAGE_ENGINE /* Return comma-separated list of used partitions in the provided given string @@ -3544,7 +3544,7 @@ void make_used_partitions_str(partition_info *part_info, String *parts_str) } } } - +#endif /**************************************************************************** * Partition interval analysis support @@ -3582,7 +3582,7 @@ void make_used_partitions_str(partition_info *part_info, String *parts_str) this criteria, and also sets some auxilary fields that the function uses. */ - +#ifdef WITH_PARTITION_STORAGE_ENGINE static void set_up_range_analysis_info(partition_info *part_info) { enum_monotonicity_info minfo; @@ -3978,4 +3978,5 @@ static uint32 get_next_subpartition_via_walking(PARTITION_ITERATOR *part_iter) part_iter->start_val++; return part_iter->part_info->get_subpartition_id(part_iter->part_info); } +#endif From 0f1fa93af8708ea34326039f844ddd6ac053687c Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 5 Jan 2006 17:47:07 +0300 Subject: [PATCH 7/7] WL#2985 "Partition-pruning", "range walking" addition: better comments. --- sql/handler.h | 5 +- sql/opt_range.cc | 113 ++++++++++++++++++++++++++++++++++--------- sql/sql_partition.cc | 24 +++++---- 3 files changed, 105 insertions(+), 37 deletions(-) diff --git a/sql/handler.h b/sql/handler.h index 931527ecedd..07bb9f7cad4 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -690,11 +690,10 @@ public: /* A bitmap of partitions used by the current query. Usage pattern: - * It is guaranteed that all partitions are set to be unused on query start. + * The handler->extra(HA_EXTRA_RESET) call at query start/end sets all + partitions to be unused. * Before index/rnd_init(), partition pruning code sets the bits for used partitions. - * The handler->extra(HA_EXTRA_RESET) call at query end sets all partitions - to be unused. */ MY_BITMAP used_partitions; diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 331261c26cd..cac7b304f1b 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -2115,7 +2115,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, The list of intervals we'll obtain will look like this: ((t1.a, t1.b) = (1,'foo')), ((t1.a, t1.b) = (2,'bar')), - ((t1,a, t1.b) > (10,'zz')) (**) + ((t1,a, t1.b) > (10,'zz')) 2. for each interval I { @@ -2574,30 +2574,95 @@ int find_used_partitions_imerge(PART_PRUNE_PARAM *ppar, SEL_IMERGE *imerge) This function * recursively walks the SEL_ARG* tree collecting partitioning "intervals" * finds the partitions one needs to use to get rows in these intervals - * marks these partitions as used - - NOTES - WHAT IS CONSIDERED TO BE "INTERVALS" - A partition pruning "interval" is equivalent to condition in one of the - forms: - - "partition_field1=const1 AND ... AND partition_fieldN=constN" (1) - "subpartition_field1=const1 AND ... AND subpartition_fieldN=constN" (2) - "(1) AND (2)" (3) - - In (1) and (2) all [sub]partitioning fields must be used, and "x=const" - includes "x IS NULL". - - If partitioning is performed using - - PARTITION BY RANGE(unary_monotonic_func(single_partition_field)), - - then the following is also an interval: + * marks these partitions as used. + The next session desribes the process in greater detail. + + IMPLEMENTATION + TYPES OF RESTRICTIONS THAT WE CAN OBTAIN PARTITIONS FOR + We can find out which [sub]partitions to use if we obtain restrictions on + [sub]partitioning fields in the following form: + 1. "partition_field1=const1 AND ... AND partition_fieldN=constN" + 1.1 Same as (1) but for subpartition fields - " const1 OP1 single_partition_field OP2 const2" (4) - - where OP1 and OP2 are '<' OR '<=', and const_i can be +/- inf. - Everything else is not a partition pruning "interval". + If partitioning supports interval analysis (i.e. partitioning is a + function of a single table field, and partition_info:: + get_part_iter_for_interval != NULL), then we can also use condition in + this form: + 2. "const1 <=? partition_field <=? const2" + 2.1 Same as (2) but for subpartition_field + + INFERRING THE RESTRICTIONS FROM SEL_ARG TREE + + The below is an example of what SEL_ARG tree may represent: + + (start) + | $ + | Partitioning keyparts $ subpartitioning keyparts + | $ + | ... ... $ + | | | $ + | +---------+ +---------+ $ +-----------+ +-----------+ + \-| par1=c1 |--| par2=c2 |-----| subpar1=c3|--| subpar2=c5| + +---------+ +---------+ $ +-----------+ +-----------+ + | $ | | + | $ | +-----------+ + | $ | | subpar2=c6| + | $ | +-----------+ + | $ | + | $ +-----------+ +-----------+ + | $ | subpar1=c4|--| subpar2=c8| + | $ +-----------+ +-----------+ + | $ + | $ + +---------+ $ +------------+ +------------+ + | par1=c2 |------------------| subpar1=c10|--| subpar2=c12| + +---------+ $ +------------+ +------------+ + | $ + ... $ + + The up-down connections are connections via SEL_ARG::left and + SEL_ARG::right. A horizontal connection to the right is the + SEL_ARG::next_key_part connection. + + find_used_partitions() traverses the entire tree via recursion on + * SEL_ARG::next_key_part (from left to right on the picture) + * SEL_ARG::left|right (up/down on the pic). Left-right recursion is + performed for each depth level. + + Recursion descent on SEL_ARG::next_key_part is used to accumulate (in + ppar->arg_stack) constraints on partitioning and subpartitioning fields. + For the example in the above picture, one of stack states is: + in find_used_partitions(key_tree = "subpar2=c5") (***) + in find_used_partitions(key_tree = "subpar1=c3") + in find_used_partitions(key_tree = "par2=c2") (**) + in find_used_partitions(key_tree = "par1=c1") + in prune_partitions(...) + We apply partitioning limits as soon as possible, e.g. when we reach the + depth (**), we find which partition(s) correspond to "par1=c1 AND par2=c2", + and save them in ppar->part_iter. + When we reach the depth (***), we find which subpartition(s) correspond to + "subpar1=c3 AND subpar2=c5", and then mark appropriate subpartitions in + appropriate subpartitions as used. + + It is possible that constraints on some partitioning fields are missing. + For the above example, consider this stack state: + in find_used_partitions(key_tree = "subpar2=c12") (***) + in find_used_partitions(key_tree = "subpar1=c10") + in find_used_partitions(key_tree = "par1=c2") + in prune_partitions(...) + Here we don't have constraints for all partitioning fields. Since we've + never set the ppar->part_iter to contain used set of partitions, we use + its default "all partitions" value. We get subpartition id for + "subpar1=c3 AND subpar2=c5", and mark that subpartition as used in every + partition. + + The inverse is also possible: we may get constraints on partitioning + fields, but not constraints on subpartitioning fields. In that case, + calls to find_used_partitions() with depth below (**) will return -1, + and we will mark entire partition as used. + + TODO + Replace recursion on SEL_ARG::left and SEL_ARG::right with a loop RETURN 1 OK, one or more [sub]partitions are marked as used. diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index ef25184191f..0943e6d5b0e 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -3673,11 +3673,11 @@ typedef uint32 (*get_endpoint_func)(partition_info*, bool left_endpoint, DESCRIPTION Initialize partition set iterator to walk over the interval in - ordered-list-of-partitions (for RANGE partitioning) or - ordered-list-of-list-constants (for LIST partitioning) space. + ordered-array-of-partitions (for RANGE partitioning) or + ordered-array-of-list-constants (for LIST partitioning) space. IMPLEMENTATION - This function is applied when partitioning is done by + This function is used when partitioning is done by (ascending_func(t.field)), and we can map an interval in t.field space into a sub-array of partition_info::range_int_array or partition_info::list_array (see get_partition_id_range_for_endpoint, @@ -3686,7 +3686,7 @@ typedef uint32 (*get_endpoint_func)(partition_info*, bool left_endpoint, The function performs this interval mapping, and sets the iterator to traverse the sub-array and return appropriate partitions. - RETURN + RETURN 0 - No matching partitions (iterator not initialized) 1 - Ok, iterator intialized for traversal of matching partitions. -1 - All partitions would match (iterator not initialized) @@ -3760,7 +3760,7 @@ int get_part_iter_for_interval_via_mapping(partition_info *part_info, /* - Partitioning Interval Analysis: Initialize iterator to walk integer interval + Partitioning Interval Analysis: Initialize iterator to walk field interval SYNOPSIS get_part_iter_for_interval_via_walking() @@ -3776,7 +3776,8 @@ int get_part_iter_for_interval_via_mapping(partition_info *part_info, DESCRIPTION Initialize partition set iterator to walk over interval in integer field space. That is, for "const1 <=? t.field <=? const2" interval, initialize - the iterator to do this: + the iterator to return a set of [sub]partitions obtained with the + following procedure: get partition id for t.field = const1, return it get partition id for t.field = const1+1, return it ... t.field = const1+2, ... @@ -3790,7 +3791,7 @@ int get_part_iter_for_interval_via_mapping(partition_info *part_info, "c1 <=? t.field <=? c2", where c1 and c2 are finite. Intervals with +inf/-inf, and [NULL, c1] interval can be processed but that is more tricky and I don't have time to do it right now. - + Additionally we have these requirements: * number of values in the interval must be less then number of [sub]partitions, and @@ -3799,7 +3800,7 @@ int get_part_iter_for_interval_via_mapping(partition_info *part_info, The rationale behind these requirements is that if they are not met we're likely to hit most of the partitions and traversing the interval will only add overhead. So it's better return "all partitions used" in - this case. + that case. RETURN 0 - No matching partitions, iterator not initialized @@ -3917,7 +3918,7 @@ uint32 get_next_partition_id_range(PARTITION_ITERATOR* part_iter) part_iter Partition set iterator structure DESCRIPTION - This is special implementation of PARTITION_ITERATOR::get_next() for + This implementation of PARTITION_ITERATOR::get_next() is special for LIST partitioning: it enumerates partition ids in part_info->list_array[i] where i runs over [min_idx, max_idx] interval. @@ -3937,13 +3938,16 @@ uint32 get_next_partition_id_list(PARTITION_ITERATOR *part_iter) /* - PARTITION_ITERATOR::get_next implementation: walk over integer interval + PARTITION_ITERATOR::get_next implementation: walk over field-space interval SYNOPSIS get_next_partition_via_walking() part_iter Partitioning iterator DESCRIPTION + This implementation of PARTITION_ITERATOR::get_next() returns ids of + partitions that contain records with partitioning field value within + [start_val, end_val] interval. RETURN partition id