From 1b42e282d64f718627bda40ffa18e591f35cb925 Mon Sep 17 00:00:00 2001 From: Rex Johnston Date: Tue, 15 Sep 2026 14:49:30 +1200 Subject: [PATCH] PQ: Code tidy ups and fixups of tidyups. parallel_query_aggregate.test: fix statistics impersonalize language in can_parallel_scan_jointab_access() remove dead code (trace_parallel_scan_options) add an assert to ensure we do not use Item_sum_avg without two packed fields minor comment fixups remove enum element NESTED_LOOP_DECLINED and use a local bool instead fix silly code in pwt_row_layout::build_aggregates (throwing away a pointer then searching for it again later); 'one' starts as nullptr, because GCC cannot see that whole_table is unchanged between the two blocks and warns -Wmaybe-uninitialized in an ASAN build remove pwt_batch* worker->manager streaming functions tidy up Parallel_scan_partitioner::Scan_ctx::create_chunks to use create_chunks_unsplit The 'one= nullptr' initialisation was added with Claude Code (Opus 5.5), which traced the -Wmaybe-uninitialized warning to this change and folded the fix in here. --- mysql-test/main/parallel_query_aggregate.test | 1 + mysql-test/main/parallel_query_sort.result | 2 +- mysql-test/main/parallel_query_why.result | 2 +- sql/sql_parallel_execution.cc | 100 ++++--- sql/sql_parallel_transport.cc | 261 +----------------- sql/sql_parallel_workers.h | 3 +- sql/sql_select.cc | 7 +- sql/sql_select.h | 3 +- storage/innobase/btr/btr0pscan.cc | 29 +- storage/innobase/include/btr0pscan.h | 6 +- 10 files changed, 68 insertions(+), 346 deletions(-) diff --git a/mysql-test/main/parallel_query_aggregate.test b/mysql-test/main/parallel_query_aggregate.test index 12ac491cffb..f97d255bbce 100644 --- a/mysql-test/main/parallel_query_aggregate.test +++ b/mysql-test/main/parallel_query_aggregate.test @@ -16,6 +16,7 @@ --source include/not_embedded.inc --source include/have_sequence.inc --source include/have_debug.inc +--source include/innodb_stable_estimates.inc --disable_service_connection diff --git a/mysql-test/main/parallel_query_sort.result b/mysql-test/main/parallel_query_sort.result index e5b01269760..9560b752f99 100644 --- a/mysql-test/main/parallel_query_sort.result +++ b/mysql-test/main/parallel_query_sort.result @@ -73,7 +73,7 @@ Parallel_queries_executed 0 SELECT JSON_EXTRACT(trace, '$**.parallel_scan_declined_because') AS why FROM information_schema.optimizer_trace; why -["an index supplies the ORDER BY order, so we can't split rows", "no table this plan reads can be divided"] +["an index supplies the ORDER BY order, rows aren't splittable", "no table this plan reads can be divided"] SET SESSION optimizer_trace= @save_trace; SET SESSION parallel_worker_threads=default; DROP TABLE t1; diff --git a/mysql-test/main/parallel_query_why.result b/mysql-test/main/parallel_query_why.result index a2640da3999..d1a525710a9 100644 --- a/mysql-test/main/parallel_query_why.result +++ b/mysql-test/main/parallel_query_why.result @@ -26,7 +26,7 @@ why ["grouped: a select list item reads a column outside an aggregate that GROUP BY does not name, so its value is whichever row the terminal happened to hold", "the plan needs a temporary table and the workers cannot pre-aggregate into it"] # an index supplying the ORDER BY why -["an index supplies the ORDER BY order, so we can't split rows", "no table this plan reads can be divided"] +["an index supplies the ORDER BY order, rows aren't splittable", "no table this plan reads can be divided"] # a sort this thread cannot do over its container why ["the manager cannot sort what the workers produce (see pwt_manager_sort_order)", "no table this plan reads can be divided"] diff --git a/sql/sql_parallel_execution.cc b/sql/sql_parallel_execution.cc index b90cf371e44..cb704523824 100644 --- a/sql/sql_parallel_execution.cc +++ b/sql/sql_parallel_execution.cc @@ -196,48 +196,6 @@ bool jointab_can_be_parallel_scanned(JOIN_TAB *tab, bool trace) } -/** - @brief - Trace our parallel scan table option. -*/ - -void trace_parallel_scan_options(JOIN *join) -{ - if (unlikely(join->thd->trace_started())) - { - JOIN_TAB *scan_tab= first_linear_tab(join, WITH_BUSH_ROOTS, - WITHOUT_CONST_TABLES); - JOIN_TAB *sorted= NULL; - for (uint t= join->const_tables; t < join->table_count; t++) - if (join->join_tab[t].filesort) - { - sorted= join->join_tab + t; - break; - } - - const bool divisible= jointab_can_be_parallel_scanned(scan_tab, false) && - !sorted; - Json_writer_object trace_pscan(join->thd); - if (divisible) - trace_pscan.add("chosen_for_parallel_scan", scan_tab->table->alias.c_ptr()); - else - { - trace_pscan.add("parallel_scan_abandoned", - scan_tab ? scan_tab->table->alias.c_ptr() : ""); - /* Named so it can be grepped for: "cause" is a common trace key. */ - trace_pscan.add("parallel_scan_abandoned_because", - sorted ? - "the rows would have to reach the join sorted" : - join->ordered_index_usage == join->ordered_index_group_by ? - "an index now supplies the GROUP BY order" : - join->ordered_index_usage == join->ordered_index_order_by ? - "an index now supplies the ORDER BY order" : - "the access path can no longer be divided in chunks"); - } - } -} - - /** @brief The whole condition this table has to be filtered by, whatever the plan did @@ -2105,6 +2063,31 @@ bool pwt_manager::setup_worker_preagg(THD *thd, pwt_worker *worker) DBUG_ASSERT(worker->exec.result.table->s->fields == layout.recv.table->s->fields); +#ifndef DBUG_OFF + /* + An AVG partial is only mergeable if the container holds its count beside + its sum. Item_sum_avg::create_tmp_field() packs the two into one binary + string field -- but only when the table has a group, which is the whole + reason the whole-table shape is given a constant group. Nothing else + enforces that, so check it here: the unpacked form is a plain double or + decimal field, and the manager would read it as a sum with no count. + */ + for (uint i= 0; i < layout.n_sums; i++) + { + Item_sum *s= worker->exec.sums[i]; + if (s->sum_func() != Item_sum::AVG_FUNC) + continue; + Item_sum_avg *avg= (Item_sum_avg *) s; + Field *f= worker->exec.result.table->field[layout.n_ship_base + i]; + DBUG_ASSERT(f->type() == MYSQL_TYPE_STRING && + f->charset() == &my_charset_bin && + f->pack_length() == + sizeof(longlong) + + (avg->result_type() == DECIMAL_RESULT ? avg->dec_bin_size + : sizeof(double))); + } +#endif + /* Aggregation JOIN_TAB send to end_update(). It reads the table, the param (for the key buffer, the Copy_field pairs and @@ -2230,11 +2213,14 @@ static enum_nested_loop_state pwt_end_send(JOIN *join, JOIN_TAB *join_tab, if (pwt_self->manager->row_layout().grouped) { THD *thd= pwt_self->thd; - // record local memory used so that if a HEAP->Aria conversion happens... + /* + record local memory used so that if a we allocate another block, or + HEAP->Aria conversion happens, then owed gets transferred to the manager + */ const int64 before= thd->status_var.local_memory_used; const enum_nested_loop_state rc= pwt_self->exec.aggr_tab->aggr->put_record(); const int64 owed= thd->status_var.local_memory_used - before; - if (unlikely(owed)) // we can account for it here + if (owed) { thd->status_var.local_memory_used= before; pwt_self->sink->account_spilled_memory(owed); @@ -2935,8 +2921,8 @@ bool can_parallel_scan_jointab_access(JOIN_TAB *join_tab, bool trace) if (join_tab->join->ordered_index_usage != JOIN::ordered_index_void) return pwt_decline(join_tab->join, trace, join_tab->join->ordered_index_usage == JOIN::ordered_index_group_by - ? "an index supplies the GROUP BY order, so we can't split rows" - : "an index supplies the ORDER BY order, so we can't split rows"); + ? "an index supplies the GROUP BY order, rows aren't splittable" + : "an index supplies the ORDER BY order, rows aren't splittable"); if (!((join_tab->type == JT_ALL || join_tab->type == JT_RANGE) && join_tab->read_first_record == join_init_read_record)) return pwt_decline(join_tab->join, trace, @@ -3180,13 +3166,15 @@ void parallel_join_check(JOIN *join) @brief Called from do_select() in sql_select.cc, run either the worker side - join, the scan only in the workers, or return NESTED_LOOP_DECLINED + join, the scan only in the workers, or set declined */ -enum_nested_loop_state do_select_parallel(JOIN *join) +enum_nested_loop_state do_select_parallel(JOIN *join, bool *declined) { + *declined= true; + if (!join->worker_side_parallel) - return NESTED_LOOP_DECLINED; + return NESTED_LOOP_ERROR; /* PROTOTYPE, behind debug_dbug='+d,pwt_scan_only'. The workers are a @@ -3197,12 +3185,15 @@ enum_nested_loop_state do_select_parallel(JOIN *join) */ if (pwt_scan_only_enabled()) { + int rc; JOIN_TAB *scan_tab= first_linear_tab(join, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); join->worker_side_parallel= false; - if (run_scan_only_workers(join, scan_tab) > 0) - return NESTED_LOOP_ERROR; - return NESTED_LOOP_DECLINED; + rc= run_scan_only_workers(join, scan_tab); + if (rc == 0) + return NESTED_LOOP_OK; + *declined= false; + return NESTED_LOOP_ERROR; } else { @@ -3215,11 +3206,14 @@ enum_nested_loop_state do_select_parallel(JOIN *join) */ int wr= run_worker_side_join(join, scan_tab); if (wr >= 0) + { + *declined= false; return wr ? NESTED_LOOP_ERROR : NESTED_LOOP_OK; + } else join->worker_side_parallel= false; // declined, run serially } - return NESTED_LOOP_DECLINED; + return NESTED_LOOP_ERROR; } diff --git a/sql/sql_parallel_transport.cc b/sql/sql_parallel_transport.cc index 91df74584ce..4ad2e78a3fd 100644 --- a/sql/sql_parallel_transport.cc +++ b/sql/sql_parallel_transport.cc @@ -394,6 +394,7 @@ bool pwt_row_layout::build(THD *thd, JOIN *join_arg, TABLE **tables, bool pwt_row_layout::build_aggregates(THD *thd, ORDER *plan_group) { + Item *one= nullptr; n_sums= 0; for (Item_sum **s= join->sum_funcs; *s; s++) n_sums++; @@ -438,7 +439,7 @@ bool pwt_row_layout::build_aggregates(THD *thd, ORDER *plan_group) if (whole_table) { DBUG_ASSERT(!n_group); - Item *one= new (thd->mem_root) Item_int(thd, (longlong) 1, 1); + one= new (thd->mem_root) Item_int(thd, (longlong) 1, 1); if (!one || result_defn.push_back(one, thd->mem_root)) { my_error(ER_OUTOFMEMORY, MYF(0), (int) sizeof(Item_int)); @@ -454,13 +455,8 @@ bool pwt_row_layout::build_aggregates(THD *thd, ORDER *plan_group) } if (whole_table) { - List_iterator_fast di(result_defn); - Item *defn_item= nullptr; - for (uint j= 0; j < result_defn.elements; j++) - defn_item= di++; // the constant, pushed last - bzero((char *) &group_defn[0], sizeof(ORDER)); - group_defn[0].item_ptr= defn_item; + group_defn[0].item_ptr= one; group_defn[0].item= &group_defn[0].item_ptr; group_defn[0].direction= ORDER::ORDER_ASC; group_defn[0].next= nullptr; @@ -555,7 +551,7 @@ bool pwt_row_layout::make_container_from(THD *thd, List &defn, ORDER *group) { /* - out needs it's own TMP_TABLE_PARAM because + out needs its own TMP_TABLE_PARAM because 1) create_tmp_table() overwrites param->func_count with the number of items it actually has to copy, so a second table built from the same param allocates fewer fields than the layout needs. @@ -1159,252 +1155,3 @@ pwt_row_source *pwt_create_transport(THD *thd, pwt_manager *mgr, } return src; } - -#if 0 -/* - pwt_batch* classes implement streaming from workers to the manager - they may be useful in the future, so left here. -*/ - -bool pwt_batch_sink::init(pwt_manager *mgr, pwt_batch_source *peer_arg, - uint reclength_arg) -{ - manager= mgr; - peer= peer_arg; - reclength= reclength_arg; - count= 0; - full= false; - rows= (uchar*) my_malloc(key_memory_pwt_batch_rows, - (size_t) PWT_ROW_GANULARITY * reclength, MYF(MY_WME)); - return rows == nullptr; -} - - -void pwt_batch_sink::cleanup() -{ - my_free(rows); - rows= nullptr; -} - - -/** - @brief - Hand this worker's filled buffer to the manager. - - @description - Marks the buffer ready and blocks until the manager has drained it (clears - 'full') or asks the producers to stop. On return the buffer is the worker's - again: either to refill, or to abandon. - - This is the only place a worker waits for the manager, and it is what the - temporary-table transport exists to remove. - - @return - true the consumer asked us to stop - false the buffer was drained; refill it -*/ - -bool pwt_batch_sink::handoff() -{ - DBUG_ENTER("pwt_batch_sink::handoff"); - mysql_mutex_lock(&manager->LOCK_data); - if (manager->workers_must_stop) - { - mysql_mutex_unlock(&manager->LOCK_data); - DBUG_RETURN(true); - } - full= true; - mysql_cond_signal(&manager->COND_data_avail); // wake the consumer - while (full && !manager->workers_must_stop) - { - mysql_cond_wait(&peer->COND_data_space, &manager->LOCK_data); - DBUG_PRINT("info", ("worker wakes")); - } - bool stopped= manager->workers_must_stop; - mysql_mutex_unlock(&manager->LOCK_data); - DBUG_RETURN(stopped); -} - - -/* - @brief - Take one finished row: copy its record image into the buffer, handing the - buffer over when it fills. -*/ - -int pwt_batch_sink::emit_row(const uchar *rec) -{ - memcpy(rows + (size_t) count * reclength, rec, reclength); - if (++count == PWT_ROW_GANULARITY) - { - if (handoff()) // manager asked us to stop - return PWT_EMIT_STOP; - count= 0; // buffer drained; refill - } - return PWT_EMIT_OK; -} - - -/* - Hand over the final partial buffer. A stop arriving now is not interesting: - this producer has finished anyway. -*/ - -bool pwt_batch_sink::flush() -{ - if (count) - handoff(); - return false; -} - - -/***************************************************************************** - pwt_batch_source -- the consuming end of the batch transport -*****************************************************************************/ - -bool pwt_batch_source::init(THD *thd, pwt_manager *mgr, uint n_workers, - uint reclength_arg) -{ - manager= mgr; - n_sinks= n_workers; - reclength= reclength_arg; - cur= nullptr; - cur_cursor= 0; - if (!(sinks= thd->alloc(n_workers))) - return true; - for (uint i= 0; i < n_workers; i++) - sinks[i]= nullptr; - mysql_cond_init(key_COND_pwt_data_space, &COND_data_space, nullptr); - inited= true; - return false; -} - - -pwt_row_sink *pwt_batch_source::make_sink(THD *thd, uint worker_nr, - pwt_row_container *container) -{ - DBUG_ASSERT(worker_nr < n_sinks); - /* - Not used: this transport copies the record out of the container rather than - keeping it, so it needs only the size, which it has from the layout. - */ - (void) container; - pwt_batch_sink *s= new (thd->mem_root) pwt_batch_sink; - if (!s || s->init(manager, this, reclength)) - { - my_error(ER_OUTOFMEMORY, MYF(0), - (int) (PWT_ROW_GANULARITY * reclength)); - return nullptr; - } - sinks[worker_nr]= s; - return s; -} - - -/* - Release every producer blocked waiting for its buffer back, so it sees the - manager's stop request. The caller sets that request; this only wakes them. -*/ - -void pwt_batch_source::wake_producers() -{ - mysql_mutex_assert_owner(&manager->LOCK_data); - mysql_cond_broadcast(&COND_data_space); -} - - -void pwt_batch_source::cleanup() -{ - if (inited) - { - mysql_cond_destroy(&COND_data_space); - inited= false; - } -} - - -/* - @brief - Copy the next result row's record image into dst. - - @description - Drains one worker's buffer at a time (cur), advancing cur_cursor through - its rows; when the buffer is exhausted it releases that worker to refill - and picks the next ready one. Blocks when no buffer is momentarily ready. - - This is also the manager's only wait, so it is where the team's own state - is noticed: a worker killed is propagated to the manager's THD, a worker - error aborts, and "no buffer ready and nobody still running" is end of - data. - - @return - 0 = row copied into dst, -1 = end of data, 1 = error. -*/ - -int pwt_batch_source::next_row(uchar *dst) -{ - DBUG_ENTER("pwt_batch_source::next_row"); - THD *thd= manager->thd; - struct timespec wait; - wait.tv_nsec= 0; - - for (;;) - { - if (cur) // draining a worker's buffer - { - if (cur_cursor < cur->count) - { - memcpy(dst, cur->rows + (size_t) cur_cursor * reclength, reclength); - cur_cursor++; - DBUG_RETURN(0); - } - // buffer drained; release the worker so it can refill - mysql_mutex_lock(&manager->LOCK_data); - pwt_batch_sink *drained= cur; - cur= nullptr; - drained->full= false; // buffer is the worker's again - mysql_cond_broadcast(&COND_data_space); // wake it to refill - mysql_mutex_unlock(&manager->LOCK_data); - // fall through and look for the next ready worker - } - - // find the next worker whose buffer is filled and ready - pwt_batch_sink *next= nullptr; - PSI_stage_info old_stage; - mysql_mutex_lock(&manager->LOCK_data); - for (;;) - { - for (uint i= 0; i < n_sinks; i++) - if (sinks[i] && sinks[i]->full) - { - next= sinks[i]; - break; - } - if (next) - break; - - int res; - if ((res= manager->locked__process_manager_wakeup()) || - (res=(thd->killed != NOT_KILLED))) - { - mysql_mutex_unlock(&manager->LOCK_data); - DBUG_RETURN(res); - } - // wait for a batch, a finishing worker, or a 1s tick to re-check killed. - // ENTER_COND/EXIT_COND publish the "Reading data from parallel workers" - // stage and register the cond so a KILL of the manager wakes it. - wait.tv_sec= time(0) + 1; - thd->ENTER_COND(&manager->COND_data_avail, &manager->LOCK_data, - &stage_reading_data_from_parallel_worker, &old_stage); - mysql_cond_timedwait(&manager->COND_data_avail, &manager->LOCK_data, - &wait); - thd->EXIT_COND(&old_stage); // unlocks LOCK_data - mysql_mutex_lock(&manager->LOCK_data); // re-lock for the next pass - } - cur= next; - cur_cursor= 0; // start of next's buffer - mysql_mutex_unlock(&manager->LOCK_data); - // loop back and drain cur - } -} -#endif diff --git a/sql/sql_parallel_workers.h b/sql/sql_parallel_workers.h index 5a910fe2586..7ed4eb87272 100644 --- a/sql/sql_parallel_workers.h +++ b/sql/sql_parallel_workers.h @@ -532,8 +532,7 @@ extern bool can_parallel_scan_jointab_access(JOIN_TAB *join_tab, bool trace= false); extern bool pwt_decline(JOIN *join, bool trace, const char *why); extern bool pwt_scan_only_enabled(); -extern void trace_parallel_scan_options(JOIN *join); extern void parallel_join_check(JOIN *join); -extern enum_nested_loop_state do_select_parallel(JOIN *join); +extern enum_nested_loop_state do_select_parallel(JOIN *join, bool *declined); #endif diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 858d8fac701..8421822afda 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -3546,9 +3546,6 @@ int JOIN::optimize_stage2() } } - if (worker_side_parallel) - trace_parallel_scan_options(this); - if (having) having_is_correlated= MY_TEST(having->used_tables() & OUTER_REF_TABLE_BIT); tmp_having= having; @@ -24292,6 +24289,7 @@ do_select(JOIN *join, Procedure *procedure) } else { + bool declined= false; DBUG_EXECUTE_IF("show_explain_probe_do_select", if (dbug_user_var_equals_int(join->thd, "show_explain_probe_select_id", @@ -24313,7 +24311,8 @@ do_select(JOIN *join, Procedure *procedure) JOIN_TAB *join_tab= join->join_tab + (join->tables_list ? join->const_tables : 0); - if ((error= do_select_parallel(join)) == NESTED_LOOP_DECLINED) + error= do_select_parallel(join, &declined); + if (declined) { if (join->outer_ref_cond && !join->outer_ref_cond->val_bool()) error= NESTED_LOOP_NO_MORE_ROWS; diff --git a/sql/sql_select.h b/sql/sql_select.h index 8e8b65ada7f..f713be3f3a8 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -444,8 +444,7 @@ enum enum_nested_loop_state { NESTED_LOOP_KILLED= -2, NESTED_LOOP_ERROR= -1, NESTED_LOOP_OK= 0, NESTED_LOOP_NO_MORE_ROWS= 1, - NESTED_LOOP_QUERY_LIMIT= 3, NESTED_LOOP_CURSOR_LIMIT= 4, - NESTED_LOOP_DECLINED= 5 + NESTED_LOOP_QUERY_LIMIT= 3, NESTED_LOOP_CURSOR_LIMIT= 4 }; diff --git a/storage/innobase/btr/btr0pscan.cc b/storage/innobase/btr/btr0pscan.cc index 885a8f49805..19a3697dd61 100644 --- a/storage/innobase/btr/btr0pscan.cc +++ b/storage/innobase/btr/btr0pscan.cc @@ -808,14 +808,15 @@ dberr_t Parallel_scan_partitioner::Scan_ctx::create_chunk(const Bounds &bounds, return (err); } -dberr_t Parallel_scan_partitioner::Scan_ctx::create_chunks_unsplit() +dberr_t Parallel_scan_partitioner::Scan_ctx::create_chunks_unsplit(bool resplit, + size_t split_point) { size_t i{}; for (auto bounds : m_bounds_list) { const bool last = (i + 1 == m_bounds_list.size()); - if (dberr_t err= create_chunk(bounds, false, + if (dberr_t err= create_chunk(bounds, resplit && (i >= split_point), last && m_config.m_scan_range.m_end_inclusive)) return err; @@ -901,27 +902,7 @@ dberr_t Parallel_scan_partitioner::Scan_ctx::create_chunks() } } - size_t i{}; - - for (auto bounds : m_bounds_list) { - /* Only the last chunk ends at the caller's upper bound, so only it can - be inclusive. */ - const bool last = (i + 1 == m_bounds_list.size()); - - auto err = create_chunk(bounds, i >= split_point, - last && m_config.m_scan_range.m_end_inclusive); - - if (err != DB_SUCCESS) { - return (err); - } - - ++i; - } - - m_bounds_list.clear(); - m_bounds_list.shrink_to_fit(); - - return DB_SUCCESS; + return create_chunks_unsplit(true, split_point); } dberr_t Parallel_scan_partitioner::add_scan( @@ -1016,7 +997,7 @@ dberr_t Parallel_scan_partitioner::create_chunks() scan_ctx->coalesce_bounds(std::max(share, size_t{1})); - if (dberr_t err = scan_ctx->create_chunks_unsplit()) + if (dberr_t err = scan_ctx->create_chunks_unsplit(false, 0)) return err; } diff --git a/storage/innobase/include/btr0pscan.h b/storage/innobase/include/btr0pscan.h index 6d36f4d4167..68350810a40 100644 --- a/storage/innobase/include/btr0pscan.h +++ b/storage/innobase/include/btr0pscan.h @@ -475,7 +475,8 @@ class Parallel_scan_partitioner::Scan_ctx { the division was decided knowing every range's size, so there is nothing left for a worker to correct. @return DB_SUCCESS or error code. */ - [[nodiscard]] dberr_t create_chunks_unsplit(); + [[nodiscard]] dberr_t create_chunks_unsplit(bool resplit, + size_t split_point); /** Walk one level further down, to the children of the sub-trees this range's boundaries name, and keep what that finds in place of them. @@ -484,11 +485,12 @@ class Parallel_scan_partitioner::Scan_ctx { wants -- coalesce_bounds() cuts them back down. What the walk is for is the count: the pages a range covers is how much work it holds, and the only measure of that the tree offers without descending into it. + NOTE: MAX_PARALLEL_SCAN_RANGES = 128 see sql/sql_parallel_execution.cc @return DB_SUCCESS or error code. */ [[nodiscard]] dberr_t partition_deeper(); /** Merge this range's boundaries into runs, leaving it with at most - 'target' chunks. Adjacent chunks are contiguous intervals sharing an + 2x'target'-1 chunks. Adjacent chunks are contiguous intervals sharing an endpoint, so a run of them merges into the first one's start and the last one's end. @param[in] target the most chunks this range should produce. */