Skip to content

Commit dc052d5

Browse files
MDEV-15621 Auto add RANGE COLUMNS partitions by interval
Allow auto partitioning by interval in PARTITION BY RANGE COLUMNS PARTITION BY RANGE COLUMNS (col_name) INTERVAL interval [AUTO] ( PARTITION partition_name VALUES LESS THAN (value) [, PARTITION partition_name VALUES LESS THAN (value) ... ] ) where - col_name is the name of one column of type DATE or DATETIME or TIMESTAMP - at least one partition is supplied, and the highest partition cannot have MAXVALUE range - INTERVAL interval is a positive time interval. it can be mariadb format or oracle NUMTODSINTERVAL/NUMTOYMINTERVAL format. Like versioning, the smallest unit is second, i.e. no subsecond like microsecond. - DATE column cannot have interval with values less than a day When performing DML on such a table, it will first add partitions by the specified interval until the partition covers the current time. Partition addition will not cause an implicit commit like DDL normally does. The partitions are named pN. Otherwise the table behaves exactly the same as a normal RANGE COLUMNS partitioned table. Note that TIMESTAMP is not allowed as a type for PARTITION BY RANGE COLUMNS otherwise.
1 parent d5d1a2e commit dc052d5

11 files changed

Lines changed: 2865 additions & 16 deletions

mysql-test/main/partition_range_interval.result

Lines changed: 1704 additions & 0 deletions
Large diffs are not rendered by default.

mysql-test/main/partition_range_interval.test

Lines changed: 580 additions & 0 deletions
Large diffs are not rendered by default.

sql/lex.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,8 @@ SYMBOL sql_functions[] = {
778778
{ "NOW", SYM(NOW_SYM)},
779779
{ "NTH_VALUE", SYM(NTH_VALUE_SYM)},
780780
{ "NTILE", SYM(NTILE_SYM)},
781+
{ "NUMTODSINTERVAL", SYM(NUMTODSINTERVAL_SYM)},
782+
{ "NUMTOYMINTERVAL", SYM(NUMTOYMINTERVAL_SYM)},
781783
{ "POSITION", SYM(POSITION_SYM)},
782784
{ "PERCENT_RANK", SYM(PERCENT_RANK_SYM)},
783785
{ "PERCENTILE_CONT", SYM(PERCENTILE_CONT_SYM)},

sql/partition_info.cc

Lines changed: 216 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ bool partition_info::set_up_default_partitions(THD *thd, handler *file,
400400
num_parts= 2;
401401
use_default_num_partitions= false;
402402
}
403-
else if (part_type != HASH_PARTITION)
403+
else if (part_type != HASH_PARTITION && !is_range_interval())
404404
{
405405
const char *error_string;
406406
if (part_type == RANGE_PARTITION)
@@ -891,6 +891,39 @@ bool partition_info::vers_set_hist_part(THD *thd, uint *create_count)
891891
return false;
892892
}
893893

894+
/*
895+
Determine the number of range interval partitions to create, like
896+
partition_info::vers_set_hist_part.
897+
898+
TODO(MDEV-15621): consider making this more efficient by using a
899+
hash, possibly like Partition_share::partition_name_hash
900+
*/
901+
bool partition_info::range_interval_set_count(THD* thd, uint *create_count)
902+
{
903+
/* At least one range partition is defined */
904+
DBUG_ASSERT(partitions.elements > 0);
905+
partition_element *el= partitions.elem(partitions.elements - 1);
906+
Item *item= el->get_col_val(0).item_expression;
907+
MYSQL_TIME cur_time, end_time;
908+
thd->variables.time_zone->gmt_sec_to_TIME(&end_time, thd->query_start());
909+
longlong cur= item->val_datetime_packed(thd);
910+
unpack_time(cur, &cur_time, MYSQL_TIMESTAMP_DATETIME);
911+
longlong end= pack_time(&end_time);
912+
*create_count= 0;
913+
while (cur <= end)
914+
{
915+
if (date_add_interval(thd, &cur_time, int_type, interval))
916+
return true;
917+
cur= pack_time(&cur_time);
918+
++*create_count;
919+
if (partitions.elements + *create_count > MAX_PARTITIONS)
920+
{
921+
my_error(ER_TOO_MANY_PARTITIONS_ERROR, MYF(0));
922+
return true;
923+
}
924+
}
925+
return false;
926+
}
894927

895928
/**
896929
@brief Run fast_alter_partition_table() to add new history partitions
@@ -1014,6 +1047,103 @@ bool vers_create_partitions(THD *thd, TABLE_LIST* tl, uint num_parts)
10141047
return result;
10151048
}
10161049

1050+
/*
1051+
similar to vers_create_partitions, create range interval partitions
1052+
*/
1053+
bool range_interval_create_partitions(THD* thd, TABLE_LIST* tl, uint num_parts)
1054+
{
1055+
bool result= true;
1056+
Table_specification_st create_info;
1057+
Alter_info alter_info;
1058+
TABLE *table= tl->table;
1059+
partition_info *save_part_info= thd->work_part_info;
1060+
/* TODO: this may still trigger MSAN unitialised? */
1061+
bool save_no_write_to_binlog= thd->lex->no_write_to_binlog;
1062+
thd->lex->no_write_to_binlog= true;
1063+
1064+
DBUG_ASSERT(!thd->is_error());
1065+
DBUG_ASSERT(num_parts);
1066+
1067+
{
1068+
alter_info.reset();
1069+
alter_info.partition_flags= ALTER_PARTITION_ADD;
1070+
create_info.init();
1071+
create_info.alter_info= &alter_info;
1072+
Alter_table_ctx alter_ctx(thd, tl, 1, &table->s->db, &table->s->table_name);
1073+
1074+
MDL_REQUEST_INIT(&tl->mdl_request, MDL_key::TABLE, tl->db.str,
1075+
tl->table_name.str, MDL_SHARED_NO_WRITE, MDL_TRANSACTION);
1076+
if (thd->mdl_context.acquire_lock(&tl->mdl_request,
1077+
thd->variables.lock_wait_timeout))
1078+
goto exit;
1079+
table->mdl_ticket= tl->mdl_request.ticket;
1080+
1081+
create_info.db_type= table->s->db_type();
1082+
DBUG_ASSERT(create_info.db_type);
1083+
1084+
partition_info *part_info= new partition_info();
1085+
if (unlikely(!part_info))
1086+
{
1087+
my_error(ER_OUT_OF_RESOURCES, MYF(0));
1088+
goto exit;
1089+
}
1090+
part_info->use_default_num_partitions= false;
1091+
part_info->use_default_num_subpartitions= false;
1092+
part_info->num_parts= num_parts;
1093+
part_info->num_subparts= table->part_info->num_subparts;
1094+
part_info->subpart_type= table->part_info->subpart_type;
1095+
part_info->num_columns= table->part_info->num_columns;
1096+
part_info->part_type= RANGE_PARTITION;
1097+
/* for partition_info::fix_parser_data to exit early */
1098+
part_info->int_type= table->part_info->int_type;
1099+
1100+
thd->work_part_info= part_info;
1101+
bool partition_changed= false;
1102+
bool fast_alter_partition= false;
1103+
if (prep_alter_part_table(thd, table, &alter_info, &create_info,
1104+
&partition_changed, &fast_alter_partition))
1105+
{
1106+
my_error(ER_INTERNAL_ERROR, MYF(ME_WARNING),
1107+
tl->db.str, tl->table_name.str);
1108+
goto exit;
1109+
}
1110+
if (!fast_alter_partition)
1111+
{
1112+
my_error(ER_INTERNAL_ERROR, MYF(ME_WARNING),
1113+
tl->db.str, tl->table_name.str);
1114+
goto exit;
1115+
}
1116+
DBUG_ASSERT(partition_changed);
1117+
if (mysql_prepare_alter_table(thd, table, &create_info, &alter_info,
1118+
&alter_ctx))
1119+
{
1120+
my_error(ER_INTERNAL_ERROR, MYF(ME_WARNING),
1121+
tl->db.str, tl->table_name.str);
1122+
goto exit;
1123+
}
1124+
1125+
alter_info.db= alter_ctx.db;
1126+
alter_info.table_name= alter_ctx.table_name;
1127+
if (fast_alter_partition_table(thd, table, &alter_info, &alter_ctx,
1128+
&create_info, tl))
1129+
{
1130+
my_error(ER_INTERNAL_ERROR, MYF(ME_WARNING),
1131+
tl->db.str, tl->table_name.str);
1132+
goto exit;
1133+
}
1134+
}
1135+
1136+
result= false;
1137+
// NOTE: we have to return DA_EMPTY for new command
1138+
DBUG_ASSERT(thd->get_stmt_da()->is_ok());
1139+
thd->get_stmt_da()->reset_diagnostics_area();
1140+
thd->variables.option_bits|= OPTION_BINLOG_THIS;
1141+
1142+
exit:
1143+
thd->lex->no_write_to_binlog= save_no_write_to_binlog;
1144+
thd->work_part_info= save_part_info;
1145+
return result;
1146+
}
10171147

10181148
/**
10191149
Warn at the end of DML command if the last history partition is out of LIMIT.
@@ -2320,6 +2450,7 @@ bool partition_info::fix_parser_data(THD *thd)
23202450
partition_element *part_elem;
23212451
uint num_elements;
23222452
uint i= 0, j, k;
2453+
int sql_command= thd_sql_command(thd);
23232454
DBUG_ENTER("partition_info::fix_parser_data");
23242455

23252456
if (!(part_type == RANGE_PARTITION ||
@@ -2334,13 +2465,25 @@ bool partition_info::fix_parser_data(THD *thd)
23342465
DBUG_RETURN(true);
23352466
}
23362467
/* If not set, use DEFAULT = 2 for CREATE and ALTER! */
2337-
if ((thd_sql_command(thd) == SQLCOM_CREATE_TABLE ||
2338-
thd_sql_command(thd) == SQLCOM_ALTER_TABLE) &&
2468+
if ((sql_command == SQLCOM_CREATE_TABLE ||
2469+
sql_command == SQLCOM_ALTER_TABLE) &&
23392470
key_algorithm == KEY_ALGORITHM_NONE)
23402471
key_algorithm= KEY_ALGORITHM_55;
23412472
}
23422473
DBUG_RETURN(FALSE);
23432474
}
2475+
/*
2476+
TODO(MDEV-15621): we exit here for range interval partitions
2477+
because if this is called from prep_alter_part_table then
2478+
partition data is not calculated (in
2479+
check_range_interval_constants) yet. But maybe we shouldn't exit
2480+
here in user CREATE/ALTER TABLE statements for other checks.
2481+
*/
2482+
if (sql_command != SQLCOM_CREATE_TABLE &&
2483+
sql_command != SQLCOM_ALTER_TABLE && is_range_interval())
2484+
{
2485+
DBUG_RETURN(FALSE);
2486+
}
23442487
if (is_sub_partitioned() && list_of_subpart_fields)
23452488
{
23462489
/* KEY subpartitioning, check ALGORITHM = N. Should not pass the parser! */
@@ -2785,6 +2928,76 @@ bool partition_info::vers_init_info(THD * thd)
27852928
return false;
27862929
}
27872930

2931+
bool partition_info::set_interval(THD* thd, Item* ival, interval_type type,
2932+
const char *table_name)
2933+
{
2934+
bool error= get_interval_value(thd, ival, type, &interval) ||
2935+
interval.neg || interval.second_part ||
2936+
!(interval.year || interval.month || interval.day || interval.hour ||
2937+
interval.minute || interval.second);
2938+
if (error)
2939+
{
2940+
my_error(ER_PART_WRONG_VALUE, MYF(0), table_name, "INTERVAL");
2941+
return true;
2942+
}
2943+
int_type= type;
2944+
return false;
2945+
}
2946+
2947+
bool partition_info::set_interval(int num, LEX_CSTRING &type, bool is_ds,
2948+
const char *table_name)
2949+
{
2950+
if (num < 0)
2951+
goto end;
2952+
if (is_ds)
2953+
{
2954+
if (type.length == 3 && !strncasecmp(type.str, "DAY", 3))
2955+
{
2956+
interval.day= num;
2957+
int_type= INTERVAL_DAY;
2958+
return false;
2959+
}
2960+
else if (type.length == 4 && !strncasecmp(type.str, "HOUR", 4))
2961+
{
2962+
interval.hour= num;
2963+
int_type= INTERVAL_HOUR;
2964+
return false;
2965+
}
2966+
else if (type.length == 6)
2967+
{
2968+
if (!strncasecmp(type.str, "MINUTE", 6))
2969+
{
2970+
interval.minute= num;
2971+
int_type= INTERVAL_MINUTE;
2972+
return false;
2973+
}
2974+
else if (!strncasecmp(type.str, "SECOND", 6))
2975+
{
2976+
interval.second= num;
2977+
int_type= INTERVAL_SECOND;
2978+
return false;
2979+
}
2980+
}
2981+
}
2982+
else
2983+
{
2984+
if (type.length == 4 && !strncasecmp(type.str, "YEAR", 4))
2985+
{
2986+
interval.year= num;
2987+
int_type= INTERVAL_YEAR;
2988+
return false;
2989+
}
2990+
else if (type.length == 5 && !strncasecmp(type.str, "MONTH", 5))
2991+
{
2992+
interval.month= num;
2993+
int_type= INTERVAL_MONTH;
2994+
return false;
2995+
}
2996+
}
2997+
end:
2998+
my_error(ER_PART_WRONG_VALUE, MYF(0), table_name, "INTERVAL");
2999+
return true;
3000+
}
27883001

27893002
/**
27903003
Assign INTERVAL and STARTS for SYSTEM_TIME partitions.

sql/partition_info.h

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ class partition_info : public DDL_LOG_STATE, public Sql_alloc
207207
part_column_list_val *list_col_array;
208208
};
209209

210+
/* TODO: change to pointer */
211+
INTERVAL interval;
212+
enum interval_type int_type;
213+
210214
Vers_part_info *vers_info;
211215

212216
/********************************************
@@ -319,7 +323,7 @@ class partition_info : public DDL_LOG_STATE, public Sql_alloc
319323
restore_part_field_ptrs(NULL), restore_subpart_field_ptrs(NULL),
320324
part_expr(NULL), subpart_expr(NULL), item_free_list(NULL),
321325
bitmaps_are_initialized(FALSE),
322-
list_array(NULL), vers_info(NULL), err_value(0),
326+
list_array(NULL), int_type(INTERVAL_LAST), vers_info(NULL), err_value(0),
323327
part_info_string(NULL),
324328
curr_part_elem(NULL), current_partition(NULL),
325329
curr_list_object(0), num_columns(0), table(NULL),
@@ -348,6 +352,9 @@ class partition_info : public DDL_LOG_STATE, public Sql_alloc
348352
temp_partitions.empty();
349353
part_field_list.empty();
350354
subpart_field_list.empty();
355+
interval.second_part= interval.second= interval.minute=
356+
interval.hour= interval.day= interval.month= interval.year= 0;
357+
interval.neg= FALSE;
351358
}
352359
~partition_info() = default;
353360

@@ -417,11 +424,16 @@ class partition_info : public DDL_LOG_STATE, public Sql_alloc
417424
bool field_in_partition_expr(Field *field) const;
418425

419426
bool vers_init_info(THD *thd);
427+
bool set_interval(THD* thd, Item* ival, interval_type type,
428+
const char *table_name);
429+
bool set_interval(int num, LEX_CSTRING& type, bool is_ds,
430+
const char *table_name);
420431
bool vers_set_interval(THD *thd, Item *interval,
421432
interval_type int_type, Item *starts,
422433
bool auto_part, const char *table_name);
423434
bool vers_set_limit(ulonglong limit, bool auto_part, const char *table_name);
424435
bool vers_set_hist_part(THD* thd, uint *create_count);
436+
bool range_interval_set_count(THD* thd, uint *create_count);
425437
bool vers_require_hist_part(THD *thd) const
426438
{
427439
return part_type == VERSIONING_PARTITION &&
@@ -430,6 +442,8 @@ class partition_info : public DDL_LOG_STATE, public Sql_alloc
430442
void vers_check_limit(THD *thd);
431443
bool vers_fix_field_list(THD *thd);
432444
void vers_update_el_ids();
445+
bool is_range_interval() const
446+
{ return int_type != INTERVAL_LAST && part_type == RANGE_PARTITION; }
433447
partition_element *get_partition(uint part_id)
434448
{
435449
List_iterator<partition_element> it(partitions);
@@ -452,6 +466,7 @@ void part_type_error(THD *thd, partition_info *work_part_info,
452466
uint32 get_next_partition_id_range(struct st_partition_iter* part_iter);
453467
bool check_partition_dirs(partition_info *part_info);
454468
bool vers_create_partitions(THD* thd, TABLE_LIST* tl, uint num_parts);
469+
bool range_interval_create_partitions(THD* thd, TABLE_LIST* tl, uint num_parts);
455470

456471
/* Initialize the iterator to return a single partition with given part_id */
457472

@@ -549,12 +564,12 @@ Lex_ident_partition make_partition_name(char *move_ptr, uint i)
549564
inline
550565
uint partition_info::next_part_no(uint new_parts) const
551566
{
552-
if (part_type != VERSIONING_PARTITION)
567+
if (part_type != VERSIONING_PARTITION && !is_range_interval())
553568
return num_parts;
554569
DBUG_ASSERT(new_parts > 0);
555570
/* Choose first non-occupied name suffix */
556-
uint32 suffix= num_parts - 1;
557-
DBUG_ASSERT(suffix > 0);
571+
uint32 suffix= part_type == VERSIONING_PARTITION ? num_parts - 1 : 0;
572+
DBUG_ASSERT(suffix > 0 || part_type != VERSIONING_PARTITION);
558573
char part_name[MAX_PART_NAME_SIZE + 1];
559574
List_iterator_fast<partition_element> it(table->part_info->partitions);
560575
for (uint cur_part= 0; cur_part < new_parts; ++cur_part, ++suffix)

sql/share/errmsg-utf8.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12404,3 +12404,9 @@ ER_WARN_QB_NAME_PATH_VIEW_NOT_FOUND
1240412404
eng "Hint %s is ignored. `%s` required at element #%u of the path is not found in the target query block."
1240512405
ER_WARN_QB_NAME_PATH_NOT_SUPPORTED_INSIDE_VIEW
1240612406
eng "Hint %s is ignored. QB_NAME hints with path are not supported inside view definitions."
12407+
ER_PARTITION_INTERVAL_NOT_LIST
12408+
eng "LIST partition type does not support INTERVAL"
12409+
ER_PARTITION_INTERVAL_FINER_THAN_DATE
12410+
eng "RANGE COLUMN partition by a DATE with INTERVAL smaller than date"
12411+
ER_PARTITION_INTERVAL_MAXVALUE
12412+
eng "MAXVALUE is not allowed in range partitioning with interval"

0 commit comments

Comments
 (0)