[Maria-developers] MWL#90 combined patch for review

Hi Monty, Please find attached the complete patch of MWL#90 for review. The tree with the code is on launchpad/buildbot: https://code.launchpad.net/~maria-captains/maria/5.3-subqueries-mwl90 http://buildbot.askmonty.org/buildbot/grid?branch=5.3-subqueries-mwl90 and it has no failures other than those also found in mainline 5.3. The merge from 5.3-main was about a week ago. BR Sergey -- Sergey Petrunia, Software Developer Monty Program AB, http://askmonty.org Blog: http://s.petrunia.net/blog

On Tue, Feb 22, 2011 at 05:24:37PM +0300, Sergey Petrunya wrote:
BR Sergey -- Sergey Petrunia, Software Developer Monty Program AB, http://askmonty.org Blog: http://s.petrunia.net/blog

On Tue, Feb 22, 2011 at 06:03:38PM +0300, Sergey Petrunya wrote:
Please find below dgcov coverage report for the tree. My summary is that uncovered parts are - error handling - handling of semi-joins together with outer joins (a known bug/problem area) - make_in_exists_conversion() which serves as a fallback in case we've failed to convert the subquery to [merged] semi-join. I think I should be able to come up with a test for this. -- dgcov.out file starts -- File: sql/item_subselect.cc ------------------------------------------------------------------------------- | 505: 3844: uint len= my_snprintf(buf, sizeof(buf), "<subquery%d>", subquery_id); | -: 3845: char *name; | 505: 3846: if (!(name= (char*)thd->alloc(len + 1))) | #####: 3847: DBUG_RETURN(TRUE); | 505: 3848: memcpy(name, buf, len+1); | -: 3849: . 505: 3850: if (!(result= new select_materialize_with_stats)) File: sql/item_subselect.h ------------------------------------------------------------------------------- . -: 536: THD * get_thd() { return thd; } . -: 537: virtual int prepare()= 0; . -: 538: virtual void fix_length_and_dec(Item_cache** row)= 0; | #####: 539: virtual int optimize() { DBUG_ASSERT(0); return 0; } . -: 540: /* . -: 541: Execute the engine . -: 542: File: sql/opt_subselect.cc ------------------------------------------------------------------------------- | -: 527:*/ | -: 528: | -: 529:static | #####: 530:bool make_in_exists_conversion(THD *thd, JOIN *join, Item_in_subselect *item) | -: 531:{ | #####: 532: DBUG_ENTER("make_in_exists_conversion"); | #####: 533: JOIN *child_join= item->unit->first_select()->join; | -: 534: Item_subselect::trans_res res; | #####: 535: item->changed= 0; | #####: 536: item->fixed= 0; | -: 537: | #####: 538: SELECT_LEX *save_select_lex= thd->lex->current_select; | #####: 539: thd->lex->current_select= item->unit->first_select(); | -: 540: | #####: 541: res= item->select_transformer(child_join); | -: 542: | #####: 543: thd->lex->current_select= save_select_lex; | -: 544: | #####: 545: if (res == Item_subselect::RES_ERROR) | #####: 546: DBUG_RETURN(TRUE); | -: 547: | #####: 548: item->changed= 1; | #####: 549: item->fixed= 1; | -: 550: | #####: 551: Item *substitute= item->substitution; | #####: 552: bool do_fix_fields= !item->substitution->fixed; | -: 553: /* | -: 554: The Item_subselect has already been wrapped with Item_in_optimizer, so we | -: 555: should search for item->optimizer, not 'item'. | -: 556: */ | #####: 557: Item *replace_me= item->optimizer; | #####: 558: DBUG_ASSERT(replace_me==substitute); | -: 559: | -: 560: Item **tree= (item->emb_on_expr_nest == (TABLE_LIST*)1)? | #####: 561: &join->conds : &(item->emb_on_expr_nest->on_expr); | #####: 562: if (replace_where_subcondition(join, tree, replace_me, substitute, | -: 563: do_fix_fields)) | #####: 564: DBUG_RETURN(TRUE); | #####: 565: item->substitution= NULL; | -: 566: | #####: 567: if (!thd->stmt_arena->is_conventional()) | -: 568: { | -: 569: tree= (item->emb_on_expr_nest == (TABLE_LIST*)1)? | -: 570: &join->select_lex->prep_where : | #####: 571: &(item->emb_on_expr_nest->prep_on_expr); | -: 572: | #####: 573: if (replace_where_subcondition(join, tree, replace_me, substitute, | -: 574: FALSE)) | #####: 575: DBUG_RETURN(TRUE); | -: 576: } | #####: 577: DBUG_RETURN(FALSE); | -: 578:} | -: 579: | -: 580: | 1451: 695: if ((*in_subq)->is_flattenable_semijoin) | -: 696: { | 1375: 697: if (convert_subq_to_sj(join, *in_subq)) | #####: 698: DBUG_RETURN(TRUE); | -: 699: } | -: 700: else | -: 701: { | 76: 702: if (convert_subq_to_jtbm(join, *in_subq, &remove_item)) | #####: 703: DBUG_RETURN(TRUE); | -: 704: } | 1451: 705: if (remove_item) | -: 706: { | 76: 1236: DBUG_ENTER("convert_subq_to_jtbm"); | -: 1237: | 76: 1238: if (subq_pred->setup_engine(TRUE)) | #####: 1239: DBUG_RETURN(TRUE); | -: 1240: | 76: 1241: if (subq_pred->engine->engine_type() != subselect_engine::HASH_SJ_ENGINE) | -: 1242: { | #####: 1243: *remove_item= FALSE; | #####: 1244: make_in_exists_conversion(parent_join->thd, parent_join, subq_pred); | #####: 1245: DBUG_RETURN(FALSE); | -: 1246: } | 76: 1247: *remove_item= TRUE; | -: 1248: | 76: 1252: if (!(tbl_alias= (char*)parent_join->thd->calloc(sizeof(alias_mask)+5)) || | -: 1253: !(jtbm= alloc_join_nest(parent_join->thd))) //todo: this is not a join nest! | -: 1254: { | #####: 1255: DBUG_RETURN(TRUE); | -: 1256: } | -: 1257: | 76: 1258: jtbm->join_list= emb_join_list; | -: 1302: /* Inject sj_on_expr into the parent's WHERE or ON */ | 76: 1303: if (emb_tbl_nest) | -: 1304: { | #####: 1305: DBUG_ASSERT(0); | -: 1306: /*emb_tbl_nest->on_expr= and_items(emb_tbl_nest->on_expr, | -: 1307: sj_nest->sj_on_expr); | -: 1308: emb_tbl_nest->on_expr->fix_fields(parent_join->thd, &emb_tbl_nest->on_expr); | -: 2748: */ | 601: 2749: if (!(tab_ref->cond_guards= (bool**) thd->calloc(sizeof(uint*)*tmp_key_parts))) | -: 2750: { | #####: 2751: DBUG_RETURN(TRUE); | -: 2752: } | -: 2753: . 601: 2754: tab_ref->key_err= 1; | 52: 4010: hash_sj_engine->is_materialized= TRUE; | -: 4011: | 52: 4012: if (hash_sj_engine->materialize_join->error || tab->join->thd->is_fatal_error) | #####: 4013: DBUG_RETURN(NESTED_LOOP_ERROR); | -: 4014: } | -: 4015: } | 455621: 4016: else if (tab->bush_children) | 137: 4032: if ((rc= sub_select(join, join_tab, FALSE/* no EOF */)) < 0 || | -: 4033: (rc= sub_select(join, join_tab, TRUE/* now EOF */)) < 0) | -: 4034: { | #####: 4035: join->return_tab= save_return_tab; | #####: 4036: DBUG_RETURN(rc); /* it's NESTED_LOOP_(ERROR|KILLED)*/ | -: 4037: } | 137: 4038: join->return_tab= save_return_tab; | 137: 4039: sjm->materialized= TRUE; File: sql/sql_base.cc ------------------------------------------------------------------------------- | 10: 7784: Item *item= table_list->jtbm_subselect; | 10: 7785: if (item->fix_fields(thd, &item)) | -: 7786: { | #####: 7787: my_error(ER_TOO_MANY_TABLES,MYF(0),MAX_TABLES); | #####: 7788: DBUG_RETURN(1); | -: 7789: } | 10: 7790: DBUG_ASSERT(item == table_list->jtbm_subselect); | 10: 7791: if (table_list->jtbm_subselect->setup_engine(FALSE)) | #####: 7792: DBUG_RETURN(1); | -: 7793: } . -: 7794: } . -: 7795: File: sql/sql_join_cache.cc ------------------------------------------------------------------------------- . -: 2140: } . -: 2141: | 4620: 2142: if ((rc= join_tab_execution_startup(join_tab)) < 0) | #####: 2143: goto finish2; | -: 2144: . -: 2145: /* Prepare to retrieve all records of the joined table */ | 4620: 2146: if ((error= join_tab_scan->open())) File: sql/sql_select.cc ------------------------------------------------------------------------------- | -: 7556: { | -: 7557: /* Push condition to handler */ | 75098: 7558: if (!tab->table->file->cond_push(push_cond)) | #####: 7559: tab->table->file->pushed_cond= push_cond; | -: 7560: } . -: 7561: } . -: 7562: } | 136998: 8558: if (tab->bush_children) | -: 8559: { | 670: 8560: if (setup_sj_materialization(tab)) | #####: 8561: return TRUE; | -: 8562: } | -: 8563: . 136998: 8564: TABLE *table=tab->table; . -:14093: | 2548539:14094: if (rc != NESTED_LOOP_NO_MORE_ROWS && | -:14095: (rc= join_tab_execution_startup(join_tab)) < 0) | #####:14096: DBUG_RETURN(rc); | -:14097: . 2548539:14098: if (join_tab->loosescan_match_tab) . 4:14099: join_tab->loosescan_match_tab->found_match= FALSE; File: sql/sql_test.cc ------------------------------------------------------------------------------- | 8: 210: if (tab->use_quick == 2) | -: 211: fprintf(DBUG_FILE, | -: 212: " quick select checked for each record (keys: %s)\n", | #####: 213: tab->select->quick_keys.print(buf)); | 8: 214: else if (tab->select->quick) | -: 215: { | #####: 216: fprintf(DBUG_FILE, " quick select used:\n"); | #####: 217: tab->select->quick->dbug_dump(18, FALSE); | -: 218: } | -: 219: else | 8: 220: VOID(fputs(" select used\n",DBUG_FILE)); ------------------------------------------------------------------------------- 1667 line(s) in 16 source file(s) modified in revision(s). 50 line(s) not covered by tests. For documentation, see http://forge.mysql.com/wiki/DGCov_doc BR Sergey -- Sergey Petrunia, Software Developer Monty Program AB, http://askmonty.org Blog: http://s.petrunia.net/blog

Hi! Here is part 1 of the review; part 2 should come later today.
"Sergey" == Sergey Petrunya <psergey@askmonty.org> writes:
Sergey> On Tue, Feb 22, 2011 at 05:24:37PM +0300, Sergey Petrunya wrote:
<cut>
Please remove comment as this is not an issue anymore.
Don't reset res
Move the above to after Item *substitions; No reason to have many :public :protected blocks
You should set this to zero in Item_subselect::Item_subselect() !
Remove startup_cost as it's not used <cut>
Please move all public variables to one block.
You removed //6 comment without updating the comment for the block that describes 6. Please update comment!
Instead of doing: thd->thd_marker.emb_on_expr_nest == (TABLE_LIST*)0x1 please create an inline function with a meningfull name to test test or at least replace (TABLE_LIST*) 0x with a define or both.
+ { + in_subs->emb_on_expr_nest= thd->thd_marker.emb_on_expr_nest;
Here you could make things cleared and set in_subs->emb_on_expr_nest to a define. <cut>
Please add a comment why you set item->fixed to 0 as this is not obvious
item->substitution -> substitute
The code belove makes a permanent change to the prepared statement tree. Please add a comment about this! Also please add a test where you do a prepared statement that hits his code and excecute it twice with different parameters.
Consider doing a function: Item_in_subselect::original_item() { return is_flattenable_semijoin ? self : item->optimizer; } This would make the above code a bit easier to understand by doing: Item *replace_me= (*in_subq)->original_item() This would thus be a bit like real_item()
The above code can also be made easier with (*in_subq)->original_item(). The above comment is a good one for original_item()!
The above loop is identical to doing: get_partial_join_cost(join, join->table, startup_cost, &rows); *out_rows= (ha_rows) rows;
/* Please ensure that the code below is covered with some test case !*/
You should return value of make_in_exists_conversion() as this may fail...
+ DBUG_RETURN(FALSE); + }
Add an explicit {} on an empty row, as otherwise you may get compiler warnings from some compilers
Add an explicit {} on an empty row...
+ tl->next_local= jtbm;
Do you really have to put the table last ? It would be much esier to put it first...
Where is it checked that this doesn't exceed MAX_TABLES? Add a comment for the function that guarantees this and an DBUG_ASSERT
A fast way to generate the name would be to use the following function: #define subquery_table_name_length= sizeof("<subquery9999>"); void create_temporary_subquery_table_name(char *to, uint number) { DBUG_ASSERT(number < 10000); to= strmov(to, "<subquery"); to= int10_to_str((int) number, to, 10); to[0]= '>'; to[1]= 0; } This is significantly faster than my_snprintf(). This would also simplify the other code you have when you generate the name.
Please remove the comment and change the above to: DBUG_ASSERT(!emb_tbl_nest);
+enum_nested_loop_state join_tab_execution_startup(JOIN_TAB *tab) +{
<cut>
move setting of the last 2 variables inside the loop belpw
Regards, Monty

Hi! Part 2 (of 3) Sorry got guests for birthday party that I have to make food for, so I have to finnish the last part on Sunday.
"Sergey" == Sergey Petrunya <psergey@askmonty.org> writes:
<cut>
--- 5.3-noc/sql/sql_join_cache.cc 2011-02-17 15:42:51.000000000 +0300
Is it possible that the new start_tab also have bush_childrens or is this a case for if (prev_cache). If it's the later (conclusion we took on IRC), move the if to the first if part. To make this clear, add at end: DBUG_ASSERT(!start_tab->bush_children);
+ + tab= start_tab;
Above code is a simpler if you use start_tab in all the places and only last set tab= start_tab
Remove comment
Remove psgergey-note and do a proper comment before the goto.
an 'else' is missing here. Please spend a short time trying to create a test case that catches this bug, as it will ensure that this part of the code is properly tested...
<cut>
<cut>
Remove comment
+++ maria-5.3-subqueries-r36-noc/sql/sql_select.cc 2011-02-22 17:19:29.000000000 +0300
Add comment: /* Skip the const tables from the upper level JOIN_TAB's. We don't have to do this for SJM nests as the const tables are pulled out from these and added to the upper level JOIN_TAB. */
+ uint first_tab_offs= const_tables;
<cut>
Remove comments
This is a big change that causes a lot of code to be re-indented which will cause merge conflicts. The above also exposes a little too much how the tables are organized. Please replace loop with either: for (tab= first_linear_tab(...) ; tab ; tab= next_linear_tab(...)) Or add a set of similar functions to loop over join_tab_ranges: This also simplifies the usage of-
Did not understand the above change as const tables should still be first in join_tab[] and all tables in 'table' should have a correspoding join_tab element. The fact that we added some sjm tables, should not change this, should it? After discussion on IRC: There is no mapping between join->join_tab[] and join->table[] anymore. join->table contains all 'real tables' while join_tab also contains sjm (materialized) tables. This makes me worried that one may accidently use 'join->tables' wrongly when they mean join->top_jtrange_tables. Suggestion. Rename join->tables to 'join->real_tables' Rename join->top_jtrange_tables to 'join->join_tab_tables' or Rename join->tables to 'join->table_count' Rename join->top_jtrange_tables to 'join->join_tab_count'
Remove comment
Don't understand the above change. please revert it. (no need to have () around table_count)
Revert, as we here know that s->table == table (assignment done a couple of lines before)
Remove the above two lines as all elements in s are guaranteed to be 0
s->dependent= tables->dep_tables; s->key_dependent= 0;
You can remove the above line too
Add a comment why the following is set as we don't set it below.
Add comment: /* All tables where const tables */
+ return NULL; +}
Add comment: If one is scanning with include_bush_roots, subquent calls to next_linear_tab() will return: ot1 ot2 sjm it1 it2 it3 ot3 .... When include_bush_rooots=FALSE we get: ot1 ot2 it1 it2 it3 ot3 .... (note the lack of sjm)
Here is the above function with more comments and I moved the bush_root_tab handling into one place JOIN_TAB *next_linear_tab(JOIN* join, JOIN_TAB* tab, bool include_bush_roots) { if (include_bush_roots && tab->bush_children) { /* This JOIN_TAB is a SJM nest; Start from first table in nest */ return tab->bush_children->start; } DBUG_ASSERT(!tab->last_leaf_in_bush || tab->bush_root_tab); if (tab->bush_root_tab) /* Are we inside an SJM nest */ { /* Inside SJM nest */ if (!tab->last_leaf_in_bush) return tab+1; /* Return next in nest */ /* Continue from the sjm on the top level */ tab= tab->bush_root_tab; } /* If no more JOIN_TAB's on the top level */ if (++tab == join->join_tab + join->top_jtrange_tables) return NULL; if (!include_bush_roots && tab->bush_children) { /* This JOIN_TAB is a SJM nest; Start from first table in nest */ tab= tab->bush_children->start; } return tab; } Conclusion from IRC: <spetrunia> if the caller intends to enumerate bush roots, too, then he starts from the first join_tab (that is an sjm) <spetrunia> if the caller intends to skip bush roots, then its his responsibility to start from non-sjm <spetrunia> Hm.. is suppose we could isolate the above logic in first_linear_tab() function <spetrunia> because right now it's all over the place <montywi> yes, that is confusing me a bit <montywi> it would be good to always start with 'first linear_tab()' and then do next_linear_tab() <montywi> and first_linear tab would also have the same include_bush_roots argument <montywi> wouldn't that solve it? <spetrunia> yes Please change then the logic as above.
Good comment. More of these!
Replace with: if (tab == join->join_tab + join->top_jtrange_tables) Because this is simpler and similar to the earlier test in same function
I think it's better to have a separate function for the first case as it makes things faster and easier to understand: JOIN_TAB *start_depth_first_tab(JOIN *join) { JOIN_TAB* tab; if (join->const_tables == join->top_jtrange_tables) return NULL; tab= join->join_tab + join->const_tables; return (tab->bush_children) ? tab->bush_children->start : tab; }
Please move the code for replacing join to another function to keep get_best_combination() small. Check for out of memory condition here.
Check for out of memory condition here.
Please move this to another function to keep get_best_combination() short.
Above remove all setting of things to 0 or NULL as bzero does this for you!
Test for out of memory!
+ JOIN_TAB_RANGE *jt_range= new JOIN_TAB_RANGE;
Test for out of memory!
Remove comment
+ join->join_tab_ranges.push_back(jt_range);
Test for out of memory!
Remove setting of the above NULL's
Why set root_range->end here (and below) ?
Remove comment.
You can do this simpler by having sjm_saved_tab set to 0 at start and reset to 0 when sjm_nest_end is set. This allows you to simple do: j->bush_root_tab= sjm_saved_tab;
+ else + root_range->end= j+1;
Why set root_range->end here (and above) ? Isn't it better to set this once end of loop ? Becasue now you set it for every found sjm...
+ loop_end: + j->records_read= (ha_rows)join->best_positions[tablenr].records_read;
Why the above addition? (Was not part of the original code) Add at least a comment about this.
If you use my suggestion about simpliying setting of bush_root_tab you should set sjm_save_tab to NULL here.
Remove comment
You can do: top_jtrange_tables= table = 1; /* We have only one table and one join_tab*/
To make things safe, change earlier: !(parent->join_tab_reexec= (JOIN_TAB*) thd->alloc(sizeof(JOIN_TAB)))) to !(parent->join_tab_reexec= (JOIN_TAB*) thd->calloc(sizeof(JOIN_TAB)))) To guarantee that all elements are 0
Please see if you can fix this to use first_linear_tab() ... next_linear_tab(join, tab, FALSE)) to not get another indentation level (and simpler code)
When is that?
Why remove the above? (I can see that could be wrong as we don't know if embedding exists, and it should at least be inside the if below, but why remove it?)
Please rename 'sj_inner_tables' to sj_inner_table_map' to make the above a bit easier to read. Also, as we spoke on phone that it would be good that after the WL is done, you add some documentation also of the emb_sj_nest into the comment of opt_subselect.c. It would be good to have the whole tab->bush_children structure documented in one place and how and why one should use JOIN_TAB to connect to TABLE_LIST. (The reason is that a goal should be that after we have created JOIN_TAB we should never have to again refer to TABLE_LIST objects and to reach this goal we need to understand how things works now)
Regards, Monty

Hi! Here is the third and last part of the review.
"Sergey" == Sergey Petrunya <psergey@askmonty.org> writes:
<cut>
When is the above fix going to happen ? (Which worklog). What is the effect if we execute the continue? a) Wrong result b) Crash 3) Slower code ? Please add a comment about this if it's 3) If it's 1 or 2 we should fix it before pushing to trunk.
Wouldn't it be better to fix the 'for' loop, as otherwise you may get a crash if tab->table is not true for the tab == last_tab. How about this for a better for loop for (JOIN_TAB *tab= first_linear_tab(join, TRUE), prev_tab= 0; prev_tab != last_tab; prev_tab= tab, tab = next_linear_tab(join, tab, TRUE)) Note that we don't have to test for tab == 0 as we will always find last_tab first.
The above was a bit complex if. How about: if (((!tab->bush_root_tab && !(i <= no_jbuf_after)) || tab->loosescan_match_tab || tab->bush_children) goto no_join_cache; Don't really understand how we could use join_cache with SMJ_NEST child (ie, a tab with tab->bush_root_tab set).
Remove comments. (Please remove all these before you request a review as the deleted lines will show up nicely in the diff anyway).
Remove comment
Remove comment
Remove above ifdefed block
Isn't it more clear to set prev_tab as part of the for loop? (This will make it independent of how next_linear_tab() is implemented)
@@ -8154,12 +8441,22 @@
Remove commented code
Isn't the above loop wrong ? It was supposed to only loop over const tables, not skip const tables.
- for (i=join->const_tables ; i < join->tables ; i++) + //for (i=join->const_tables ; i < join->tables ; i++)
Remove comment
Set prev_tab in the for loop. This allows us to get automaticly the second part of the following if done:
In other words use: for (tab= first_linear_tab(join, TRUE), i= join->const_tables, prev_tab=0; tab; prev_tab= tab; tab= next_linear_tab(join, tab, TRUE))
Why do you assign and increment i? I can't see it beeing used anymore in the code.
- for (i=join->const_tables ; i < join->tables ; i++) + //for (i=join->const_tables ; i < join->tables ; i++)
Remove comment.
Remove comment.
The above needs a comment like: /* We should not set tab->next_select for the last table in the SMJ-nest, as this is already set in setup_sj_materialization(). */ (It would be nicer to have next_select set to 0 by default and here only set it if it was not set before).
Isn't the above same as (join->join_tab_ranges.head()->end -1).next_select= 0 ?
Wouldn't it be better to also here use the normal for loop for JOIN_TAB's to hide the implementation a bit ? for (tab= first_linear_tab(join, TRUE), tab; tab= next_linear_tab(join, tab, FALSE))
Don't understand the comment. If top_jtrange_tables is 0 then tab is 0 and the for loop will never execute. anyway, it would be better to use our standard loop: for (tab= first_linear_tab(join, FALSE), tab; tab= next_linear_tab(join, tab, TRUE)) instead of having yet another way to do the loop over JOIN_TAB's
Same here; Change to a basic for loop
Please update comment to be correct.
Add a comment why the above has to be saved, reset & restored. (Didn't se anything in the loop that would obviously need this)
Remove above as you are setting it below.
You should only set null_offset if the field has a null_ptr. It's strange that the old code worked without setting null_bit.... Found the issue; in ha_heap.cc we corrected a wrongly set null bit value. However we should not count in this behavior (which the above fix ensures)
Add comment: /* init_read_record resets all elements of tab->read_record(). Remember things that we don't want to have reset. */ I think it would be better to move copy_field and copy_field_end to JOIN_TAB as these don't have anything directly to do with the read_record functions.
<cut>
Please replace with a function that goes over all ranges, so that we don't get an extra indentation level here. (Part of suggestion of review 1)
Remove comment
Shouldn't you call tab->table->file->info(HA_STATUS_VARIABLE) in the last case ? If not, where is it called now?
Removed comment as it's not accurate anymore <cut>
diff -urN --exclude='.*' 5.3-noc/sql/sql_show.cc maria-5.3-subqueries-r36-noc/sql/sql_show.cc
--- 5.3-noc/sql/sql_show.cc 2011-01-27 21:39:33.000000000 +0300
+JOIN_TAB *first_linear_tab(JOIN *join, bool after_const_tables); +JOIN_TAB *next_linear_tab(JOIN* join, JOIN_TAB* tab, bool include_bush_roots);
Please add the prototypes in sql_select.h and include this instead of having prototypes to functions in many places.
Remove comment
Remove comment
Store (jt_range->end - jt_range->start) in a variable and use it above and below. <cut>
+ for (i= 0; i < (jt_range->end - jt_range->start); i++)
<cut>
Add also a comment here what this functions means (copy from table.h)
Regards, Monty
participants (2)
-
Michael Widenius
-
Sergey Petrunya