From: Sergey Petrunia Date: December 26 2005 5:40am Subject: bk commit into 5.1 tree (sergefp:1.1972) List-Archive: http://lists.mysql.com/commits/407 Message-Id: <20051226054017.B5FEA2D7B69@newbox.mylan> Below is the list of changes that have just been committed into a local 5.1 repository of psergey. When psergey does a push these changes will be propagated to the main repository and, within 24 hours after the push, to the public repository. For information on how to access the public repository see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html ChangeSet 1.1972 05/12/26 08:40:09 sergefp@stripped +5 -0 WL#2985 "Partition Pruning": post-review fixes: - Added more comments. - Added a RANGE_OPT_PARAM::remove_jump_scans flag that disables construction of index_merge SEL_TREEs that represent unusable conditions like "key1part1type == SEL_ARG::IMPOSSIBLE) => + (type == SEL_TREE::IMPOSSIBLE) + */ enum Type { IMPOSSIBLE, ALWAYS, MAYBE, KEY, KEY_SMALLER } type; SEL_TREE(enum Type type_arg) :type(type_arg) {} SEL_TREE() :type(KEY) @@ -354,6 +385,8 @@ */ bool using_real_indexes; + bool remove_jump_scans; + /* used_key_no -> table_key_no translation table. Only makes sense if using_real_indexes==TRUE @@ -380,7 +413,7 @@ uint *imerge_cost_buff; /* buffer for index_merge cost estimates */ uint imerge_cost_buff_size; /* size of the buffer */ - /* TRUE if last checked tree->key can be used for ROR-scan */ + /* TRUE if last checked tree->key can be used for ROR-scan */ bool is_ror_scan; }; @@ -1810,6 +1843,7 @@ param.needed_reg= &needed_reg; param.imerge_cost_buff_size= 0; param.using_real_indexes= TRUE; + param.remove_jump_scans= TRUE; thd->no_errors=1; // Don't warn about NULL init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0); @@ -2005,6 +2039,87 @@ ****************************************************************************/ #ifdef WITH_PARTITION_STORAGE_ENGINE +/* + PartitionPruningModule + + This part of the code does partition pruning. Partition pruning solves the + following problem: given a query over partitioned tables, find partitions + that we will not need to access (i.e. partitions that we can assume to be + empty) when executing the query. + The set of partitions to prune doesn't depend on which query execution + plan will be used to execute the query. + + HOW IT WORKS + + Partition pruning module makes use of RangeAnalysisModule. The following + examples show how the problem of partition pruning can be reduced to the + range analysis problem: + + EXAMPLE 1 + Consider a query: + + SELECT * FROM t1 WHERE (t1.a < 5 OR t1.a = 10) AND t1.a > 3 AND t1.b='z' + + where table t1 is partitioned using PARTITION BY RANGE(t1.a). An apparent + way to find the used (i.e. not pruned away) partitions is as follows: + + 1. analyze the WHERE clause and extract the list of intervals over t1.a + for the above query we will get this list: {(3 < t1.a < 5), (t1.a=10)} + + 2. for each interval I + { + find partitions that have non-empty intersection with I; + mark them as used; + } + + EXAMPLE 2 + Suppose the table is partitioned by HASH(part_func(t1.a, t1.b)). Then + we need to: + + 1. Analyze the WHERE clause and get a list of intervals over (t1.a, t1.b). + 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')) (**) + + 2. for each interval I + { + if (the interval has form "(t1.a, t1.b) = (const1, const2)" ) + { + calculate HASH(part_func(t1.a, t1.b)); + find which partition has records with this hash value and mark + it as used; + } + else + { + mark all partitions as used; + break; + } + } + + For both examples the step #1 is exactly what RangeAnalysisModule could + be used to do, if it was provided with appropriate index description + (array of KEY_PART structures). + In example #1, we need to provide it with description of index(t1.a), + in example #2, we need to provide it with description of index(t1.a, t1.b). + + These index descriptions are further called "partitioning index + descriptions". Note that it doesn't matter if such indexes really exist, + as range analysis module only uses the description. + + Putting it all together, partitioning module works as follows: + + prune_partitions() { + call create_partition_index_descrition(); + + call get_mm_tree(); // invoke the RangeAnalysisModule + + // analyze the obtained interval list and get used partitions + call find_used_partitions(); + } + +*/ + struct st_part_prune_param; struct st_part_opt_info; @@ -2019,7 +2134,7 @@ */ typedef struct st_part_prune_param { - RANGE_OPT_PARAM range_param; /* Range optimizer parameters */ + RANGE_OPT_PARAM range_param; /* Range analyzer parameters */ /*************************************************************** Following fields are filled in based solely on partitioning @@ -2096,18 +2211,30 @@ /*************************************************************** Following fields are used to store an 'iterator' that can be - used to obtain a set of used of used partitions. + used to obtain a set of used artitions. **************************************************************/ /* - Start number, end number and + Start and end+1 partition "numbers". They can have two meanings depending + 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 */ - part_num_to_partition_id_func part_num_to_part_id; 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; } PART_PRUNE_PARAM; static bool create_partition_index_descrition(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); +static int find_used_partitions_imerge_list(PART_PRUNE_PARAM *ppar, + List &merges); static void mark_all_partitions_as_used(partition_info *part_info); static uint32 part_num_to_part_id_range(PART_PRUNE_PARAM* prune_par, uint32 num); @@ -2185,6 +2312,7 @@ range_par->keys= 1; // one index range_par->using_real_indexes= FALSE; + range_par->remove_jump_scans= FALSE; range_par->real_keynr[0]= 0; thd->no_errors=1; // Don't warn about NULL @@ -2196,8 +2324,7 @@ int res; tree= get_mm_tree(range_par, pprune_cond); - if (!tree || (tree->type != SEL_TREE::KEY && - tree->type != SEL_TREE::KEY_SMALLER)) + if (!tree) goto all_used; if (tree->type == SEL_TREE::IMPOSSIBLE) @@ -2205,6 +2332,9 @@ retval= TRUE; goto end; } + + if (tree->type != SEL_TREE::KEY && tree->type != SEL_TREE::KEY_SMALLER) + goto all_used; if (tree->merges.is_empty()) { @@ -2220,27 +2350,30 @@ } else { - res= 0; - DBUG_ASSERT(tree->merges.elements == 1); - SEL_IMERGE *imerge= tree->merges.head(); - for (SEL_TREE **ptree= imerge->trees; ptree < imerge->trees_next; ptree++) - { - 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; - if (-1 == (res |= find_used_partitions(&prune_param, (*ptree)->keys[0]))) + if (tree->merges.elements == 1) + { + 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))) goto all_used; } } - if (!res) - retval= TRUE; + /* + res == 0 => no used partitions => retval=TRUE + res == 1 => some used partitions => retval=FALSE + res == -1 - we jump over this line to all_used: + */ + retval= test(!res); goto end; all_used: + retval= FALSE; // some partitions are used mark_all_partitions_as_used(prune_param.part_info); end: thd->no_errors=0; @@ -2316,16 +2449,113 @@ bitmap_set_bit(&part_info->used_partitions, start); } -static -uint32 part_num_to_part_id_range(PART_PRUNE_PARAM* prune_par, uint32 num) +/* 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 + find_used_partitions_imerge_list + ppar Partition pruning context. + key_tree Intervals tree to perform pruning for. + + DESCRIPTION + 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. + + RETURN + See find_used_partitions() +*/ + +static int find_used_partitions_imerge_list(PART_PRUNE_PARAM *ppar, + List &merges) +{ + MY_BITMAP all_merges; + uint bitmap_bytes; + uint32 *bitmap_buf; + uint n_bits= ppar->part_info->used_partitions.n_bits; + bitmap_bytes= bitmap_buffer_size(n_bits); + if (!(bitmap_buf= (uint32*)alloc_root(ppar->range_param.mem_root, + bitmap_bytes))) + { + /* + Fallback, process just first SEL_IMERGE. This can leave us with more + partitions marked as used then actually needed. + */ + return find_used_partitions_imerge(ppar, merges.head()); + } + bitmap_init(&all_merges, bitmap_buf, n_bits, FALSE); + bitmap_set_prefix(&all_merges, n_bits); + + List_iterator it(merges); + SEL_IMERGE *imerge; + while ((imerge=it++)) + { + int res= find_used_partitions_imerge(ppar, imerge); + if (!res) + { + /* no used partitions on one ANDed imerge => no used partitions at all */ + return 0; + } + + if (res != -1) + bitmap_intersect(&all_merges, &ppar->part_info->used_partitions); + + if (bitmap_is_clear_all(&all_merges)) + return 0; + + bitmap_clear_all(&ppar->part_info->used_partitions); + } + memcpy(ppar->part_info->used_partitions.bitmap, all_merges.bitmap, + bitmap_bytes); + return 1; +} + + +/* + Find the set of used partitions for SEL_IMERGE structure + SYNOPSIS + find_used_partitions_imerge() + ppar Partition pruning context. + key_tree Intervals tree to perform pruning for. + + DESCRIPTION + SEL_IMERGE represents "tree1 OR tree2 OR ...". The implementation is + trivial - just use mark used partitions for each tree and bail out early + if for some tree_{i} all partitions are used. + + RETURN + See find_used_partitions(). +*/ + static -uint32 part_num_to_part_id_list(PART_PRUNE_PARAM* prune_par, uint32 num) +int find_used_partitions_imerge(PART_PRUNE_PARAM *ppar, SEL_IMERGE *imerge) { - return prune_par->part_info->list_array[num].partition_id; + int res= 0; + for (SEL_TREE **ptree= imerge->trees; ptree < imerge->trees_next; ptree++) + { + 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; + if (-1 == (res |= find_used_partitions(ppar, (*ptree)->keys[0]))) + return -1; + } + return res; } @@ -2370,8 +2600,8 @@ 1 OK, one or more [sub]partitions are marked as used. 0 The passed condition doesn't match any partitions -1 Couldn't infer any partition pruning "intervals" from the passed - SEL_ARG* tree. Marking partitions as used is the responsibility of - the caller. + SEL_ARG* tree (which means that all partitions should be marked as + used) Marking partitions as used is the responsibility of the caller. */ static @@ -2401,11 +2631,15 @@ key_parts);); /* Find minimum */ if (key_tree->min_flag & NO_MIN_RANGE) - { ppar->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 + 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 || @@ -2419,11 +2653,9 @@ } } - /* Find maximum */ - if (key_tree->min_flag & NO_MAX_RANGE) - { + /* 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, @@ -2441,7 +2673,7 @@ 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 + Save our intent to mark full partition as used if we will not be able to obtain further limits on subpartitions */ set_full_part_if_bad_ret= TRUE; @@ -2507,7 +2739,6 @@ bitmap_set_bit(&part_info->used_partitions, ppar->part_num_to_part_id(ppar, num) * part_info->no_subparts + subpart_id); - ppar->start_part_num++; } res= 1; /* Some partitions were marked as used */ goto pop_and_go_right; @@ -2726,8 +2957,7 @@ key_part->length= (*field)->pack_length_in_rec(); /* psergey-todo: check yet again if this is correct for tricky field types, - e.g. see "Fix a fatal error in decimal key handling" in - open_binary_frm(). + e.g. see "Fix a fatal error in decimal key handling" in open_binary_frm() */ key_part->store_length= (*field)->pack_length(); if ((*field)->real_maybe_null()) @@ -2768,7 +2998,7 @@ { fprintf(DBUG_FILE, "%s%s", p==parts?"":" ,", p->field->field_name); } - fprintf(DBUG_FILE, ");\n"); + fputs(");\n", DBUG_FILE); DBUG_UNLOCK_FILE; DBUG_VOID_RETURN; } @@ -2780,7 +3010,9 @@ fprintf(DBUG_FILE, "NULL"); else { - String str; + char buf[256]; + String str(buf, sizeof(buf), &my_charset_bin); + str.length(0); String *pstr; pstr= field->val_str(&str); fprintf(DBUG_FILE, "'%s'", pstr->c_ptr_safe()); @@ -2795,7 +3027,7 @@ DBUG_LOCK_FILE; if (!(arg->min_flag & NO_MIN_RANGE)) { - arg->field->set_key_image((char*)(arg->min_value), part->length); + store_key_image_to_rec(part->field, (char*)(arg->min_value), part->length); dbug_print_field(part->field); if (arg->min_flag & NEAR_MIN) fputs(" < ", DBUG_FILE); @@ -2811,9 +3043,10 @@ fputs(" < ", DBUG_FILE); else fputs(" <= ", DBUG_FILE); - arg->field->set_key_image((char*)(arg->max_value), part->length); + store_key_image_to_rec(part->field, (char*)(arg->min_value), part->length); dbug_print_field(part->field); } + fputs("\n", DBUG_FILE); DBUG_UNLOCK_FILE; DBUG_VOID_RETURN; } @@ -2837,12 +3070,14 @@ DBUG_ENTER("dbug_print_onepoint_range"); DBUG_LOCK_FILE; SEL_ARG **end= start + num; + for (SEL_ARG **arg= start; arg != end; arg++) { Field *field= (*arg)->field; fprintf(DBUG_FILE, "%s%s=", (arg==start)?"":", ", field->field_name); dbug_print_field(field); } + fputs("\n", DBUG_FILE); DBUG_UNLOCK_FILE; DBUG_VOID_RETURN; } @@ -5062,7 +5297,8 @@ using index_merge. */ -bool sel_trees_can_be_ored(SEL_TREE *tree1, SEL_TREE *tree2, RANGE_OPT_PARAM* param) +bool sel_trees_can_be_ored(SEL_TREE *tree1, SEL_TREE *tree2, + RANGE_OPT_PARAM* param) { key_map common_keys= tree1->keys_map; DBUG_ENTER("sel_trees_can_be_ored"); @@ -5088,6 +5324,79 @@ DBUG_RETURN(FALSE); } + +/* + Remove the trees that are not suitable for record retrieval. + SYNOPSIS + param Range analysis parameter + tree Tree to be processed, tree->type is KEY or KEY_SMALLER + + DESCRIPTION + This function walks through tree->keys[] and removes the SEL_ARG* trees + that are not "maybe" trees (*) and cannot be used to construct quick range + selects. + (*) - have type MAYBE or MAYBE_KEY. Perhaps we should remove trees of + these types here as well. + + A SEL_ARG* tree cannot be used to construct quick select if it has + tree->part != 0. (e.g. it could represent "keypart2 < const"). + + WHY THIS FUNCTION IS NEEDED + + Normally we allow construction of SEL_TREE objects that have SEL_ARG + trees that do not allow quick range select construction. For example for + " keypart1=1 AND keypart2=2 " the execution will proceed as follows: + tree1= SEL_TREE { SEL_ARG{keypart1=1} } + tree2= SEL_TREE { SEL_ARG{keypart2=2} } -- can't make quick range select + from this + call tree_and(tree1, tree2) -- this joins SEL_ARGs into a usable SEL_ARG + tree. + + There is an exception though: when we construct index_merge SEL_TREE, + any SEL_ARG* tree that cannot be used to construct quick range select can + be removed, because current range analysis code doesn't provide any way + that tree could be later combined with another tree. + Consider an example: we should not construct + st1 = SEL_TREE { + merges = SEL_IMERGE { + SEL_TREE(t.key1part1 = 1), + SEL_TREE(t.key2part2 = 2) -- (*) + } + }; + because + - (*) cannot be used to construct quick range select, + - There is no execution path that would cause (*) to be converted to + a tree that could be used. + + The latter is easy to verify: first, notice that the only way to convert + (*) into a usable tree is to call tree_and(something, (*)). + + Second look at what tree_and/tree_or function would do when passed a + SEL_TREE that has the structure like st1 tree has, and conlcude that + tree_and(something, (*)) will not be called. + + RETURN + 0 Ok, some suitable trees left + 1 No tree->keys[] left. +*/ + +static bool remove_nonrange_trees(RANGE_OPT_PARAM *param, SEL_TREE *tree) +{ + bool res= FALSE; + for (uint i=0; i < param->keys; i++) + { + if (tree->keys[i]) + { + if (tree->keys[i]->part) + tree->keys[i]= NULL; + else + res= TRUE; + } + } + return res; +} + + static SEL_TREE * tree_or(RANGE_OPT_PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2) { @@ -5131,6 +5440,13 @@ /* ok, two trees have KEY type but cannot be used without index merge */ if (tree1->merges.is_empty() && tree2->merges.is_empty()) { + if (param->remove_jump_scans) + { + bool no_trees= remove_nonrange_trees(param, tree1); + no_trees= no_trees || remove_nonrange_trees(param, tree2); + if (no_trees) + DBUG_RETURN(new SEL_TREE(SEL_TREE::ALWAYS)); + } SEL_IMERGE *merge; /* both trees are "range" trees, produce new index merge structure */ if (!(result= new SEL_TREE()) || !(merge= new SEL_IMERGE()) || @@ -5153,7 +5469,9 @@ /* one tree is index merge tree and another is range tree */ if (tree1->merges.is_empty()) swap_variables(SEL_TREE*, tree1, tree2); - + + if (param->remove_jump_scans && remove_nonrange_trees(param, tree2)) + DBUG_RETURN(new SEL_TREE(SEL_TREE::ALWAYS)); /* add tree2 to tree1->merges, checking if it collapses to ALWAYS */ if (imerge_list_or_tree(param, &tree1->merges, tree2)) result= new SEL_TREE(SEL_TREE::ALWAYS); --- 1.118/sql/table.h 2005-12-07 09:28:13 +03:00 +++ 1.119/sql/table.h 2005-12-26 08:39:58 +03:00 @@ -297,6 +297,7 @@ FILESORT_INFO sort; #ifdef WITH_PARTITION_STORAGE_ENGINE partition_info *part_info; /* Partition related information */ + bool no_partitions_used; /* If true, all partitions have been pruned away */ #endif bool fill_item_list(List *item_list) const; --- 1.17/sql/sql_partition.cc 2005-12-22 12:28:50 +03:00 +++ 1.18/sql/sql_partition.cc 2005-12-26 08:39:58 +03:00 @@ -2493,9 +2493,10 @@ DBUG_RETURN(TRUE); } + /* - Find the part of part_info->list_array that corresponds to given interval - + Find the sub-array part_info->list_array that corresponds to given interval + SYNOPSIS get_list_array_idx_for_endpoint() part_info Partitioning info (partitioning type must be LIST) @@ -2504,17 +2505,16 @@ include_endpoint TRUE iff the interval includes the endpoint DESCRIPTION - This function finds the part of part_info->list_array where values of + This function finds the sub-array of part_info->list_array where values of list_array[idx].list_value are contained within the specifed interval. list_array is ordered by list_value, so 1. For [a; +inf) or (a; +inf)-type intervals (left_endpoint==TRUE), the - sought array part starts at some index idx and continues till array - end. + sought sub-array starts at some index idx and continues till array end. The function returns first number idx, such that list_array[idx].list_value is contained within the passed interval. 2. For (-inf; a] or (-inf; a)-type intervals (left_endpoint==FALSE), the - sought array part starts at array start and continues till some last + sought sub-array starts at array start and continues till some last index idx. The function returns first number idx, such that list_array[idx].list_value is NOT contained within the passed interval. @@ -2522,14 +2522,14 @@ returned. NOTE - The caller will call this function and then will run along the part of + The caller will call this function and then will run along the sub-array of list_array to collect partition ids. If the number of list values is significantly higher then number of partitions, this could be slow and we could invent some other approach. The "run over list array" part is already wrapped in a get_next()-like function. RETURN - The edge of corresponding part_info->list_array part. + The edge of corresponding sub-array of part_info->list_array */ uint32 get_list_array_idx_for_endpoint(partition_info *part_info, @@ -2541,6 +2541,7 @@ uint list_index; longlong list_value; uint min_list_index= 0, max_list_index= part_info->no_list_values - 1; + /* Get the partitioning function value for the endpoint */ longlong part_func_value= part_info->part_expr->val_int(); while (max_list_index >= min_list_index) { @@ -2596,8 +2597,8 @@ /* - Find the part of part_info->range_int_array that covers the given interval - + Find the sub-array of part_info->range_int_array that covers given interval + SYNOPSIS get_partition_id_range_for_endpoint() part_info Partitioning info (partitioning type must be RANGE) @@ -2607,9 +2608,9 @@ interval DESCRIPTION - This function finds the part of part_info->range_int_array where the + This function finds the sub-array of part_info->range_int_array where the elements have non-empty intersections with the given interval. - + A range_int_array element at index idx represents the interval [range_int_array[idx-1], range_int_array[idx]), @@ -2617,14 +2618,13 @@ intervals are disjoint and ordered by their right bound, so 1. For [a; +inf) or (a; +inf)-type intervals (left_endpoint==TRUE), the - sought array part starts at some index idx and continues till array - end. + sought sub-array starts at some index idx and continues till array end. The function returns first number idx, such that the interval represented by range_int_array[idx] has non empty intersection with the passed interval. 2. For (-inf; a] or (-inf; a)-type intervals (left_endpoint==FALSE), the - sought array part starts at array start and continues till some last + sought sub-array starts at array start and continues till some last index idx. The function returns first number idx, such that the interval represented by range_int_array[idx] has EMPTY intersection with the @@ -2634,7 +2634,7 @@ returned. RETURN - The edge of corresponding part_info->range_int_array part. + The edge of corresponding part_info->range_int_array sub-array. */ uint32 get_partition_id_range_for_endpoint(partition_info *part_info, @@ -2645,6 +2645,7 @@ longlong *range_array= part_info->range_int_array; uint max_partition= part_info->no_parts - 1; uint min_part_id= 0, max_part_id= max_partition, loc_part_id; + /* Get the partitioning function value for the endpoint */ longlong part_func_value= part_info->part_expr->val_int(); while (max_part_id > min_part_id) { --- 1.1/mysql-test/r/partition_pruning.result 2005-12-22 12:28:51 +03:00 +++ 1.2/mysql-test/r/partition_pruning.result 2005-12-26 08:39:57 +03:00 @@ -213,3 +213,48 @@ id select_type table partitions type possible_keys key key_len ref rows Extra 1 SIMPLE t1 p0,p1 ALL NULL NULL NULL NULL 3 Using where drop table t1; +create table t1 ( +a1 int not null +) +partition by range (a1) ( +partition p0 values less than (3), +partition p1 values less than (6), +partition p2 values less than (9) +); +insert into t1 values (1),(2),(3); +explain partitions select * from t1 where a1 > 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 a1 >= 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 a1 < 3 and a1 > 3; +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +drop table t1; +create table t3 (a int, b int) +partition by list(a) subpartition by hash(b) subpartitions 4 ( +partition p0 values in (1), +partition p1 values in (2), +partition p2 values in (3), +partition p3 values in (4) +); +insert into t3 values (1,1),(2,2),(3,3); +explain partitions select * from t3 where a=2 or b=1; +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE t3 p0_sp1,p1_sp0,p1_sp1,p1_sp2,p1_sp3,p2_sp1,p3_sp1 ALL NULL NULL NULL NULL 3 Using where +explain partitions select * from t3 where a=4 or b=2; +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE t3 p0_sp2,p1_sp2,p2_sp2,p3_sp0,p3_sp1,p3_sp2,p3_sp3 ALL NULL NULL NULL NULL 3 Using where +explain partitions select * from t3 where (a=2 or b=1) and (a=4 or b=2) ; +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE t3 p1_sp2,p3_sp1 ALL NULL NULL NULL NULL 3 Using where +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; +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE t1 p0 ALL NULL NULL NULL NULL 2 Using where +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; --- 1.1/mysql-test/t/partition_pruning.test 2005-12-22 12:28:51 +03:00 +++ 1.2/mysql-test/t/partition_pruning.test 2005-12-26 08:39:58 +03:00 @@ -192,4 +192,46 @@ explain partitions select * from t1 where a='b'; drop table t1; +# +# Test cases for bugs found in code review: +# +create table t1 ( + a1 int not null +) +partition by range (a1) ( + partition p0 values less than (3), + partition p1 values less than (6), + partition p2 values less than (9) +); +insert into t1 values (1),(2),(3); +explain partitions select * from t1 where a1 > 3; +explain partitions select * from t1 where a1 >= 3; +explain partitions select * from t1 where a1 < 3 and a1 > 3; +drop table t1; + +# +create table t3 (a int, b int) + partition by list(a) subpartition by hash(b) subpartitions 4 ( + partition p0 values in (1), + partition p1 values in (2), + partition p2 values in (3), + partition p3 values in (4) + ); +insert into t3 values (1,1),(2,2),(3,3); + +explain partitions select * from t3 where a=2 or b=1; +explain partitions select * from t3 where a=4 or b=2; +explain partitions select * from t3 where (a=2 or b=1) and (a=4 or b=2) ; + +# Test for NULLs +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 +explain partitions select * from t1 where a is not null; +drop table t1; + +# No tests for NULLs in RANGE(monotonic_expr()) - they depend on BUG#15447 +# being fixed.