From: Date: May 28 2007 12:31am Subject: bk commit into 5.0 tree (gshchepa:1.2501) BUG#27827 List-Archive: http://lists.mysql.com/commits/27423 X-Bug: 27827 Message-Id: <20070527223158.2C30D3D4106@localhost.localdomain> Below is the list of changes that have just been committed into a local 5.0 repository of uchum. When uchum does a push these changes will be propagated to the main repository and, within 24 hours after the push, to the public repository. For information on how to access the public repository see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html ChangeSet@stripped, 2007-05-28 03:31:51+05:00, gshchepa@stripped +6 -0 Fixed bug #27827. Actually there are 2 different bugs and bugfixes: 1. ON expressions was never included into CHECK OPTION checks for UPDATE and INSERT statements. 2. CHECK OPTION expression was checked over expired record buffers (with arbitrary data in the fields). The st_table_list::prep_check_option() function has been improved to include ON expression into check_option. Also it was optimized to prevent unnecessary rebuilding of check_option expression on every EXECUTE statement. Rowids of tables used in CHECK OPTION expression was added to temporary table rows. The multi_update::do_updates() method was improved to restore necessary record buffers before view_check_option() calculation. mysql-test/r/view.result@stripped, 2007-05-28 03:31:10+05:00, gshchepa@stripped +123 -0 Updated test case for bug #27827. mysql-test/t/view.test@stripped, 2007-05-28 03:31:08+05:00, gshchepa@stripped +96 -0 Updated test case for bug #27827. sql/sql_class.h@stripped, 2007-05-28 03:28:06+05:00, gshchepa@stripped +11 -0 Fixed bug #27827. The multi_update::rowid_fields variable has been added. sql/sql_update.cc@stripped, 2007-05-28 03:30:14+05:00, gshchepa@stripped +127 -15 Fixed bug #27827. Rowids of tables used in CHECK OPTION expression was added to temporary table rows. The multi_update::do_updates() method was improved to restore necessary record buffers before view_check_option() calculation. sql/table.cc@stripped, 2007-05-28 03:30:29+05:00, gshchepa@stripped +68 -19 Fixed bug #27827. The st_table_list::prep_check_option() function has been improved to include ON expression into check_option. Also it was optimized to prevent unnecessary rebuilding of check_option expression on every EXECUTE statement. sql/table.h@stripped, 2007-05-28 03:28:50+05:00, gshchepa@stripped +2 -0 Fixed bug #27827. The st_table_list::check_option_processed variable has beed added to prevent double building of check_option expression item tree. # This is a BitKeeper patch. What follows are the unified diffs for the # set of deltas contained in the patch. The rest of the patch, the part # that BitKeeper cares about, is below these diffs. # User: gshchepa # Host: gleb.loc # Root: /home/uchum/work/bk/mysql-5.0-opt-27827-fresh --- 1.331/sql/sql_class.h 2007-05-15 14:56:04 +05:00 +++ 1.332/sql/sql_class.h 2007-05-28 03:28:06 +05:00 @@ -2283,6 +2283,17 @@ class multi_update :public select_result List *fields, *values; List **fields_for_table, **values_for_table; uint table_count; + /* + ref_keys_for_table is a list of items describing + temporary table fields used to keep rowids of tables + necessary for view_check_option() calculation. + UPDATE of VIEW can update only one table, so only + one temporary table can be created, and only one list + of rowids is required. + For regular multi-update UPDATE statements this + list is empty. + */ + List rowid_fields; Copy_field *copy_field; enum enum_duplicates handle_duplicates; bool do_update, trans_safe; --- 1.217/sql/sql_update.cc 2007-05-12 00:18:46 +05:00 +++ 1.218/sql/sql_update.cc 2007-05-28 03:30:14 +05:00 @@ -1067,7 +1067,8 @@ int multi_update::prepare(List &no /* Allocate copy fields */ max_fields=0; for (i=0 ; i < table_count ; i++) - set_if_bigger(max_fields, fields_for_table[i]->elements); + set_if_bigger(max_fields, fields_for_table[i]->elements + + sizeof(table_map) * 8); // max. number of tables copy_field= new Copy_field[max_fields]; DBUG_RETURN(thd->is_fatal_error != 0); } @@ -1140,6 +1141,44 @@ static bool safe_update_on_fly(THD *thd, /* + Create Item_field to store rowid as string. + + SYNOPSIS + create_rowid_field() + table Table to obtain rowid info. + field_name Name for the new field. + + RETURN + Newly allocated Item_field. + + DESCRIPTION + This function creates new item with Field_string to + use as rowid field prototype for temporary table + creation. +*/ + +static Item_field * +create_rowid_field(TABLE *table, const char *field_name) +{ + Field_string *field= new Field_string(table->file->ref_length, 0, + field_name, + table, &my_charset_bin); + if (!field) + return NULL; + /* + The field will be converted to varstring when creating tmp table if + table to be updated was created by mysql 4.1. Deny this. + */ + field->can_alter_field_type= 0; + Item_field *ifield= new Item_field((Field *) field); + if (!ifield) + return NULL; + ifield->maybe_null= 0; + return ifield; +} + + +/* Initialize table for multi table IMPLEMENTATION @@ -1160,6 +1199,17 @@ multi_update::initialize_tables(JOIN *jo trans_safe= transactional_tables= main_table->file->has_transactions(); table_to_update= 0; + /* + UPDATE has at least one field&value pair, OTOH only one table fields may + be updated in the updatable VIEW. + For updatable VIEW the first_table_for_update variable is a saved pointer + to the single updatable table. For regular multi-UPDATE this variable + may points to an arbitrary updatable table. + */ + DBUG_ASSERT(fields->elements); + table_map first_table_for_update= + ((Item_field *) fields->head())->field->table->map; + /* Create a temporary table for keys to all tables, except main table */ for (table_ref= update_tables; table_ref; table_ref= table_ref->next_local) { @@ -1181,6 +1231,23 @@ multi_update::initialize_tables(JOIN *jo } } + if (table->map == first_table_for_update && table_ref->check_option) + { + table_map map= table_ref->check_option->used_tables(); + map&= ~first_table_for_update; + for (TABLE_LIST *tbl_ref =leaves; + map && tbl_ref; + map&= ~tbl_ref->table->map, tbl_ref= tbl_ref->next_leaf) + { + TABLE *tbl= tbl_ref->table; + ifield= create_rowid_field(tbl, tbl->alias); + if (!ifield) + DBUG_RETURN(1); + if (rowid_fields.push_back(ifield)) + DBUG_RETURN(1); + } + } + TMP_TABLE_PARAM *tmp_param= tmp_table_param+cnt; /* @@ -1189,20 +1256,12 @@ multi_update::initialize_tables(JOIN *jo original row so that we can find and update it */ - /* ok to be on stack as this is not referenced outside of this func */ - Field_string offset(table->file->ref_length, 0, "offset", - table, &my_charset_bin); - /* - The field will be converted to varstring when creating tmp table if - table to be updated was created by mysql 4.1. Deny this. - */ - offset.can_alter_field_type= 0; - if (!(ifield= new Item_field(((Field *) &offset)))) - DBUG_RETURN(1); - ifield->maybe_null= 0; + ifield= create_rowid_field(table, "offset"); if (temp_fields.push_front(ifield)) DBUG_RETURN(1); + temp_fields.concat(&rowid_fields); + /* Make an unique key over the first field to avoid duplicated updates */ bzero((char*) &group, sizeof(group)); group.asc= 1; @@ -1350,10 +1409,31 @@ bool multi_update::send_data(List { int error; TABLE *tmp_table= tmp_tables[offset]; - fill_record(thd, tmp_table->field+1, *values_for_table[offset], 1); /* Store pointer to row */ memcpy((char*) tmp_table->field[0]->ptr, (char*) table->file->ref, table->file->ref_length); + /* Store regular updated fields to row */ + List_iterator_fast values_it(*values_for_table[offset]); + uint field_num = 1; // skip already stored rowid of updated table + for (uint elements = fields_for_table[offset]->elements; + field_num <= elements; + field_num++) + { + Item *value= values_it++; + if (value->save_in_field(tmp_table->field[field_num], 0) == -1) + DBUG_RETURN(1); + } + /* For updatable VIEW: store rowids of tables used in CHECK OPTION */ + List_iterator_fast fi(rowid_fields); + while (Item_field *field= (Item_field *) fi++) + { + TABLE *tbl= field->field->table; + tbl->file->position(tbl->record[0]); + memcpy((char*) tmp_table->field[field_num]->ptr, + (char*) tbl->file->ref, tbl->file->ref_length); + field_num++; + } + /* Write row, ignoring duplicated updates to a row */ error= tmp_table->file->write_row(tmp_table->record[0]); if (error != HA_ERR_FOUND_DUPP_KEY && error != HA_ERR_FOUND_DUPP_UNIQUE) @@ -1406,6 +1486,7 @@ int multi_update::do_updates(bool from_s int local_error; ha_rows org_updated; TABLE *table, *tmp_table; + List_iterator_fast rowid_it(rowid_fields); DBUG_ENTER("do_updates"); do_update= 0; // Don't retry this function @@ -1415,6 +1496,7 @@ int multi_update::do_updates(bool from_s { byte *ref_pos; bool can_compare_record; + uint offset= cur_table->shared; table = cur_table->table; if (table == table_to_update) @@ -1425,13 +1507,24 @@ int multi_update::do_updates(bool from_s (void) table->file->ha_rnd_init(0); table->file->extra(HA_EXTRA_NO_CACHE); + rowid_it.rewind(); + while (Item_field *item= (Item_field *) rowid_it++) + { + TABLE *tbl= item->field->table; + if (tbl->file->ha_rnd_init(1)) + goto err; + tbl->file->extra(HA_EXTRA_CACHE); + } + /* Setup copy functions to copy fields from temporary table */ - List_iterator_fast field_it(*fields_for_table[cur_table->shared]); + List_iterator_fast field_it(*fields_for_table[offset]); Field **field= tmp_table->field+1; // Skip row pointer Copy_field *copy_field_ptr= copy_field, *copy_field_end; - for ( ; *field ; field++) + for (uint i= 0, elements= fields_for_table[offset]->elements; + *field && i < elements; + field++, i++) { Item_field *item= (Item_field* ) field_it++; (copy_field_ptr++)->set(item->field, *field, 0); @@ -1468,6 +1561,18 @@ int multi_update::do_updates(bool from_s copy_field_ptr++) (*copy_field_ptr->do_copy)(copy_field_ptr); + rowid_it.rewind(); + Item_field *ifield; + for (uint fn= 1 + fields_for_table[offset]->elements; + (ifield= (Item_field *) rowid_it++); + fn++) + { + TABLE *tbl= ifield->field->table; + if((local_error= tbl->file->rnd_pos(tbl->record[0], + tmp_table->field[fn]->ptr))) + goto err; + } + if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, TRG_ACTION_BEFORE, TRUE)) @@ -1508,6 +1613,10 @@ int multi_update::do_updates(bool from_s } (void) table->file->ha_rnd_end(); (void) tmp_table->file->ha_rnd_end(); + rowid_it.rewind(); + while (Item_field *item= (Item_field *) rowid_it++) + item->field->table->file->ha_rnd_end(); + } DBUG_RETURN(0); @@ -1521,6 +1630,9 @@ err: err2: (void) table->file->ha_rnd_end(); (void) tmp_table->file->ha_rnd_end(); + rowid_it.rewind(); + while (Item_field *item= (Item_field *) rowid_it++) + item->field->table->file->ha_rnd_end(); if (updated != org_updated) { --- 1.250/sql/table.cc 2007-04-24 23:34:50 +05:00 +++ 1.251/sql/table.cc 2007-05-28 03:30:29 +05:00 @@ -1986,6 +1986,50 @@ bool st_table_list::prep_where(THD *thd, /* + Collect and AND underlying ON expression. + + SYNOPSIS + thd - thread handler + table - pointer to VIEW + cascade - collect ON expressions of underlying VIEWs or not + + DESCRIPTION + This function collects ON expression of underlying JOINs of + table `table' and returns ANDed expression. + The collect_on_exprs() function is used in the prep_check_option() + method only. + + RETURN + Pointer to the newly allocated Item. +*/ + +static Item * +collect_on_exprs(THD *thd, TABLE_LIST *table, bool cascade) +{ + DBUG_ENTER("collect_on_exprs"); + + Item *ret= NULL; + DBUG_PRINT("info", ("alias: %s", table->alias)); + if (table->on_expr) + { +#ifndef DBUG_OFF + String s; + table->on_expr->print(&s); + DBUG_PRINT("info", ("on_expr: [%s]", s.c_ptr())); +#endif + ret= table->on_expr->copy_andor_structure(thd); + } + if (table->nested_join && (cascade || !table->embedding || !table->view)) + { + List_iterator li(table->nested_join->join_list); + while (TABLE_LIST *tbl= li++) + ret= and_conds(ret, collect_on_exprs(thd, tbl, cascade)); + } + DBUG_RETURN(ret); +} + + +/* Prepare check option expression of table SYNOPSIS @@ -2001,7 +2045,7 @@ bool st_table_list::prep_where(THD *thd, VIEW_CHECK_LOCAL option. NOTE - This method build check options for every call + This method builds check options for using on every call (usual execution or every SP/PS call) This method have to be called after WHERE preparation (st_table_list::prep_where) @@ -2014,51 +2058,56 @@ bool st_table_list::prep_where(THD *thd, bool st_table_list::prep_check_option(THD *thd, uint8 check_opt_type) { DBUG_ENTER("st_table_list::prep_check_option"); + bool cascade= check_opt_type == VIEW_CHECK_CASCADED; for (TABLE_LIST *tbl= merge_underlying_list; tbl; tbl= tbl->next_local) { /* see comment of check_opt_type parameter */ - if (tbl->view && - tbl->prep_check_option(thd, - ((check_opt_type == VIEW_CHECK_CASCADED) ? - VIEW_CHECK_CASCADED : - VIEW_CHECK_NONE))) - { + if (tbl->view && tbl->prep_check_option(thd, (cascade ? + VIEW_CHECK_CASCADED : + VIEW_CHECK_NONE))) DBUG_RETURN(TRUE); - } } - if (check_opt_type) + if (check_opt_type && !check_option_processed) { - Item *item= 0; + Query_arena *arena= thd->stmt_arena, backup; + arena= thd->activate_stmt_arena_if_needed(&backup); // For easier test + if (where) { DBUG_ASSERT(where->fixed); - item= where->copy_andor_structure(thd); + check_option= where->copy_andor_structure(thd); } - if (check_opt_type == VIEW_CHECK_CASCADED) + if (cascade) { for (TABLE_LIST *tbl= merge_underlying_list; tbl; tbl= tbl->next_local) { if (tbl->check_option) - item= and_conds(item, tbl->check_option); + check_option= and_conds(check_option, tbl->check_option->copy_andor_structure(thd)); } } - if (item) - thd->change_item_tree(&check_option, item); + check_option= and_conds(check_option, collect_on_exprs(thd, this, cascade)); + + if (arena) + thd->restore_active_arena(arena, &backup); + check_option_processed= TRUE; + } if (check_option) { - const char *save_where= thd->where; - thd->where= "check option"; +#ifndef DBUG_OFF + String s; + check_option->print(&s); + DBUG_PRINT("info", ("check_option: %s", s.c_ptr())); +#endif if (!check_option->fixed && check_option->fix_fields(thd, &check_option) || check_option->check_cols(1)) { DBUG_RETURN(TRUE); } - thd->where= save_where; } DBUG_RETURN(FALSE); } @@ -2150,7 +2199,7 @@ void st_table_list::cleanup_items() check CHECK OPTION condition SYNOPSIS - check_option() + st_table_list::view_check_option() ignore_failure ignore check option fail RETURN --- 1.143/sql/table.h 2007-05-22 19:05:31 +05:00 +++ 1.144/sql/table.h 2007-05-28 03:28:50 +05:00 @@ -685,6 +685,8 @@ typedef struct st_table_list bool compact_view_format; /* Use compact format for SHOW CREATE VIEW */ /* view where processed */ bool where_processed; + /* VIEW CHECK OPTION expression precessed */ + bool check_option_processed; /* FRMTYPE_ERROR if any type is acceptable */ enum frm_type_enum required_type; char timestamp_buffer[20]; /* buffer for timestamp (19+1) */ --- 1.200/mysql-test/r/view.result 2007-05-10 00:17:20 +05:00 +++ 1.201/mysql-test/r/view.result 2007-05-28 03:31:10 +05:00 @@ -3367,4 +3367,127 @@ SHOW CREATE VIEW v1; View Create View v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select cast(1.23456789 as decimal(8,0)) AS `col` DROP VIEW v1; +CREATE TABLE t1 (a1 INT); +CREATE TABLE t2 (a2 INT); +CREATE TABLE t3 (a3 INT); +CREATE TABLE t4 (a4 INT); +INSERT INTO t1 VALUES (1),(2); +INSERT INTO t2 VALUES (1),(2); +INSERT INTO t3 VALUES (1),(2); +INSERT INTO t4 VALUES (1),(2); +CREATE VIEW v1 AS SELECT t2.a2 AS a FROM t2 +JOIN t1 ON t1.a1=t2.a2 WHERE t2.a2 < 3 WITH CHECK OPTION; +SELECT * FROM v1; +a +1 +2 +UPDATE v1 SET a=1; +ERROR HY000: CHECK OPTION failed 'test.v1' +UPDATE v1 SET a=3; +ERROR HY000: CHECK OPTION failed 'test.v1' +PREPARE t FROM 'UPDATE v1 SET a=3'; +EXECUTE t; +ERROR HY000: CHECK OPTION failed 'test.v1' +EXECUTE t; +ERROR HY000: CHECK OPTION failed 'test.v1' +INSERT INTO v1(a) VALUES (3); +ERROR HY000: CHECK OPTION failed 'test.v1' +UPDATE v1 SET a=1 WHERE a=1; +SELECT * FROM v1; +a +1 +2 +SELECT * FROM t1; +a1 +1 +2 +SELECT * FROM t2; +a2 +1 +2 +CREATE VIEW v2 AS SELECT t2.a2 AS a FROM t1 +JOIN t2 ON t1.a1=t2.a2 WHERE t2.a2 < 3 WITH CHECK OPTION; +SELECT * FROM v2; +a +1 +2 +UPDATE v2 SET a=1; +ERROR HY000: CHECK OPTION failed 'test.v2' +UPDATE v2 SET a=3; +ERROR HY000: CHECK OPTION failed 'test.v2' +PREPARE t FROM 'UPDATE v2 SET a=3'; +EXECUTE t; +ERROR HY000: CHECK OPTION failed 'test.v2' +EXECUTE t; +ERROR HY000: CHECK OPTION failed 'test.v2' +INSERT INTO v2(a) VALUES (3); +ERROR HY000: CHECK OPTION failed 'test.v2' +UPDATE v2 SET a=1 WHERE a=1; +SELECT * FROM v2; +a +1 +2 +SELECT * FROM t1; +a1 +1 +2 +SELECT * FROM t2; +a2 +1 +2 +CREATE VIEW v3 AS SELECT t4.a4 AS a +FROM (t1 JOIN t2 ON t1.a1=t2.a2) +JOIN (t3 JOIN t4 ON t3.a3=t4.a4) +ON t2.a2=t3.a3 WHERE t4.a4 < 3 WITH CHECK OPTION; +SELECT * FROM v3; +a +1 +2 +UPDATE v3 SET a=1; +ERROR HY000: CHECK OPTION failed 'test.v3' +UPDATE v3 SET a=3; +ERROR HY000: CHECK OPTION failed 'test.v3' +PREPARE t FROM 'UPDATE v3 SET a=3'; +EXECUTE t; +ERROR HY000: CHECK OPTION failed 'test.v3' +EXECUTE t; +ERROR HY000: CHECK OPTION failed 'test.v3' +INSERT INTO v3(a) VALUES (3); +ERROR HY000: CHECK OPTION failed 'test.v3' +UPDATE v3 SET a=1 WHERE a=1; +SELECT * FROM v2; +a +1 +2 +SELECT * FROM t1; +a1 +1 +2 +SELECT * FROM t2; +a2 +1 +2 +SELECT * FROM t3; +a3 +1 +2 +DROP VIEW v1,v2,v3; +DROP TABLE t1,t2,t3; +CREATE TABLE t1 (a INT); +CREATE TABLE t2 (b INT, c INT DEFAULT 0); +INSERT INTO t1 (a) VALUES (1), (2); +INSERT INTO t2 (b) VALUES (1), (2); +CREATE VIEW v1 AS SELECT t2.b,t2.c FROM t1, t2 +WHERE t1.a=t2.b AND t2.b < 3 WITH CHECK OPTION; +SELECT * FROM v1; +b c +1 0 +2 0 +UPDATE v1 SET c=1 WHERE b=1; +SELECT * FROM v1; +b c +1 1 +2 0 +DROP VIEW v1; +DROP TABLE t1,t2; End of 5.0 tests. --- 1.183/mysql-test/t/view.test 2007-05-10 00:17:20 +05:00 +++ 1.184/mysql-test/t/view.test 2007-05-28 03:31:08 +05:00 @@ -3233,4 +3233,100 @@ CREATE VIEW v1 AS SELECT CAST(1.23456789 SHOW CREATE VIEW v1; DROP VIEW v1; +# +# Bug #27827: CHECK OPTION ignores ON expressions during UPDATES and +# inserts, CHECK OPTION expression calculation over expired field +# data buffers. +# + +# Test case for ON condition: +CREATE TABLE t1 (a1 INT); +CREATE TABLE t2 (a2 INT); +CREATE TABLE t3 (a3 INT); +CREATE TABLE t4 (a4 INT); +INSERT INTO t1 VALUES (1),(2); +INSERT INTO t2 VALUES (1),(2); +INSERT INTO t3 VALUES (1),(2); +INSERT INTO t4 VALUES (1),(2); + +# UPDATE without using of temporary tables: +CREATE VIEW v1 AS SELECT t2.a2 AS a FROM t2 + JOIN t1 ON t1.a1=t2.a2 WHERE t2.a2 < 3 WITH CHECK OPTION; +SELECT * FROM v1; +--error 1369 +UPDATE v1 SET a=1; +--error 1369 +UPDATE v1 SET a=3; +PREPARE t FROM 'UPDATE v1 SET a=3'; +--error 1369 +EXECUTE t; +--error 1369 +EXECUTE t; +--error 1369 +INSERT INTO v1(a) VALUES (3); +UPDATE v1 SET a=1 WHERE a=1; +SELECT * FROM v1; +SELECT * FROM t1; +SELECT * FROM t2; + +# UPDATE via temporary table: +CREATE VIEW v2 AS SELECT t2.a2 AS a FROM t1 + JOIN t2 ON t1.a1=t2.a2 WHERE t2.a2 < 3 WITH CHECK OPTION; +SELECT * FROM v2; +--error 1369 +UPDATE v2 SET a=1; +--error 1369 +UPDATE v2 SET a=3; +PREPARE t FROM 'UPDATE v2 SET a=3'; +--error 1369 +EXECUTE t; +--error 1369 +EXECUTE t; +--error 1369 +INSERT INTO v2(a) VALUES (3); +UPDATE v2 SET a=1 WHERE a=1; +SELECT * FROM v2; +SELECT * FROM t1; +SELECT * FROM t2; + +# Nesting joins: +CREATE VIEW v3 AS SELECT t4.a4 AS a + FROM (t1 JOIN t2 ON t1.a1=t2.a2) + JOIN (t3 JOIN t4 ON t3.a3=t4.a4) + ON t2.a2=t3.a3 WHERE t4.a4 < 3 WITH CHECK OPTION; +SELECT * FROM v3; +--error 1369 +UPDATE v3 SET a=1; +--error 1369 +UPDATE v3 SET a=3; +PREPARE t FROM 'UPDATE v3 SET a=3'; +--error 1369 +EXECUTE t; +--error 1369 +EXECUTE t; +--error 1369 +INSERT INTO v3(a) VALUES (3); +UPDATE v3 SET a=1 WHERE a=1; + +SELECT * FROM v2; +SELECT * FROM t1; +SELECT * FROM t2; +SELECT * FROM t3; + +DROP VIEW v1,v2,v3; +DROP TABLE t1,t2,t3; + +# Test case for calculation of CHECK OPTION expr. over expired buffers: +CREATE TABLE t1 (a INT); +CREATE TABLE t2 (b INT, c INT DEFAULT 0); +INSERT INTO t1 (a) VALUES (1), (2); +INSERT INTO t2 (b) VALUES (1), (2); +CREATE VIEW v1 AS SELECT t2.b,t2.c FROM t1, t2 + WHERE t1.a=t2.b AND t2.b < 3 WITH CHECK OPTION; +SELECT * FROM v1; +UPDATE v1 SET c=1 WHERE b=1; +SELECT * FROM v1; +DROP VIEW v1; +DROP TABLE t1,t2; + --echo End of 5.0 tests.