summaryrefslogtreecommitdiff
path: root/sql/sql_union.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/sql_union.cc')
-rw-r--r--sql/sql_union.cc363
1 files changed, 309 insertions, 54 deletions
diff --git a/sql/sql_union.cc b/sql/sql_union.cc
index 71d0e331101..f644bf43e2d 100644
--- a/sql/sql_union.cc
+++ b/sql/sql_union.cc
@@ -48,15 +48,67 @@ bool mysql_union(THD *thd, LEX *lex, select_result *result,
** store records in temporary table for UNION
***************************************************************************/
-int select_union::prepare(List<Item> &list, SELECT_LEX_UNIT *u)
+int select_unit::prepare(List<Item> &list, SELECT_LEX_UNIT *u)
{
unit= u;
return 0;
}
+/**
+ This called by SELECT_LEX_UNIT::exec when select changed
+*/
-int select_union::send_data(List<Item> &values)
+void select_unit::change_select()
{
+ uint current_select_number= thd->lex->current_select->select_number;
+ DBUG_ENTER("select_unit::change_select");
+ DBUG_PRINT("enter", ("select in unit change: %u -> %u",
+ curr_sel, current_select_number));
+ DBUG_ASSERT(curr_sel != current_select_number);
+ curr_sel= current_select_number;
+ /* New SELECT processing starts */
+ DBUG_ASSERT(table->file->inited == 0);
+ switch (thd->lex->current_select->linkage)
+ {
+ case INTERSECT_TYPE:
+ intersect_mark->value= prev_step= curr_step;
+ curr_step= current_select_number;
+ case EXCEPT_TYPE:
+ step= thd->lex->current_select->linkage;
+ break;
+ default:
+ step= UNION_TYPE;
+ break;
+ }
+ DBUG_VOID_RETURN;
+}
+/**
+ Fill temporary tables for UNION/EXCEPT/INTERSECT
+
+ @Note
+UNION:
+ just add records to the table (with 'counter' field first if INTERSECT
+ present in the sequence).
+EXCEPT:
+ looks for the record in the table (with 'counter' field first if
+ INTERSECT present in the sequence) and delete it if found
+INTESECT:
+ looks for the same record with 'counter' field of previous operation,
+ put as a 'counter' number of the current SELECT.
+ We scan the table and remove all records which marked with not last
+ 'counter' after processing all records in send_eof and only if it last
+ SELECT of sequence of INTERSECTS.
+
+ @param values List of record items to process.
+
+ @retval 0 - OK
+ @retval -1 - duplicate
+ @retval 1 - error
+*/
+int select_unit::send_data(List<Item> &values)
+{
+ int rc;
+ int not_reported_error= 0;
if (unit->offset_limit_cnt)
{ // using limit offset,count
unit->offset_limit_cnt--;
@@ -66,44 +118,189 @@ int select_union::send_data(List<Item> &values)
return 0;
if (table->no_rows_with_nulls)
table->null_catch_flags= CHECK_ROW_FOR_NULLS_TO_REJECT;
- fill_record(thd, table, table->field, values, TRUE, FALSE);
+ if (intersect_mark)
+ {
+ fill_record(thd, table, table->field + 1, values, TRUE, FALSE);
+ table->field[0]->store((ulonglong) curr_step, 1);
+ }
+ else
+ fill_record(thd, table, table->field, values, TRUE, FALSE);
if (thd->is_error())
- return 1;
+ {
+ rc= 1;
+ goto end;
+ }
if (table->no_rows_with_nulls)
{
table->null_catch_flags&= ~CHECK_ROW_FOR_NULLS_TO_REJECT;
if (table->null_catch_flags)
- return 0;
+ {
+ rc= 0;
+ goto end;
+ }
}
- if ((write_err= table->file->ha_write_tmp_row(table->record[0])))
+ // select_unit::change_select() change step & Co correctly for each SELECT
+ switch (step)
{
- if (write_err == HA_ERR_FOUND_DUPP_KEY)
+ case UNION_TYPE:
{
+ if ((write_err= table->file->ha_write_tmp_row(table->record[0])))
+ {
+ if (write_err == HA_ERR_FOUND_DUPP_KEY)
+ {
+ /*
+ Inform upper level that we found a duplicate key, that should not
+ be counted as part of limit
+ */
+ rc= -1;
+ goto end;
+ }
+ bool is_duplicate= FALSE;
+ /* create_internal_tmp_table_from_heap will generate error if needed */
+ if (table->file->is_fatal_error(write_err, HA_CHECK_DUP) &&
+ create_internal_tmp_table_from_heap(thd, table,
+ tmp_table_param.start_recinfo,
+ &tmp_table_param.recinfo,
+ write_err, 1, &is_duplicate))
+ {
+ rc= 1;
+ goto end;
+ }
+
+ if (is_duplicate)
+ {
+ rc= -1;
+ goto end;
+ }
+ }
+ break;
+ }
+ case EXCEPT_TYPE:
+ {
+ int find_res;
/*
- Inform upper level that we found a duplicate key, that should not
- be counted as part of limit
+ The temporary table uses very first index or constrain for
+ checking unique constrain.
*/
- return -1;
+ if (!(find_res= table->file->find_unique_row(table->record[0], 0)))
+ {
+ DBUG_ASSERT(!table->triggers);
+ table->status|= STATUS_DELETED;
+ not_reported_error= table->file->ha_delete_tmp_row(table->record[0]);
+ rc= MY_TEST(not_reported_error);
+ goto end;
+ }
+ else
+ {
+ if ((rc= not_reported_error= (find_res != 1)))
+ goto end;
+ }
+ break;
}
- bool is_duplicate= FALSE;
- /* create_internal_tmp_table_from_heap will generate error if needed */
- if (table->file->is_fatal_error(write_err, HA_CHECK_DUP) &&
- create_internal_tmp_table_from_heap(thd, table,
- tmp_table_param.start_recinfo,
- &tmp_table_param.recinfo,
- write_err, 1, &is_duplicate))
- return 1;
- if (is_duplicate)
- return -1;
+ case INTERSECT_TYPE:
+ {
+ int find_res;
+ /*
+ The temporary table uses very first index or constrain for
+ checking unique constrain.
+ */
+ if (!(find_res= table->file->find_unique_row(table->record[0], 0)))
+ {
+ DBUG_ASSERT(!table->triggers);
+ if (table->field[0]->val_int() != prev_step)
+ {
+ rc= 0;
+ goto end;
+ }
+ store_record(table, record[1]);
+ table->field[0]->store(curr_step, 0);
+ not_reported_error= table->file->ha_update_tmp_row(table->record[1],
+ table->record[0]);
+ rc= MY_TEST(not_reported_error);
+ DBUG_ASSERT(rc != HA_ERR_RECORD_IS_THE_SAME);
+ goto end;
+ }
+ else
+ {
+ if ((rc= not_reported_error= (find_res != 1)))
+ goto end;
+ }
+ break;
+ }
+ default:
+ DBUG_ASSERT(0);
}
- return 0;
+ rc= 0;
+
+end:
+ if (not_reported_error)
+ {
+ DBUG_ASSERT(rc);
+ table->file->print_error(not_reported_error, MYF(0));
+ }
+ return rc;
+}
+
+bool select_unit::send_eof()
+{
+ if (step != INTERSECT_TYPE ||
+ (thd->lex->current_select->next_select() &&
+ thd->lex->current_select->next_select()->linkage == INTERSECT_TYPE))
+ {
+ /*
+ it is not INTESECT or next SELECT in the sequence is INTERSECT so no
+ need filtering (the last INTERSECT in this sequence of intersects will
+ filter).
+ */
+ return 0;
+ }
+
+ /*
+ It is last select in the sequence of INTERSECTs so we should filter out
+ all records except marked with actual counter.
+
+ TODO: as optimization for simple case this could be moved to
+ 'fake_select' WHERE condition
+ */
+ handler *file= table->file;
+ int error;
+
+ if (file->ha_rnd_init_with_error(1))
+ return 1;
+
+ do
+ {
+ error= file->ha_rnd_next(table->record[0]);
+ if (error)
+ {
+ if (error == HA_ERR_END_OF_FILE)
+ {
+ error= 0;
+ break;
+ }
+ if (unlikely(error == HA_ERR_RECORD_DELETED))
+ {
+ error= 0;
+ continue;
+ }
+ break;
+ }
+ if (table->field[0]->val_int() != curr_step)
+ error= file->ha_delete_tmp_row(table->record[0]);
+ } while (!error);
+ file->ha_rnd_end();
+
+ if (error)
+ table->file->print_error(error, MYF(0));
+
+ return(MY_TEST(error));
}
int select_union_recursive::send_data(List<Item> &values)
{
- int rc= select_union::send_data(values);
+ int rc= select_unit::send_data(values);
if (write_err != HA_ERR_FOUND_DUPP_KEY)
{
@@ -122,13 +319,7 @@ int select_union_recursive::send_data(List<Item> &values)
}
-bool select_union::send_eof()
-{
- return 0;
-}
-
-
-bool select_union::flush()
+bool select_unit::flush()
{
int error;
if ((error=table->file->extra(HA_EXTRA_NO_CACHE)))
@@ -139,11 +330,12 @@ bool select_union::flush()
return 0;
}
+
/*
Create a temporary table to store the result of select_union.
SYNOPSIS
- select_union::create_result_table()
+ select_unit::create_result_table()
thd thread handle
column_types a list of items used to define columns of the
temporary table
@@ -154,6 +346,7 @@ bool select_union::flush()
bit_fields_as_long convert bit fields to ulonglong
create_table whether to physically create result table
keep_row_order keep rows in order as they were inserted
+ hidden number of hidden fields (for INTERSECT)
DESCRIPTION
Create a temporary table that is used to store the result of a UNION,
@@ -165,16 +358,18 @@ bool select_union::flush()
*/
bool
-select_union::create_result_table(THD *thd_arg, List<Item> *column_types,
+select_unit::create_result_table(THD *thd_arg, List<Item> *column_types,
bool is_union_distinct, ulonglong options,
const char *alias,
bool bit_fields_as_long, bool create_table,
- bool keep_row_order)
+ bool keep_row_order,
+ uint hidden)
{
DBUG_ASSERT(table == 0);
tmp_table_param.init();
tmp_table_param.field_count= column_types->elements;
tmp_table_param.bit_fields_as_long= bit_fields_as_long;
+ tmp_table_param.hidden_field_count= hidden;
if (! (table= create_tmp_table(thd_arg, &tmp_table_param, *column_types,
(ORDER*) 0, is_union_distinct, 1,
@@ -202,12 +397,14 @@ select_union_recursive::create_result_table(THD *thd_arg,
const char *alias,
bool bit_fields_as_long,
bool create_table,
- bool keep_row_order)
+ bool keep_row_order,
+ uint hidden)
{
- if (select_union::create_result_table(thd_arg, column_types,
- is_union_distinct, options,
- "", bit_fields_as_long,
- create_table, keep_row_order))
+ if (select_unit::create_result_table(thd_arg, column_types,
+ is_union_distinct, options,
+ "", bit_fields_as_long,
+ create_table, keep_row_order,
+ hidden))
return true;
if (! (incr_table= create_tmp_table(thd_arg, &tmp_table_param, *column_types,
@@ -252,7 +449,7 @@ select_union_recursive::create_result_table(THD *thd_arg,
tables of JOIN - exec_tmp_table_[1 | 2].
*/
-void select_union::cleanup()
+void select_unit::cleanup()
{
table->file->extra(HA_EXTRA_RESET_STATE);
table->file->ha_delete_all_rows();
@@ -263,7 +460,7 @@ void select_union_recursive::cleanup()
{
if (table)
{
- select_union::cleanup();
+ select_unit::cleanup();
free_tmp_table(thd, table);
}
@@ -450,6 +647,7 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result,
bool is_rec_result_table_created= false;
select_result *tmp_result;
bool is_union_select;
+ bool have_except= FALSE, have_intersect= FALSE;
bool instantiate_tmp_table= false;
DBUG_ENTER("st_select_lex_unit::prepare");
DBUG_ASSERT(thd == thd_arg);
@@ -496,13 +694,28 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result,
thd_arg->lex->current_select= sl= first_sl;
found_rows_for_union= first_sl->options & OPTION_FOUND_ROWS;
- is_union_select= is_union() || fake_select_lex;
+ is_union_select= is_unit_op() || fake_select_lex;
+ for (SELECT_LEX *s= first_sl; s; s= s->next_select())
+ {
+ switch (s->linkage)
+ {
+ case INTERSECT_TYPE:
+ have_intersect= TRUE;
+ break;
+ case EXCEPT_TYPE:
+ have_except= TRUE;
+ break;
+ default:
+ break;
+ }
+ }
/* Global option */
if (is_union_select || is_recursive)
{
- if (is_union() && !union_needs_tmp_table())
+ if (is_unit_op() && !union_needs_tmp_table() &&
+ !have_except && !have_intersect)
{
SELECT_LEX *last= first_select();
while (last->next_select())
@@ -517,7 +730,7 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result,
else
{
if (!is_recursive)
- union_result= new (thd_arg->mem_root) select_union(thd_arg);
+ union_result= new (thd_arg->mem_root) select_unit(thd_arg);
else
{
with_element->rec_result=
@@ -539,8 +752,9 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result,
{
bool can_skip_order_by;
sl->options|= SELECT_NO_UNLOCK;
- JOIN *join= new JOIN(thd_arg, sl->item_list,
- sl->options | thd_arg->variables.option_bits | additional_options,
+ JOIN *join= new JOIN(thd_arg, sl->item_list,
+ (sl->options | thd_arg->variables.option_bits |
+ additional_options),
tmp_result);
/*
setup_tables_done_option should be set only for very first SELECT,
@@ -652,7 +866,8 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result,
MY_TEST(union_distinct),
create_options, derived->alias,
false,
- instantiate_tmp_table, false))
+ instantiate_tmp_table, false,
+ 0))
goto err;
if (!derived->table)
derived->table= derived->derived_result->table=
@@ -733,12 +948,47 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result,
if (global_parameters()->ftfunc_list->elements)
create_options= create_options | TMP_TABLE_FORCE_MYISAM;
-
- if (!is_recursive &&
- union_result->create_result_table(thd, &types, MY_TEST(union_distinct),
- create_options, "", false,
- instantiate_tmp_table, false))
- goto err;
+ if (!is_recursive)
+ {
+ uint hidden= 0;
+ if (have_intersect)
+ {
+ hidden= 1;
+ if (!intersect_mark)
+ {
+ /*
+ For intersect we add a hidden column first that contains
+ the current select number of the time when the row was
+ added to the temporary table
+ */
+
+ Query_arena *arena, backup_arena;
+ arena= thd->activate_stmt_arena_if_needed(&backup_arena);
+
+ intersect_mark= new (thd_arg->mem_root) Item_int(thd, 0);
+
+ if (arena)
+ thd->restore_active_arena(arena, &backup_arena);
+
+ if (!intersect_mark)
+ goto err;
+ }
+ else
+ intersect_mark->value= 0; //reset
+ types.push_front(union_result->intersect_mark= intersect_mark);
+ union_result->intersect_mark->name= (char *)"___";
+ }
+ bool error=
+ union_result->create_result_table(thd, &types,
+ MY_TEST(union_distinct),
+ create_options, "", false,
+ instantiate_tmp_table, false,
+ hidden);
+ if (intersect_mark)
+ types.pop();
+ if (error)
+ goto err;
+ }
if (fake_select_lex && !fake_select_lex->first_cond_optimization)
{
save_tablenr= result_table_list.tablenr_exec;
@@ -764,6 +1014,9 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result,
arena= thd->activate_stmt_arena_if_needed(&backup_arena);
saved_error= table->fill_item_list(&item_list);
+ // Item_list is inherited from 'types', so there could be the counter
+ if (intersect_mark)
+ item_list.pop(); // remove intersect counter
if (arena)
thd->restore_active_arena(arena, &backup_arena);
@@ -815,7 +1068,7 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result,
We're in execution of a prepared statement or stored procedure:
reset field items to point at fields from the created temporary table.
*/
- table->reset_item_list(&item_list);
+ table->reset_item_list(&item_list, intersect_mark ? 1 : 0);
}
}
@@ -951,6 +1204,8 @@ bool st_select_lex_unit::exec()
{
ha_rows records_at_start= 0;
thd->lex->current_select= sl;
+ if (union_result)
+ union_result->change_select();
if (fake_select_lex)
{
if (sl != &thd->lex->select_lex)
@@ -1339,7 +1594,7 @@ void st_select_lex_unit::reinit_exec_mechanism()
prepared= optimized= executed= 0;
optimize_started= 0;
#ifndef DBUG_OFF
- if (is_union())
+ if (is_unit_op())
{
List_iterator_fast<Item> it(item_list);
Item *field;
@@ -1421,7 +1676,7 @@ List<Item> *st_select_lex_unit::get_column_types(bool for_cursor)
}
- if (is_union())
+ if (is_unit_op())
{
DBUG_ASSERT(prepared);
/* Types are generated during prepare */