Repository navigation
Conversation
There was a problem hiding this comment.
Code Review
This pull request improves the ALTER TABLE FORCE syntax and implements automatic partitioning of pre-existing historical data for system-versioned tables (MDEV-25529). Key changes include adding high-precision timestamp helpers, updating parser rules to allow flexible ordering of partitioning clauses, and introducing logic to calculate the required history partitions based on the table's actual historical time range. The feedback suggests renaming the generic cmp function in my_time.h to avoid global namespace collisions, removing a temporary TODO comment, initializing best_idx to prevent compiler warnings, and simplifying the partition calculation logic in vers_calc_hist_parts.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Pull request overview
This PR addresses MDEV-25529 by ensuring that when ALTER TABLE ... PARTITION BY SYSTEM_TIME ... AUTO is used on a system-versioned table, pre-existing historical rows are accounted for when auto-creating history partitions (including adjusting STARTS when needed). It also expands ALTER TABLE grammar around FORCE + partitioning clauses and adds supporting timestamp/time utilities plus new regression tests.
Changes:
- Derive historical
ROW ENDtimestamp range (index-first, scan fallback) and use it during versioning partition auto-setup on ALTER to size partitions and/or adjustSTARTS. - Add
my_timespec_tand session-timezone timestamp formatting helpers used in warnings/debug paths. - Extend parser grammar for more
ALTER TABLEclause orderings and add/refresh mysql-test coverage for both MDEV-25529 behavior and FORCE syntax.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
sql/table.h |
Declares TABLE::vers_get_history_range() for versioned-table history range discovery. |
sql/sql_yacc.yy |
Extends ALTER TABLE grammar to allow additional orderings for partitioning/remove partitioning with other alter clauses. |
sql/sql_type.h |
Declares Temporal_hybrid(THD*, my_timespec_t) ctor for high-precision timestamps. |
sql/sql_type.cc |
Implements Temporal_hybrid(THD*, my_timespec_t) conversion via session time zone. |
sql/sql_time.h |
Adds interval2sec() / interval2usec() helpers to replace macro-based interval math. |
sql/sql_time.cc |
Switches interval microsecond calculations to the new helpers. |
sql/sql_table.cc |
Minor refactor in duplicate-key check predicate naming; adds include needed for key utilities. |
sql/sql_partition.cc |
Main logic: on relevant ALTER, compute history range, adjust STARTS, and compute needed history partitions (incl. month/year stepping). |
sql/sql_class.h |
Adds THD::timestamp_to_string() overloads and TimestampString helper for lazy formatting. |
sql/sql_class.cc |
Implements THD::timestamp_to_string() using Temporal_hybrid and marks TIME_ZONE_USED. |
sql/share/errmsg-utf8.txt |
Reworks/introduces warnings related to STARTS and slow history scanning; adds new warning messages. |
sql/partition_info.h |
Tracks whether STARTS was explicitly provided; adds vers_set_starts() API. |
sql/partition_info.cc |
Implements vers_set_starts() rounding; improves interval warning message; adds OOM error on default partition element allocation. |
sql/field.h |
Adds Field_vers_trx_id::get_timestamp() override declaration. |
sql/field.cc |
Implements transaction-registry timestamp lookup for TRX_ID-versioned fields. |
sql/event_data_objects.cc |
Expands documentation comment for interval scheduling behavior. |
mysql-test/suite/versioning/t/partition2.test |
Adds comprehensive regression test for MDEV-25529 (index vs scan paths, STARTS adjustments, month/year intervals, etc.). |
mysql-test/suite/versioning/r/partition2.result |
Expected output for the new versioning partition regression test. |
mysql-test/suite/versioning/r/partition.result |
Updates expected warnings text for STARTS-beyond-query messages. |
mysql-test/main/partition.test |
Adds FORCE syntax regression coverage for ALTER TABLE partitioning/remove partitioning. |
mysql-test/main/partition.result |
Expected output for new FORCE syntax test cases. |
include/my_time.h |
Introduces my_timespec_t, ordering operators, and min/max constants for high-precision timestamps. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
b53bf6a to
e49a9c5
Compare
419c5cd to
74ccf25
Compare
74ccf25 to
2fee395
Compare
There was a problem hiding this comment.
mreview report
Verdict: request-changes
Summary
- Tier: standard
- Blockers: 2
- Important: 35
- Nits: 33
- Praise: 9
Blocker
-
- Blocker [sql/sql_partition.cc:6164-6169] The new history-range block is guarded by
vers_info->auto_histbut never checksvers_info->interval.is_set(), soPARTITION BY SYSTEM_TIME LIMIT n AUTOon a table that already has history also enters it. In that case vers_set_starts() writes an interval start for a LIMIT scheme, and vers_calc_hist_parts() readsinterval.step. The Vers_part_info constructor never initialisesstep(onlyinterval.type= INTERVAL_LAST), so this is an uninitialised read. If the step bytes are 0, the code hitsDBUG_ASSERT(i_sec > 0)and then dividesrange / i_secby zero in release builds. If they are garbage, it computes a nonsense partition count or runs the month/year loop. The test only runslimit 1 autoon an empty table. Fix: addpart_info->vers_info->interval.is_set()to the condition and add a test with LIMIT AUTO plus existing history. (bug) — by code-reviewer
- Blocker [sql/sql_partition.cc:6164-6169] The new history-range block is guarded by
-
- Blocker [sql/sql_partition.cc:6288]
vers_calc_hist_parts()is off by one whenmax_tslands exactly on an interval boundary. It computesceil(range / i_sec), but partition k covers[start + k*i, start + (k+1)*i), so a row withrow_end == start + k*ineeds k+1 history partitions. The month/year loop has the same bug (while (start < end)). Today such a row silently overflows into the last history partition, which is the defect this MDEV is meant to fix. The test even records it: inpartition2.result,alter table t1 PARTITION by system_time interval 1 hour autogivesPARTITIONS 3and puts x=3 (row_end02:00:00.000000) into p1 = [01:00, 02:00), while the explicit 6-partition layout just above puts x=3 into p2. Fix:*hist_parts= range / i_sec + 1, andwhile (start <= end)in the calendar loop. Theend == startspecial case then becomes redundant. Re-record the result. (S4 / logging-and-errors.md: "Tell the truth") — by silent-failure-hunter
- Blocker [sql/sql_partition.cc:6288]
Important
-
- Important [git 5478199] The commit body of "MDEV-25529 Comments" lists "* THD::used comment", but the commit only touches sql/event_data_objects.cc. No THD::used comment is added anywhere in the PR. (commit-and-process.md:153, PR/commit message must match the patch; S3) — by comment-analyzer
-
- Important [git 5f8310e] The commit "MDEV-25529 TimestampString for printing timestamps" has no body. It also silently renames the public error symbol
ER_PART_STARTS_BEYOND_INTERVALtoWARN_VERS_STARTS_BEYOND_INTERVALand changes its message format (new%sarguments). The libmariadb submodule still defines the old name (libmariadb/include/mysqld_error.h:1214). A user-visible rename like this must be stated in the commit message. (commit-and-process.md:Commit message, "body explains what ... the fix does"; S3) — by comment-analyzer
- Important [git 5f8310e] The commit "MDEV-25529 TimestampString for printing timestamps" has no body. It also silently renames the public error symbol
-
- Important [include/my_time.h:310] The new
my_timespec_t {my_time_t sec; ulong usec;}copies the existingstruct my_timeval {tv_sec; tv_usec}(include/my_time.h:133) and its C++ wrappersTimeval(sql/structs.h:1078, which already assertsusec < 1000000) andTimestamp(sql/sql_type.h:2858, which already hascmp()andto_datetime(thd)). The PR should reuseTimestamp/Timevalrather than add a third seconds+microseconds type that has fewer invariants (S2) — by type-design-analyzer
- Important [include/my_time.h:310] The new
-
- Important [include/my_time.h:317] A file-scope
static inline int cmp(my_timespec_t, my_timespec_t)is placed insideC_MODE_START/ENDin a header that C files and installed server/plugin headers (include/mysql/server/) also use. C has no overloading, so any C translation unit that defines its owncmp(strings/dtoa.c, unittest/mysys/queues-t.c and ma_dtoa.c already do) and includes my_time.h will fail to compile. Rename it tomy_timespec_cmp(). Two bots flagged this and the author dismissed it, but the collision risk is real. (cited from coding-style.md:Naming, "Watch for system-macro collisions") — by code-reviewer (also flagged by type-design-analyzer)
- Important [include/my_time.h:317] A file-scope
-
- Important [mysql-test/suite/versioning/t/partition2.test:129]
--echo # STARTS doesn't change without FORCEis misleading. SHOW CREATE right after it shows STARTS changed from 2000-01-01 to the user's '2005-01-01'. What does not happen is the adjustment to the history range. Reword to "STARTS is not adjusted to the history range without FORCE". This line lands in the .result file. (S3) — by comment-analyzer
- Important [mysql-test/suite/versioning/t/partition2.test:129]
-
- Important [mysql-test/suite/versioning/t/partition2.test:230] The new test file has no
--echo # End of 13.0 testsfooter. The block added to mysql-test/main/partition.test after--echo # End of 10.6 testsalso lacks anEnd of 13.0 testsmarker. (cited from testing.md:Headers and footers; checklist.md:Test footer) — by code-reviewer
- Important [mysql-test/suite/versioning/t/partition2.test:230] The new test file has no
-
- Important [mysql-test/suite/versioning/t/partition2.test:77] The test comment
# Last history: 2000-01-01 02:00:00.00000021is factually wrong. The recorded row_end is2000-01-01 02:00:00.000000, and the value has 8 fractional digits, which TIMESTAMP(6) cannot hold. (S3) — by comment-analyzer
- Important [mysql-test/suite/versioning/t/partition2.test:77] The test comment
-
- Important [sql/partition_info.cc:2860-2861]
WARN_VERS_STARTS_BEYOND_INTERVALis suppressed wheneverauto_hist && ALTER_RECREATE, on the assumption thatprep_alter_part_table()will fix STARTS later. That fix only happens when the table has history anduse_default_partitionsis set. With no history, or with an explicit partition list, a STARTS later than query time is now accepted with no warning at all. The check also runs at parse time, so it depends on token order. In the recorded results,alter table t1 force partition by ... starts '2003-...' autoemits only 4265, butalter table t1 partition by ... starts '2004-...' auto forceemits both 4164 ("STARTS ... is later than query timestamp") and 4265 ("... was set to 2000-01-01"). Those two warnings contradict each other, and that order dependence contradicts the commit claim that the clauses "can appear in any order". Drop the parse-time suppression and decide inprep_alter_part_table(), where the outcome is known. (S3 / logging-and-errors.md: "Tell the truth") — by silent-failure-hunter
- Important [sql/partition_info.cc:2860-2861]
-
- Important [sql/partition_info.cc:2860-2862] The
!(auto_hist && (alter_info.flags & ALTER_RECREATE))suppression of WARN_VERS_STARTS_BEYOND_INTERVAL runs in the parser and depends on token order. With the new grammar, trailing... AUTO FORCEsets ALTER_RECREATE only after vers_set_interval() has run. The result file shows this:force partition by ... starts '2003-..' autoemits only 4265 (partition2.result line 241), whilepartition by ... starts '2004-..' auto forceemits both 4164 and 4265 (line 260). The suppression also fires when FORCE is given and the table has no history, so the warning is lost even though STARTS stays in the future. Fix: decide in prep_alter_part_table(), where it is known whether STARTS was actually rewritten, rather than in the parser. (bug) — by code-reviewer
- Important [sql/partition_info.cc:2860-2862] The
-
- Important [sql/partition_info.cc:2861] The FORCE-syntax commit (08acd39) says alter_list and partitioning "can appear in any order". But vers_set_interval() runs as a grammar action, so
ALTER_RECREATEis only set when FORCE comes before PARTITION BY. The result file shows the effect. Prefixforce partition by ... starts '2003-...'gets only WARN_VERS_WRONG_STARTS. Postfix... starts '2004-...' auto forcegets both 4164 ("STARTS ... later than query timestamp") and 4265 ("... was set to 2000-01-01"), which contradict each other. The suppression condition also has no comment explaining why it exists. Make the check independent of order (move it after parsing), or document that it depends on order. (S3; coding-style.md:Comments) — by comment-analyzer
- Important [sql/partition_info.cc:2861] The FORCE-syntax commit (08acd39) says alter_list and partitioning "can appear in any order". But vers_set_interval() runs as a grammar action, so
-
- Important [sql/partition_info.cc:2891] The vers_set_starts() Doxygen says it clears components "until the value aligns with the coarsest unit present in interval.step". The code stops at the finest non-zero unit. For example,
INTERVAL '1:30' HOUR_MINUTEkeeps minutes and clears only seconds. Replace "coarsest" with "finest (smallest) non-zero unit", and note that day, month and year are never cleared. (S3) — by comment-analyzer
- Important [sql/partition_info.cc:2891] The vers_set_starts() Doxygen says it clears components "until the value aligns with the coarsest unit present in interval.step". The code stops at the finest non-zero unit. For example,
-
- Important [sql/partition_info.cc:2900] All three callers of the new
vers_set_starts()(partition_info.cc:2874 viainterval_starts_error, sql_partition.cc:6192 and 6207) handle failure with the samemy_error(ER_PART_WRONG_VALUE, MYF(0), <table>, "STARTS"). The error belongs insidevers_set_starts(thd, ts, table_name). The two sql_partition.cc branches would then collapse into oneif (!starts_clause || interval.start > min_ts.sec)block that warns only whenstarts_clauseis set (S5) — by type-design-analyzer
- Important [sql/partition_info.cc:2900] All three callers of the new
-
- Important [sql/partition_info.cc:520-521] The ER_OUT_OF_RESOURCES fix in set_up_default_partitions() (line 453) leaves the identical silent
else goto end;in the sibling set_up_default_subpartitions() fornew partition_element(part_elem). Fix both, or neither. (S2) — by code-reviewer
- Important [sql/partition_info.cc:520-521] The ER_OUT_OF_RESOURCES fix in set_up_default_partitions() (line 453) leaves the identical silent
-
- Important [sql/sql_class.h:8729] The new type
TimestampStringis CamelCase. The project convention for new types is Capital_snake_case, so it should beTimestamp_string. (cited from coding-style.md:Critical baseline, snake_case / Capital_snake_case row) — by code-reviewer (also flagged by type-design-analyzer)
- Important [sql/sql_class.h:8729] The new type
-
- Important [sql/sql_class.h:8759]
cstr(uint dec= 0)drops the microsecond part by default. That defeats themy_timespec_tconstructor, which exists to carry it: at sql/sql_partition.cc:6198str_min_ts.cstr()prints the earliest row_end2000-01-01 00:10:00.220000as2000-01-01 00:10:00(see theWARN_VERS_WRONG_STARTSlines in suite/versioning/r/partition2.result). Precision should be a required argument, or themy_timespec_tpath should default to 6 (S3; cited from api-and-architecture.md:API design discipline, "Don't add default values for behaviour-changing ... parameters") — by type-design-analyzer
- Important [sql/sql_class.h:8759]
-
- Important [sql/sql_class.h:8762] On failure
cstr()silently returns the literal"ERROR"and pastes it into a user-visible warning. Failure meansString::allocOOM, andreal_allocusesMY_WME, so ER_OUTOFMEMORY is already in the DA. Callers such asvers_set_interval()keep going as if nothing happened, and the statement continues with an error set (set_ok_statusasserts!is_set()in debug). Let the caller see the failure (return NULL / checkthd->is_error()after formatting), or at minimum document the OOM consequence. (logging-and-errors.md: "The message tells the user what happened") — by silent-failure-hunter
- Important [sql/sql_class.h:8762] On failure
-
- Important [sql/sql_partition.cc:6164] The new history-range block runs for TRX_ID-versioned tables. The commit message says so: "It works in vers_get_history_range() but ... create temporary table fails". So ALTER does a TRT lookup per row on the scan path, and each
TR_table::queryis itself a full TRT read. That makes the work O(N*M), and it can raise and swallow errors (previous finding), all before an ALTER that is guaranteed to be rejected later withER_VERS_FIELD_WRONG_TYPEat frm open. Addtable->versioned(VERS_TIMESTAMP)to the block condition, or reject TRX_ID first, so the user gets the real error up front and no wasted scan. (S4: check at the wrong layer) — by silent-failure-hunter
- Important [sql/sql_partition.cc:6164] The new history-range block runs for TRX_ID-versioned tables. The commit message says so: "It works in vers_get_history_range() but ... create temporary table fails". So ALTER does a TRT lookup per row on the scan path, and each
-
- Important [sql/sql_partition.cc:6171-6172]
if (*fast_alter_table) *fast_alter_table= false;is a check-then-assign with no effect beyond*fast_alter_table= false;. Also, the condition on line 6169 quietly overrides an explicitPARTITIONS N:use_default_partitionsis still true when only a count is given, and the user's N is replaced without a note. The commit message only mentions an explicit partition list being honoured. Either honour N (for example max(N, computed)) or document and test the override. (bug/clarity; confidence ~80) — by code-reviewer
- Important [sql/sql_partition.cc:6171-6172]
-
- Important [sql/sql_partition.cc:6177,6196,6211,6221] Every new failure exit uses
DBUG_RETURN(true). All the other failures inside theif (thd->work_part_info)block usegoto err, which resets*fast_alter_tableand restorestable->part_info= saved_part_info. The new block also changespart_info(partitions.empty(),default_partitions_setup= false,interval.start) beforevers_calc_hist_parts()can fail. In the fast-ALTER branchpart_infois the livetab_part_info, not a clone. Usegoto err, and do thepartitions.empty()/flag changes only aftervers_calc_hist_parts()succeeds. (S6) — by silent-failure-hunter
- Important [sql/sql_partition.cc:6177,6196,6211,6221] Every new failure exit uses
-
- Important [sql/sql_partition.cc:6182-6186, 6284-6290, 6302] There is an off-by-one when the latest history row_end falls exactly on a partition boundary. vers_get_partition_id() and the vers_set_hist_part() sibling treat the left boundary as closed and the right as open, so
ts == start + k*intervalbelongs to history partition k and needs k+1 partitions. The new code takes the ceiling of(end-start)/intervalin the seconds path and loopswhile (start < end)in the month/year path, so it returns k. The sibling useswhile (h <= q). Theend == start → 1special case shows the inconsistency:end == start + intervalalso returns 1. Fix: computefloor((max_ts.sec - start)/i_sec) + 1on the unrounded seconds value, use<=in the loop, and drop theusec → sec++rounding, since partitioning compares whole seconds only. (bug; S6) — by code-reviewer
- Important [sql/sql_partition.cc:6182-6186, 6284-6290, 6302] There is an off-by-one when the latest history row_end falls exactly on a partition boundary. vers_get_partition_id() and the vers_set_hist_part() sibling treat the left boundary as closed and the right as open, so
-
- Important [sql/sql_partition.cc:6187-6211] The
vers_set_starts(thd, min_ts.sec)call followed bymy_error(ER_PART_WRONG_VALUE, ..., "STARTS"); DBUG_RETURN(true);is copy-pasted in both branches. Restructure asif (!starts_clause || interval.start > min_ts.sec) { save old start; if (vers_set_starts()) error; if (starts_clause) warn; }. (S5) — by code-reviewer
- Important [sql/sql_partition.cc:6187-6211] The
-
- Important [sql/sql_partition.cc:6192-6212] The
vers_set_starts()+my_error(ER_PART_WRONG_VALUE, MYF(0), table->s->table_name.str, "STARTS"); DBUG_RETURN(true);block is copied twice, differing only in the condition that leads to it. Restructure sovers_set_starts()is called and checked once, with the warning emitted only in the starts_clause case. (S5 / S1) — by silent-failure-hunter
- Important [sql/sql_partition.cc:6192-6212] The
-
- Important [sql/sql_partition.cc:6293,6306,6313,6320]
ER_TOO_MANY_PARTITIONS_ERRORis raised bare. The user is not told that the history range divided by INTERVAL is the cause, nor which table. The siblingvers_set_hist_part()(partition_info.cc:877-881) raises it as a warning followed byER_VERS_HIST_PART_FAILEDwith db and table names.ER_DATA_OUT_OF_RANGE, "TIMESTAMP", "INTERVAL"likewise names no table. Follow the sibling pattern and name the table. (S6 / logging-and-errors.md: "Include the table/constraint/object name") — by silent-failure-hunter
- Important [sql/sql_partition.cc:6293,6306,6313,6320]
-
- Important [sql/sql_partition.cc:6300-6310] vers_calc_hist_parts() steps month/year boundaries in
thd->variables.time_zone. The code that actually builds the boundaries (range_int_array, around sql_partition.cc:1581) and the auto-create sibling vers_set_hist_part() (partition_info.cc:866) both usemy_tz_OFFSET0. Under a session time zone with DST, the computed count can differ from the boundaries that get built. Fix: usemy_tz_OFFSET0like the siblings, and consider the sibling'sMAX_PARTITIONS - 2cap. (S6) — by code-reviewer
- Important [sql/sql_partition.cc:6300-6310] vers_calc_hist_parts() steps month/year boundaries in
-
- Important [sql/sql_partition.cc:6300-6315]
vers_calc_hist_parts()steps month/year boundaries in the session time zone (thd->variables.time_zone). The partitions are actually built bycheck_vers_constants()(sql_partition.cc:1581) andvers_set_hist_part()(partition_info.cc:866), and both step inmy_tz_OFFSET0. In DST zones the computed count can differ from the real boundaries by one. Also,TIME_to_gmt_sec()returnsER_WARN_INVALID_TIMESTAMP(tztime.cc:968) for a time that falls in a DST gap. The new code treats any non-zero error as fatal, so a valid ALTER fails with the misleading "TIMESTAMP value is out of range in 'INTERVAL'". The sibling only fails onER_WARN_DATA_OUT_OF_RANGE, and even then it clamps the last partition. Usemy_tz_OFFSET0as the siblings do. The tests only run withtime_zone='+00:00', so this is untested. (S2 / S6) — by silent-failure-hunter
- Important [sql/sql_partition.cc:6300-6315]
-
- Important [sql/sql_table.cc:13947] The vers_end_best_idx() Doxygen says the index's "first and last entries yield the range", and vers_get_history_range() at :13998 repeats "reads its first and last entries". The code actually reads
ha_index_first()and thenha_index_read_map(..., HA_READ_BEFORE_KEY)from a max-valued key, i.e. the last historical entry before the current rows. The real last entry is a current row with the MY_TIMESPEC_MAX row_end. Say "the first entry and the last entry before the current-row (max) key". (S3) — by comment-analyzer
- Important [sql/sql_table.cc:13947] The vers_end_best_idx() Doxygen says the index's "first and last entries yield the range", and vers_get_history_range() at :13998 repeats "reads its first and last entries". The code actually reads
-
- Important [sql/sql_table.cc:13971-13987] vers_end_best_idx() iterates over all
s->keysand ignoress->keys_in_use. AfterALTER TABLE ... DISABLE KEYSon MyISAM/Aria, it can pick a disabled non-unique(row_end, ...)index, and ha_index_first() then fails (HA_ERR_WRONG_INDEX) or returns no rows, so the ALTER either errors out or treats the history as empty. Fix: also requires->keys_in_use.is_set(idx). (bug) — by code-reviewer
- Important [sql/sql_table.cc:13971-13987] vers_end_best_idx() iterates over all
-
- Important [sql/sql_table.cc:13994] The vers_get_history_range() Doxygen does not document the no-history result (
min_ts == MY_TIMESPEC_MAX,max_ts == MY_TIMESPEC_MIN). The caller in sql_partition.cc:6178 and itsDBUG_ASSERT(max_ts == MY_TIMESPEC_MIN)depend on that contract. Add it to the@paramtext for min_ts/max_ts. (coding-style.md:Comments, "add a comment when surprising-but-correct code can mislead") — by comment-analyzer
- Important [sql/sql_table.cc:13994] The vers_get_history_range() Doxygen does not document the no-history result (
-
- Important [sql/sql_table.cc:14011]
@retval true Handler or conversion erroris wrong. Only handler errors seterror. A get_timestamp() failure (the TRX_ID path returns -1 with a warning) is not detected and flows silently into min_ts/max_ts. Drop "or conversion", or check the -1 result. — by comment-analyzer
- Important [sql/sql_table.cc:14011]
-
- Important [sql/sql_table.cc:14038, 14125] vers_get_history_range() calls
file->ha_external_lock(thd, F_RDLCK)and laterha_external_unlock()without checkingthd->locked_tables_mode. UnderLOCK TABLES t1 WRITE; ALTER TABLE t1 PARTITION BY SYSTEM_TIME ... AUTO, the TABLE is already externally locked. That trips the "consecutive calls for lock without unlocking" assertion in handler::ha_external_lock() (handler.cc:7996), and in release builds the final unlock drops a lock that LOCK TABLES still believes it holds. Fix: skip the lock/unlock whenfile->get_lock_type() != F_UNLCK(or under LTM), and add a LOCK TABLES case to partition2.test. (bug) — by code-reviewer
- Important [sql/sql_table.cc:14038, 14125] vers_get_history_range() calls
-
- Important [sql/sql_table.cc:14056,14061,14097 / sql/field.cc:2453] The
-1thatget_timestamp()returns on a TRT miss is never checked.TR_table::query()has already raisedmy_error(ER_VERS_NO_TRX_ID)(table.cc:10772), or the TRT open error, as an ERROR.get_timestamp()then pushes the same code again as a warning, andvers_get_history_range()returns success (error == 0) whilethd->is_error()is set. The caller then goes on withmin_ts.sec/max_ts.sec == -1. On 64-bit,MY_TIME_T_MIN == 0, somax_ts.sec == -1takes the "no history" branch, andDBUG_ASSERT(max_ts == MY_TIMESPEC_MIN)fails in debug builds. A badmin_tsgives a misleadingER_PART_WRONG_VALUE 'STARTS'. The doc comment ("reported as a warning ... caller can still continue") is false. Fix: checkthd->is_error()(or return a bool status) after eachget_timestamp()call invers_get_history_range()and fail. (logging-and-errors.md: "Don't lose context") — by silent-failure-hunter
- Important [sql/sql_table.cc:14056,14061,14097 / sql/field.cc:2453] The
-
- Important [sql/sql_table.cc:14071-14104] The debug cross-check (
test_mdev-25529) does not resetmin_ts/max_tsbefore thegoto jump_scanscan. The scan therefore starts from the index results and can only lower min or raise max. An index path that reports a min that is too small or a max that is too large is never caught, which defeats the "compare the results" purpose stated in the commit and the docstring. Fix: reset both to MY_TIMESPEC_MAX/MIN after saving min_ts2/max_ts2. (bug in test harness) — by code-reviewer
- Important [sql/sql_table.cc:14071-14104] The debug cross-check (
-
- Important [sql/sql_table.cc:14073] The comment "Test both index and scan, compare the results between them" (and the matching sentence in the vers_get_history_range() Doxygen at :14001-14003) claims an independent cross-check. But
min_ts/max_tsare not reset to MY_TIMESPEC_MAX/MIN beforegoto jump_scan, so the scan at :14097-14104 can only widen the index result. If the index path returns a range that is too wide (for example a min below the true min),DBUG_ASSERT(min_ts == min_ts2)still passes. Either reset the two values before the scan, or reword the comment to say the check is one-sided. (S3) — by comment-analyzer
- Important [sql/sql_table.cc:14073] The comment "Test both index and scan, compare the results between them" (and the matching sentence in the vers_get_history_range() Doxygen at :14001-14003) claims an independent cross-check. But
-
- Important [sql/sql_type.cc:408]
Temporal_hybrid(THD*, my_timespec_t)has the same body as the existingDatetime::Datetime(THD*, const my_timeval&)(sql/sql_type.cc:1116), but leaves outthd->used|= THD::TIME_ZONE_USED. That flag is set one layer up inTHD::timestamp_to_string, so any future direct user of the constructor will silently skip it.Datetime(thd, Timeval(sec, usec)).to_string(str, dec)already does the whole job (S2) — by type-design-analyzer
- Important [sql/sql_type.cc:408]
-
- Important [sql/sql_type.h:1424]
Temporal_hybrid::to_stringnow returns NULL whenalloc()fails, but its siblingsInterval_DDhhmmssff::to_string(sql/sql_type.h:1626),Time::to_string(2064) andDatetime::to_string(2752) keep the old "returnstrwith stale length" shape. Either fix all four together (preferably as a separate commit) or keep this PR's change local to the new caller (S6) — by type-design-analyzer
- Important [sql/sql_type.h:1424]
Nit
-
- Nit [git 3615202] The commit "set_up_default_partitions() ER_OUT_OF_RESOURCES error" also adds the unrelated
TODO (newbie)comment. (commit-and-process.md:Commit structure, "split unrelated changes") — by comment-analyzer
- Nit [git 3615202] The commit "set_up_default_partitions() ER_OUT_OF_RESOURCES error" also adds the unrelated
-
- Nit [git 8d25e36] The subject "cleanup for vers_set_starts() and starts_clause" is not accurate. Besides the vers_set_starts() extraction, the commit adds a new
Vers_part_info::starts_clausemember that is unused until 2fee395. Either describe it as preparation, or squash it. (commit-and-process.md:Commit structure) — by comment-analyzer
- Nit [git 8d25e36] The subject "cleanup for vers_set_starts() and starts_clause" is not accurate. Besides the vers_set_starts() extraction, the commit adds a new
-
- Nit [include/my_time.h:313]
ulong usecis used in a new struct. It is defensible only because it has to matchField::get_timestamp(ulong*); a short comment saying so would preempt the comment from reviewers. (cited from coding-style.md:Critical baseline, no long/ulong) — by code-reviewer (also flagged by type-design-analyzer)
- Nit [include/my_time.h:313]
-
- Nit [mysql-test/main/partition.test:3151] The test header
--echo # MDEV-25529 ALTER TABLE FORCE syntax improveduses the commit subject, not the MDEV title. (testing.md:37) — by comment-analyzer
- Nit [mysql-test/main/partition.test:3151] The test header
-
- Nit [mysql-test/main/partition.test:3162] "Strange syntax, but ..." is an opinion. State the fact, e.g. "alter_list and partitioning may be combined in either order (supported since cd483c5, 2005)". The commit hash checks out. (coding-style.md:Comments, "state facts, no opinions") — by comment-analyzer
-
- Nit [sql/event_data_objects.cc:692] The "MDEV-25529 Comments" commit documents get_next_time() in the event scheduler, which has nothing to do with this MDEV. Split it out or drop it. (cited from anti-patterns.md:Commit / process, "Don't bundle unrelated changes") — by code-reviewer (also flagged by comment-analyzer)
-
- Nit [sql/field.cc:2420] The
@noterationale for the TRT lookup ("registry row may have been purged ...; the caller can still continue") is not supported by the code. No caller checks the -1 sentinel. vers_get_history_range() stores it as a real timestamp, and the sibling Field_vers_trx_id::get_date() treats the same case as an error (return true). Remove the speculative rationale, or document that callers must check for -1. (S3) — by comment-analyzer
- Nit [sql/field.cc:2420] The
-
- Nit [sql/field.cc:2424]
@param[in]and@param[out](also at sql/sql_table.cc:13961@param[out] best_idx) go against the project Doxygen sub-style "no [in]/[out] markers". That rulebook entry is listed under InnoDB, so apply it only if the server-wide style is meant to match. (checklist.md:InnoDB-specific, Doxygen) — by comment-analyzer
- Nit [sql/field.cc:2424]
-
- Nit [sql/field.cc:2458]
Field_vers_trx_id::get_timestampreports "TRX not found" by returning-1as amy_time_t, and that value can flow into amy_timespec_twith no flag. The author disputes reachability (TRX_ID versioning is rejected later), but the sentinel is part of a value type and should at least be noted in thevers_get_history_rangecontract (cited from anti-patterns.md:Specific subtle bugs, "-1 smuggled into ... slot") — by type-design-analyzer
- Nit [sql/field.cc:2458]
-
- Nit [sql/partition_info.cc:432] The
// TODO (newbie): how is it freed? ...comment is an open question, not a statement of fact. partition_element is Sql_alloc'ed on the THD mem_root, so either state that or drop the comment. (cited from coding-style.md:Comments, "State facts") — by code-reviewer (also flagged by comment-analyzer, silent-failure-hunter)
- Nit [sql/partition_info.cc:432] The
-
- Nit [sql/partition_info.h:93]
starts_clausehas no comment and it doesn't strictly mean "the user wrote STARTS in this statement".vers_set_intervalalso sets it on themysql_unpack_partition()(frm) path, and the copy-ctor andoperator=propagate it. A one-line comment on what the flag means and when it is valid would help (own lens: invariant expression) — by type-design-analyzer
- Nit [sql/partition_info.h:93]
-
- Nit [sql/share/errmsg-utf8.txt:12147] ER_PART_STARTS_BEYOND_INTERVAL is renamed to WARN_VERS_STARTS_BEYOND_INTERVAL and its argument count goes from 1 to 3. libmariadb/include/mysqld_error.h still exports the old name for 4164, so the server and Connector/C now disagree on the symbol. Not ABI-breaking (same number), but worth a note in the commit. (cited from api-and-architecture.md:Connector / ABI) — by code-reviewer
-
- Nit [sql/sql_class.h:4783]
THD::timestamp_to_string(String*, uint dec, ts)orders its parameters differently from its neighbourTHD::timestamp_to_TIME(MYSQL_TIME*, my_time_t ts, ulong sec_part, ...). It also adds another formatting entry point toTHDwhenDatetime(thd, Timeval)already provides this (cited from api-and-architecture.md:API design discipline, "De-THD direction") — by type-design-analyzer
- Nit [sql/sql_class.h:4783]
-
- Nit [sql/sql_class.h:8729] The new class
TimestampStringhas no class-level comment and uses CamelCase. Project style isCapital_snake_casefor new types (e.g.Timestamp_string). (coding-style.md:18) — by comment-analyzer
- Nit [sql/sql_class.h:8729] The new class
-
- Nit [sql/sql_class.h:8741]
TimestampStringis copyable through the defaultedStringcopy-ctor, which sharesPtrwithout owning it, and it keeps aTHD*only to read the time zone later. If the class stays, delete the copy operations and format in the constructor (own lens: ownership/lifetime) — by type-design-analyzer
- Nit [sql/sql_class.h:8741]
-
- Nit [sql/sql_partition.cc:103] The vers_calc_hist_parts() Doxygen sits on the static forward declaration among undocumented prototypes. The definition at :6264 has none. Put the doc block on the definition, as the surrounding code does. Also, "number of partitions would exceed MAX_PARTITIONS" is ambiguous: the check is
hist_parts >= MAX_PARTITIONS, so the limit applies to history + current. (S6) — by comment-analyzer
- Nit [sql/sql_partition.cc:103] The vers_calc_hist_parts() Doxygen sits on the static forward declaration among undocumented prototypes. The definition at :6264 has none. Put the doc block on the definition, as the surrounding code does. Also, "number of partitions would exceed MAX_PARTITIONS" is ambiguous: the check is
-
- Nit [sql/sql_partition.cc:123]
vers_calc_hist_partshas a forward declaration whose Doxygen block sits about 6000 lines from the definition at sql_partition.cc:6264. Define it beforeprep_alter_part_tableand keep the doc next to the body (own lens) — by type-design-analyzer
- Nit [sql/sql_partition.cc:123]
-
- Nit [sql/sql_partition.cc:6164-6170] When an explicit partition list is given with AUTO FORCE, or AUTO is used on an already-partitioned table without FORCE ("STARTS doesn't change without FORCE"), the history re-layout is silently skipped. Existing history then stays crammed into p0/the last partition, with no note telling the user to add FORCE or drop the explicit list. A NOTE would make this fallback visible. — by silent-failure-hunter
-
- Nit [sql/sql_partition.cc:6169] The
(!*fast_alter_table || (alter_info->flags & ALTER_RECREATE))gate, together withalt_part_info->use_default_partitions, encodes the rules "FORCE is required on fast ALTER" and "an explicit partition list disables this". Those rules are only in the commit message. A short comment above theifwould keep them next to the code. (coding-style.md:Comments) — by comment-analyzer
- Nit [sql/sql_partition.cc:6169] The
-
- Nit [sql/sql_partition.cc:6177] The new error exits use
DBUG_RETURN(true)(:6177, :6196, :6211, :6221). The sibling error paths in the sameif (thd->work_part_info)block (:6145, :6246) usegoto err, which resets*fast_alter_tableand restoressaved_part_info. Match the sibling factoring, or comment on why the restore is not needed here. (S6) — by comment-analyzer
- Nit [sql/sql_partition.cc:6177] The new error exits use
-
- Nit [sql/sql_partition.cc:6178] "No history" is encoded in
my_timespec_titself: the caller testsmax_ts.sec > MY_TIME_T_MINbut the else-branch assertsmax_ts == MY_TIMESPEC_MIN(sec and usec both). On 64-bit,MY_TIME_T_MINis 0, so the test and the assert disagree for{0, usec>0}. Returning an explicit emptiness flag, or a small{min, max}struct with anis_empty()method, fromvers_get_history_rangewould make the sentinel unnecessary (own lens: invariant expression) — by type-design-analyzer
- Nit [sql/sql_partition.cc:6178] "No history" is encoded in
-
- Nit [sql/sql_partition.cc:6182] Rounding
max_tsup to the next whole second whenusec != 0is a deliberate ceiling, but it has no comment. Add one saying why (vers_calc_hist_parts() works in whole seconds, and the history end must be covered). (coding-style.md:Comments, "add a comment when surprising-but-correct code can mislead") — by comment-analyzer
- Nit [sql/sql_partition.cc:6182] Rounding
-
- Nit [sql/sql_partition.cc:6195] The
my_error(ER_PART_WRONG_VALUE, MYF(0), table->s->table_name.str, "STARTS"); DBUG_RETURN(true);block is duplicated at :6210. Compute "need to reset STARTS" once (!starts_clause || interval.start > min_ts.sec) and call vers_set_starts() in one place. (S5) — by comment-analyzer
- Nit [sql/sql_partition.cc:6195] The
-
- Nit [sql/sql_partition.cc:6199-6202] Lines exceed 80 columns (
push_warning_printf(... WARN_VERS_WRONG_STARTS, ER_THD(...)and the cstr() argument line), and there is a stray blank line before the closing brace. (cited from coding-style.md:Critical baseline, 80-col) — by code-reviewer
- Nit [sql/sql_partition.cc:6199-6202] Lines exceed 80 columns (
-
- Nit [sql/sql_table.cc:13958] The
@note keys_in_use_for_query is intentionally not consulted...repeats the inline comment inside the loop at :13974 word for word. Keep only one. (code-comments-trim-obvious) — by comment-analyzer
- Nit [sql/sql_table.cc:13958] The
-
- Nit [sql/sql_table.cc:13966]
vers_end_best_idx(uint &best_idx)returns both theKEY*and its index through an out-param that stays uninitialized when nothing is found. The caller then rebuilds the same pointer (key_info + best_idx, sql_table.cc:14044). One return value is enough: the index withMAX_KEYas "none", or theKEY*withbest_key - key_info(cited from api-and-architecture.md:Errors and return values; checklist.md:Code, "Initialise output variables on all paths") — by type-design-analyzer
- Nit [sql/sql_table.cc:13966]
-
- Nit [sql/sql_table.cc:14018]
// Find best keyabovevers_end_best_idx(best_idx)restates the call. Remove it. (code-comments-trim-obvious) — by comment-analyzer
- Nit [sql/sql_table.cc:14018]
-
- Nit [sql/sql_table.cc:14044]
KEY *best_key_info= key_info + best_idx;duplicatesbest_key, which vers_end_best_idx() already returns. Usebest_key, or have the helper return only the index (MAX_KEY when none fits). — by code-reviewer
- Nit [sql/sql_table.cc:14044]
-
- Nit [sql/sql_table.cc:14083]
WARN_VERS_SLOW_ROW_ENDsays what happened but not what to do about it. Suggest wording that tells the user to add an index whose first column is ROW END (and not DESC). (logging-and-errors.md: "The message tells the user what happened and what they can do about it") — by silent-failure-hunter
- Nit [sql/sql_table.cc:14083]
-
- Nit [sql/sql_table.cc:14129]
/* Other handler errors are fatal */was copied from sql_update.cc, where it follows an "If (ignore && error is ignorable)..." comment. Here there is no earlier case, so "Other" refers to nothing. Drop the comment or say "fatal handler errors get ME_FATAL". (S3) — by comment-analyzer
- Nit [sql/sql_table.cc:14129]
-
- Nit [sql/sql_time.h:169] interval2sec() and interval2usec() are documented as converting "an interval value". The only interval2usec() caller (sql/sql_time.cc:647) also applies it to
ltime, a MYSQL_TIME point in time. The docs also do not say thatyear,monthandnegare ignored, a precondition vers_calc_hist_parts() relies on. Mention both. — by comment-analyzer
- Nit [sql/sql_time.h:169] interval2sec() and interval2usec() are documented as converting "an interval value". The only interval2usec() caller (sql/sql_time.cc:647) also applies it to
-
- Nit [sql/sql_time.h:179] The
interval2secdoc says it converts "an interval value to a total number of seconds", but it ignoresyear,monthandneg, andinterval2usecis also applied to a plainMYSQL_TIME(sql_time.cc:647). The doc should state those limits. Onlyvers_calc_hist_partsguards the year/month case (S3) — by type-design-analyzer
- Nit [sql/sql_time.h:179] The
-
- Nit [sql/sql_yacc.yy / commit 08acd39] The commit body claims improved "handling of AUTO with FORCE", but the change only lets
alter_listtrail the partitioning clause. Reword it. (S3) — by code-reviewer
- Nit [sql/sql_yacc.yy / commit 08acd39] The commit body claims improved "handling of AUTO with FORCE", but the change only lets
Praise
-
- Praise [sql/field.cc:2453]
Field_vers_trx_id::get_timestampnow sets*sec_parton every path, including the TRT-miss path (cited from checklist.md:Code, "Initialise output variables on all paths") — by type-design-analyzer
- Praise [sql/field.cc:2453]
-
- Praise [sql/partition_info.cc:452]
set_up_default_partitions()no longer fails silently on anew partition_element()/push_backfailure. It now sets an error beforegoto end, which matches the other exits in the function. — by silent-failure-hunter
- Praise [sql/partition_info.cc:452]
-
- Praise [sql/sql_class.cc:9129] The THD::timestamp_to_string() Doxygen is accurate, including the TIME_ZONE_USED side effect, which is easy to overlook. — by comment-analyzer
-
- Praise [sql/sql_class.h:8736] The TimestampString constructor and cstr() docs are accurate. The value is copied at construction and "ERROR" is returned on failure, which is what makes the
str_interval/str_interval2before/after use in sql_partition.cc:6191-6200 correct. — by comment-analyzer
- Praise [sql/sql_class.h:8736] The TimestampString constructor and cstr() docs are accurate. The value is copied at construction and "ERROR" is returned on failure, which is what makes the
-
- Praise [sql/sql_table.cc:14120-14136]
vers_get_history_range()sends every handler error (external_lock, index_init, rnd_init, read) throughprint_errorwithME_FATALclassification, and restores the column bitmaps on all paths. The only gap is the TRT-miss case above. — by silent-failure-hunter
- Praise [sql/sql_table.cc:14120-14136]
-
- Praise [sql/sql_time.cc:27] The
/* Daynumber from year 0 to 9999-12-31 */comment was misplaced above the old COMBINE macro. It now sits on MAX_DAY_NUMBER (3652424), which it actually describes. Good comment-rot fix. — by comment-analyzer
- Praise [sql/sql_time.cc:27] The
-
- Praise [sql/sql_time.cc:647] Replacing the untyped
COMBINEmacro with inline helpers, and moving the misplaced "Daynumber" comment ontoMAX_DAY_NUMBER, are good cleanups — by type-design-analyzer
- Praise [sql/sql_time.cc:647] Replacing the untyped
-
- Praise [sql/sql_time.h:178-199] Replacing the COMBINE macro with typed interval2sec()/interval2usec() helpers, and reusing them in vers_calc_hist_parts(), is a good cleanup-before-fix commit. — by code-reviewer
-
- Praise [sql/sql_type.h:1424] The fix to Temporal_hybrid::to_string(), so it returns NULL on alloc failure instead of a string object with garbage length, is a real latent-bug fix and is correctly split into its own commit. — by code-reviewer (also flagged by silent-failure-hunter)
|
Is embedded crash related ? |
Triage of mreview reportRejected findings, grouped by the original importance tier, most important first, Total rejected: 16 (5 important, 11 nits). Important rejected (5)
Nits rejected (11)
Notes on findings kept but overlapping
|
71d6445 to
58e20ec
Compare
Regression:
|
sanja-byelkin
left a comment
There was a problem hiding this comment.
Review round 2 (head 58e20ec)
This round covers only new issues. Everything from round 1, your triage reply, and the STARTS-warning regression posted earlier is left out. Line numbers refer to 58e20ec.
Blockers
-
Server crash on Aria: the history range is read before the table is locked. (
sql/sql_table.ccvers_get_history_range(), called fromprep_alter_part_table())prep_alter_part_table()runs frommysql_alter_table()beforelock_tables(). At that point onlyha_external_lock(F_RDLCK)is taken, underMDL_SHARED_UPGRADABLE, with no THR_LOCK and no Aria transaction setup.- On a transactional Aria table with a
(row_end, ...)index,ha_index_first()crashes with SIGSEGV:trnman_can_read_from <- maria_rfirst <- handler::ha_index_first <- vers_get_history_range <- prep_alter_part_table <- mysql_alter_table - The same statement under
LOCK TABLESworks. - Even without the crash,
MDL_SHARED_UPGRADABLElets concurrent DML in. History rows written after the range was sampled land in the last computed history partition, silently. That is the very problem this MDEV fixes. - Read the range after the MDL upgrade and
lock_tables(), or take a proper read lock for it. - Test case below.
-
After
ALTER ... AUTOwithout STARTS, every UPDATE/DELETE fails. (sql/sql_partition.cc~6259,vers_calc_hist_parts())- STARTS is now aligned to the oldest history row, but the layout is sized only up to
max_ts, never up toquery_start(). - With history from 2000,
alter table t1 partition by system_time interval 1 hour autosucceeds withPARTITIONS 2. Then:update t1 set x= 2; Warning 1499 Too many partitions (including subpartitions) were defined Error 4189 Versioned table `test`.`t1`: adding HISTORY partition(s) failed - Before this PR, STARTS defaulted to
query_start()and DML kept working. An explicit old STARTS with AUTO already failed like this; the PR makes the default ALTER hit it. partition2.result:619-621already records this 1499/4189 pair under LOCK TABLES with no explanation.- Size against
max(max_ts, query_start()), and reject (or don't align) when that needs too many partitions. Add a test that runs DML after such an ALTER.
- STARTS is now aligned to the oldest history row, but the layout is sized only up to
Important
- ALTER fails in DST zones where midnight doesn't exist. (
sql/partition_info.cc:2944,vers_set_starts())- It is now applied to the oldest history row_end. It rounds down in the session time zone and treats any
TIME_to_timestamp()error as fatal. - In America/Santiago, Asuncion, Havana or Asia/Beirut, with
INTERVAL n DAYand an oldest row_end on a DST-start day, local 00:00 doesn't exist. The ALTER then fails withER_PART_WRONG_VALUE ... 'STARTS'for a STARTS the user never wrote, and whether it fails depends on the stored data. - Accept
ER_WARN_INVALID_TIMESTAMP(TIME_to_gmt_sec()already adjusts out of the gap), or round in UTC asvers_calc_hist_parts()now does.
- It is now applied to the oldest history row_end. It rounds down in the session time zone and treats any
Timestamp_stringduplicates the ErrConv/ErrBuff formatters. (sql/sql_class.h:8832)ErrConvTime/ErrBuff::set_mysql_time()insql/sql_error.halready do this.- Compared with them, it heap-allocates a String per instance (three per
WARN_VERS_WRONG_STARTS), has an OOM path, needs a non-constcstr(), and picks the precision implicitly from the constructor overload. - An
ErrConvTimestamp : public ErrConvwith a fixed stack buffer can't fail.
- A test comment is false. (
mysql-test/main/partition.test:3162)# alter_list and partitioning may be combined in either order (since cd483c55, 2005): onlyalter_list partitioningdates from 2005.- The reverse order (
remove partitioning add column y, the next statement) is added by this PR's ownsql_yacc.yychange in 05b36ad.
Nits
Timestamp_string::cstr()(sql_class.h:8864-8872) is documented as "empty on failure", but on OOMto_string()leavesPtr == NULLandc_ptr_safe()fails too, so it returns NULL and the warning prints(null). A!is_valid_temporal()failure also puts nothing in the diagnostics area, although the doc says the error was already reported. Return""explicitly, or fix the doc.THD::timestamp_to_string(String*, my_time_t, uint)(sql_class.h:4866) has no callers. Drop it.WARN_VERS_SLOW_ROW_END(sql_table.cc:14438,14506-14510) is raised before any row is read: on nearly every AUTO ALTER (the default layout has no row_end-leading index) and on empty tables. When the index exists butindexes_are_disabled(), "ROW END index not found ... add an index" is wrong advice; the user needs ENABLE KEYS.vers_calc_hist_parts()(sql_partition.cc:6355-6394) pastesmy_error(..., ME_WARNING); my_error(ER_VERS_HIST_PART_FAILED, ...); return true;four times, and two copies are identical. Use onefail:exit, as the siblingvers_set_hist_part()does.if (end < start) return false;(sql_partition.cc:6335) reports success with*hist_parts == 0. The caller then buildsnum_parts= 1and fails later with a confusingER_VERS_WRONG_PARTS. Make it aDBUG_ASSERT, or a real error.vers_set_starts()summary (partition_info.cc:2912): "Round down ... to the partition interval boundary" overstates it. It truncates to the finest non-zero unit (INTERVAL 2 HOUR keeps the odd hour, a month interval keeps the day).- The block comment "Align STARTS to the oldest historical row_end" (
sql_partition.cc:6205-6208) omits that a user STARTS <= min_ts is kept unchanged. WARN_VERS_WRONG_STARTS(errmsg-utf8.txt:12428) says "is above earliest history date". Its sibling 4164 says "is later than", and the value is a microsecond timestamp, not a date.interval2sec()/interval2usec()(sql_time.h:183,200) are unconstrainedtemplate <typename T>. Two overloads,(const INTERVAL*)and(const MYSQL_TIME*), would state the accepted types.event_data_objects.cc:690-694: the old first line "Computes the sum of a timestamp plus interval." now contradicts the new paragraph under it. Replace it rather than keep both.- The LOCK TABLES block in
partition2.test:278says it only checks "must not lock again", but its result records unexplained 1499/4189 (see Blocker 2). Pin the timestamp, or--echowhy they are expected. - The new footer in
main/partition.test:3167is single-line, while the rest of the file uses the framed three-line--echo #style. - Commits:
Test case for Blocker 1
This fails (server crash) on 58e20ec. The expected result was recorded with engine=myisam, which takes the same code path without crashing, so it shows the correct layout.
mysql-test/suite/versioning/t/partition_auto_aria.test:
--source include/have_partition.inc
set @@session.time_zone='+00:00';
--echo #
--echo # MDEV-25529 ALTER ... AUTO crashes on Aria: history range is read
--echo # before the table is locked
--echo #
create table t1 (x int,
row_start timestamp(6) as row start invisible,
row_end timestamp(6) as row end invisible,
period for system_time (row_start, row_end),
index (row_end, row_start)) with system versioning engine=aria;
set timestamp= unix_timestamp('2000-01-01 00:00:00');
insert t1 values (0);
set timestamp= unix_timestamp('2000-01-01 01:00:00');
update t1 set x= 1;
set timestamp= unix_timestamp('2000-01-01 02:00:00');
update t1 set x= 2;
set timestamp= unix_timestamp('2000-01-01 03:00:00');
alter table t1 partition by system_time interval 1 hour auto;
select x, row_end from t1 partition (p0);
select x, row_end from t1 partition (p1);
select x from t1 partition (pn);
drop table t1;
set timestamp= default;
--echo # End of 13.2 tests
mysql-test/suite/versioning/r/partition_auto_aria.result:
set @@session.time_zone='+00:00';
#
# MDEV-25529 ALTER ... AUTO crashes on Aria: history range is read
# before the table is locked
#
create table t1 (x int,
row_start timestamp(6) as row start invisible,
row_end timestamp(6) as row end invisible,
period for system_time (row_start, row_end),
index (row_end, row_start)) with system versioning engine=aria;
set timestamp= unix_timestamp('2000-01-01 00:00:00');
insert t1 values (0);
set timestamp= unix_timestamp('2000-01-01 01:00:00');
update t1 set x= 1;
set timestamp= unix_timestamp('2000-01-01 02:00:00');
update t1 set x= 2;
set timestamp= unix_timestamp('2000-01-01 03:00:00');
alter table t1 partition by system_time interval 1 hour auto;
select x, row_end from t1 partition (p0);
x row_end
0 2000-01-01 01:00:00.000000
select x, row_end from t1 partition (p1);
x row_end
1 2000-01-01 02:00:00.000000
select x from t1 partition (pn);
x
2
drop table t1;
set timestamp= default;
# End of 13.2 tests
Review round 2: rejected itemsEverything else from round 2 and the Nits
Commits
|
Out of scope: issues in vanilla code, to be fixed under a separate MDEVThese review comments concern code that predates MDEV-25529. They are left for a separate MDEV.
|
58e20ec to
f94440c
Compare
f94440c to
984e301
Compare
cf62202 to
065e608
Compare
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
Temporal_hybrid::to_string() returned the destination String with a stale length when String::alloc() failed, instead of signalling the failure. Return NULL on allocation failure. Apply the same fix to the sibling to_string() methods of Interval_DDhhmmssff, Time, Datetime and Date, which shared the identical flaw, so all five report OOM consistently. The val_str() implementations that return the to_string() result directly did not set null_value, so a NULL result would be taken for a value. They now go through the new Item::check_str(), which sets null_value from the result, so a NULL does not stick to the item for the next execution. The time_to_string_oom debug keyword makes Time::to_string() fail as on out of memory.
Turn the COMBINE macro of date_add_interval() into inline functions with overloads for INTERVAL and MYSQL_TIME, so that the history partition sizing can reuse the seconds part. Move the "Daynumber" comment that sat above the macro to MAX_DAY_NUMBER, which it describes. MAX_DAY_NUMBER moves to sql_time.h as a constexpr (the unused copy in item_timefunc.cc is removed), and the range check of date_add_interval() becomes the inline interval_days_out_of_range(), so that the sizing can check that the seconds do not overflow.
Add ErrConvTimestamp, an ErrConv that formats a Timestamp in the session time zone into a fixed buffer for warning and error messages, with the number of fractional digits given explicitly. ErrBuff::set_mysql_time() gets an optional digits argument for it. Use it when printing STARTS and the history range. Rename the error symbol ER_PART_STARTS_BEYOND_INTERVAL to WARN_VERS_STARTS_BEYOND_INTERVAL and extend its message: it now takes the STARTS timestamp and the query timestamp as arguments, so the number of format arguments changes from one to three. The error number (4164) is unchanged, so this is not an ABI break, but libmariadb still exports the old symbol name for that number until its submodule is updated.
Extracts the STARTS-alignment logic into vers_set_starts() so it can be reused. If the aligned local time falls into a DST gap (e.g. a midnight that does not exist), the start of the gap is used instead of failing with ER_PART_WRONG_VALUE. If it falls before the TIMESTAMP range (e.g. a local midnight before 1970), the timestamp itself is used. Also adds the Vers_part_info::starts_clause flag in preparation for later commits. The flag records whether STARTS was given explicitly (by the user or in the unpacked frm), which the auto-partitioning code uses to decide whether to re-align STARTS to the existing history.
Lets an alter_list be supplied alongside a partitioning or remove_partitioning clause in either order. This allows the FORCE clause to be added to an existing command, and lets AUTO and FORCE be specified together, which later commits of this task rely on.
4c3a1e5 to
d0dba1f
Compare
Maybe, but that was fixed after mreview iterations. |
d0dba1f to
10b5d7f
Compare
…ed as specified by ALTER Adds logic into prep_alter_part_table() for INTERVAL ... AUTO to read the history range (vers_get_history_range()) and size the new layout from it. STARTS is aligned to the oldest history row_end (vers_set_starts()) if it was not specified by the user, or if the user specified it later than that; in the latter case WARN_VERS_WRONG_STARTS is printed. The history partitions cover both the newest history row_end and the query time, so DML after the ALTER does not need to create partitions. A PARTITIONS N clause is kept if it gives more partitions. An explicit subpartition list is replaced by as many default subpartitions. If the layout needs more than MAX_PARTITIONS partitions (counting subpartitions), or the INTERVAL is longer than the date range, the ALTER fails with ER_VERS_HIST_PART_FAILED. Month and year intervals are stepped in UTC; a boundary past the maximum TIMESTAMP ends a partial last history partition. In case of fast ALTER TABLE, f.ex. when partitioning already exists, the above logic is ignored unless FORCE clause is specified. When user specifies partition list explicitly the above logic is ignored even with FORCE clause. With FORCE on a table already partitioned by SYSTEM_TIME the work is done on a clone of the partitioning info. The parsed settings are copied into the clone's vers_info with Vers_part_info::copy_settings(), which keeps the clone's now_part and hist_part (the parsed ones are NULL). Before reading the range, the metadata lock is upgraded to MDL_SHARED_NO_WRITE, and then the copy does not run online, so DML cannot add history that would not fit the computed layout until the ALTER ends (Alter_info::vers_history_read). LOCK=NONE and ALGORITHM=INPLACE/NOCOPY/INSTANT are rejected for such ALTER before the upgrade, so they do not wait for concurrent DML; the ALGORITHM error is reported by the new Alter_info::report_algorithm_unsupported(), which mysql_alter_table() uses as well. The range is read under a table lock taken with mysql_lock_tables(), as some engines (Aria) set up their transaction in THR_LOCK callbacks. The scan can be killed. WARN_VERS_STARTS_BEYOND_INTERVAL for ALTER .. AUTO is checked after the alignment, so it is not printed when STARTS was re-aligned to the history. vers_get_history_range() detects if an index can be used for row_end min/max stats and if so it gets it with ha_index_first() and HA_READ_BEFORE_KEY (as it must ignore current data). Otherwise it does a table scan to read the stats. Current rows with the pre-11.5 row_end marker are not taken for history either. In the index they sort below history after 2038, so the minimum is read after them. There is test_mdev-25529 debug keyword to check the both and compare results. WARN_VERS_SLOW_ROW_END is printed if the scan read any row and there is no usable index (a disabled one does not count as missing). Static key_cmp was renamed to key_eq to resolve compilation after key.h was included as key_cmp was already declared there.
10b5d7f to
cc85c4f
Compare
Please, review.