1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-29 05:21:33 +03:00

table.h, sql_select.h:

Added the code processing on expressions for applying
  multiple equalities.
sql_select.cc:
  Post-merge fixes for Item_equal patch.
  Added the code processing on expressions for applying
  multiple equalities.
Many files:
  Post-merge fixes for Item_equal patch.
item_cmpfunc.cc:
  Post-merge fixes for Item_equal patch.
  Fixed a problem when an equality field=const cannot be applied to
  the predicate P(field,c) for constant propagation as a conversion
  of field is needed.
item.h, item.cc:
  Fixed a problem when an equality field=const cannot be applied to
  the predicate P(field,c) for constant propagation as a conversion
  of field is needed.


mysql-test/r/func_test.result:
  Post-merge fixes for Item_equal patch.
mysql-test/r/index_merge.result:
  Post-merge fixes for Item_equal patch.
mysql-test/r/join_nested.result:
  Post-merge fixes for Item_equal patch.
mysql-test/r/range.result:
  Post-merge fixes for Item_equal patch.
sql/item.cc:
  Fixed a problem when an equality field=const cannot be applied to
  the predicate P(field,c) for constant propagation as a conversion
  of field is needed.
sql/item.h:
  Fixed a problem when an equality field=const cannot be applied to
  the predicate P(field,c) for constant propagation as a conversion
  of field is needed.
sql/item_cmpfunc.cc:
  Post-merge fixes for Item_equal patch.
  Fixed a problem when an equality field=const cannot be applied to
  the predicate P(field,c) for constant propagation as a conversion
  of field is needed.
sql/opt_range.cc:
  Post-merge fixes for Item_equal patch.
sql/sql_select.cc:
  Post-merge fixes for Item_equal patch.
  Added the code processing on expressions for applying
  multiple equalities.
sql/sql_select.h:
  Added the code processing on expressions for applying
  multiple equalities.
sql/table.h:
  Added the code processing on expressions for applying
  multiple equalities.
This commit is contained in:
unknown
2004-10-19 14:12:55 -07:00
parent 881534fb80
commit 05933f13f7
12 changed files with 416 additions and 160 deletions

View File

@ -91,8 +91,10 @@ static int return_zero_rows(JOIN *join, select_result *res,TABLE_LIST *tables,
uint select_options, const char *info,
Item *having, Procedure *proc,
SELECT_LEX_UNIT *unit);
static COND *build_all_equal_items(COND *cond,
COND_EQUAL *inherited);
static COND *build_equal_items(COND *cond,
COND_EQUAL *inherited,
List<TABLE_LIST> *join_list,
COND_EQUAL **cond_equal_ref);
static COND* substitute_for_best_equal_field(COND *cond,
COND_EQUAL *cond_equal,
void *table_join_idx);
@ -530,17 +532,29 @@ JOIN::optimize()
}
}
#endif
/* Eliminate NOT operators */
conds= eliminate_not_funcs(conds);
DBUG_EXECUTE("where", print_where(conds, "after negation elimination"););
SELECT_LEX *sel= thd->lex->current_select;
if (sel->first_cond_optimization)
{
TABLE_LIST *tables;
for (tables= tables_list; tables; tables= tables->next)
{
if (tables->on_expr)
tables->on_expr= eliminate_not_funcs(tables->on_expr);
}
/*
The following code will allocate the new items in a permanent
MEMROOT for prepared statements and stored procedures.
*/
Item_arena *arena= thd->current_arena, backup;
if (arena->is_conventional())
arena= 0; // For easier test
else
thd->set_n_backup_item_arena(arena, &backup);
sel->first_cond_optimization= 0;
/* Convert all outer joins to inner joins if possible */
conds= simplify_joins(this, join_list, conds, TRUE);
sel->prep_where= conds ? conds->copy_andor_structure(thd) : 0;
if (arena)
thd->restore_backup_item_arena(arena, &backup);
}
/*
@ -550,32 +564,8 @@ JOIN::optimize()
that occurs in a function set a pointer to the multiple equality
predicate. Substitute a constant instead of this field if the
multiple equality contains a constant.
*/
if (conds)
{
conds= build_all_equal_items(conds, NULL);
conds->update_used_tables();
if (conds->type() == Item::COND_ITEM &&
((Item_cond*) conds)->functype() == Item_func::COND_AND_FUNC)
cond_equal= &((Item_cond_and*) conds)->cond_equal;
else if (conds->type() == Item::FUNC_ITEM &&
((Item_cond*) conds)->functype() == Item_func::MULT_EQUAL_FUNC)
{
cond_equal= new COND_EQUAL;
cond_equal->current_level.push_back((Item_equal *) conds);
}
}
{
TABLE_LIST *tables;
for (tables= tables_list; tables; tables= tables->next)
{
if (tables->on_expr)
{
tables->on_expr= build_all_equal_items(tables->on_expr, cond_equal);
tables->on_expr->update_used_tables();
}
}
}
*/
conds= build_equal_items(conds, NULL, join_list, &cond_equal);
conds= optimize_cond(this, conds,&cond_value);
if (thd->net.report_error)
@ -669,31 +659,6 @@ JOIN::optimize()
if (const_tables && !thd->locked_tables &&
!(select_options & SELECT_NO_UNLOCK))
mysql_unlock_some_tables(thd, table, const_tables);
/*
Among the equal fields belonging to the same multiple equality
choose the one that is to be retrieved first and substitute
all references to these in where condition for a reference for
the selected field.
*/
if (conds)
{
conds= substitute_for_best_equal_field(conds, cond_equal, map2table);
conds->update_used_tables();
}
{
TABLE_LIST *tables;
for (tables= tables_list; tables; tables= tables->next)
{
if (tables->on_expr)
{
tables->on_expr= substitute_for_best_equal_field(tables->on_expr,
cond_equal,
map2table);
tables->on_expr->update_used_tables();
map2table[tables->table->tablenr]->on_expr= tables->on_expr;
}
}
}
if (!conds && outer_join)
{
/* Handle the case where we have an OUTER JOIN without a WHERE */
@ -710,6 +675,32 @@ JOIN::optimize()
make_outerjoin_info(this);
/*
Among the equal fields belonging to the same multiple equality
choose the one that is to be retrieved first and substitute
all references to these in where condition for a reference for
the selected field.
*/
if (conds)
{
conds= substitute_for_best_equal_field(conds, cond_equal, map2table);
conds->update_used_tables();
}
/*
Permorm the the optimization on fields evaluation mentioned above
for all on expressions.
*/
for (JOIN_TAB *tab= join_tab + const_tables; tab < join_tab + tables ; tab++)
{
if (*tab->on_expr_ref)
{
*tab->on_expr_ref= substitute_for_best_equal_field(*tab->on_expr_ref,
tab->cond_equal,
map2table);
(*tab->on_expr_ref)->update_used_tables();
}
}
if (make_join_select(this, select, conds))
{
zero_result_cause=
@ -1545,8 +1536,6 @@ JOIN::exec()
/*
table->keyuse is set in the case there was an original WHERE clause
on the table that was optimized away.
table->on_expr tells us that it was a LEFT JOIN and there will be
at least one row generated from the table.
*/
if (curr_table->select_cond ||
(curr_table->keyuse && !curr_table->first_inner))
@ -1646,6 +1635,7 @@ JOIN::cleanup()
tmp_table_param.copy_field=0;
DBUG_RETURN(tmp_join->cleanup());
}
cond_equal= 0;
lock=0; // It's faster to unlock later
join_free(1);
@ -1794,7 +1784,7 @@ Cursor::fetch(ulong num_rows)
{
THD *thd= join->thd;
JOIN_TAB *join_tab= join->join_tab + join->const_tables;;
COND *on_expr= join_tab->on_expr;
COND *on_expr= *join_tab->on_expr_ref;
COND *select_cond= join_tab->select_cond;
READ_RECORD *info= &join_tab->read_record;
@ -2156,7 +2146,8 @@ make_join_statistics(JOIN *join,TABLE_LIST *tables,COND *conds,
s->dependent= tables->dep_tables;
s->key_dependent= 0;
if ((s->on_expr=tables->on_expr))
s->on_expr_ref= &tables->on_expr;
if (*s->on_expr_ref)
{
/* s is the only inner table of an outer join */
if (!table->file->records)
@ -2378,7 +2369,7 @@ make_join_statistics(JOIN *join,TABLE_LIST *tables,COND *conds,
SQL_SELECT *select;
select= make_select(s->table, found_const_table_map,
found_const_table_map,
s->on_expr ? s->on_expr : conds,
*s->on_expr_ref ? *s->on_expr_ref : conds,
&error);
records= get_quick_record_count(join->thd, select, s->table,
&s->const_keys, join->row_limit);
@ -2396,7 +2387,7 @@ make_join_statistics(JOIN *join,TABLE_LIST *tables,COND *conds,
join->const_table_map|= s->table->map;
set_position(join,const_count++,s,(KEYUSE*) 0);
s->type= JT_CONST;
if (s->on_expr)
if (*s->on_expr_ref)
{
/* Generate empty row */
s->info= "Impossible ON condition";
@ -2774,16 +2765,26 @@ add_key_fields(JOIN_TAB *stat,KEY_FIELD **key_fields,uint *and_level,
case Item_func::OPTIMIZE_NONE:
break;
case Item_func::OPTIMIZE_KEY:
// BETWEEN, IN, NOT
{
// BETWEEN, IN, NE
if (cond_func->key_item()->real_item()->type() == Item::FIELD_ITEM &&
!(cond_func->used_tables() & OUTER_REF_TABLE_BIT))
add_key_field(key_fields,*and_level,cond_func,
((Item_field*)(cond_func->key_item()->real_item()))->field,
cond_func->argument_count() == 2 &&
cond_func->functype() == Item_func::IN_FUNC,
cond_func->arguments()+1, cond_func->argument_count()-1,
usable_tables);
{
Item **values= cond_func->arguments()+1;
if (cond_func->functype() == Item_func::NE_FUNC &&
cond_func->arguments()[1]->real_item()->type() == Item::FIELD_ITEM &&
!(cond_func->arguments()[0]->used_tables() & OUTER_REF_TABLE_BIT))
values--;
add_key_equal_fields(key_fields, *and_level, cond_func,
(Item_field*) (cond_func->key_item()->real_item()),
cond_func->argument_count() == 2 &&
cond_func->functype() == Item_func::IN_FUNC,
values,
cond_func->argument_count()-1,
usable_tables);
}
break;
}
case Item_func::OPTIMIZE_OP:
{
bool equal_func=(cond_func->functype() == Item_func::EQ_FUNC ||
@ -3048,9 +3049,9 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab,
for inner tables in outer joins these keys will be taken
into account as well.
*/
if (join_tab[i].on_expr)
if (*join_tab[i].on_expr_ref)
{
add_key_fields(join_tab,&end,&and_level,join_tab[i].on_expr,
add_key_fields(join_tab,&end,&and_level,*join_tab[i].on_expr_ref,
join_tab[i].table->map);
}
else
@ -4654,7 +4655,7 @@ get_best_combination(JOIN *join)
form=join->table[tablenr]=j->table;
used_tables|= form->map;
form->reginfo.join_tab=j;
if (!j->on_expr)
if (!*j->on_expr_ref)
form->reginfo.not_exists_optimize=0; // Only with LEFT JOIN
if (j->type == JT_CONST)
continue; // Handled in make_join_stat..
@ -4909,7 +4910,7 @@ make_simple_join(JOIN *join,TABLE *tmp_table)
join_tab->type= JT_ALL; /* Map through all records */
join_tab->keys.init(~0); /* test everything in quick */
join_tab->info=0;
join_tab->on_expr=0;
join_tab->on_expr_ref=0;
join_tab->last_inner= 0;
join_tab->first_unmatched= 0;
join_tab->ref.key = -1;
@ -4978,7 +4979,7 @@ add_found_match_trig_cond(JOIN_TAB *tab, COND *cond, JOIN_TAB *root_tab)
first inner table of the embedding outer join operation, if there is any,
through the field t0->first_upper.
The on expression for the outer join operation is attached to the
corresponding first inner table through the field t0->on_expr.
corresponding first inner table through the field t0->on_expr_ref.
Here ti are structures of the JOIN_TAB type.
EXAMPLE
@ -4992,8 +4993,8 @@ add_found_match_trig_cond(JOIN_TAB *tab, COND *cond, JOIN_TAB *root_tab)
is selected, the following references will be set;
t4->last_inner=[t4], t4->first_inner=[t4], t4->first_upper=[t2]
t2->last_inner=[t4], t2->first_inner=t3->first_inner=[t2],
on expression (t1.a=t2.a AND t1.b=t3.b) will be attached to t2->on_expr,
while t3.a=t4.a will be attached to t4->on_expr.
on expression (t1.a=t2.a AND t1.b=t3.b) will be attached to
*t2->on_expr_ref, while t3.a=t4.a will be attached to *t4->on_expr_ref.
NOTES
The function assumes that the simplification procedure has been
@ -5020,7 +5021,8 @@ make_outerjoin_info(JOIN *join)
is in the query above.)
*/
tab->last_inner= tab->first_inner= tab;
tab->on_expr= tbl->on_expr;
tab->on_expr_ref= &tbl->on_expr;
tab->cond_equal= tbl->cond_equal;
if (embedding)
tab->first_upper= embedding->nested_join->first_nested;
}
@ -5034,7 +5036,8 @@ make_outerjoin_info(JOIN *join)
Save reference to it in the nested join structure.
*/
nested_join->first_nested= tab;
tab->on_expr= embedding->on_expr;
tab->on_expr_ref= &embedding->on_expr;
tab->cond_equal= tbl->cond_equal;
if (embedding->embedding)
tab->first_upper= embedding->embedding->nested_join->first_nested;
}
@ -5069,10 +5072,10 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
for (JOIN_TAB *tab= join->join_tab+join->const_tables;
tab < join->join_tab+join->tables ; tab++)
{
if (tab->on_expr)
if (*tab->on_expr_ref)
{
JOIN_TAB *cond_tab= tab->first_inner;
COND *tmp= make_cond_for_table(tab->on_expr,
COND *tmp= make_cond_for_table(*tab->on_expr_ref,
join->const_table_map,
(table_map) 0);
if (!tmp)
@ -5210,7 +5213,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
{
/* Join with outer join condition */
COND *orig_cond=sel->cond;
sel->cond= and_conds(sel->cond, tab->on_expr);
sel->cond= and_conds(sel->cond, *tab->on_expr_ref);
if (sel->cond && !sel->cond->fixed)
sel->cond->fix_fields(join->thd, 0, &sel->cond);
if (sel->test_quick_select(join->thd, tab->keys,
@ -5225,7 +5228,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
we have to check isn't it only "impossible ON" instead
*/
sel->cond=orig_cond;
if (!tab->on_expr ||
if (!*tab->on_expr_ref ||
sel->test_quick_select(join->thd, tab->keys,
used_tables & ~ current_map,
(join->select_options &
@ -5290,7 +5293,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
Table tab is the last inner table of an outer join.
An on expression is always attached to it.
*/
COND *on_expr= first_inner_tab->on_expr;
COND *on_expr= *first_inner_tab->on_expr_ref;
table_map used_tables= join->const_table_map |
OUTER_REF_TABLE_BIT | RAND_TABLE_BIT;
@ -5804,7 +5807,7 @@ remove_const(JOIN *join,ORDER *first_order, COND *cond, bool *simple_order)
table_map not_const_tables= ~join->const_table_map;
table_map ref;
prev_ptr= &first_order;
*simple_order= join->join_tab[join->const_tables].on_expr ? 0 : 1;
*simple_order= *join->join_tab[join->const_tables].on_expr_ref ? 0 : 1;
/* NOTE: A variable of not_const_tables ^ first_table; breaks gcc 2.7 */
@ -6024,7 +6027,7 @@ finish:
general case, its own constant for each fields from the multiple
equality. But at the same time it would allow us to get rid
of constant propagation completely: it would be done by the call
to build_all_equal_items.
to build_equal_items_for_cond.
IMPLEMENTATION
The implementation does not follow exactly the above rules to
@ -6156,7 +6159,7 @@ static bool check_equality(Item *item, COND_EQUAL *cond_equal)
bool copyfl;
if (field_item->result_type() == STRING_RESULT &&
((Field_str *) field_item)->charset() !=
((Field_str *) field_item->field)->charset() !=
((Item_cond *) item)->compare_collation())
return FALSE;
@ -6192,7 +6195,7 @@ static bool check_equality(Item *item, COND_EQUAL *cond_equal)
Replace all equality predicates in a condition by multiple equality items
SYNOPSIS
build_all_equal_items()
build_equal_items_for_cond()
cond condition(expression) where to make replacement
inherited path to all inherited multiple equality items
@ -6253,7 +6256,7 @@ static bool check_equality(Item *item, COND_EQUAL *cond_equal)
pointer to the transformed condition
*/
static COND *build_all_equal_items(COND *cond,
static COND *build_equal_items_for_cond(COND *cond,
COND_EQUAL *inherited)
{
Item_equal *item_equal;
@ -6315,7 +6318,7 @@ static COND *build_all_equal_items(COND *cond,
while((item= li++))
{
Item *new_item;
if ((new_item = build_all_equal_items(item, inherited))!= item)
if ((new_item = build_equal_items_for_cond(item, inherited))!= item)
{
/* This replacement happens only for standalone equalities */
li.replace(new_item);
@ -6351,6 +6354,113 @@ static COND *build_all_equal_items(COND *cond,
(byte *) inherited);
cond->update_used_tables();
}
return cond;
}
/*
Build multiple equalities for a condition and all on expressions that
inherit these multiple equalities
SYNOPSIS
build_equal_items()
cond condition to build the multiple equalities for
inherited path to all inherited multiple equality items
join_list list of join tables to which the condition refers to
cond_equal_ref :out pointer to the structure to place built equalities in
DESCRIPTION
The function first applies the build_equal_items_for_cond function
to build all multiple equalities for condition cond utilizing equalities
referred through the parameter inherited. The extended set of
equalities is returned in the structure referred by the cond_equal_ref
parameter. After this the function calls itself recursively for
all on expressions whose direct references can be found in join_list
and who inherit directly the multiple equalities just having built.
NOTES
The on expression used in an outer join operation inherits all equalities
from the on expression of the embedding join, if there is any, or
otherwise - from the where condition.
This fact is not obvious, but presumably can be proved.
Consider the following query:
SELECT * FROM (t1,t2) LEFT JOIN (t3,t4) ON t1.a=t3.a AND t2.a=t4.a
WHERE t1.a=t2.a;
If the on expression in the query inherits =(t1.a,t2.a), then we
can build the multiple equality =(t1.a,t2.a,t3.a,t4.a) that infers
the equality t3.a=t4.a. Although the on expression
t1.a=t3.a AND t2.a=t4.a AND t3.a=t4.a is not equivalent to the one
in the query the latter can be replaced by the former: the new query
will return the same result set as the original one.
Interesting that multiple equality =(t1.a,t2.a,t3.a,t4.a) allows us
to use t1.a=t3.a AND t3.a=t4.a under the on condition:
SELECT * FROM (t1,t2) LEFT JOIN (t3,t4) ON t1.a=t3.a AND t3.a=t4.a
WHERE t1.a=t2.a
This query equivalent to:
SELECT * FROM (t1 LEFT JOIN (t3,t4) ON t1.a=t3.a AND t3.a=t4.a),t2
WHERE t1.a=t2.a
Similarly the original query can be rewritten to the query:
SELECT * FROM (t1,t2) LEFT JOIN (t3,t4) ON t2.a=t4.a AND t3.a=t4.a
WHERE t1.a=t2.a
that is equivalent to:
SELECT * FROM (t2 LEFT JOIN (t3,t4)ON t2.a=t4.a AND t3.a=t4.a), t1
WHERE t1.a=t2.a
Thus, applying equalities from the where condition we basically
can get more freedom in performing join operations.
Althogh we don't use this property now, it probably makes sense to use
it in the future.
RETURN
pointer to the transformed condition containing multiple equalities
*/
static COND *build_equal_items(COND *cond,
COND_EQUAL *inherited,
List<TABLE_LIST> *join_list,
COND_EQUAL **cond_equal_ref)
{
COND_EQUAL *cond_equal= 0;
if (cond)
{
cond= build_equal_items_for_cond(cond, inherited);
cond->update_used_tables();
if (cond->type() == Item::COND_ITEM &&
((Item_cond*) cond)->functype() == Item_func::COND_AND_FUNC)
cond_equal= &((Item_cond_and*) cond)->cond_equal;
else if (cond->type() == Item::FUNC_ITEM &&
((Item_cond*) cond)->functype() == Item_func::MULT_EQUAL_FUNC)
{
cond_equal= new COND_EQUAL;
cond_equal->current_level.push_back((Item_equal *) cond);
}
}
if (cond_equal)
{
cond_equal->upper_levels= inherited;
inherited= cond_equal;
}
*cond_equal_ref= cond_equal;
if (join_list)
{
TABLE_LIST *table;
List_iterator<TABLE_LIST> li(*join_list);
while ((table= li++))
{
if (table->on_expr)
{
List<TABLE_LIST> *join_list= table->nested_join ?
&table->nested_join->join_list : NULL;
table->on_expr= build_equal_items(table->on_expr,
inherited,
join_list,
&table->cond_equal);
}
}
}
return cond;
}
@ -6447,6 +6557,11 @@ static Item *eliminate_item_equal(COND *cond, COND_EQUAL *upper_levels,
{
List<Item> eq_list;
Item_func_eq *eq_item= 0;
if (((Item *) item_equal)->const_item() && !item_equal->val_int())
{
cond= new Item_int((char*) "FALSE",0,1);
return cond;
}
Item *item_const= item_equal->get_const();
Item_equal_iterator it(*item_equal);
Item *head;
@ -6484,6 +6599,7 @@ static Item *eliminate_item_equal(COND *cond, COND_EQUAL *upper_levels,
if (!eq_item)
return 0;
eq_item->set_cmp_func();
eq_item->quick_fix_field();
}
}
@ -6495,6 +6611,9 @@ static Item *eliminate_item_equal(COND *cond, COND_EQUAL *upper_levels,
cond= new Item_cond_and(eq_list);
else
((Item_cond *) cond)->add_at_head(&eq_list);
cond->quick_fix_field();
cond->update_used_tables();
return cond;
}
@ -6589,7 +6708,6 @@ static COND* substitute_for_best_equal_field(COND *cond,
return cond;
}
/*
change field = field to field = const for each found field = const in the
and_level
@ -6998,38 +7116,15 @@ simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top)
}
}
DBUG_RETURN(conds);
}
static COND *
optimize_cond(JOIN *join, COND *conds, Item::cond_result *cond_value)
{
THD *thd= join->thd;
SELECT_LEX *select= thd->lex->current_select;}
SELECT_LEX *select= thd->lex->current_select;
DBUG_ENTER("optimize_cond");
if (select->first_cond_optimization)
{
/*
The following code will allocate the new items in a permanent
MEMROOT for prepared statements and stored procedures.
*/
Item_arena *arena= thd->current_arena, backup;
if (arena->is_conventional())
arena= 0; // For easier test
else
thd->set_n_backup_item_arena(arena, &backup);
select->first_cond_optimization= 0;
/* Convert all outer joins to inner joins if possible */
conds= simplify_joins(join, join->join_list, conds, TRUE);
select->prep_where= conds ? conds->copy_andor_structure(thd) : 0;
if (arena)
thd->restore_backup_item_arena(arena, &backup);
}
if (!conds)
{
*cond_value= Item::COND_TRUE;
@ -7047,6 +7142,7 @@ optimize_cond(JOIN *join, COND *conds, Item::cond_result *cond_value)
DBUG_EXECUTE("where",print_where(conds,"after const change"););
conds= remove_eq_conds(thd, conds, cond_value) ;
DBUG_EXECUTE("info",print_where(conds,"after remove"););
}
DBUG_RETURN(conds);
}
@ -8871,9 +8967,9 @@ join_read_const_table(JOIN_TAB *tab, POSITION *pos)
table->file->extra(HA_EXTRA_NO_KEYREAD);
}
}
if (tab->on_expr && !table->null_row)
if (*tab->on_expr_ref && !table->null_row)
{
if ((table->null_row= test(tab->on_expr->val_int() == 0)))
if ((table->null_row= test((*tab->on_expr_ref)->val_int() == 0)))
mark_as_null_row(table);
}
if (!table->null_row)