mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-28 17:15:19 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			2072 lines
		
	
	
		
			62 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2072 lines
		
	
	
		
			62 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* Copyright (c) 2004, 2011, Oracle and/or its affiliates. All rights reserved.
 | |
| 
 | |
|    This program is free software; you can redistribute it and/or modify
 | |
|    it under the terms of the GNU General Public License as published by
 | |
|    the Free Software Foundation; version 2 of the License.
 | |
| 
 | |
|    This program is distributed in the hope that it will be useful,
 | |
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|    GNU General Public License for more details.
 | |
| 
 | |
|    You should have received a copy of the GNU General Public License
 | |
|    along with this program; if not, write to the Free Software
 | |
|    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | |
| */
 | |
| 
 | |
| #define MYSQL_LEX 1
 | |
| #include "mysql_priv.h"
 | |
| #include "sql_select.h"
 | |
| #include "parse_file.h"
 | |
| #include "sp.h"
 | |
| #include "sp_head.h"
 | |
| #include "sp_cache.h"
 | |
| 
 | |
| #define MD5_BUFF_LENGTH 33
 | |
| 
 | |
| const LEX_STRING view_type= { C_STRING_WITH_LEN("VIEW") };
 | |
| 
 | |
| static int mysql_register_view(THD *thd, TABLE_LIST *view,
 | |
| 			       enum_view_create_mode mode);
 | |
| 
 | |
| const char *updatable_views_with_limit_names[]= { "NO", "YES", NullS };
 | |
| TYPELIB updatable_views_with_limit_typelib=
 | |
| {
 | |
|   array_elements(updatable_views_with_limit_names)-1, "",
 | |
|   updatable_views_with_limit_names,
 | |
|   0
 | |
| };
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Make a unique name for an anonymous view column
 | |
|   SYNOPSIS
 | |
|     target        reference to the item for which a new name has to be made
 | |
|     item_list     list of items within which we should check uniqueness of
 | |
|                   the created name
 | |
|     last_element  the last element of the list above
 | |
| 
 | |
|   NOTE
 | |
|     Unique names are generated by adding 'My_exp_' to the old name of the
 | |
|     column. In case the name that was created this way already exists, we
 | |
|     add a numeric postfix to its end (i.e. "1") and increase the number
 | |
|     until the name becomes unique. If the generated name is longer than
 | |
|     NAME_LEN, it is truncated.
 | |
| */
 | |
| 
 | |
| static void make_unique_view_field_name(Item *target,
 | |
|                                         List<Item> &item_list,
 | |
|                                         Item *last_element)
 | |
| {
 | |
|   char *name= (target->orig_name ?
 | |
|                target->orig_name :
 | |
|                target->name);
 | |
|   size_t name_len;
 | |
|   uint attempt;
 | |
|   char buff[NAME_LEN+1];
 | |
|   List_iterator_fast<Item> itc(item_list);
 | |
| 
 | |
|   for (attempt= 0;; attempt++)
 | |
|   {
 | |
|     Item *check;
 | |
|     bool ok= TRUE;
 | |
| 
 | |
|     if (attempt)
 | |
|       name_len= my_snprintf(buff, NAME_LEN, "My_exp_%d_%s", attempt, name);
 | |
|     else
 | |
|       name_len= my_snprintf(buff, NAME_LEN, "My_exp_%s", name);
 | |
| 
 | |
|     do
 | |
|     {
 | |
|       check= itc++;
 | |
|       if (check != target &&
 | |
|           my_strcasecmp(system_charset_info, buff, check->name) == 0)
 | |
|       {
 | |
|         ok= FALSE;
 | |
|         break;
 | |
|       }
 | |
|     } while (check != last_element);
 | |
|     if (ok)
 | |
|       break;
 | |
|     itc.rewind();
 | |
|   }
 | |
| 
 | |
|   target->orig_name= target->name;
 | |
|   target->set_name(buff, name_len, system_charset_info);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Check if items with same names are present in list and possibly
 | |
|   generate unique names for them.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     item_list             list of Items which should be checked for duplicates
 | |
|     gen_unique_view_name  flag: generate unique name or return with error when
 | |
|                           duplicate names are found.
 | |
| 
 | |
|   DESCRIPTION
 | |
|     This function is used on view creation and preparation of derived tables.
 | |
|     It checks item_list for items with duplicate names. If it founds two
 | |
|     items with same name and conversion to unique names isn't allowed, or
 | |
|     names for both items are set by user - function fails.
 | |
|     Otherwise it generates unique name for one item with autogenerated name
 | |
|     using make_unique_view_field_name()
 | |
| 
 | |
|   RETURN VALUE
 | |
|     FALSE no duplicate names found, or they are converted to unique ones
 | |
|     TRUE  duplicate names are found and they can't be converted or conversion
 | |
|           isn't allowed
 | |
| */
 | |
| 
 | |
| bool check_duplicate_names(List<Item> &item_list, bool gen_unique_view_name)
 | |
| {
 | |
|   Item *item;
 | |
|   List_iterator_fast<Item> it(item_list);
 | |
|   List_iterator_fast<Item> itc(item_list);
 | |
|   DBUG_ENTER("check_duplicate_names");
 | |
| 
 | |
|   while ((item= it++))
 | |
|   {
 | |
|     Item *check;
 | |
|     /* treat underlying fields like set by user names */
 | |
|     if (item->real_item()->type() == Item::FIELD_ITEM)
 | |
|       item->is_autogenerated_name= FALSE;
 | |
|     itc.rewind();
 | |
|     while ((check= itc++) && check != item)
 | |
|     {
 | |
|       if (my_strcasecmp(system_charset_info, item->name, check->name) == 0)
 | |
|       {
 | |
|         if (!gen_unique_view_name)
 | |
|           goto err;
 | |
|         if (item->is_autogenerated_name)
 | |
|           make_unique_view_field_name(item, item_list, item);
 | |
|         else if (check->is_autogenerated_name)
 | |
|           make_unique_view_field_name(check, item_list, item);
 | |
|         else
 | |
|           goto err;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN(FALSE);
 | |
| 
 | |
| err:
 | |
|   my_error(ER_DUP_FIELDNAME, MYF(0), item->name);
 | |
|   DBUG_RETURN(TRUE);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Check if auto generated column names are conforming and
 | |
|   possibly generate a conforming name for them if not.
 | |
| 
 | |
|   @param item_list  List of Items which should be checked
 | |
| */
 | |
| 
 | |
| static void make_valid_column_names(List<Item> &item_list)
 | |
| {
 | |
|   Item *item;
 | |
|   uint name_len;
 | |
|   List_iterator_fast<Item> it(item_list);
 | |
|   char buff[NAME_LEN];
 | |
|   DBUG_ENTER("make_valid_column_names");
 | |
| 
 | |
|   for (uint column_no= 1; (item= it++); column_no++)
 | |
|   {
 | |
|     if (!item->is_autogenerated_name || !check_column_name(item->name))
 | |
|       continue;
 | |
|     name_len= my_snprintf(buff, NAME_LEN, "Name_exp_%u", column_no);
 | |
|     item->orig_name= item->name;
 | |
|     item->set_name(buff, name_len, system_charset_info);
 | |
|   }
 | |
| 
 | |
|   DBUG_VOID_RETURN;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Fill defined view parts
 | |
| 
 | |
|   SYNOPSIS
 | |
|     fill_defined_view_parts()
 | |
|       thd                current thread.
 | |
|       view               view to operate on
 | |
| 
 | |
|   DESCRIPTION
 | |
|     This function will initialize the parts of the view 
 | |
|     definition that are not specified in ALTER VIEW
 | |
|     to their values from CREATE VIEW.
 | |
|     The view must be opened to get its definition.
 | |
|     We use a copy of the view when opening because we want 
 | |
|     to preserve the original view instance.
 | |
| 
 | |
|   RETURN VALUE
 | |
|     TRUE                 can't open table
 | |
|     FALSE                success
 | |
| */
 | |
| static bool
 | |
| fill_defined_view_parts (THD *thd, TABLE_LIST *view)
 | |
| {
 | |
|   LEX *lex= thd->lex;
 | |
|   bool not_used;
 | |
|   TABLE_LIST decoy;
 | |
| 
 | |
|   memcpy (&decoy, view, sizeof (TABLE_LIST));
 | |
| 
 | |
|   /*
 | |
|     Let's reset decoy.view before calling open_table(): when we start
 | |
|     supporting ALTER VIEW in PS/SP that may save us from a crash.
 | |
|   */
 | |
| 
 | |
|   decoy.view= NULL;
 | |
| 
 | |
|   /*
 | |
|     open_table() will return NULL if 'decoy' is idenitifying a view *and*
 | |
|     there is no TABLE object for that view in the table cache. However,
 | |
|     decoy.view will be set to 1.
 | |
| 
 | |
|     If there is a TABLE-instance for the oject identified by 'decoy',
 | |
|     open_table() will return that instance no matter if it is a table or
 | |
|     a view.
 | |
| 
 | |
|     Thus, there is no need to check for the return value of open_table(),
 | |
|     since the return value itself does not mean anything.
 | |
|   */
 | |
| 
 | |
|   open_table(thd, &decoy, thd->mem_root, ¬_used, OPEN_VIEW_NO_PARSE);
 | |
| 
 | |
|   if (!decoy.view)
 | |
|   {
 | |
|     /* It's a table. */
 | |
|     my_error(ER_WRONG_OBJECT, MYF(0), view->db, view->table_name, "VIEW");
 | |
|     return TRUE;
 | |
|   }
 | |
| 
 | |
|   if (!lex->definer)
 | |
|   {
 | |
|     view->definer.host= decoy.definer.host;
 | |
|     view->definer.user= decoy.definer.user;
 | |
|     lex->definer= &view->definer;
 | |
|   }
 | |
|   if (lex->create_view_algorithm == VIEW_ALGORITHM_UNDEFINED)
 | |
|     lex->create_view_algorithm= (uint8) decoy.algorithm;
 | |
|   if (lex->create_view_suid == VIEW_SUID_DEFAULT)
 | |
|     lex->create_view_suid= decoy.view_suid ? 
 | |
|       VIEW_SUID_DEFINER : VIEW_SUID_INVOKER;
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
| 
 | |
| /**
 | |
|   @brief CREATE VIEW privileges pre-check.
 | |
| 
 | |
|   @param thd thread handler
 | |
|   @param tables tables used in the view
 | |
|   @param views views to create
 | |
|   @param mode VIEW_CREATE_NEW, VIEW_ALTER, VIEW_CREATE_OR_REPLACE
 | |
| 
 | |
|   @retval FALSE Operation was a success.
 | |
|   @retval TRUE An error occured.
 | |
| */
 | |
| 
 | |
| bool create_view_precheck(THD *thd, TABLE_LIST *tables, TABLE_LIST *view,
 | |
|                           enum_view_create_mode mode)
 | |
| {
 | |
|   LEX *lex= thd->lex;
 | |
|   /* first table in list is target VIEW name => cut off it */
 | |
|   TABLE_LIST *tbl;
 | |
|   SELECT_LEX *select_lex= &lex->select_lex;
 | |
|   SELECT_LEX *sl;
 | |
|   bool res= TRUE;
 | |
|   DBUG_ENTER("create_view_precheck");
 | |
| 
 | |
|   /*
 | |
|     Privilege check for view creation:
 | |
|     - user has CREATE VIEW privilege on view table
 | |
|     - user has DROP privilege in case of ALTER VIEW or CREATE OR REPLACE
 | |
|     VIEW
 | |
|     - user has some (SELECT/UPDATE/INSERT/DELETE) privileges on columns of
 | |
|     underlying tables used on top of SELECT list (because it can be
 | |
|     (theoretically) updated, so it is enough to have UPDATE privilege on
 | |
|     them, for example)
 | |
|     - user has SELECT privilege on columns used in expressions of VIEW select
 | |
|     - for columns of underly tables used on top of SELECT list also will be
 | |
|     checked that we have not more privileges on correspondent column of view
 | |
|     table (i.e. user will not get some privileges by view creation)
 | |
|   */
 | |
|   if ((check_access(thd, CREATE_VIEW_ACL, view->db, &view->grant.privilege,
 | |
|                     0, 0, is_schema_db(view->db, view->db_length)) ||
 | |
|        check_grant(thd, CREATE_VIEW_ACL, view, 0, 1, 0)) ||
 | |
|       (mode != VIEW_CREATE_NEW &&
 | |
|        (check_access(thd, DROP_ACL, view->db, &view->grant.privilege,
 | |
|                      0, 0, is_schema_db(view->db, view->db_length)) ||
 | |
|         check_grant(thd, DROP_ACL, view, 0, 1, 0))))
 | |
|     goto err;
 | |
| 
 | |
|   for (sl= select_lex; sl; sl= sl->next_select())
 | |
|   {
 | |
|     for (tbl= sl->get_table_list(); tbl; tbl= tbl->next_local)
 | |
|     {
 | |
|       /*
 | |
|         Ensure that we have some privileges on this table, more strict check
 | |
|         will be done on column level after preparation,
 | |
|       */
 | |
|       if (check_some_access(thd, VIEW_ANY_ACL, tbl))
 | |
|       {
 | |
|         my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
 | |
|                  "ANY", thd->security_ctx->priv_user,
 | |
|                  thd->security_ctx->priv_host, tbl->table_name);
 | |
|         goto err;
 | |
|       }
 | |
|       /*
 | |
|         Mark this table as a table which will be checked after the prepare
 | |
|         phase
 | |
|       */
 | |
|       tbl->table_in_first_from_clause= 1;
 | |
| 
 | |
|       /*
 | |
|         We need to check only SELECT_ACL for all normal fields, fields for
 | |
|         which we need "any" (SELECT/UPDATE/INSERT/DELETE) privilege will be
 | |
|         checked later
 | |
|       */
 | |
|       tbl->grant.want_privilege= SELECT_ACL;
 | |
|       /*
 | |
|         Make sure that all rights are loaded to the TABLE::grant field.
 | |
| 
 | |
|         tbl->table_name will be correct name of table because VIEWs are
 | |
|         not opened yet.
 | |
|       */
 | |
|       fill_effective_table_privileges(thd, &tbl->grant, tbl->db,
 | |
|                                       tbl->table_name);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (&lex->select_lex != lex->all_selects_list)
 | |
|   {
 | |
|     /* check tables of subqueries */
 | |
|     for (tbl= tables; tbl; tbl= tbl->next_global)
 | |
|     {
 | |
|       if (!tbl->table_in_first_from_clause)
 | |
|       {
 | |
|         if (check_access(thd, SELECT_ACL, tbl->db,
 | |
|                          &tbl->grant.privilege, 0, 0, test(tbl->schema_table)) ||
 | |
|             check_grant(thd, SELECT_ACL, tbl, 0, 1, 0))
 | |
|           goto err;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   /*
 | |
|     Mark fields for special privilege check ("any" privilege)
 | |
|   */
 | |
|   for (sl= select_lex; sl; sl= sl->next_select())
 | |
|   {
 | |
|     List_iterator_fast<Item> it(sl->item_list);
 | |
|     Item *item;
 | |
|     while ((item= it++))
 | |
|     {
 | |
|       Item_field *field;
 | |
|       if ((field= item->filed_for_view_update()))
 | |
|       {
 | |
|         /*
 | |
|          any_privileges may be reset later by the Item_field::set_field
 | |
|          method in case of a system temporary table.
 | |
|         */
 | |
|         field->any_privileges= 1;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   res= FALSE;
 | |
| 
 | |
| err:
 | |
|   DBUG_RETURN(res || thd->is_error());
 | |
| }
 | |
| 
 | |
| #else
 | |
| 
 | |
| bool create_view_precheck(THD *thd, TABLE_LIST *tables, TABLE_LIST *view,
 | |
|                           enum_view_create_mode mode)
 | |
| {
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| #endif
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Creating/altering VIEW procedure
 | |
| 
 | |
|   @param thd thread handler
 | |
|   @param views views to create
 | |
|   @param mode VIEW_CREATE_NEW, VIEW_ALTER, VIEW_CREATE_OR_REPLACE
 | |
| 
 | |
|   @note This function handles both create and alter view commands.
 | |
| 
 | |
|   @retval FALSE Operation was a success.
 | |
|   @retval TRUE An error occured.
 | |
| */
 | |
| 
 | |
| bool mysql_create_view(THD *thd, TABLE_LIST *views,
 | |
|                        enum_view_create_mode mode)
 | |
| {
 | |
|   LEX *lex= thd->lex;
 | |
|   bool link_to_local;
 | |
|   /* first table in list is target VIEW name => cut off it */
 | |
|   TABLE_LIST *view= lex->unlink_first_table(&link_to_local);
 | |
|   TABLE_LIST *tables= lex->query_tables;
 | |
|   TABLE_LIST *tbl;
 | |
|   SELECT_LEX *select_lex= &lex->select_lex;
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|   SELECT_LEX *sl;
 | |
| #endif
 | |
|   SELECT_LEX_UNIT *unit= &lex->unit;
 | |
|   bool res= FALSE;
 | |
|   DBUG_ENTER("mysql_create_view");
 | |
| 
 | |
|   /* This is ensured in the parser. */
 | |
|   DBUG_ASSERT(!lex->proc_list.first && !lex->result &&
 | |
|               !lex->param_list.elements);
 | |
| 
 | |
|   if (mode == VIEW_ALTER && fill_defined_view_parts(thd, view))
 | |
|   {
 | |
|     res= TRUE;
 | |
|     goto err;
 | |
|   }
 | |
| 
 | |
|   sp_cache_invalidate();
 | |
| 
 | |
|   if (!lex->definer)
 | |
|   {
 | |
|     /*
 | |
|       DEFINER-clause is missing; we have to create default definer in
 | |
|       persistent arena to be PS/SP friendly.
 | |
|       If this is an ALTER VIEW then the current user should be set as
 | |
|       the definer.
 | |
|     */
 | |
|     Query_arena original_arena;
 | |
|     Query_arena *ps_arena = thd->activate_stmt_arena_if_needed(&original_arena);
 | |
| 
 | |
|     if (!(lex->definer= create_default_definer(thd)))
 | |
|       res= TRUE;
 | |
| 
 | |
|     if (ps_arena)
 | |
|       thd->restore_active_arena(ps_arena, &original_arena);
 | |
| 
 | |
|     if (res)
 | |
|       goto err;
 | |
|   }
 | |
| 
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|   /*
 | |
|     check definer of view:
 | |
|       - same as current user
 | |
|       - current user has SUPER_ACL
 | |
|   */
 | |
|   if (lex->definer &&
 | |
|       (strcmp(lex->definer->user.str, thd->security_ctx->priv_user) != 0 ||
 | |
|        my_strcasecmp(system_charset_info,
 | |
|                      lex->definer->host.str,
 | |
|                      thd->security_ctx->priv_host) != 0))
 | |
|   {
 | |
|     if (!(thd->security_ctx->master_access & SUPER_ACL))
 | |
|     {
 | |
|       my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "SUPER");
 | |
|       res= TRUE;
 | |
|       goto err;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       if (!is_acl_user(lex->definer->host.str,
 | |
|                        lex->definer->user.str))
 | |
|       {
 | |
|         push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
 | |
|                             ER_NO_SUCH_USER,
 | |
|                             ER(ER_NO_SUCH_USER),
 | |
|                             lex->definer->user.str,
 | |
|                             lex->definer->host.str);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   if ((res= create_view_precheck(thd, tables, view, mode)))
 | |
|     goto err;
 | |
| 
 | |
|   if (open_and_lock_tables(thd, tables))
 | |
|   {
 | |
|     res= TRUE;
 | |
|     goto err;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     check that tables are not temporary  and this VIEW do not used in query
 | |
|     (it is possible with ALTERing VIEW).
 | |
|     open_and_lock_tables can change the value of tables,
 | |
|     e.g. it may happen if before the function call tables was equal to 0. 
 | |
|   */ 
 | |
|   for (tbl= lex->query_tables; tbl; tbl= tbl->next_global)
 | |
|   {
 | |
|     /* is this table view and the same view which we creates now? */
 | |
|     if (tbl->view &&
 | |
|         strcmp(tbl->view_db.str, view->db) == 0 &&
 | |
|         strcmp(tbl->view_name.str, view->table_name) == 0)
 | |
|     {
 | |
|       my_error(ER_NO_SUCH_TABLE, MYF(0), tbl->view_db.str, tbl->view_name.str);
 | |
|       res= TRUE;
 | |
|       goto err;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       tbl->table can be NULL when tbl is a placeholder for a view
 | |
|       that is indirectly referenced via a stored function from the
 | |
|       view being created. We don't check these indirectly
 | |
|       referenced views in CREATE VIEW so they don't have table
 | |
|       object.
 | |
|     */
 | |
|     if (tbl->table)
 | |
|     {
 | |
|       /* is this table temporary and is not view? */
 | |
|       if (tbl->table->s->tmp_table != NO_TMP_TABLE && !tbl->view &&
 | |
|           !tbl->schema_table)
 | |
|       {
 | |
|         my_error(ER_VIEW_SELECT_TMPTABLE, MYF(0), tbl->alias);
 | |
|         res= TRUE;
 | |
|         goto err;
 | |
|       }
 | |
|       /*
 | |
|         Copy the privileges of the underlying VIEWs which were filled by
 | |
|         fill_effective_table_privileges
 | |
|         (they were not copied at derived tables processing)
 | |
|       */
 | |
|       tbl->table->grant.privilege= tbl->grant.privilege;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* prepare select to resolve all fields */
 | |
|   lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_VIEW;
 | |
|   if (unit->prepare(thd, 0, 0))
 | |
|   {
 | |
|     /*
 | |
|       some errors from prepare are reported to user, if is not then
 | |
|       it will be checked after err: label
 | |
|     */
 | |
|     res= TRUE;
 | |
|     goto err;
 | |
|   }
 | |
| 
 | |
|   /* view list (list of view fields names) */
 | |
|   if (lex->view_list.elements)
 | |
|   {
 | |
|     List_iterator_fast<Item> it(select_lex->item_list);
 | |
|     List_iterator_fast<LEX_STRING> nm(lex->view_list);
 | |
|     Item *item;
 | |
|     LEX_STRING *name;
 | |
| 
 | |
|     if (lex->view_list.elements != select_lex->item_list.elements)
 | |
|     {
 | |
|       my_message(ER_VIEW_WRONG_LIST, ER(ER_VIEW_WRONG_LIST), MYF(0));
 | |
|       res= TRUE;
 | |
|       goto err;
 | |
|     }
 | |
|     while ((item= it++, name= nm++))
 | |
|     {
 | |
|       item->set_name(name->str, (uint) name->length, system_charset_info);
 | |
|       item->is_autogenerated_name= FALSE;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Check if the auto generated column names are conforming. */
 | |
|   make_valid_column_names(select_lex->item_list);
 | |
| 
 | |
|   if (check_duplicate_names(select_lex->item_list, 1))
 | |
|   {
 | |
|     res= TRUE;
 | |
|     goto err;
 | |
|   }
 | |
| 
 | |
| #ifndef NO_EMBEDDED_ACCESS_CHECKS
 | |
|   /*
 | |
|     Compare/check grants on view with grants of underlying tables
 | |
|   */
 | |
| 
 | |
|   fill_effective_table_privileges(thd, &view->grant, view->db,
 | |
|                                   view->table_name);
 | |
| 
 | |
|   /*
 | |
|     Make sure that the current user does not have more column-level privileges
 | |
|     on the newly created view than he/she does on the underlying
 | |
|     tables. E.g. it must not be so that the user has UPDATE privileges on a
 | |
|     view column of he/she doesn't have it on the underlying table's
 | |
|     corresponding column. In that case, return an error for CREATE VIEW.
 | |
|    */
 | |
|   {
 | |
|     Item *report_item= NULL;
 | |
|     /* 
 | |
|        This will hold the intersection of the priviliges on all columns in the
 | |
|        view.
 | |
|      */
 | |
|     uint final_priv= VIEW_ANY_ACL;
 | |
|     
 | |
|     for (sl= select_lex; sl; sl= sl->next_select())
 | |
|     {
 | |
|       DBUG_ASSERT(view->db);                     /* Must be set in the parser */
 | |
|       List_iterator_fast<Item> it(sl->item_list);
 | |
|       Item *item;
 | |
|       while ((item= it++))
 | |
|       {
 | |
|         Item_field *fld= item->filed_for_view_update();
 | |
|         uint priv= (get_column_grant(thd, &view->grant, view->db,
 | |
|                                      view->table_name, item->name) &
 | |
|                     VIEW_ANY_ACL);
 | |
| 
 | |
|         if (fld && !fld->field->table->s->tmp_table)
 | |
|         {
 | |
| 
 | |
|           final_priv&= fld->have_privileges;
 | |
| 
 | |
|           if (~fld->have_privileges & priv)
 | |
|             report_item= item;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     
 | |
|     if (!final_priv && report_item)
 | |
|     {
 | |
|       my_error(ER_COLUMNACCESS_DENIED_ERROR, MYF(0),
 | |
|                "create view", thd->security_ctx->priv_user,
 | |
|                thd->security_ctx->priv_host, report_item->name,
 | |
|                view->table_name);
 | |
|       res= TRUE;
 | |
|       goto err;
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   if (wait_if_global_read_lock(thd, 0, 0))
 | |
|   {
 | |
|     res= TRUE;
 | |
|     goto err;
 | |
|   }
 | |
|   VOID(pthread_mutex_lock(&LOCK_open));
 | |
|   res= mysql_register_view(thd, view, mode);
 | |
| 
 | |
|   if (mysql_bin_log.is_open())
 | |
|   {
 | |
|     String buff;
 | |
|     const LEX_STRING command[3]=
 | |
|       {{ C_STRING_WITH_LEN("CREATE ") },
 | |
|        { C_STRING_WITH_LEN("ALTER ") },
 | |
|        { C_STRING_WITH_LEN("CREATE OR REPLACE ") }};
 | |
| 
 | |
|     buff.append(command[thd->lex->create_view_mode].str,
 | |
|                 command[thd->lex->create_view_mode].length);
 | |
|     view_store_options(thd, views, &buff);
 | |
|     buff.append(STRING_WITH_LEN("VIEW "));
 | |
|     /* Test if user supplied a db (ie: we did not use thd->db) */
 | |
|     if (views->db && views->db[0] &&
 | |
|         (thd->db == NULL || strcmp(views->db, thd->db)))
 | |
|     {
 | |
|       append_identifier(thd, &buff, views->db,
 | |
|                         views->db_length);
 | |
|       buff.append('.');
 | |
|     }
 | |
|     append_identifier(thd, &buff, views->table_name,
 | |
|                       views->table_name_length);
 | |
|     if (lex->view_list.elements)
 | |
|     {
 | |
|       List_iterator_fast<LEX_STRING> names(lex->view_list);
 | |
|       LEX_STRING *name;
 | |
|       int i;
 | |
|       
 | |
|       for (i= 0; (name= names++); i++)
 | |
|       {
 | |
|         buff.append(i ? ", " : "(");
 | |
|         append_identifier(thd, &buff, name->str, name->length);
 | |
|       }
 | |
|       buff.append(')');
 | |
|     }
 | |
|     buff.append(STRING_WITH_LEN(" AS "));
 | |
|     buff.append(views->source.str, views->source.length);
 | |
| 
 | |
|     int errcode= query_error_code(thd, TRUE);
 | |
|     if (thd->binlog_query(THD::STMT_QUERY_TYPE,
 | |
|                           buff.ptr(), buff.length(), FALSE, FALSE, errcode))
 | |
|       res= TRUE;
 | |
|   }
 | |
| 
 | |
|   VOID(pthread_mutex_unlock(&LOCK_open));
 | |
|   if (mode != VIEW_CREATE_NEW)
 | |
|     query_cache_invalidate3(thd, view, 0);
 | |
|   start_waiting_global_read_lock(thd);
 | |
|   if (res)
 | |
|     goto err;
 | |
| 
 | |
|   my_ok(thd);
 | |
|   lex->link_first_table_back(view, link_to_local);
 | |
|   DBUG_RETURN(0);
 | |
| 
 | |
| err:
 | |
|   thd_proc_info(thd, "end");
 | |
|   lex->link_first_table_back(view, link_to_local);
 | |
|   unit->cleanup();
 | |
|   DBUG_RETURN(res || thd->is_error());
 | |
| }
 | |
| 
 | |
| 
 | |
| /* number of required parameters for making view */
 | |
| static const int required_view_parameters= 14;
 | |
| 
 | |
| /*
 | |
|   table of VIEW .frm field descriptors
 | |
| 
 | |
|   Note that one should NOT change the order for this, as it's used by
 | |
|   parse()
 | |
| */
 | |
| static File_option view_parameters[]=
 | |
| {{{ C_STRING_WITH_LEN("query")},
 | |
|   my_offsetof(TABLE_LIST, select_stmt),
 | |
|   FILE_OPTIONS_ESTRING},
 | |
|  {{ C_STRING_WITH_LEN("md5")},
 | |
|   my_offsetof(TABLE_LIST, md5),
 | |
|   FILE_OPTIONS_STRING},
 | |
|  {{ C_STRING_WITH_LEN("updatable")},
 | |
|   my_offsetof(TABLE_LIST, updatable_view),
 | |
|   FILE_OPTIONS_ULONGLONG},
 | |
|  {{ C_STRING_WITH_LEN("algorithm")},
 | |
|   my_offsetof(TABLE_LIST, algorithm),
 | |
|   FILE_OPTIONS_ULONGLONG},
 | |
|  {{ C_STRING_WITH_LEN("definer_user")},
 | |
|   my_offsetof(TABLE_LIST, definer.user),
 | |
|   FILE_OPTIONS_STRING},
 | |
|  {{ C_STRING_WITH_LEN("definer_host")},
 | |
|   my_offsetof(TABLE_LIST, definer.host),
 | |
|   FILE_OPTIONS_STRING},
 | |
|  {{ C_STRING_WITH_LEN("suid")},
 | |
|   my_offsetof(TABLE_LIST, view_suid),
 | |
|   FILE_OPTIONS_ULONGLONG},
 | |
|  {{ C_STRING_WITH_LEN("with_check_option")},
 | |
|   my_offsetof(TABLE_LIST, with_check),
 | |
|   FILE_OPTIONS_ULONGLONG},
 | |
|  {{ C_STRING_WITH_LEN("timestamp")},
 | |
|   my_offsetof(TABLE_LIST, timestamp),
 | |
|   FILE_OPTIONS_TIMESTAMP},
 | |
|  {{ C_STRING_WITH_LEN("create-version")},
 | |
|   my_offsetof(TABLE_LIST, file_version),
 | |
|   FILE_OPTIONS_ULONGLONG},
 | |
|  {{ C_STRING_WITH_LEN("source")},
 | |
|   my_offsetof(TABLE_LIST, source),
 | |
|   FILE_OPTIONS_ESTRING},
 | |
|  {{(char*) STRING_WITH_LEN("client_cs_name")},
 | |
|   my_offsetof(TABLE_LIST, view_client_cs_name),
 | |
|   FILE_OPTIONS_STRING},
 | |
|  {{(char*) STRING_WITH_LEN("connection_cl_name")},
 | |
|   my_offsetof(TABLE_LIST, view_connection_cl_name),
 | |
|   FILE_OPTIONS_STRING},
 | |
|  {{(char*) STRING_WITH_LEN("view_body_utf8")},
 | |
|   my_offsetof(TABLE_LIST, view_body_utf8),
 | |
|   FILE_OPTIONS_ESTRING},
 | |
|  {{NullS, 0},			0,
 | |
|   FILE_OPTIONS_STRING}
 | |
| };
 | |
| 
 | |
| static LEX_STRING view_file_type[]= {{(char*) STRING_WITH_LEN("VIEW") }};
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Register VIEW (write .frm & process .frm's history backups)
 | |
| 
 | |
|   SYNOPSIS
 | |
|     mysql_register_view()
 | |
|     thd		- thread handler
 | |
|     view	- view description
 | |
|     mode	- VIEW_CREATE_NEW, VIEW_ALTER, VIEW_CREATE_OR_REPLACE
 | |
| 
 | |
|   RETURN
 | |
|      0	OK
 | |
|     -1	Error
 | |
|      1	Error and error message given
 | |
| */
 | |
| 
 | |
| static int mysql_register_view(THD *thd, TABLE_LIST *view,
 | |
| 			       enum_view_create_mode mode)
 | |
| {
 | |
|   LEX *lex= thd->lex;
 | |
| 
 | |
|   /*
 | |
|     View definition query -- a SELECT statement that fully defines view. It
 | |
|     is generated from the Item-tree built from the original (specified by
 | |
|     the user) query. The idea is that generated query should eliminates all
 | |
|     ambiguities and fix view structure at CREATE-time (once for all).
 | |
|     Item::print() virtual operation is used to generate view definition
 | |
|     query.
 | |
| 
 | |
|     INFORMATION_SCHEMA query (IS query) -- a SQL statement describing a
 | |
|     view that is shown in INFORMATION_SCHEMA. Basically, it is 'view
 | |
|     definition query' with text literals converted to UTF8 and without
 | |
|     character set introducers.
 | |
| 
 | |
|     For example:
 | |
|       Let's suppose we have:
 | |
|         CREATE TABLE t1(a INT, b INT);
 | |
|       User specified query:
 | |
|         CREATE VIEW v1(x, y) AS SELECT * FROM t1;
 | |
|       Generated query:
 | |
|         SELECT a AS x, b AS y FROM t1;
 | |
|       IS query:
 | |
|         SELECT a AS x, b AS y FROM t1;
 | |
| 
 | |
|     View definition query is stored in the client character set.
 | |
|   */
 | |
|   char view_query_buff[4096];
 | |
|   String view_query(view_query_buff,
 | |
|                     sizeof (view_query_buff),
 | |
|                     thd->charset());
 | |
| 
 | |
|   char is_query_buff[4096];
 | |
|   String is_query(is_query_buff,
 | |
|                   sizeof (is_query_buff),
 | |
|                   system_charset_info);
 | |
| 
 | |
|   char md5[MD5_BUFF_LENGTH];
 | |
|   bool can_be_merged;
 | |
|   char dir_buff[FN_REFLEN + 1], path_buff[FN_REFLEN + 1];
 | |
|   LEX_STRING dir, file, path;
 | |
|   int error= 0;
 | |
|   DBUG_ENTER("mysql_register_view");
 | |
| 
 | |
|   /* Generate view definition and IS queries. */
 | |
|   view_query.length(0);
 | |
|   is_query.length(0);
 | |
|   {
 | |
|     ulong sql_mode= thd->variables.sql_mode & MODE_ANSI_QUOTES;
 | |
|     thd->variables.sql_mode&= ~MODE_ANSI_QUOTES;
 | |
| 
 | |
|     lex->unit.print(&view_query, QT_ORDINARY);
 | |
|     lex->unit.print(&is_query, QT_IS);
 | |
| 
 | |
|     thd->variables.sql_mode|= sql_mode;
 | |
|   }
 | |
|   DBUG_PRINT("info", ("View: %s", view_query.ptr()));
 | |
| 
 | |
|   /* fill structure */
 | |
|   view->source= thd->lex->create_view_select;
 | |
| 
 | |
|   if (!thd->make_lex_string(&view->select_stmt, view_query.ptr(),
 | |
|                             view_query.length(), false))
 | |
|   {
 | |
|     my_error(ER_OUT_OF_RESOURCES, MYF(0));
 | |
|     error= -1;
 | |
|     goto err;   
 | |
|   }
 | |
| 
 | |
|   view->file_version= 1;
 | |
|   view->calc_md5(md5);
 | |
|   if (!(view->md5.str= (char*) thd->memdup(md5, 32)))
 | |
|   {
 | |
|     my_error(ER_OUT_OF_RESOURCES, MYF(0));
 | |
|     error= -1;
 | |
|     goto err;   
 | |
|   }
 | |
|   view->md5.length= 32;
 | |
|   can_be_merged= lex->can_be_merged();
 | |
|   if (lex->create_view_algorithm == VIEW_ALGORITHM_MERGE &&
 | |
|       !lex->can_be_merged())
 | |
|   {
 | |
|     push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_VIEW_MERGE,
 | |
|                  ER(ER_WARN_VIEW_MERGE));
 | |
|     lex->create_view_algorithm= VIEW_ALGORITHM_UNDEFINED;
 | |
|   }
 | |
|   view->algorithm= lex->create_view_algorithm;
 | |
|   view->definer.user= lex->definer->user;
 | |
|   view->definer.host= lex->definer->host;
 | |
|   view->view_suid= lex->create_view_suid;
 | |
|   view->with_check= lex->create_view_check;
 | |
|   if ((view->updatable_view= (can_be_merged &&
 | |
|                               view->algorithm != VIEW_ALGORITHM_TMPTABLE)))
 | |
|   {
 | |
|     /* TODO: change here when we will support UNIONs */
 | |
|     for (TABLE_LIST *tbl= lex->select_lex.table_list.first;
 | |
| 	 tbl;
 | |
| 	 tbl= tbl->next_local)
 | |
|     {
 | |
|       if ((tbl->view && !tbl->updatable_view) || tbl->schema_table)
 | |
|       {
 | |
| 	view->updatable_view= 0;
 | |
| 	break;
 | |
|       }
 | |
|       for (TABLE_LIST *up= tbl; up; up= up->embedding)
 | |
|       {
 | |
| 	if (up->outer_join)
 | |
| 	{
 | |
| 	  view->updatable_view= 0;
 | |
| 	  goto loop_out;
 | |
| 	}
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| loop_out:
 | |
|   /* print file name */
 | |
|   dir.length= build_table_filename(dir_buff, sizeof(dir_buff) - 1,
 | |
|                                    view->db, "", "", 0);
 | |
|   dir.str= dir_buff;
 | |
| 
 | |
|   path.length= build_table_filename(path_buff, sizeof(path_buff) - 1,
 | |
|                                     view->db, view->table_name, reg_ext, 0);
 | |
|   path.str= path_buff;
 | |
| 
 | |
|   file.str= path.str + dir.length;
 | |
|   file.length= path.length - dir.length;
 | |
| 
 | |
|   /* init timestamp */
 | |
|   if (!view->timestamp.str)
 | |
|     view->timestamp.str= view->timestamp_buffer;
 | |
| 
 | |
|   /* check old .frm */
 | |
|   {
 | |
|     char path_buff[FN_REFLEN];
 | |
|     LEX_STRING path;
 | |
|     File_parser *parser;
 | |
| 
 | |
|     path.str= path_buff;
 | |
|     fn_format(path_buff, file.str, dir.str, "", MY_UNPACK_FILENAME);
 | |
|     path.length= strlen(path_buff);
 | |
| 
 | |
|     if (!access(path.str, F_OK))
 | |
|     {
 | |
|       if (mode == VIEW_CREATE_NEW)
 | |
|       {
 | |
| 	my_error(ER_TABLE_EXISTS_ERROR, MYF(0), view->alias);
 | |
|         error= -1;
 | |
|         goto err;
 | |
|       }
 | |
| 
 | |
|       if (!(parser= sql_parse_prepare(&path, thd->mem_root, 0)))
 | |
|       {
 | |
|         error= 1;
 | |
|         goto err;
 | |
|       }
 | |
| 
 | |
|       if (!parser->ok() || !is_equal(&view_type, parser->type()))
 | |
|       {
 | |
|         my_error(ER_WRONG_OBJECT, MYF(0), view->db, view->table_name, "VIEW");
 | |
|         error= -1;
 | |
|         goto err;
 | |
|       }
 | |
| 
 | |
|       /*
 | |
|         TODO: read dependence list, too, to process cascade/restrict
 | |
|         TODO: special cascade/restrict procedure for alter?
 | |
|       */
 | |
|     }
 | |
|     else
 | |
|    {
 | |
|       if (mode == VIEW_ALTER)
 | |
|       {
 | |
| 	my_error(ER_NO_SUCH_TABLE, MYF(0), view->db, view->alias);
 | |
|         error= -1;
 | |
|         goto err;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Initialize view creation context from the environment. */
 | |
| 
 | |
|   view->view_creation_ctx= View_creation_ctx::create(thd);
 | |
| 
 | |
|   /*
 | |
|     Set LEX_STRING attributes in view-structure for parser to create
 | |
|     frm-file.
 | |
|   */
 | |
| 
 | |
|   lex_string_set(&view->view_client_cs_name,
 | |
|                  view->view_creation_ctx->get_client_cs()->csname);
 | |
| 
 | |
|   lex_string_set(&view->view_connection_cl_name,
 | |
|                  view->view_creation_ctx->get_connection_cl()->name);
 | |
| 
 | |
|   if (!thd->make_lex_string(&view->view_body_utf8, is_query.ptr(),
 | |
|                             is_query.length(), false))
 | |
|   {
 | |
|     my_error(ER_OUT_OF_RESOURCES, MYF(0));
 | |
|     error= -1;
 | |
|     goto err;   
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Check that table of main select do not used in subqueries.
 | |
| 
 | |
|     This test can catch only very simple cases of such non-updateable views,
 | |
|     all other will be detected before updating commands execution.
 | |
|     (it is more optimisation then real check)
 | |
| 
 | |
|     NOTE: this skip cases of using table via VIEWs, joined VIEWs, VIEWs with
 | |
|     UNION
 | |
|   */
 | |
|   if (view->updatable_view &&
 | |
|       !lex->select_lex.master_unit()->is_union() &&
 | |
|       !(lex->select_lex.table_list.first)->next_local &&
 | |
|       find_table_in_global_list(lex->query_tables->next_global,
 | |
| 				lex->query_tables->db,
 | |
| 				lex->query_tables->table_name))
 | |
|   {
 | |
|     view->updatable_view= 0;
 | |
|   }
 | |
| 
 | |
|   if (view->with_check != VIEW_CHECK_NONE &&
 | |
|       !view->updatable_view)
 | |
|   {
 | |
|     my_error(ER_VIEW_NONUPD_CHECK, MYF(0), view->db, view->table_name);
 | |
|     error= -1;
 | |
|     goto err;
 | |
|   }
 | |
| 
 | |
|   if (sql_create_definition_file(&dir, &file, view_file_type,
 | |
| 				 (uchar*)view, view_parameters))
 | |
|   {
 | |
|     error= thd->is_error() ? -1 : 1;
 | |
|     goto err;
 | |
|   }
 | |
|   DBUG_RETURN(0);
 | |
| err:
 | |
|   view->select_stmt.str= NULL;
 | |
|   view->select_stmt.length= 0;
 | |
|   view->md5.str= NULL;
 | |
|   view->md5.length= 0;
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|   read VIEW .frm and create structures
 | |
| 
 | |
|   SYNOPSIS
 | |
|     mysql_make_view()
 | |
|     thd			Thread handler
 | |
|     parser		parser object
 | |
|     table		TABLE_LIST structure for filling
 | |
|     flags               flags
 | |
|   RETURN
 | |
|     0 ok
 | |
|     1 error
 | |
| */
 | |
| 
 | |
| bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table,
 | |
|                      uint flags)
 | |
| {
 | |
|   SELECT_LEX *end, *view_select;
 | |
|   LEX *old_lex, *lex;
 | |
|   Query_arena *arena, backup;
 | |
|   TABLE_LIST *top_view= table->top_table();
 | |
|   bool parse_status;
 | |
|   bool result, view_is_mergeable;
 | |
|   TABLE_LIST *UNINIT_VAR(view_main_select_tables);
 | |
| 
 | |
|   DBUG_ENTER("mysql_make_view");
 | |
|   DBUG_PRINT("info", ("table: 0x%lx (%s)", (ulong) table, table->table_name));
 | |
| 
 | |
|   if (table->view)
 | |
|   {
 | |
|     /*
 | |
|       It's an execution of a PS/SP and the view has already been unfolded
 | |
|       into a list of used tables. Now we only need to update the information
 | |
|       about granted privileges in the view tables with the actual data
 | |
|       stored in MySQL privilege system.  We don't need to restore the
 | |
|       required privileges (by calling register_want_access) because they has
 | |
|       not changed since PREPARE or the previous execution: the only case
 | |
|       when this information is changed is execution of UPDATE on a view, but
 | |
|       the original want_access is restored in its end.
 | |
|     */
 | |
|     if (!table->prelocking_placeholder && table->prepare_security(thd))
 | |
|     {
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|     DBUG_PRINT("info",
 | |
|                ("VIEW %s.%s is already processed on previous PS/SP execution",
 | |
|                 table->view_db.str, table->view_name.str));
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
| 
 | |
|   if (table->index_hints && table->index_hints->elements)
 | |
|   {
 | |
|     my_error(ER_KEY_DOES_NOT_EXITS, MYF(0),
 | |
|              table->index_hints->head()->key_name.str, table->table_name);
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
|   /* check loop via view definition */
 | |
|   for (TABLE_LIST *precedent= table->referencing_view;
 | |
|        precedent;
 | |
|        precedent= precedent->referencing_view)
 | |
|   {
 | |
|     if (precedent->view_name.length == table->table_name_length &&
 | |
|         precedent->view_db.length == table->db_length &&
 | |
|         my_strcasecmp(system_charset_info,
 | |
|                       precedent->view_name.str, table->table_name) == 0 &&
 | |
|         my_strcasecmp(system_charset_info,
 | |
|                       precedent->view_db.str, table->db) == 0)
 | |
|     {
 | |
|       my_error(ER_VIEW_RECURSIVE, MYF(0),
 | |
|                top_view->view_db.str, top_view->view_name.str);
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     For now we assume that tables will not be changed during PS life (it
 | |
|     will be TRUE as far as we make new table cache).
 | |
|   */
 | |
|   old_lex= thd->lex;
 | |
|   arena= thd->stmt_arena;
 | |
|   if (arena->is_conventional())
 | |
|     arena= 0;
 | |
|   else
 | |
|     thd->set_n_backup_active_arena(arena, &backup);
 | |
| 
 | |
|   /* init timestamp */
 | |
|   if (!table->timestamp.str)
 | |
|     table->timestamp.str= table->timestamp_buffer;
 | |
|   /* prepare default values for old format */
 | |
|   table->view_suid= TRUE;
 | |
|   table->definer.user.str= table->definer.host.str= 0;
 | |
|   table->definer.user.length= table->definer.host.length= 0;
 | |
| 
 | |
|   /*
 | |
|     TODO: when VIEWs will be stored in cache, table mem_root should
 | |
|     be used here
 | |
|   */
 | |
|   if (parser->parse((uchar*)table, thd->mem_root, view_parameters,
 | |
|                     required_view_parameters, &file_parser_dummy_hook))
 | |
|     goto err;
 | |
| 
 | |
|   /*
 | |
|     check old format view .frm
 | |
|   */
 | |
|   if (!table->definer.user.str)
 | |
|   {
 | |
|     DBUG_ASSERT(!table->definer.host.str &&
 | |
|                 !table->definer.user.length &&
 | |
|                 !table->definer.host.length);
 | |
|     push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
 | |
|                         ER_VIEW_FRM_NO_USER, ER(ER_VIEW_FRM_NO_USER),
 | |
|                         table->db, table->table_name);
 | |
|     get_default_definer(thd, &table->definer);
 | |
|   }
 | |
|   if (flags & OPEN_VIEW_NO_PARSE)
 | |
|   {
 | |
|     DBUG_RETURN(FALSE);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Save VIEW parameters, which will be wiped out by derived table
 | |
|     processing
 | |
|   */
 | |
|   table->view_db.str= table->db;
 | |
|   table->view_db.length= table->db_length;
 | |
|   table->view_name.str= table->table_name;
 | |
|   table->view_name.length= table->table_name_length;
 | |
| 
 | |
|   /*TODO: md5 test here and warning if it is differ */
 | |
| 
 | |
|   /*
 | |
|     Initialize view definition context by character set names loaded from
 | |
|     the view definition file. Use UTF8 character set if view definition
 | |
|     file is of old version and does not contain the character set names.
 | |
|   */
 | |
| 
 | |
|   table->view_creation_ctx= View_creation_ctx::create(thd, table);
 | |
| 
 | |
|   /*
 | |
|     TODO: TABLE mem root should be used here when VIEW will be stored in
 | |
|     TABLE cache
 | |
| 
 | |
|     now Lex placed in statement memory
 | |
|   */
 | |
|   table->view= lex= thd->lex= (LEX*) new(thd->mem_root) st_lex_local;
 | |
| 
 | |
|   {
 | |
|     char old_db_buf[NAME_LEN+1];
 | |
|     LEX_STRING old_db= { old_db_buf, sizeof(old_db_buf) };
 | |
|     bool dbchanged;
 | |
|     Parser_state parser_state;
 | |
|     if (parser_state.init(thd, table->select_stmt.str,
 | |
|                           table->select_stmt.length))
 | |
|         goto err;
 | |
| 
 | |
|     /* 
 | |
|       Use view db name as thread default database, in order to ensure
 | |
|       that the view is parsed and prepared correctly.
 | |
|     */
 | |
|     if ((result= mysql_opt_change_db(thd, &table->view_db, &old_db, 1,
 | |
|                                      &dbchanged)))
 | |
|       goto end;
 | |
| 
 | |
|     lex_start(thd);
 | |
|     view_select= &lex->select_lex;
 | |
|     view_select->select_number= ++thd->select_number;
 | |
| 
 | |
|     ulong saved_mode= thd->variables.sql_mode;
 | |
|     /* switch off modes which can prevent normal parsing of VIEW
 | |
|       - MODE_REAL_AS_FLOAT            affect only CREATE TABLE parsing
 | |
|       + MODE_PIPES_AS_CONCAT          affect expression parsing
 | |
|       + MODE_ANSI_QUOTES              affect expression parsing
 | |
|       + MODE_IGNORE_SPACE             affect expression parsing
 | |
|       - MODE_NOT_USED                 not used :)
 | |
|       * MODE_ONLY_FULL_GROUP_BY       affect execution
 | |
|       * MODE_NO_UNSIGNED_SUBTRACTION  affect execution
 | |
|       - MODE_NO_DIR_IN_CREATE         affect table creation only
 | |
|       - MODE_POSTGRESQL               compounded from other modes
 | |
|       - MODE_ORACLE                   compounded from other modes
 | |
|       - MODE_MSSQL                    compounded from other modes
 | |
|       - MODE_DB2                      compounded from other modes
 | |
|       - MODE_MAXDB                    affect only CREATE TABLE parsing
 | |
|       - MODE_NO_KEY_OPTIONS           affect only SHOW
 | |
|       - MODE_NO_TABLE_OPTIONS         affect only SHOW
 | |
|       - MODE_NO_FIELD_OPTIONS         affect only SHOW
 | |
|       - MODE_MYSQL323                 affect only SHOW
 | |
|       - MODE_MYSQL40                  affect only SHOW
 | |
|       - MODE_ANSI                     compounded from other modes
 | |
|                                       (+ transaction mode)
 | |
|       ? MODE_NO_AUTO_VALUE_ON_ZERO    affect UPDATEs
 | |
|       + MODE_NO_BACKSLASH_ESCAPES     affect expression parsing
 | |
|     */
 | |
|     thd->variables.sql_mode&= ~(MODE_PIPES_AS_CONCAT | MODE_ANSI_QUOTES |
 | |
|                                 MODE_IGNORE_SPACE | MODE_NO_BACKSLASH_ESCAPES);
 | |
| 
 | |
|     /* Parse the query. */
 | |
| 
 | |
|     parse_status= parse_sql(thd, & parser_state, table->view_creation_ctx);
 | |
| 
 | |
|     /* Restore environment. */
 | |
| 
 | |
|     if ((old_lex->sql_command == SQLCOM_SHOW_FIELDS) ||
 | |
|         (old_lex->sql_command == SQLCOM_SHOW_CREATE))
 | |
|         lex->sql_command= old_lex->sql_command;
 | |
| 
 | |
|     thd->variables.sql_mode= saved_mode;
 | |
| 
 | |
|     if (dbchanged && mysql_change_db(thd, &old_db, TRUE))
 | |
|       goto err;
 | |
|   }
 | |
|   if (!parse_status)
 | |
|   {
 | |
|     TABLE_LIST *view_tables= lex->query_tables;
 | |
|     TABLE_LIST *view_tables_tail= 0;
 | |
|     TABLE_LIST *tbl;
 | |
|     Security_context *security_ctx;
 | |
| 
 | |
|     /*
 | |
|       Check rights to run commands (EXPLAIN SELECT & SHOW CREATE) which show
 | |
|       underlying tables.
 | |
|       Skip this step if we are opening view for prelocking only.
 | |
|     */
 | |
|     if (!table->prelocking_placeholder &&
 | |
|         (old_lex->sql_command == SQLCOM_SELECT && old_lex->describe))
 | |
|     {
 | |
|       /*
 | |
|         The user we run EXPLAIN as (either the connected user who issued
 | |
|         the EXPLAIN statement, or the definer of a SUID stored routine
 | |
|         which contains the EXPLAIN) should have both SHOW_VIEW_ACL and
 | |
|         SELECT_ACL on the view being opened as well as on all underlying
 | |
|         views since EXPLAIN will disclose their structure. This user also
 | |
|         should have SELECT_ACL on all underlying tables of the view since
 | |
|         this EXPLAIN will disclose information about the number of rows in it.
 | |
| 
 | |
|         To perform this privilege check we create auxiliary TABLE_LIST object
 | |
|         for the view in order a) to avoid trashing "table->grant" member for
 | |
|         original table list element, which contents can be important at later
 | |
|         stage for column-level privilege checking b) get TABLE_LIST object
 | |
|         with "security_ctx" member set to 0, i.e. forcing check_table_access()
 | |
|         to use active user's security context.
 | |
| 
 | |
|         There is no need for creating similar copies of TABLE_LIST elements
 | |
|         for underlying tables since they just have been constructed and thus
 | |
|         have TABLE_LIST::security_ctx == 0 and fresh TABLE_LIST::grant member.
 | |
| 
 | |
|         Finally at this point making sure we have SHOW_VIEW_ACL on the views
 | |
|         will suffice as we implicitly require SELECT_ACL anyway.
 | |
|       */
 | |
|         
 | |
|       TABLE_LIST view_no_suid;
 | |
|       bzero(static_cast<void *>(&view_no_suid), sizeof(TABLE_LIST));
 | |
|       view_no_suid.db= table->db;
 | |
|       view_no_suid.table_name= table->table_name;
 | |
| 
 | |
|       DBUG_ASSERT(view_tables == NULL || view_tables->security_ctx == NULL);
 | |
| 
 | |
|       if (check_table_access(thd, SELECT_ACL, view_tables, UINT_MAX, TRUE) ||
 | |
|           check_table_access(thd, SHOW_VIEW_ACL, &view_no_suid, UINT_MAX, TRUE))
 | |
|       {
 | |
|         my_message(ER_VIEW_NO_EXPLAIN, ER(ER_VIEW_NO_EXPLAIN), MYF(0));
 | |
|         goto err;
 | |
|       }
 | |
|     }
 | |
|     else if (!table->prelocking_placeholder &&
 | |
|              (old_lex->sql_command == SQLCOM_SHOW_CREATE) &&
 | |
|              !table->belong_to_view)
 | |
|     {
 | |
|       if (check_table_access(thd, SHOW_VIEW_ACL, table, UINT_MAX, FALSE))
 | |
|         goto err;
 | |
|     }
 | |
| 
 | |
|     if (!(table->view_tables=
 | |
|           (List<TABLE_LIST>*) new(thd->mem_root) List<TABLE_LIST>))
 | |
|       goto err;
 | |
|     /*
 | |
|       mark to avoid temporary table using and put view reference and find
 | |
|       last view table
 | |
|     */
 | |
|     for (tbl= view_tables;
 | |
|          tbl;
 | |
|          tbl= (view_tables_tail= tbl)->next_global)
 | |
|     {
 | |
|       tbl->skip_temporary= 1;
 | |
|       tbl->belong_to_view= top_view;
 | |
|       tbl->referencing_view= table;
 | |
|       tbl->prelocking_placeholder= table->prelocking_placeholder;
 | |
|       /*
 | |
|         First we fill want_privilege with SELECT_ACL (this is needed for the
 | |
|         tables which belongs to view subqueries and temporary table views,
 | |
|         then for the merged view underlying tables we will set wanted
 | |
|         privileges of top_view
 | |
|       */
 | |
|       tbl->grant.want_privilege= SELECT_ACL;
 | |
|       /*
 | |
|         After unfolding the view we lose the list of tables referenced in it
 | |
|         (we will have only a list of underlying tables in case of MERGE
 | |
|         algorithm, which does not include the tables referenced from
 | |
|         subqueries used in view definition).
 | |
|         Let's build a list of all tables referenced in the view.
 | |
|       */
 | |
|       table->view_tables->push_back(tbl);
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       Put tables of VIEW after VIEW TABLE_LIST
 | |
| 
 | |
|       NOTE: It is important for UPDATE/INSERT/DELETE checks to have this
 | |
|       tables just after VIEW instead of tail of list, to be able check that
 | |
|       table is unique. Also we store old next table for the same purpose.
 | |
|     */
 | |
|     if (view_tables)
 | |
|     {
 | |
|       if (table->next_global)
 | |
|       {
 | |
|         view_tables_tail->next_global= table->next_global;
 | |
|         table->next_global->prev_global= &view_tables_tail->next_global;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         old_lex->query_tables_last= &view_tables_tail->next_global;
 | |
|       }
 | |
|       view_tables->prev_global= &table->next_global;
 | |
|       table->next_global= view_tables;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       If the view's body needs row-based binlogging (e.g. the VIEW is created
 | |
|       from SELECT UUID()), the top statement also needs it.
 | |
|     */
 | |
|     if (lex->is_stmt_unsafe())
 | |
|       old_lex->set_stmt_unsafe();
 | |
|     view_is_mergeable= (table->algorithm != VIEW_ALGORITHM_TMPTABLE &&
 | |
|                         lex->can_be_merged());
 | |
| 
 | |
|     if (view_is_mergeable)
 | |
|     {
 | |
|       /*
 | |
|         Currently 'view_main_select_tables' differs from 'view_tables'
 | |
|         only then view has CONVERT_TZ() function in its select list.
 | |
|         This may change in future, for example if we enable merging of
 | |
|         views with subqueries in select list.
 | |
|       */
 | |
|       view_main_select_tables= lex->select_lex.table_list.first;
 | |
| 
 | |
|       /*
 | |
|         Let us set proper lock type for tables of the view's main
 | |
|         select since we may want to perform update or insert on
 | |
|         view. This won't work for view containing union. But this is
 | |
|         ok since we don't allow insert and update on such views
 | |
|         anyway.
 | |
|       */
 | |
|       for (tbl= view_main_select_tables; tbl; tbl= tbl->next_local)
 | |
|         tbl->lock_type= table->lock_type;
 | |
|       /*
 | |
|         If the view is mergeable, we might want to
 | |
|         INSERT/UPDATE/DELETE into tables of this view. Preserve the
 | |
|         original sql command and 'duplicates' of the outer lex.
 | |
|         This is used later in set_trg_event_type_for_command.
 | |
|       */
 | |
|       lex->sql_command= old_lex->sql_command;
 | |
|       lex->duplicates= old_lex->duplicates;
 | |
|     }
 | |
|     /*
 | |
|       This method has a dependency on the proper lock type being set,
 | |
|       so in case of views should be called here.
 | |
|     */
 | |
|     lex->set_trg_event_type_for_tables();
 | |
| 
 | |
|     /*
 | |
|       If we are opening this view as part of implicit LOCK TABLES, then
 | |
|       this view serves as simple placeholder and we should not continue
 | |
|       further processing.
 | |
|     */
 | |
|     if (table->prelocking_placeholder)
 | |
|       goto ok2;
 | |
| 
 | |
|     old_lex->derived_tables|= (DERIVED_VIEW | lex->derived_tables);
 | |
| 
 | |
|     /* move SQL_NO_CACHE & Co to whole query */
 | |
|     old_lex->safe_to_cache_query= (old_lex->safe_to_cache_query &&
 | |
| 				   lex->safe_to_cache_query);
 | |
|     /* move SQL_CACHE to whole query */
 | |
|     if (view_select->options & OPTION_TO_QUERY_CACHE)
 | |
|       old_lex->select_lex.options|= OPTION_TO_QUERY_CACHE;
 | |
| 
 | |
|     if (table->view_suid)
 | |
|     {
 | |
|       /*
 | |
|         For suid views prepare a security context for checking underlying
 | |
|         objects of the view.
 | |
|       */
 | |
|       if (!(table->view_sctx= (Security_context *)
 | |
|             thd->stmt_arena->alloc(sizeof(Security_context))))
 | |
|         goto err;
 | |
|       security_ctx= table->view_sctx;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       /*
 | |
|         For non-suid views inherit security context from view's table list.
 | |
|         This allows properly handle situation when non-suid view is used
 | |
|         from within suid view.
 | |
|       */
 | |
|       security_ctx= table->security_ctx;
 | |
|     }
 | |
| 
 | |
|     /* Assign the context to the tables referenced in the view */
 | |
|     if (view_tables)
 | |
|     {
 | |
|       DBUG_ASSERT(view_tables_tail);
 | |
|       for (tbl= view_tables; tbl != view_tables_tail->next_global;
 | |
|            tbl= tbl->next_global)
 | |
|         tbl->security_ctx= security_ctx;
 | |
|     }
 | |
| 
 | |
|     /* assign security context to SELECT name resolution contexts of view */
 | |
|     for(SELECT_LEX *sl= lex->all_selects_list;
 | |
|         sl;
 | |
|         sl= sl->next_select_in_list())
 | |
|       sl->context.security_ctx= security_ctx;
 | |
| 
 | |
|     /*
 | |
|       Setup an error processor to hide error messages issued by stored
 | |
|       routines referenced in the view
 | |
|     */
 | |
|     for (SELECT_LEX *sl= lex->all_selects_list;
 | |
|          sl;
 | |
|          sl= sl->next_select_in_list())
 | |
|     {
 | |
|       sl->context.error_processor= &view_error_processor;
 | |
|       sl->context.error_processor_data= (void *)table;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       check MERGE algorithm ability
 | |
|       - algorithm is not explicit TEMPORARY TABLE
 | |
|       - VIEW SELECT allow merging
 | |
|       - VIEW used in subquery or command support MERGE algorithm
 | |
|     */
 | |
|     if (view_is_mergeable &&
 | |
|         (table->select_lex->master_unit() != &old_lex->unit ||
 | |
|          old_lex->can_use_merged()) &&
 | |
|         !old_lex->can_not_use_merged())
 | |
|     {
 | |
|       /* lex should contain at least one table */
 | |
|       DBUG_ASSERT(view_main_select_tables != 0);
 | |
| 
 | |
|       List_iterator_fast<TABLE_LIST> ti(view_select->top_join_list);
 | |
| 
 | |
|       table->effective_algorithm= VIEW_ALGORITHM_MERGE;
 | |
|       DBUG_PRINT("info", ("algorithm: MERGE"));
 | |
|       table->updatable= (table->updatable_view != 0);
 | |
|       table->effective_with_check=
 | |
|         old_lex->get_effective_with_check(table);
 | |
|       table->merge_underlying_list= view_main_select_tables;
 | |
| 
 | |
|       /* Fill correct wanted privileges. */
 | |
|       for (tbl= view_main_select_tables; tbl; tbl= tbl->next_local)
 | |
|         tbl->grant.want_privilege= top_view->grant.orig_want_privilege;
 | |
| 
 | |
|       /* prepare view context */
 | |
|       lex->select_lex.context.resolve_in_table_list_only(view_main_select_tables);
 | |
|       lex->select_lex.context.outer_context= 0;
 | |
|       lex->select_lex.context.select_lex= table->select_lex;
 | |
|       lex->select_lex.select_n_having_items+=
 | |
|         table->select_lex->select_n_having_items;
 | |
| 
 | |
|       /*
 | |
|         Tables of the main select of the view should be marked as belonging
 | |
|         to the same select as original view (again we can use LEX::select_lex
 | |
|         for this purprose because we don't support MERGE algorithm for views
 | |
|         with unions).
 | |
|       */
 | |
|       for (tbl= lex->select_lex.get_table_list(); tbl; tbl= tbl->next_local)
 | |
|         tbl->select_lex= table->select_lex;
 | |
| 
 | |
|       {
 | |
|         if (view_main_select_tables->next_local)
 | |
|         {
 | |
|           table->multitable_view= TRUE;
 | |
|           if (table->belong_to_view)
 | |
|            table->belong_to_view->multitable_view= TRUE;
 | |
|         }
 | |
|         /* make nested join structure for view tables */
 | |
|         NESTED_JOIN *nested_join;
 | |
|         if (!(nested_join= table->nested_join=
 | |
|               (NESTED_JOIN *) thd->calloc(sizeof(NESTED_JOIN))))
 | |
|           goto err;
 | |
|         nested_join->join_list= view_select->top_join_list;
 | |
| 
 | |
|         /* re-nest tables of VIEW */
 | |
|         ti.rewind();
 | |
|         while ((tbl= ti++))
 | |
|         {
 | |
|           tbl->join_list= &nested_join->join_list;
 | |
|           tbl->embedding= table;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       /* Store WHERE clause for post-processing in setup_underlying */
 | |
|       table->where= view_select->where;
 | |
|       /*
 | |
|         Add subqueries units to SELECT into which we merging current view.
 | |
|         unit(->next)* chain starts with subqueries that are used by this
 | |
|         view and continues with subqueries that are used by other views.
 | |
|         We must not add any subquery twice (otherwise we'll form a loop),
 | |
|         to do this we remember in end_unit the first subquery that has
 | |
|         been already added.
 | |
| 
 | |
|         NOTE: we do not support UNION here, so we take only one select
 | |
|       */
 | |
|       SELECT_LEX_NODE *end_unit= table->select_lex->slave;
 | |
|       SELECT_LEX_UNIT *next_unit;
 | |
|       for (SELECT_LEX_UNIT *unit= lex->select_lex.first_inner_unit();
 | |
|            unit;
 | |
|            unit= next_unit)
 | |
|       {
 | |
|         if (unit == end_unit)
 | |
|           break;
 | |
|         SELECT_LEX_NODE *save_slave= unit->slave;
 | |
|         next_unit= unit->next_unit();
 | |
|         unit->include_down(table->select_lex);
 | |
|         unit->slave= save_slave; // fix include_down initialisation
 | |
|       }
 | |
| 
 | |
|       /* 
 | |
|         We can safely ignore the VIEW's ORDER BY if we merge into union 
 | |
|         branch, as order is not important there.
 | |
|       */
 | |
|       if (!table->select_lex->master_unit()->is_union())
 | |
|         table->select_lex->order_list.push_back(&lex->select_lex.order_list);
 | |
|       /*
 | |
| 	This SELECT_LEX will be linked in global SELECT_LEX list
 | |
| 	to make it processed by mysql_handle_derived(),
 | |
| 	but it will not be included to SELECT_LEX tree, because it
 | |
| 	will not be executed
 | |
|       */ 
 | |
|       goto ok;
 | |
|     }
 | |
| 
 | |
|     table->effective_algorithm= VIEW_ALGORITHM_TMPTABLE;
 | |
|     DBUG_PRINT("info", ("algorithm: TEMPORARY TABLE"));
 | |
|     view_select->linkage= DERIVED_TABLE_TYPE;
 | |
|     table->updatable= 0;
 | |
|     table->effective_with_check= VIEW_CHECK_NONE;
 | |
|     old_lex->subqueries= TRUE;
 | |
| 
 | |
|     /* SELECT tree link */
 | |
|     lex->unit.include_down(table->select_lex);
 | |
|     lex->unit.slave= view_select; // fix include_down initialisation
 | |
| 
 | |
|     table->derived= &lex->unit;
 | |
|   }
 | |
|   else
 | |
|     goto err;
 | |
| 
 | |
| ok:
 | |
|   /* global SELECT list linking */
 | |
|   end= view_select;	// primary SELECT_LEX is always last
 | |
|   end->link_next= old_lex->all_selects_list;
 | |
|   old_lex->all_selects_list->link_prev= &end->link_next;
 | |
|   old_lex->all_selects_list= lex->all_selects_list;
 | |
|   lex->all_selects_list->link_prev=
 | |
|     (st_select_lex_node**)&old_lex->all_selects_list;
 | |
| 
 | |
| ok2:
 | |
|   DBUG_ASSERT(lex == thd->lex);
 | |
|   thd->lex= old_lex;                            // Needed for prepare_security
 | |
|   result= !table->prelocking_placeholder && table->prepare_security(thd);
 | |
| 
 | |
|   lex_end(lex);
 | |
| end:
 | |
|   if (arena)
 | |
|     thd->restore_active_arena(arena, &backup);
 | |
|   thd->lex= old_lex;
 | |
|   DBUG_RETURN(result);
 | |
| 
 | |
| err:
 | |
|   DBUG_ASSERT(thd->lex == table->view);
 | |
|   lex_end(thd->lex);
 | |
|   delete table->view;
 | |
|   table->view= 0;	// now it is not VIEW placeholder
 | |
|   result= 1;
 | |
|   goto end;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   drop view
 | |
| 
 | |
|   SYNOPSIS
 | |
|     mysql_drop_view()
 | |
|     thd		- thread handler
 | |
|     views	- views to delete
 | |
|     drop_mode	- cascade/check
 | |
| 
 | |
|   RETURN VALUE
 | |
|     FALSE OK
 | |
|     TRUE  Error
 | |
| */
 | |
| 
 | |
| bool mysql_drop_view(THD *thd, TABLE_LIST *views, enum_drop_mode drop_mode)
 | |
| {
 | |
|   char path[FN_REFLEN + 1];
 | |
|   TABLE_LIST *view;
 | |
|   String non_existant_views;
 | |
|   char *wrong_object_db= NULL, *wrong_object_name= NULL;
 | |
|   bool error= FALSE;
 | |
|   enum legacy_db_type not_used;
 | |
|   bool some_views_deleted= FALSE;
 | |
|   bool something_wrong= FALSE;
 | |
|   DBUG_ENTER("mysql_drop_view");
 | |
| 
 | |
|   VOID(pthread_mutex_lock(&LOCK_open));
 | |
|   for (view= views; view; view= view->next_local)
 | |
|   {
 | |
|     TABLE_SHARE *share;
 | |
|     frm_type_enum type= FRMTYPE_ERROR;
 | |
|     build_table_filename(path, sizeof(path) - 1,
 | |
|                          view->db, view->table_name, reg_ext, 0);
 | |
| 
 | |
|     if (access(path, F_OK) || 
 | |
|         FRMTYPE_VIEW != (type= mysql_frm_type(thd, path, ¬_used)))
 | |
|     {
 | |
|       char name[FN_REFLEN];
 | |
|       my_snprintf(name, sizeof(name), "%s.%s", view->db, view->table_name);
 | |
|       if (thd->lex->drop_if_exists)
 | |
|       {
 | |
| 	push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
 | |
| 			    ER_BAD_TABLE_ERROR, ER(ER_BAD_TABLE_ERROR),
 | |
| 			    name);
 | |
| 	continue;
 | |
|       }
 | |
|       if (type == FRMTYPE_TABLE)
 | |
|       {
 | |
|         if (!wrong_object_name)
 | |
|         {
 | |
|           wrong_object_db= view->db;
 | |
|           wrong_object_name= view->table_name;
 | |
|         }
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         if (non_existant_views.length())
 | |
|           non_existant_views.append(',');
 | |
|         non_existant_views.append(String(view->table_name,system_charset_info));
 | |
|       }
 | |
|       continue;
 | |
|     }
 | |
|     if (my_delete(path, MYF(MY_WME)))
 | |
|       error= TRUE;
 | |
| 
 | |
|     some_views_deleted= TRUE;
 | |
| 
 | |
|     /*
 | |
|       For a view, there is only one table_share object which should never
 | |
|       be used outside of LOCK_open
 | |
|     */
 | |
|     if ((share= get_cached_table_share(view->db, view->table_name)))
 | |
|     {
 | |
|       DBUG_ASSERT(share->ref_count == 0);
 | |
|       pthread_mutex_lock(&share->mutex);
 | |
|       share->ref_count++;
 | |
|       share->version= 0;
 | |
|       pthread_mutex_unlock(&share->mutex);
 | |
|       release_table_share(share, RELEASE_WAIT_FOR_DROP);
 | |
|     }
 | |
|     query_cache_invalidate3(thd, view, 0);
 | |
|     sp_cache_invalidate();
 | |
|   }
 | |
| 
 | |
|   if (wrong_object_name)
 | |
|   {
 | |
|     my_error(ER_WRONG_OBJECT, MYF(0), wrong_object_db, wrong_object_name, 
 | |
|              "VIEW");
 | |
|   }
 | |
|   if (non_existant_views.length())
 | |
|   {
 | |
|     my_error(ER_BAD_TABLE_ERROR, MYF(0), non_existant_views.c_ptr());
 | |
|   }
 | |
| 
 | |
|   something_wrong= error || wrong_object_name || non_existant_views.length();
 | |
|   if (some_views_deleted || !something_wrong)
 | |
|   {
 | |
|     /* if something goes wrong, bin-log with possible error code,
 | |
|        otherwise bin-log with error code cleared.
 | |
|      */
 | |
|     if (write_bin_log(thd, !something_wrong, thd->query(), thd->query_length()))
 | |
|       something_wrong= 1;
 | |
|   }
 | |
| 
 | |
|   VOID(pthread_mutex_unlock(&LOCK_open));
 | |
|   
 | |
|   if (something_wrong)
 | |
|   {
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
|   my_ok(thd);
 | |
|   DBUG_RETURN(FALSE);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Check type of .frm if we are not going to parse it
 | |
| 
 | |
|   SYNOPSIS
 | |
|     mysql_frm_type()
 | |
|     path	path to file
 | |
| 
 | |
|   RETURN
 | |
|     FRMTYPE_ERROR	error
 | |
|     FRMTYPE_TABLE	table
 | |
|     FRMTYPE_VIEW	view
 | |
| */
 | |
| 
 | |
| frm_type_enum mysql_frm_type(THD *thd, char *path, enum legacy_db_type *dbt)
 | |
| {
 | |
|   File file;
 | |
|   uchar header[10];	//"TYPE=VIEW\n" it is 10 characters
 | |
|   size_t error;
 | |
|   DBUG_ENTER("mysql_frm_type");
 | |
| 
 | |
|   *dbt= DB_TYPE_UNKNOWN;
 | |
| 
 | |
|   if ((file= my_open(path, O_RDONLY | O_SHARE, MYF(0))) < 0)
 | |
|     DBUG_RETURN(FRMTYPE_ERROR);
 | |
|   error= my_read(file, (uchar*) header, sizeof(header), MYF(MY_NABP));
 | |
|   my_close(file, MYF(MY_WME));
 | |
| 
 | |
|   if (error)
 | |
|     DBUG_RETURN(FRMTYPE_ERROR);
 | |
|   if (!strncmp((char*) header, "TYPE=VIEW\n", sizeof(header)))
 | |
|     DBUG_RETURN(FRMTYPE_VIEW);
 | |
| 
 | |
|   /*
 | |
|     This is just a check for DB_TYPE. We'll return default unknown type
 | |
|     if the following test is true (arg #3). This should not have effect
 | |
|     on return value from this function (default FRMTYPE_TABLE)
 | |
|   */
 | |
|   if (header[0] != (uchar) 254 || header[1] != 1 ||
 | |
|       (header[2] != FRM_VER && header[2] != FRM_VER+1 &&
 | |
|        (header[2] < FRM_VER+3 || header[2] > FRM_VER+4)))
 | |
|     DBUG_RETURN(FRMTYPE_TABLE);
 | |
| 
 | |
|   *dbt= (enum legacy_db_type) (uint) *(header + 3);
 | |
|   DBUG_RETURN(FRMTYPE_TABLE);                   // Is probably a .frm table
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   check of key (primary or unique) presence in updatable view
 | |
| 
 | |
|   SYNOPSIS
 | |
|     check_key_in_view()
 | |
|     thd     thread handler
 | |
|     view    view for check with opened table
 | |
| 
 | |
|   DESCRIPTION
 | |
|     If it is VIEW and query have LIMIT clause then check that underlying
 | |
|     table of view contain one of following:
 | |
|       1) primary key of underlying table
 | |
|       2) unique key underlying table with fields for which NULL value is
 | |
|          impossible
 | |
|       3) all fields of underlying table
 | |
| 
 | |
|   RETURN
 | |
|     FALSE   OK
 | |
|     TRUE    view do not contain key or all fields
 | |
| */
 | |
| 
 | |
| bool check_key_in_view(THD *thd, TABLE_LIST *view)
 | |
| {
 | |
|   TABLE *table;
 | |
|   Field_translator *trans, *end_of_trans;
 | |
|   KEY *key_info, *key_info_end;
 | |
|   DBUG_ENTER("check_key_in_view");
 | |
| 
 | |
|   /*
 | |
|     we do not support updatable UNIONs in VIEW, so we can check just limit of
 | |
|     LEX::select_lex
 | |
|   */
 | |
|   if ((!view->view && !view->belong_to_view) ||
 | |
|       thd->lex->sql_command == SQLCOM_INSERT ||
 | |
|       thd->lex->select_lex.select_limit == 0)
 | |
|     DBUG_RETURN(FALSE); /* it is normal table or query without LIMIT */
 | |
|   table= view->table;
 | |
|   view= view->top_table();
 | |
|   trans= view->field_translation;
 | |
|   key_info_end= (key_info= table->key_info)+ table->s->keys;
 | |
| 
 | |
|   end_of_trans=  view->field_translation_end;
 | |
|   DBUG_ASSERT(table != 0 && view->field_translation != 0);
 | |
| 
 | |
|   {
 | |
|     /*
 | |
|       We should be sure that all fields are ready to get keys from them, but
 | |
|       this operation should not have influence on Field::query_id, to avoid
 | |
|       marking as used fields which are not used
 | |
|     */
 | |
|     enum_mark_columns save_mark_used_columns= thd->mark_used_columns;
 | |
|     thd->mark_used_columns= MARK_COLUMNS_NONE;
 | |
|     DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns));
 | |
|     for (Field_translator *fld= trans; fld < end_of_trans; fld++)
 | |
|     {
 | |
|       if (!fld->item->fixed && fld->item->fix_fields(thd, &fld->item))
 | |
|       {
 | |
|         thd->mark_used_columns= save_mark_used_columns;
 | |
|         DBUG_RETURN(TRUE);
 | |
|       }
 | |
|     }
 | |
|     thd->mark_used_columns= save_mark_used_columns;
 | |
|     DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns));
 | |
|   }
 | |
|   /* Loop over all keys to see if a unique-not-null key is used */
 | |
|   for (;key_info != key_info_end ; key_info++)
 | |
|   {
 | |
|     if ((key_info->flags & (HA_NOSAME | HA_NULL_PART_KEY)) == HA_NOSAME)
 | |
|     {
 | |
|       KEY_PART_INFO *key_part= key_info->key_part;
 | |
|       KEY_PART_INFO *key_part_end= key_part + key_info->key_parts;
 | |
| 
 | |
|       /* check that all key parts are used */
 | |
|       for (;;)
 | |
|       {
 | |
|         Field_translator *k;
 | |
|         for (k= trans; k < end_of_trans; k++)
 | |
|         {
 | |
|           Item_field *field;
 | |
|           if ((field= k->item->filed_for_view_update()) &&
 | |
|               field->field == key_part->field)
 | |
|             break;
 | |
|         }
 | |
|         if (k == end_of_trans)
 | |
|           break;                                // Key is not possible
 | |
|         if (++key_part == key_part_end)
 | |
|           DBUG_RETURN(FALSE);                   // Found usable key
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   DBUG_PRINT("info", ("checking if all fields of table are used"));
 | |
|   /* check all fields presence */
 | |
|   {
 | |
|     Field **field_ptr;
 | |
|     Field_translator *fld;
 | |
|     for (field_ptr= table->field; *field_ptr; field_ptr++)
 | |
|     {
 | |
|       for (fld= trans; fld < end_of_trans; fld++)
 | |
|       {
 | |
|         Item_field *field;
 | |
|         if ((field= fld->item->filed_for_view_update()) &&
 | |
|             field->field == *field_ptr)
 | |
|           break;
 | |
|       }
 | |
|       if (fld == end_of_trans)                // If field didn't exists
 | |
|       {
 | |
|         /*
 | |
|           Keys or all fields of underlying tables are not found => we have
 | |
|           to check variable updatable_views_with_limit to decide should we
 | |
|           issue an error or just a warning
 | |
|         */
 | |
|         if (thd->variables.updatable_views_with_limit)
 | |
|         {
 | |
|           /* update allowed, but issue warning */
 | |
|           push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
 | |
|                        ER_WARN_VIEW_WITHOUT_KEY, ER(ER_WARN_VIEW_WITHOUT_KEY));
 | |
|           DBUG_RETURN(FALSE);
 | |
|         }
 | |
|         /* prohibit update */
 | |
|         DBUG_RETURN(TRUE);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN(FALSE);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   insert fields from VIEW (MERGE algorithm) into given list
 | |
| 
 | |
|   SYNOPSIS
 | |
|     insert_view_fields()
 | |
|     thd       thread handler
 | |
|     list      list for insertion
 | |
|     view      view for processing
 | |
| 
 | |
|   RETURN
 | |
|     FALSE OK
 | |
|     TRUE  error (is not sent to cliet)
 | |
| */
 | |
| 
 | |
| bool insert_view_fields(THD *thd, List<Item> *list, TABLE_LIST *view)
 | |
| {
 | |
|   Field_translator *trans_end;
 | |
|   Field_translator *trans;
 | |
|   DBUG_ENTER("insert_view_fields");
 | |
| 
 | |
|   if (!(trans= view->field_translation))
 | |
|     DBUG_RETURN(FALSE);
 | |
|   trans_end= view->field_translation_end;
 | |
| 
 | |
|   for (Field_translator *entry= trans; entry < trans_end; entry++)
 | |
|   {
 | |
|     Item_field *fld;
 | |
|     if ((fld= entry->item->filed_for_view_update()))
 | |
|       list->push_back(fld);
 | |
|     else
 | |
|     {
 | |
|       my_error(ER_NON_INSERTABLE_TABLE, MYF(0), view->alias, "INSERT");
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN(FALSE);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   checking view md5 check suum
 | |
| 
 | |
|   SINOPSYS
 | |
|     view_checksum()
 | |
|     thd     threar handler
 | |
|     view    view for check
 | |
| 
 | |
|   RETUIRN
 | |
|     HA_ADMIN_OK               OK
 | |
|     HA_ADMIN_NOT_IMPLEMENTED  it is not VIEW
 | |
|     HA_ADMIN_WRONG_CHECKSUM   check sum is wrong
 | |
| */
 | |
| 
 | |
| int view_checksum(THD *thd, TABLE_LIST *view)
 | |
| {
 | |
|   char md5[MD5_BUFF_LENGTH];
 | |
|   if (!view->view || view->md5.length != 32)
 | |
|     return HA_ADMIN_NOT_IMPLEMENTED;
 | |
|   view->calc_md5(md5);
 | |
|   return (strncmp(md5, view->md5.str, 32) ?
 | |
|           HA_ADMIN_WRONG_CHECKSUM :
 | |
|           HA_ADMIN_OK);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   rename view
 | |
| 
 | |
|   Synopsis:
 | |
|     renames a view
 | |
| 
 | |
|   Parameters:
 | |
|     thd        thread handler
 | |
|     new_db     new name of database
 | |
|     new_name   new name of view
 | |
|     view       view
 | |
| 
 | |
|   Return values:
 | |
|     FALSE      Ok 
 | |
|     TRUE       Error
 | |
| */
 | |
| bool
 | |
| mysql_rename_view(THD *thd,
 | |
|                   const char *new_db,
 | |
|                   const char *new_name,
 | |
|                   TABLE_LIST *view)
 | |
| {
 | |
|   LEX_STRING pathstr;
 | |
|   File_parser *parser;
 | |
|   char path_buff[FN_REFLEN + 1];
 | |
|   bool error= TRUE;
 | |
|   DBUG_ENTER("mysql_rename_view");
 | |
| 
 | |
|   pathstr.str= (char *) path_buff;
 | |
|   pathstr.length= build_table_filename(path_buff, sizeof(path_buff) - 1,
 | |
|                                        view->db, view->table_name,
 | |
|                                        reg_ext, 0);
 | |
| 
 | |
|   if ((parser= sql_parse_prepare(&pathstr, thd->mem_root, 1)) && 
 | |
|        is_equal(&view_type, parser->type()))
 | |
|   {
 | |
|     TABLE_LIST view_def;
 | |
|     char dir_buff[FN_REFLEN + 1];
 | |
|     LEX_STRING dir, file;
 | |
| 
 | |
|     /*
 | |
|       To be PS-friendly we should either to restore state of
 | |
|       TABLE_LIST object pointed by 'view' after using it for
 | |
|       view definition parsing or use temporary 'view_def'
 | |
|       object for it.
 | |
|     */
 | |
|     bzero(&view_def, sizeof(view_def));
 | |
|     view_def.timestamp.str= view_def.timestamp_buffer;
 | |
|     view_def.view_suid= TRUE;
 | |
| 
 | |
|     /* get view definition and source */
 | |
|     if (parser->parse((uchar*)&view_def, thd->mem_root, view_parameters,
 | |
|                       array_elements(view_parameters)-1,
 | |
|                       &file_parser_dummy_hook))
 | |
|       goto err;
 | |
| 
 | |
|     /* rename view and it's backups */
 | |
|     if (rename_in_schema_file(thd, view->db, view->table_name, new_db, new_name))
 | |
|       goto err;
 | |
| 
 | |
|     dir.str= dir_buff;
 | |
|     dir.length= build_table_filename(dir_buff, sizeof(dir_buff) - 1,
 | |
|                                      new_db, "", "", 0);
 | |
| 
 | |
|     pathstr.str= path_buff;
 | |
|     pathstr.length= build_table_filename(path_buff, sizeof(path_buff) - 1,
 | |
|                                          new_db, new_name, reg_ext, 0);
 | |
| 
 | |
|     file.str= pathstr.str + dir.length;
 | |
|     file.length= pathstr.length - dir.length;
 | |
| 
 | |
|     if (sql_create_definition_file(&dir, &file, view_file_type,
 | |
|                                    (uchar*)&view_def, view_parameters))
 | |
|     {
 | |
|       /* restore renamed view in case of error */
 | |
|       rename_in_schema_file(thd, new_db, new_name, view->db, view->table_name);
 | |
|       goto err;
 | |
|     }
 | |
|   } else
 | |
|     DBUG_RETURN(1);  
 | |
| 
 | |
|   /* remove cache entries */
 | |
|   query_cache_invalidate3(thd, view, 0);
 | |
|   sp_cache_invalidate();
 | |
|   error= FALSE;
 | |
| 
 | |
| err:
 | |
|   DBUG_RETURN(error);
 | |
| }
 |