diff options
Diffstat (limited to 'sql/sql_parse.cc')
-rw-r--r-- | sql/sql_parse.cc | 577 |
1 files changed, 319 insertions, 258 deletions
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 34a668472e2..fe2d6f8faa1 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -540,6 +540,8 @@ void init_update_queries(void) uc_update_queries[SQLCOM_DELETE_MULTI]=1; uc_update_queries[SQLCOM_DROP_INDEX]=1; uc_update_queries[SQLCOM_UPDATE_MULTI]=1; + uc_update_queries[SQLCOM_CREATE_VIEW]=1; + uc_update_queries[SQLCOM_DROP_VIEW]=1; } bool is_update_query(enum enum_sql_command command) @@ -1181,10 +1183,10 @@ int mysql_table_dump(THD* thd, char* db, char* tbl_name, int fd) db = (db && db[0]) ? db : thd->db; if (!(table_list = (TABLE_LIST*) thd->calloc(sizeof(TABLE_LIST)))) DBUG_RETURN(1); // out of memory - table_list->db = db; - table_list->real_name = table_list->alias = tbl_name; - table_list->lock_type = TL_READ_NO_INSERT; - table_list->next = 0; + table_list->db= db; + table_list->real_name= table_list->alias= tbl_name; + table_list->lock_type= TL_READ_NO_INSERT; + table_list->prev_global= &table_list; // can be removed after merge with 4.1 if (!db || check_db_name(db)) { @@ -1877,11 +1879,35 @@ mysql_execute_command(THD *thd) { int res= 0; LEX *lex= thd->lex; - TABLE_LIST *tables= (TABLE_LIST*) lex->select_lex.table_list.first; + /* first table of first SELECT_LEX */ + TABLE_LIST *first_table= (TABLE_LIST*) lex->select_lex.table_list.first; + /* list of all tables in query */ + TABLE_LIST *all_tables; + /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */ SELECT_LEX *select_lex= &lex->select_lex; + /* most outer SELECT_LEX_UNIT of query */ SELECT_LEX_UNIT *unit= &lex->unit; DBUG_ENTER("mysql_execute_command"); + /* + In many cases first table of main SELECT_LEX have special meaning => + check that it is first table in global list and relink it first in + queries_tables list if it is necessary (we need such relinking only + for queries with subqueries in select list, in this case tables of + subqueries will go to global list first) + + all_tables will differ from first_table only if most upper SELECT_LEX + do not contain tables. + + Because of above in place where should be at least one table in most + outer SELECT_LEX we have following check: + DBUG_ASSERT(first_table == all_tables); + DBUG_ASSERT(first_table == all_tables && first_table != 0); + */ + lex->first_lists_tables_same(); + /* should be assigned after making firts tables same */ + all_tables= lex->query_tables; + if (lex->sql_command != SQLCOM_CREATE_PROCEDURE && lex->sql_command != SQLCOM_CREATE_SPFUNCTION) { @@ -1895,7 +1921,7 @@ mysql_execute_command(THD *thd) that is not a SHOW command or a select that only access local variables, but for now this is probably good enough. */ - if (tables || &lex->select_lex != lex->all_selects_list) + if (all_tables || &lex->select_lex != lex->all_selects_list) mysql_reset_errors(thd); #ifdef HAVE_REPLICATION @@ -1905,7 +1931,7 @@ mysql_execute_command(THD *thd) Skip if we are in the slave thread, some table rules have been given and the table list says the query should not be replicated */ - if (all_tables_not_ok(thd,tables)) + if (all_tables_not_ok(thd, all_tables)) { /* we warn the slave SQL thread */ my_error(ER_SLAVE_IGNORED_TABLE, MYF(0)); @@ -1925,9 +1951,6 @@ mysql_execute_command(THD *thd) #endif } #endif /* !HAVE_REPLICATION */ - if (&lex->select_lex != lex->all_selects_list && - lex->unit.create_total_list(thd, lex, &tables)) - DBUG_RETURN(0); /* When option readonly is set deny operations which change tables. @@ -1953,23 +1976,24 @@ mysql_execute_command(THD *thd) } select_result *result=lex->result; - if (tables) + if (all_tables) { - res=check_table_access(thd, - lex->exchange ? SELECT_ACL | FILE_ACL : - SELECT_ACL, - tables,0); + res= check_table_access(thd, + lex->exchange ? SELECT_ACL | FILE_ACL : + SELECT_ACL, + all_tables, 0); } else - res=check_access(thd, lex->exchange ? SELECT_ACL | FILE_ACL : SELECT_ACL, - any_db,0,0,0); + res= check_access(thd, + lex->exchange ? SELECT_ACL | FILE_ACL : SELECT_ACL, + any_db, 0, 0, 0); if (res) { res=0; break; // Error message is given } - if (!(res=open_and_lock_tables(thd,tables))) + if (!(res= open_and_lock_tables(thd, all_tables))) { if (lex->describe) { @@ -2006,7 +2030,7 @@ mysql_execute_command(THD *thd) break; } } - query_cache_store_query(thd, tables); + query_cache_store_query(thd, all_tables); res=handle_select(thd, lex, result); } } @@ -2105,8 +2129,9 @@ mysql_execute_command(THD *thd) break; } case SQLCOM_DO: - if (tables && ((res= check_table_access(thd, SELECT_ACL, tables,0)) || - (res= open_and_lock_tables(thd,tables)))) + if (all_tables && + ((res= check_table_access(thd, SELECT_ACL, all_tables, 0)) || + (res= open_and_lock_tables(thd, all_tables)))) break; res= mysql_do(thd, *lex->insert_list); @@ -2188,41 +2213,45 @@ mysql_execute_command(THD *thd) case SQLCOM_BACKUP_TABLE: { - if (check_db_used(thd,tables) || - check_table_access(thd,SELECT_ACL, tables,0) || + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (check_db_used(thd, all_tables) || + check_table_access(thd, SELECT_ACL, all_tables, 0) || check_global_access(thd, FILE_ACL)) goto error; /* purecov: inspected */ thd->slow_command=TRUE; - res = mysql_backup_table(thd, tables); + res = mysql_backup_table(thd, first_table); break; } case SQLCOM_RESTORE_TABLE: { - if (check_db_used(thd,tables) || - check_table_access(thd, INSERT_ACL, tables,0) || + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (check_db_used(thd, all_tables) || + check_table_access(thd, INSERT_ACL, all_tables, 0) || check_global_access(thd, FILE_ACL)) goto error; /* purecov: inspected */ thd->slow_command=TRUE; - res = mysql_restore_table(thd, tables); + res = mysql_restore_table(thd, first_table); break; } case SQLCOM_ASSIGN_TO_KEYCACHE: { - if (check_db_used(thd, tables) || - check_access(thd, INDEX_ACL, tables->db, - &tables->grant.privilege, 0, 0)) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (check_db_used(thd, all_tables) || + check_access(thd, INDEX_ACL, first_table->db, + &first_table->grant.privilege, 0, 0)) goto error; - res= mysql_assign_to_keycache(thd, tables, &lex->name_and_length); + res= mysql_assign_to_keycache(thd, first_table, &lex->name_and_length); break; } case SQLCOM_PRELOAD_KEYS: { - if (check_db_used(thd, tables) || - check_access(thd, INDEX_ACL, tables->db, - &tables->grant.privilege, 0, 0)) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (check_db_used(thd, all_tables) || + check_access(thd, INDEX_ACL, first_table->db, + &first_table->grant.privilege, 0, 0)) goto error; - res = mysql_preload_keys(thd, tables); + res = mysql_preload_keys(thd, first_table); break; } #ifdef HAVE_REPLICATION @@ -2275,19 +2304,21 @@ mysql_execute_command(THD *thd) #ifdef HAVE_REPLICATION case SQLCOM_LOAD_MASTER_TABLE: { - if (!tables->db) - tables->db=thd->db; - if (check_access(thd,CREATE_ACL,tables->db,&tables->grant.privilege,0,0)) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (!first_table->db) + first_table->db= thd->db; + if (check_access(thd, CREATE_ACL, first_table->db, + &first_table->grant.privilege, 0, 0)) goto error; /* purecov: inspected */ if (grant_option) { /* Check that the first table has CREATE privilege */ - if (check_grant(thd, CREATE_ACL, tables, 0, 1, 0)) + if (check_grant(thd, CREATE_ACL, all_tables, 0, 1, 0)) goto error; } - if (strlen(tables->real_name) > NAME_LEN) + if (strlen(first_table->real_name) > NAME_LEN) { - net_printf(thd,ER_WRONG_TABLE_NAME, tables->real_name); + net_printf(thd, ER_WRONG_TABLE_NAME, first_table->real_name); break; } pthread_mutex_lock(&LOCK_active_mi); @@ -2295,7 +2326,7 @@ mysql_execute_command(THD *thd) fetch_master_table will send the error to the client on failure. Give error if the table already exists. */ - if (!fetch_master_table(thd, tables->db, tables->real_name, + if (!fetch_master_table(thd, first_table->db, first_table->real_name, active_mi, 0, 0)) { send_ok(thd); @@ -2307,12 +2338,13 @@ mysql_execute_command(THD *thd) case SQLCOM_CREATE_TABLE: { - /* Skip first table, which is the table we are creating */ - TABLE_LIST *create_table, *create_table_local; - tables= lex->unlink_first_table(tables, &create_table, - &create_table_local); + DBUG_ASSERT(first_table == all_tables && first_table != 0); + bool link_to_local; + // Skip first table, which is the table we are creating + TABLE_LIST *create_table= lex->unlink_first_table(&link_to_local); + TABLE_LIST *select_tables= lex->query_tables; - if ((res= create_table_precheck(thd, tables, create_table))) + if ((res= create_table_precheck(thd, select_tables, create_table))) goto unsent_create_error; #ifndef HAVE_READLINK @@ -2321,7 +2353,7 @@ mysql_execute_command(THD *thd) /* Fix names if symlinked tables */ if (append_file_to_dir(thd, &lex->create_info.data_file_name, create_table->real_name) || - append_file_to_dir(thd,&lex->create_info.index_file_name, + append_file_to_dir(thd, &lex->create_info.index_file_name, create_table->real_name)) { res=-1; @@ -2344,28 +2376,32 @@ mysql_execute_command(THD *thd) if (select_lex->item_list.elements) // With select { select_result *result; - + /* + Is table which we are changing used somewhere in other parts + of query + */ if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) && - find_real_table_in_list(tables, create_table->db, + find_real_table_in_list(select_tables, create_table->db, create_table->real_name)) { - net_printf(thd,ER_UPDATE_TABLE_USED, create_table->real_name); + net_printf(thd, ER_UPDATE_TABLE_USED, create_table->real_name); goto create_error; } - if (tables && check_table_access(thd, SELECT_ACL, tables,0)) + if (select_tables && + check_table_access(thd, SELECT_ACL, select_tables, 0)) goto create_error; // Error message is given select_lex->options|= SELECT_NO_UNLOCK; unit->set_limit(select_lex, select_lex); - if (!(res=open_and_lock_tables(thd,tables))) + if (!(res= open_and_lock_tables(thd, select_tables))) { res= -1; // If error - if ((result=new select_create(create_table->db, - create_table->real_name, - &lex->create_info, - lex->create_list, - lex->key_list, - select_lex->item_list,lex->duplicates))) + if ((result= new select_create(create_table, + &lex->create_info, + lex->create_list, + lex->key_list, + select_lex->item_list, + lex->duplicates))) { /* CREATE from SELECT give its SELECT_LEX for SELECT, @@ -2388,36 +2424,33 @@ mysql_execute_command(THD *thd) (Table_ident *)lex->name); else { - res= mysql_create_table(thd,create_table->db, - create_table->real_name, &lex->create_info, - lex->create_list, - lex->key_list,0,0,0); // do logging + res= mysql_create_table(thd, create_table->db, + create_table->real_name, &lex->create_info, + lex->create_list, + lex->key_list, 0, 0, 0); // do logging } if (!res) send_ok(thd); } - - /* put tables back for PS rexecuting */ - tables= lex->link_first_table_back(tables, create_table, - create_table_local); + lex->link_first_table_back(create_table, link_to_local); break; create_error: - res= 1; //error reported + res= 1; //error reported unsent_create_error: /* put tables back for PS rexecuting */ - tables= lex->link_first_table_back(tables, create_table, - create_table_local); + lex->link_first_table_back(create_table, link_to_local); break; } case SQLCOM_CREATE_INDEX: - if (check_one_table_access(thd, INDEX_ACL, tables)) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (check_one_table_access(thd, INDEX_ACL, all_tables)) goto error; /* purecov: inspected */ thd->slow_command=TRUE; if (end_active_trans(thd)) res= -1; else - res = mysql_create_index(thd, tables, lex->key_list); + res = mysql_create_index(thd, first_table, lex->key_list); break; #ifdef HAVE_REPLICATION @@ -2456,6 +2489,7 @@ unsent_create_error: #endif /* HAVE_REPLICATION */ case SQLCOM_ALTER_TABLE: + DBUG_ASSERT(first_table == all_tables && first_table != 0); #if defined(DONT_ALLOW_SHOW_COMMANDS) send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ goto error; @@ -2469,16 +2503,17 @@ unsent_create_error: break; } if (!select_lex->db) - select_lex->db=tables->db; - if (check_access(thd,ALTER_ACL,tables->db,&tables->grant.privilege,0,0) || + select_lex->db= first_table->db; + if (check_access(thd, ALTER_ACL, first_table->db, + &first_table->grant.privilege, 0, 0) || check_access(thd,INSERT_ACL | CREATE_ACL,select_lex->db,&priv,0,0)|| - check_merge_table_access(thd, tables->db, + check_merge_table_access(thd, first_table->db, (TABLE_LIST *) lex->create_info.merge_list.first)) goto error; /* purecov: inspected */ if (grant_option) { - if (check_grant(thd, ALTER_ACL, tables, 0, UINT_MAX, 0)) + if (check_grant(thd, ALTER_ACL, all_tables, 0, UINT_MAX, 0)) goto error; if (lex->name && !test_all_bits(priv,INSERT_ACL | CREATE_ACL)) { // Rename of table @@ -2502,7 +2537,7 @@ unsent_create_error: thd->slow_command=TRUE; res= mysql_alter_table(thd, select_lex->db, lex->name, &lex->create_info, - tables, lex->create_list, + first_table, lex->create_list, lex->key_list, select_lex->order_list.elements, (ORDER *) select_lex->order_list.first, @@ -2513,38 +2548,37 @@ unsent_create_error: #endif /*DONT_ALLOW_SHOW_COMMANDS*/ case SQLCOM_RENAME_TABLE: { + DBUG_ASSERT(first_table == all_tables && first_table != 0); TABLE_LIST *table; - if (check_db_used(thd,tables)) + if (check_db_used(thd, all_tables)) goto error; - for (table=tables ; table ; table=table->next->next) + for (table= first_table; table; table= table->next_local->next_local) { if (check_access(thd, ALTER_ACL | DROP_ACL, table->db, &table->grant.privilege,0,0) || - check_access(thd, INSERT_ACL | CREATE_ACL, table->next->db, - &table->next->grant.privilege,0,0)) + check_access(thd, INSERT_ACL | CREATE_ACL, table->next_local->db, + &table->next_local->grant.privilege, 0, 0)) goto error; if (grant_option) { - TABLE_LIST old_list,new_list; + TABLE_LIST old_list, new_list; /* we do not need initialize old_list and new_list because we will come table[0] and table->next[0] there */ - old_list=table[0]; - new_list=table->next[0]; - old_list.next=new_list.next=0; - if (check_grant(thd, ALTER_ACL, &old_list, 0, UINT_MAX, 0) || - (!test_all_bits(table->next->grant.privilege, + old_list= table[0]; + new_list= table->next_local[0]; + if (check_grant(thd, ALTER_ACL, &old_list, 0, 1, 0) || + (!test_all_bits(table->next_local->grant.privilege, INSERT_ACL | CREATE_ACL) && - check_grant(thd, INSERT_ACL | CREATE_ACL, &new_list, 0, - UINT_MAX, 0))) + check_grant(thd, INSERT_ACL | CREATE_ACL, &new_list, 0, 1, 0))) goto error; } } - query_cache_invalidate3(thd, tables, 0); + query_cache_invalidate3(thd, first_table, 0); if (end_active_trans(thd)) res= -1; - else if (mysql_rename_tables(thd,tables)) + else if (mysql_rename_tables(thd, first_table)) res= -1; break; } @@ -2563,34 +2597,37 @@ unsent_create_error: #endif #endif /* EMBEDDED_LIBRARY */ case SQLCOM_SHOW_CREATE: + DBUG_ASSERT(first_table == all_tables && first_table != 0); #ifdef DONT_ALLOW_SHOW_COMMANDS send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ goto error; #else { - if (check_db_used(thd, tables) || - check_access(thd, SELECT_ACL | EXTRA_ACL, tables->db, - &tables->grant.privilege,0,0)) + if (check_db_used(thd, all_tables) || + check_access(thd, SELECT_ACL | EXTRA_ACL, first_table->db, + &first_table->grant.privilege, 0, 0)) goto error; - res = mysqld_show_create(thd, tables); + res = mysqld_show_create(thd, first_table); break; } #endif case SQLCOM_CHECKSUM: { - if (check_db_used(thd,tables) || - check_table_access(thd, SELECT_ACL | EXTRA_ACL , tables,0)) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (check_db_used(thd, all_tables) || + check_table_access(thd, SELECT_ACL | EXTRA_ACL, all_tables, 0)) goto error; /* purecov: inspected */ - res = mysql_checksum_table(thd, tables, &lex->check_opt); + res = mysql_checksum_table(thd, first_table, &lex->check_opt); break; } case SQLCOM_REPAIR: { - if (check_db_used(thd,tables) || - check_table_access(thd,SELECT_ACL | INSERT_ACL, tables,0)) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (check_db_used(thd, all_tables) || + check_table_access(thd, SELECT_ACL | INSERT_ACL, all_tables, 0)) goto error; /* purecov: inspected */ thd->slow_command=TRUE; - res = mysql_repair_table(thd, tables, &lex->check_opt); + res= mysql_repair_table(thd, first_table, &lex->check_opt); /* ! we write after unlocking the table */ if (!res && !lex->no_write_to_binlog) { @@ -2604,20 +2641,22 @@ unsent_create_error: } case SQLCOM_CHECK: { - if (check_db_used(thd,tables) || - check_table_access(thd, SELECT_ACL | EXTRA_ACL , tables,0)) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (check_db_used(thd, all_tables) || + check_table_access(thd, SELECT_ACL | EXTRA_ACL , all_tables, 0)) goto error; /* purecov: inspected */ thd->slow_command=TRUE; - res = mysql_check_table(thd, tables, &lex->check_opt); + res = mysql_check_table(thd, first_table, &lex->check_opt); break; } case SQLCOM_ANALYZE: { - if (check_db_used(thd,tables) || - check_table_access(thd,SELECT_ACL | INSERT_ACL, tables,0)) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (check_db_used(thd, all_tables) || + check_table_access(thd, SELECT_ACL | INSERT_ACL, all_tables, 0)) goto error; /* purecov: inspected */ thd->slow_command=TRUE; - res = mysql_analyze_table(thd, tables, &lex->check_opt); + res = mysql_analyze_table(thd, first_table, &lex->check_opt); /* ! we write after unlocking the table */ if (!res && !lex->no_write_to_binlog) { @@ -2632,13 +2671,14 @@ unsent_create_error: case SQLCOM_OPTIMIZE: { - if (check_db_used(thd,tables) || - check_table_access(thd,SELECT_ACL | INSERT_ACL, tables,0)) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (check_db_used(thd, all_tables) || + check_table_access(thd, SELECT_ACL | INSERT_ACL, all_tables, 0)) goto error; /* purecov: inspected */ thd->slow_command=TRUE; res= (specialflag & (SPECIAL_SAFE_MODE | SPECIAL_NO_NEW_FUNC)) ? - mysql_recreate_table(thd, tables, 1) : - mysql_optimize_table(thd, tables, &lex->check_opt); + mysql_recreate_table(thd, first_table, 1) : + mysql_optimize_table(thd, first_table, &lex->check_opt); /* ! we write after unlocking the table */ if (!res && !lex->no_write_to_binlog) { @@ -2651,9 +2691,10 @@ unsent_create_error: break; } case SQLCOM_UPDATE: - if (update_precheck(thd, tables)) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (update_precheck(thd, all_tables)) break; - res= mysql_update(thd,tables, + res= mysql_update(thd, all_tables, select_lex->item_list, lex->value_list, select_lex->where, @@ -2666,9 +2707,10 @@ unsent_create_error: break; case SQLCOM_UPDATE_MULTI: { - if ((res= multi_update_precheck(thd, tables))) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if ((res= multi_update_precheck(thd, all_tables))) break; - res= mysql_multi_update(thd,tables, + res= mysql_multi_update(thd, all_tables, &select_lex->item_list, &lex->value_list, select_lex->where, @@ -2679,26 +2721,29 @@ unsent_create_error: case SQLCOM_REPLACE: case SQLCOM_INSERT: { - my_bool update= (lex->value_list.elements ? UPDATE_ACL : 0); - if ((res= insert_precheck(thd, tables, update))) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + my_bool update= (lex->value_list.elements ? UPDATE_ACL : 0); + if ((res= insert_precheck(thd, all_tables, update))) break; - res = mysql_insert(thd,tables,lex->field_list,lex->many_values, - select_lex->item_list, lex->value_list, - (update ? DUP_UPDATE : lex->duplicates)); + res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values, + select_lex->item_list, lex->value_list, + (update ? DUP_UPDATE : lex->duplicates)); if (thd->net.report_error) res= -1; + if (first_table->view && !first_table->contain_auto_increment) + thd->last_insert_id= 0; // do not show last insert ID if VIEW have not it break; } case SQLCOM_REPLACE_SELECT: case SQLCOM_INSERT_SELECT: { - TABLE_LIST *first_local_table= (TABLE_LIST *) select_lex->table_list.first; - if ((res= insert_select_precheck(thd, tables))) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if ((res= insert_select_precheck(thd, all_tables))) break; /* Fix lock for first table */ - if (tables->lock_type == TL_WRITE_DELAYED) - tables->lock_type= TL_WRITE; + if (first_table->lock_type == TL_WRITE_DELAYED) + first_table->lock_type= TL_WRITE; /* Don't unlock tables until command is written to binary log */ select_lex->options|= SELECT_NO_UNLOCK; @@ -2706,37 +2751,45 @@ unsent_create_error: select_result *result; unit->set_limit(select_lex, select_lex); - if (find_real_table_in_list(tables->next, tables->db, tables->real_name)) + // is table which we are changing used somewhere in other parts of query + if (find_real_table_in_list(all_tables->next_global, + first_table->db, first_table->real_name)) { /* Using same table for INSERT and SELECT */ select_lex->options |= OPTION_BUFFER_RESULT; } - - if (!(res=open_and_lock_tables(thd, tables))) + if (!(res= open_and_lock_tables(thd, all_tables))) { - if ((result=new select_insert(tables->table,&lex->field_list, - lex->duplicates))) + if ((res= mysql_insert_select_prepare(thd))) + break; + if ((result= new select_insert(first_table, first_table->table, + &lex->field_list, lex->duplicates))) /* Skip first table, which is the table we are inserting in */ - lex->select_lex.table_list.first= (byte*) first_local_table->next; + lex->select_lex.table_list.first= (byte*) first_table->next_local; /* insert/replace from SELECT give its SELECT_LEX for SELECT, and item_list belong to SELECT */ lex->select_lex.resolve_mode= SELECT_LEX::SELECT_MODE; - res=handle_select(thd,lex,result); + res= handle_select(thd, lex, result); /* revert changes for SP */ - lex->select_lex.table_list.first= (byte*) first_local_table; + lex->select_lex.table_list.first= (byte*) first_table; lex->select_lex.resolve_mode= SELECT_LEX::INSERT_MODE; if (thd->net.report_error) res= -1; } else res= -1; + + if (first_table->view && !first_table->contain_auto_increment) + thd->last_insert_id= 0; // do not show last insert ID if VIEW have not it + break; } case SQLCOM_TRUNCATE: - if (check_one_table_access(thd, DELETE_ACL, tables)) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (check_one_table_access(thd, DELETE_ACL, all_tables)) goto error; /* Don't allow this within a transaction because we want to use @@ -2747,13 +2800,15 @@ unsent_create_error: send_error(thd,ER_LOCK_OR_ACTIVE_TRANSACTION,NullS); goto error; } - res=mysql_truncate(thd,tables); + + res= mysql_truncate(thd, first_table); break; case SQLCOM_DELETE: { - if ((res= delete_precheck(thd, tables))) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if ((res= delete_precheck(thd, all_tables))) break; - res = mysql_delete(thd,tables, select_lex->where, + res = mysql_delete(thd, all_tables, select_lex->where, &select_lex->order_list, select_lex->select_limit, select_lex->options); if (thd->net.report_error) @@ -2762,13 +2817,13 @@ unsent_create_error: } case SQLCOM_DELETE_MULTI: { + DBUG_ASSERT(first_table == all_tables && first_table != 0); TABLE_LIST *aux_tables= (TABLE_LIST *)thd->lex->auxilliary_table_list.first; - TABLE_LIST *target_tbl; uint table_count; multi_delete *result; - if ((res= multi_delete_precheck(thd, tables, &table_count))) + if ((res= multi_delete_precheck(thd, all_tables, &table_count))) break; /* condition will be TRUE on SP re-excuting */ @@ -2781,33 +2836,11 @@ unsent_create_error: } thd->proc_info="init"; - if ((res=open_and_lock_tables(thd,tables))) + if ((res= open_and_lock_tables(thd, all_tables))) + break; + + if ((res= mysql_multi_delete_prepare(thd))) break; - /* Fix tables-to-be-deleted-from list to point at opened tables */ - for (target_tbl= (TABLE_LIST*) aux_tables; - target_tbl; - target_tbl= target_tbl->next) - { - target_tbl->table= target_tbl->table_list->table; - /* - Multi-delete can't be constructed over-union => we always have - single SELECT on top and have to check underlaying SELECTs of it - */ - for (SELECT_LEX_UNIT *un= lex->select_lex.first_inner_unit(); - un; - un= un->next_unit()) - { - if (un->first_select()->linkage != DERIVED_TABLE_TYPE && - un->check_updateable(target_tbl->table_list->db, - target_tbl->table_list->real_name)) - { - my_error(ER_UPDATE_TABLE_USED, MYF(0), - target_tbl->table_list->real_name); - res= -1; - break; - } - } - } if (!thd->is_fatal_error && (result= new multi_delete(thd,aux_tables, table_count))) @@ -2833,9 +2866,10 @@ unsent_create_error: } case SQLCOM_DROP_TABLE: { + DBUG_ASSERT(first_table == all_tables && first_table != 0); if (!lex->drop_temporary) { - if (check_table_access(thd,DROP_ACL,tables,0)) + if (check_table_access(thd, DROP_ACL, all_tables, 0)) goto error; /* purecov: inspected */ if (end_active_trans(thd)) { @@ -2856,16 +2890,18 @@ unsent_create_error: if (thd->slave_thread) lex->drop_if_exists= 1; } - res= mysql_rm_table(thd,tables,lex->drop_if_exists, lex->drop_temporary); + res= mysql_rm_table(thd, first_table, lex->drop_if_exists, + lex->drop_temporary); } break; case SQLCOM_DROP_INDEX: - if (check_one_table_access(thd, INDEX_ACL, tables)) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (check_one_table_access(thd, INDEX_ACL, all_tables)) goto error; /* purecov: inspected */ if (end_active_trans(thd)) res= -1; else - res = mysql_drop_index(thd, tables, &lex->alter_info); + res = mysql_drop_index(thd, first_table, &lex->alter_info); break; case SQLCOM_SHOW_DATABASES: #if defined(DONT_ALLOW_SHOW_COMMANDS) @@ -2964,40 +3000,42 @@ unsent_create_error: res= mysqld_show_collations(thd,(lex->wild ? lex->wild->ptr() : NullS)); break; case SQLCOM_SHOW_FIELDS: + DBUG_ASSERT(first_table == all_tables && first_table != 0); #ifdef DONT_ALLOW_SHOW_COMMANDS send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ goto error; #else { - char *db=tables->db; + char *db= first_table->db; remove_escape(db); // Fix escaped '_' - remove_escape(tables->real_name); + remove_escape(first_table->real_name); if (check_access(thd,SELECT_ACL | EXTRA_ACL,db, - &tables->grant.privilege, 0, 0)) + &first_table->grant.privilege, 0, 0)) goto error; /* purecov: inspected */ - if (grant_option && check_grant(thd, SELECT_ACL, tables, 2, UINT_MAX, 0)) + if (grant_option && check_grant(thd, SELECT_ACL, first_table, 2, UINT_MAX, 0)) goto error; - res= mysqld_show_fields(thd,tables, + res= mysqld_show_fields(thd, first_table, (lex->wild ? lex->wild->ptr() : NullS), lex->verbose); break; } #endif case SQLCOM_SHOW_KEYS: + DBUG_ASSERT(first_table == all_tables && first_table != 0); #ifdef DONT_ALLOW_SHOW_COMMANDS send_error(thd,ER_NOT_ALLOWED_COMMAND); /* purecov: inspected */ goto error; #else { - char *db=tables->db; + char *db= first_table->db; remove_escape(db); // Fix escaped '_' - remove_escape(tables->real_name); + remove_escape(first_table->real_name); if (check_access(thd,SELECT_ACL | EXTRA_ACL,db, - &tables->grant.privilege, 0, 0)) + &first_table->grant.privilege, 0, 0)) goto error; /* purecov: inspected */ - if (grant_option && check_grant(thd, SELECT_ACL, tables, 2, UINT_MAX, 0)) + if (grant_option && check_grant(thd, SELECT_ACL, all_tables, 2, UINT_MAX, 0)) goto error; - res= mysqld_show_keys(thd,tables); + res= mysqld_show_keys(thd, first_table); break; } #endif @@ -3007,12 +3045,13 @@ unsent_create_error: case SQLCOM_LOAD: { + DBUG_ASSERT(first_table == all_tables && first_table != 0); uint privilege= (lex->duplicates == DUP_REPLACE ? INSERT_ACL | DELETE_ACL : INSERT_ACL); if (!lex->local_file) { - if (check_access(thd,privilege | FILE_ACL,tables->db,0,0,0)) + if (check_access(thd, privilege | FILE_ACL, first_table->db, 0, 0, 0)) goto error; } else @@ -3023,19 +3062,20 @@ unsent_create_error: send_error(thd,ER_NOT_ALLOWED_COMMAND); goto error; } - if (check_one_table_access(thd, privilege, tables)) + if (check_one_table_access(thd, privilege, all_tables)) goto error; } - res=mysql_load(thd, lex->exchange, tables, lex->field_list, - lex->duplicates, (bool) lex->local_file, lex->lock_option); + res= mysql_load(thd, lex->exchange, first_table, lex->field_list, + lex->duplicates, (bool) lex->local_file, lex->lock_option); break; } case SQLCOM_SET_OPTION: { List<set_var_base> *lex_var_list= &lex->var_list; - if (tables && ((res= check_table_access(thd, SELECT_ACL, tables,0)) || - (res= open_and_lock_tables(thd,tables)))) + if (all_tables && + ((res= check_table_access(thd, SELECT_ACL, all_tables, 0)) || + (res= open_and_lock_tables(thd, all_tables)))) break; if (lex->one_shot_set && not_all_support_one_shot(lex_var_list)) { @@ -3071,17 +3111,20 @@ purposes internal to the MySQL server", MYF(0)); break; case SQLCOM_LOCK_TABLES: unlock_locked_tables(thd); - if (check_db_used(thd,tables) || end_active_trans(thd)) + if (check_db_used(thd, all_tables) || end_active_trans(thd)) goto error; - if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, tables,0)) + if (check_table_access(thd, LOCK_TABLES_ACL | SELECT_ACL, all_tables, 0)) goto error; thd->in_lock_tables=1; thd->options|= OPTION_TABLE_LOCK; - if (!(res= open_and_lock_tables(thd, tables))) + + + + if (!(res= open_and_lock_tables(thd, all_tables))) { #ifdef HAVE_QUERY_CACHE if (thd->variables.query_cache_wlock_invalidate) - query_cache.invalidate_locked_for_write(tables); + query_cache.invalidate_locked_for_write(first_table); #endif /*HAVE_QUERY_CACHE*/ thd->locked_tables=thd->lock; thd->lock=0; @@ -3259,9 +3302,10 @@ purposes internal to the MySQL server", MYF(0)); case SQLCOM_GRANT: { if (check_access(thd, lex->grant | lex->grant_tot_col | GRANT_ACL, - tables && tables->db ? tables->db : select_lex->db, - tables ? &tables->grant.privilege : 0, - tables ? 0 : 1,0)) + ((first_table && first_table->db) ? + first_table->db : select_lex->db), + first_table ? &first_table->grant.privilege : 0, + first_table ? 0 : 1, 0)) goto error; /* @@ -3300,15 +3344,15 @@ purposes internal to the MySQL server", MYF(0)); user->host.str); } } - if (tables) + if (first_table) { if (grant_option && check_grant(thd, (lex->grant | lex->grant_tot_col | GRANT_ACL), - tables, 0, UINT_MAX, 0)) + all_tables, 0, UINT_MAX, 0)) goto error; - if (!(res = mysql_table_grant(thd,tables,lex->users_list, lex->columns, - lex->grant, + if (!(res = mysql_table_grant(thd, all_tables, lex->users_list, + lex->columns, lex->grant, lex->sql_command == SQLCOM_REVOKE)) && mysql_bin_log.is_open()) { @@ -3355,14 +3399,14 @@ purposes internal to the MySQL server", MYF(0)); lex->no_write_to_binlog= 1; case SQLCOM_FLUSH: { - if (check_global_access(thd,RELOAD_ACL) || check_db_used(thd, tables)) + if (check_global_access(thd,RELOAD_ACL) || check_db_used(thd, all_tables)) goto error; /* reload_acl_and_cache() will tell us if we are allowed to write to the binlog or not. */ bool write_to_binlog; - if (reload_acl_and_cache(thd, lex->type, tables, &write_to_binlog)) + if (reload_acl_and_cache(thd, lex->type, first_table, &write_to_binlog)) send_error(thd, 0); else { @@ -3397,27 +3441,30 @@ purposes internal to the MySQL server", MYF(0)); break; #endif case SQLCOM_HA_OPEN: - if (check_db_used(thd,tables) || - check_table_access(thd,SELECT_ACL, tables,0)) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (check_db_used(thd, all_tables) || + check_table_access(thd, SELECT_ACL, all_tables, 0)) goto error; - res = mysql_ha_open(thd, tables); + res= mysql_ha_open(thd, first_table); break; case SQLCOM_HA_CLOSE: - if (check_db_used(thd,tables)) + DBUG_ASSERT(first_table == all_tables && first_table != 0); + if (check_db_used(thd, all_tables)) goto error; - res = mysql_ha_close(thd, tables); + res= mysql_ha_close(thd, first_table); break; case SQLCOM_HA_READ: + DBUG_ASSERT(first_table == all_tables && first_table != 0); /* There is no need to check for table permissions here, because if a user has no permissions to read a table, he won't be able to open it (with SQLCOM_HA_OPEN) in the first place. */ - if (check_db_used(thd,tables)) + if (check_db_used(thd, all_tables)) goto error; - res = mysql_ha_read(thd, tables, lex->ha_read_mode, lex->backup_dir, - lex->insert_list, lex->ha_rkey_mode, select_lex->where, - select_lex->select_limit, select_lex->offset_limit); + res= mysql_ha_read(thd, first_table, lex->ha_read_mode, lex->backup_dir, + lex->insert_list, lex->ha_rkey_mode, select_lex->where, + select_lex->select_limit, select_lex->offset_limit); break; case SQLCOM_BEGIN: @@ -3573,12 +3620,12 @@ purposes internal to the MySQL server", MYF(0)); LINT_INIT(smrx); /* In case the arguments are subselects... */ - if (tables && ((res= check_table_access(thd, SELECT_ACL, tables, 0)) || - (res= open_and_lock_tables(thd, tables)))) + if (all_tables && + ((res= check_table_access(thd, SELECT_ACL, all_tables, 0)) || + (res= open_and_lock_tables(thd, all_tables)))) { break; } - fix_tables_pointers(lex->all_selects_list); #ifndef EMBEDDED_LIBRARY /* @@ -3764,6 +3811,23 @@ purposes internal to the MySQL server", MYF(0)); lex->wild->ptr() : NullS)); break; } + case SQLCOM_CREATE_VIEW: + { + res= mysql_create_view(thd, thd->lex->create_view_mode); + break; + } + case SQLCOM_DROP_VIEW: + { + if (check_table_access(thd, DROP_ACL, all_tables, 0)) + goto error; + if (end_active_trans(thd)) + { + res= -1; + break; + } + res= mysql_drop_view(thd, first_table, thd->lex->drop_mode); + break; + } default: /* Impossible */ send_ok(thd); break; @@ -3836,27 +3900,28 @@ error: check_one_table_access() thd Thread handler privilege requested privelage - tables table list of command + all_tables global table list of query RETURN 0 - OK 1 - access denied, error is sent to client */ -int check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *tables) +int check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *all_tables) { - if (check_access(thd, privilege, tables->db, &tables->grant.privilege,0,0)) + if (check_access(thd, privilege, all_tables->db, + &all_tables->grant.privilege, 0, 0)) return 1; /* Show only 1 table for check_grant */ - if (grant_option && check_grant(thd, privilege, tables, 0, 1, 0)) + if (grant_option && check_grant(thd, privilege, all_tables, 0, 1, 0)) return 1; /* Check rights on tables of subselect (if exists) */ TABLE_LIST *subselects_tables; - if ((subselects_tables= tables->next)) + if ((subselects_tables= all_tables->next_global)) { - if ((check_table_access(thd, SELECT_ACL, subselects_tables,0))) + if ((check_table_access(thd, SELECT_ACL, subselects_tables, 0))) return 1; } return 0; @@ -4008,7 +4073,7 @@ check_table_access(THD *thd, ulong want_access,TABLE_LIST *tables, uint found=0; ulong found_access=0; TABLE_LIST *org_tables=tables; - for (; tables ; tables=tables->next) + for (; tables; tables= tables->next_global) { if (tables->derived || (tables->table && (int)tables->table->tmp_table)) continue; @@ -4046,7 +4111,7 @@ bool check_merge_table_access(THD *thd, char *db, { /* Check that all tables use the current database */ TABLE_LIST *tmp; - for (tmp=table_list; tmp ; tmp=tmp->next) + for (tmp= table_list; tmp; tmp= tmp->next_local) { if (!tmp->db || !tmp->db[0]) tmp->db=db; @@ -4060,7 +4125,7 @@ bool check_merge_table_access(THD *thd, char *db, static bool check_db_used(THD *thd,TABLE_LIST *tables) { - for (; tables ; tables=tables->next) + for (; tables; tables= tables->next_global) { if (!tables->db) { @@ -4154,6 +4219,7 @@ mysql_init_query(THD *thd, bool lexonly) lex->select_lex.init_query(); lex->value_list.empty(); lex->param_list.empty(); + lex->view_list.empty(); lex->unit.next= lex->unit.master= lex->unit.link_next= lex->unit.return_to=0; lex->unit.prev= lex->unit.link_prev= 0; @@ -4167,10 +4233,16 @@ mysql_init_query(THD *thd, bool lexonly) lex->select_lex.init_order(); lex->select_lex.group_list.empty(); lex->describe= 0; - lex->derived_tables= FALSE; + lex->derived_tables= 0; + lex->view_prepare_mode= FALSE; lex->lock_option= TL_READ; lex->found_colon= 0; lex->safe_to_cache_query= 1; + lex->query_tables= 0; + lex->query_tables_last= &lex->query_tables; + lex->variables_used= 0; + lex->select_lex.parent_lex= lex; + lex->empty_field_list_on_rset= 0; if (! lexonly) { thd->select_number= lex->select_lex.select_number= 1; @@ -4215,6 +4287,7 @@ mysql_new_select(LEX *lex, bool move_down) select_lex->select_number= ++lex->thd->select_number; select_lex->init_query(); select_lex->init_select(); + select_lex->parent_lex= lex; if (move_down) { /* first select_lex of subselect or derived table */ @@ -4233,6 +4306,11 @@ mysql_new_select(LEX *lex, bool move_down) } else { + if (lex->current_select->order_list.first && !lex->current_select->braces) + { + net_printf(lex->thd, ER_WRONG_USAGE, "UNION", "ORDER BY"); + return 1; + } select_lex->include_neighbour(lex->current_select); SELECT_LEX_UNIT *unit= select_lex->master_unit(); SELECT_LEX *fake= unit->fake_select_lex; @@ -4299,6 +4377,8 @@ void mysql_init_multi_delete(LEX *lex) lex->select_lex.select_limit= lex->unit.select_limit_cnt= HA_POS_ERROR; lex->select_lex.table_list.save_and_clear(&lex->auxilliary_table_list); + lex->query_tables= 0; + lex->query_tables_last= &lex->query_tables; } @@ -4931,6 +5011,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, ptr->force_index= test(table_options & TL_OPTION_FORCE_INDEX); ptr->ignore_leaves= test(table_options & TL_OPTION_IGNORE_LEAVES); ptr->derived= table->sel; + ptr->select_lex= thd->lex->current_select; ptr->cacheable_table= 1; if (use_index_arg) ptr->use_index=(List<String> *) thd->memdup((gptr) use_index_arg, @@ -4944,7 +5025,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, { for (TABLE_LIST *tables=(TABLE_LIST*) table_list.first ; tables ; - tables=tables->next) + tables=tables->next_local) { if (!my_strcasecmp(table_alias_charset, alias_str, tables->alias) && !strcmp(ptr->db, tables->db)) @@ -4954,7 +5035,10 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd, } } } - table_list.link_in_list((byte*) ptr, (byte**) &ptr->next); + table_list.link_in_list((byte*) ptr, (byte**) &ptr->next_local); + LEX *lex= thd->lex; + *(ptr->prev_global= lex->query_tables_last)= ptr; + lex->query_tables_last= &ptr->next_global; DBUG_RETURN(ptr); } @@ -5210,9 +5294,9 @@ void st_select_lex::set_lock_for_tables(thr_lock_type lock_type) DBUG_PRINT("enter", ("lock_type: %d for_update: %d", lock_type, for_update)); - for (TABLE_LIST *tables= (TABLE_LIST*) table_list.first ; - tables ; - tables=tables->next) + for (TABLE_LIST *tables= (TABLE_LIST*) table_list.first; + tables; + tables= tables->next_local) { tables->lock_type= lock_type; tables->updating= for_update; @@ -5643,7 +5727,7 @@ int mysql_drop_index(THD *thd, TABLE_LIST *table_list, ALTER_INFO *alter_info) SYNOPSIS multi_update_precheck() thd Thread handler - tables Global table list + tables Global/local table list (have to be the same) RETURN VALUE 0 OK @@ -5653,12 +5737,11 @@ int mysql_drop_index(THD *thd, TABLE_LIST *table_list, ALTER_INFO *alter_info) int multi_update_precheck(THD *thd, TABLE_LIST *tables) { - DBUG_ENTER("multi_update_precheck"); const char *msg= 0; TABLE_LIST *table; LEX *lex= thd->lex; SELECT_LEX *select_lex= &lex->select_lex; - TABLE_LIST *update_list= (TABLE_LIST*)select_lex->table_list.first; + DBUG_ENTER("multi_update_precheck"); if (select_lex->item_list.elements != lex->value_list.elements) { @@ -5669,7 +5752,7 @@ int multi_update_precheck(THD *thd, TABLE_LIST *tables) Ensure that we have UPDATE or SELECT privilege for each table The exact privilege is checked in mysql_multi_update() */ - for (table= update_list; table; table= table->next) + for (table= tables; table; table= table->next_local) { if ((check_access(thd, UPDATE_ACL, table->db, &table->grant.privilege, 0, 1) || @@ -5679,31 +5762,16 @@ int multi_update_precheck(THD *thd, TABLE_LIST *tables) grant_option && check_grant(thd, SELECT_ACL, table, 0, 1, 0))) DBUG_RETURN(1); - /* - We assign following flag only to copy of table, because it will - be checked only if query contains subqueries i.e. only if copy exists - */ - if (table->table_list) - table->table_list->table_in_update_from_clause= 1; + table->table_in_first_from_clause= 1; } /* Is there tables of subqueries? */ if (&lex->select_lex != lex->all_selects_list) { - for (table= tables; table; table= table->next) + for (table= tables; table; table= table->next_global) { - if (table->table_in_update_from_clause) - { - /* - If we check table by local TABLE_LIST copy then we should copy - grants to global table list, because it will be used for table - opening. - */ - if (table->table_list) - table->grant= table->table_list->grant; - } - else + if (!table->table_in_first_from_clause) { if (check_access(thd, SELECT_ACL, table->db, &table->grant.privilege, 0, 0) || @@ -5732,7 +5800,7 @@ int multi_update_precheck(THD *thd, TABLE_LIST *tables) SYNOPSIS multi_delete_precheck() thd Thread handler - tables Global table list + tables Global/local table list table_count Pointer to table counter RETURN VALUE @@ -5742,12 +5810,11 @@ int multi_update_precheck(THD *thd, TABLE_LIST *tables) */ int multi_delete_precheck(THD *thd, TABLE_LIST *tables, uint *table_count) { - DBUG_ENTER("multi_delete_precheck"); SELECT_LEX *select_lex= &thd->lex->select_lex; TABLE_LIST *aux_tables= (TABLE_LIST *)thd->lex->auxilliary_table_list.first; - TABLE_LIST *delete_tables= (TABLE_LIST *)select_lex->table_list.first; TABLE_LIST *target_tbl; + DBUG_ENTER("multi_delete_precheck"); *table_count= 0; @@ -5762,12 +5829,12 @@ int multi_delete_precheck(THD *thd, TABLE_LIST *tables, uint *table_count) my_error(ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE, MYF(0)); DBUG_RETURN(-1); } - for (target_tbl= aux_tables; target_tbl; target_tbl= target_tbl->next) + for (target_tbl= aux_tables; target_tbl; target_tbl= target_tbl->next_local) { (*table_count)++; /* All tables in aux_tables must be found in FROM PART */ TABLE_LIST *walk; - for (walk= delete_tables; walk; walk= walk->next) + for (walk= tables; walk; walk= walk->next_local) { if (!my_strcasecmp(table_alias_charset, target_tbl->alias, walk->alias) && @@ -5780,14 +5847,8 @@ int multi_delete_precheck(THD *thd, TABLE_LIST *tables, uint *table_count) "MULTI DELETE"); DBUG_RETURN(-1); } - if (walk->derived) - { - my_error(ER_NON_UPDATABLE_TABLE, MYF(0), target_tbl->real_name, - "DELETE"); - DBUG_RETURN(-1); - } walk->lock_type= target_tbl->lock_type; - target_tbl->table_list= walk; // Remember corresponding table + target_tbl->correspondent_table= walk; // Remember corresponding table } DBUG_RETURN(0); } |