mirror of
				https://github.com/MariaDB/server.git
				synced 2025-11-04 04:46:15 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			2390 lines
		
	
	
	
		
			73 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2390 lines
		
	
	
	
		
			73 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* Copyright (c) 2004, 2013, Oracle and/or its affiliates.
 | 
						|
   Copyright (c) 2011, 2021, MariaDB Corporation.
 | 
						|
 | 
						|
   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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1335  USA
 | 
						|
*/
 | 
						|
 | 
						|
#define MYSQL_LEX 1
 | 
						|
#include "mariadb.h"           /* NO_EMBEDDED_ACCESS_CHECKS */
 | 
						|
#include "sql_priv.h"
 | 
						|
#include "unireg.h"
 | 
						|
#include "sql_view.h"
 | 
						|
#include "sql_base.h"          // find_table_in_global_list, lock_table_names
 | 
						|
#include "sql_parse.h"         // sql_parse
 | 
						|
#include "sql_cache.h"         // query_cache_*
 | 
						|
#include "lock.h"              // MYSQL_OPEN_SKIP_TEMPORARY
 | 
						|
#include "sql_show.h"          // append_identifier
 | 
						|
#include "sql_table.h"         // build_table_filename
 | 
						|
#include "sql_db.h"            // mysql_opt_change_db, mysql_change_db
 | 
						|
#include "sql_select.h"
 | 
						|
#include "parse_file.h"
 | 
						|
#include "sp_head.h"
 | 
						|
#include "sp.h"
 | 
						|
#include "sp_cache.h"
 | 
						|
#include "datadict.h"          // dd_frm_is_view()
 | 
						|
#include "sql_derived.h"
 | 
						|
#include "opt_trace.h"
 | 
						|
#include "ddl_log.h"
 | 
						|
#include "debug.h"              // debug_crash_here
 | 
						|
#include "wsrep_mysqld.h"
 | 
						|
 | 
						|
#define MD5_BUFF_LENGTH 33
 | 
						|
 | 
						|
const LEX_CSTRING view_type= { STRING_WITH_LEN("VIEW") };
 | 
						|
 | 
						|
static int mysql_register_view(THD *thd, DDL_LOG_STATE *ddl_log_state,
 | 
						|
                               TABLE_LIST *view, enum_view_create_mode mode,
 | 
						|
                               char *backup_file_name);
 | 
						|
 | 
						|
/*
 | 
						|
  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(THD *thd, Item *target,
 | 
						|
                                        List<Item> &item_list,
 | 
						|
                                        Item *last_element)
 | 
						|
{
 | 
						|
  const char *name= (target->orig_name ?
 | 
						|
                     target->orig_name :
 | 
						|
                     target->name.str);
 | 
						|
  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.str) == 0)
 | 
						|
      {
 | 
						|
        ok= FALSE;
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    } while (check != last_element);
 | 
						|
    if (ok)
 | 
						|
      break;
 | 
						|
    itc.rewind();
 | 
						|
  }
 | 
						|
 | 
						|
  if (!target->orig_name)
 | 
						|
    target->orig_name= target->name.str;
 | 
						|
  target->set_name(thd, 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(THD *thd, 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->base_flags|= item_base_t::IS_EXPLICIT_NAME;
 | 
						|
    itc.rewind();
 | 
						|
    while ((check= itc++) && check != item)
 | 
						|
    {
 | 
						|
      if (lex_string_cmp(system_charset_info, &item->name, &check->name) == 0)
 | 
						|
      {
 | 
						|
        if (!gen_unique_view_name)
 | 
						|
          goto err;
 | 
						|
        if (!item->is_explicit_name())
 | 
						|
          make_unique_view_field_name(thd, item, item_list, item);
 | 
						|
        else if (!check->is_explicit_name())
 | 
						|
          make_unique_view_field_name(thd, check, item_list, item);
 | 
						|
        else
 | 
						|
          goto err;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  DBUG_RETURN(FALSE);
 | 
						|
 | 
						|
err:
 | 
						|
  my_error(ER_DUP_FIELDNAME, MYF(0), item->name.str);
 | 
						|
  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
 | 
						|
*/
 | 
						|
 | 
						|
void make_valid_column_names(THD *thd, List<Item> &item_list)
 | 
						|
{
 | 
						|
  Item *item;
 | 
						|
  size_t 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_explicit_name() || !check_column_name(item->name))
 | 
						|
      continue;
 | 
						|
    name_len= my_snprintf(buff, NAME_LEN, "Name_exp_%u", column_no);
 | 
						|
    item->orig_name= item->name.str;
 | 
						|
    item->set_name(thd, 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;
 | 
						|
  TABLE_LIST decoy;
 | 
						|
 | 
						|
  decoy= *view;
 | 
						|
  decoy.mdl_request.key.mdl_key_init(&view->mdl_request.key);
 | 
						|
  if (tdc_open_view(thd, &decoy, OPEN_VIEW_NO_PARSE))
 | 
						|
    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_INHERIT)
 | 
						|
    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 occurred.
 | 
						|
*/
 | 
						|
 | 
						|
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->first_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.str,
 | 
						|
                    &view->grant.privilege,
 | 
						|
                    &view->grant.m_internal,
 | 
						|
                    0, 0) ||
 | 
						|
       check_grant(thd, CREATE_VIEW_ACL, view, FALSE, 1, FALSE)) ||
 | 
						|
      (mode != VIEW_CREATE_NEW &&
 | 
						|
       (check_access(thd, DROP_ACL, view->db.str,
 | 
						|
                     &view->grant.privilege,
 | 
						|
                     &view->grant.m_internal,
 | 
						|
                     0, 0) ||
 | 
						|
        check_grant(thd, DROP_ACL, view, FALSE, 1, FALSE))))
 | 
						|
    goto err;
 | 
						|
 | 
						|
  for (sl= select_lex; sl; sl= sl->next_select())
 | 
						|
  {
 | 
						|
    for (tbl= sl->get_table_list(); tbl; tbl= tbl->next_local)
 | 
						|
    {
 | 
						|
      if (!tbl->with && tbl->select_lex)
 | 
						|
      tbl->with= tbl->select_lex->find_table_def_in_with_clauses(tbl,
 | 
						|
                                                                 NULL);
 | 
						|
      /*
 | 
						|
        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->db.str, tbl->table_name.str);
 | 
						|
        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.str, tbl->table_name.str);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    /* check tables of subqueries */
 | 
						|
    for (tbl= tables; tbl; tbl= tbl->next_global)
 | 
						|
    {
 | 
						|
      if (!tbl->table_in_first_from_clause)
 | 
						|
      {
 | 
						|
        if (check_single_table_access(thd, SELECT_ACL, tbl, FALSE))
 | 
						|
        {
 | 
						|
          tbl->replace_view_error_with_generic(thd);
 | 
						|
          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->field_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 occurred.
 | 
						|
*/
 | 
						|
 | 
						|
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->first_select_lex();
 | 
						|
  SELECT_LEX *sl;
 | 
						|
  SELECT_LEX_UNIT *unit= &lex->unit;
 | 
						|
  DDL_LOG_STATE ddl_log_state, ddl_log_state_tmp_file;
 | 
						|
  char backup_file_name[FN_REFLEN+2];
 | 
						|
  bool res= FALSE;
 | 
						|
  DBUG_ENTER("mysql_create_view");
 | 
						|
 | 
						|
  /*
 | 
						|
    This is ensured in the parser.
 | 
						|
    NOTE: Originally, the assert below contained the extra condition
 | 
						|
      && !lex->result
 | 
						|
    but in this form the assert is failed in case CREATE VIEW run under
 | 
						|
    cursor (the case when the byte 'flags' in the COM_STMT_EXECUTE packet has
 | 
						|
    the flag CURSOR_TYPE_READ_ONLY set). For the cursor use case
 | 
						|
    thd->lex->result is assigned a pointer to the class Select_materialize
 | 
						|
    inside the function mysql_open_cursor() just before handling of a statement
 | 
						|
    will be started and the function mysql_create_view() called.
 | 
						|
  */
 | 
						|
  DBUG_ASSERT(!lex->proc_list.first &&
 | 
						|
              !lex->param_list.elements);
 | 
						|
 | 
						|
  bzero(&ddl_log_state, sizeof(ddl_log_state));
 | 
						|
  bzero(&ddl_log_state_tmp_file, sizeof(ddl_log_state_tmp_file));
 | 
						|
  backup_file_name[0]= 0;
 | 
						|
  /*
 | 
						|
    We can't allow taking exclusive meta-data locks of unlocked view under
 | 
						|
    LOCK TABLES since this might lead to deadlock. Since at the moment we
 | 
						|
    can't really lock view with LOCK TABLES we simply prohibit creation/
 | 
						|
    alteration of views under LOCK TABLES.
 | 
						|
  */
 | 
						|
 | 
						|
  if (thd->locked_tables_mode)
 | 
						|
  {
 | 
						|
    my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
 | 
						|
    res= TRUE;
 | 
						|
    goto err;
 | 
						|
  }
 | 
						|
 | 
						|
  if ((res= create_view_precheck(thd, tables, view, mode)))
 | 
						|
    goto err;
 | 
						|
 | 
						|
  lex->link_first_table_back(view, link_to_local);
 | 
						|
  view->open_type= OT_BASE_ONLY;
 | 
						|
 | 
						|
  /*
 | 
						|
    ignore lock specs for CREATE statement
 | 
						|
  */
 | 
						|
  if (lex->current_select->lock_type != TL_READ_DEFAULT)
 | 
						|
  {
 | 
						|
    lex->current_select->set_lock_for_tables(TL_READ_DEFAULT, false, select_lex->skip_locked);
 | 
						|
    view->mdl_request.set_type(MDL_EXCLUSIVE);
 | 
						|
  }
 | 
						|
 | 
						|
  if (thd->open_temporary_tables(lex->query_tables) ||
 | 
						|
      open_and_lock_tables(thd, lex->query_tables, TRUE, 0))
 | 
						|
  {
 | 
						|
    res= TRUE;
 | 
						|
    goto err_no_relink;
 | 
						|
  }
 | 
						|
 | 
						|
#ifdef WITH_WSREP
 | 
						|
  /* Resolve should we replicate creation of the view.
 | 
						|
     It should be replicated if storage engine(s) associated
 | 
						|
     to view are replicated by Galera.
 | 
						|
  */
 | 
						|
  if (WSREP(thd) &&
 | 
						|
      !wsrep_should_replicate_ddl_iterate(thd, tables))
 | 
						|
  {
 | 
						|
    res= TRUE;
 | 
						|
    goto err_no_relink;
 | 
						|
  }
 | 
						|
#endif
 | 
						|
 | 
						|
  WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL);
 | 
						|
 | 
						|
  view= lex->unlink_first_table(&link_to_local);
 | 
						|
 | 
						|
  if (check_db_dir_existence(view->db.str))
 | 
						|
  {
 | 
						|
    my_error(ER_BAD_DB_ERROR, MYF(0), view->db.str);
 | 
						|
    res= TRUE;
 | 
						|
    goto err;
 | 
						|
  }
 | 
						|
 | 
						|
  if (mode == VIEW_ALTER && fill_defined_view_parts(thd, view))
 | 
						|
  {
 | 
						|
    res= TRUE;
 | 
						|
    goto err;
 | 
						|
  }
 | 
						|
 | 
						|
  if (lex->limit_rows_examined)
 | 
						|
  {
 | 
						|
    /*
 | 
						|
      LIMIT ROWS EXAMINED is not supported inside views to avoid complicated
 | 
						|
      side-effects and semantics of the clause.
 | 
						|
    */
 | 
						|
    my_error(ER_NOT_SUPPORTED_YET, MYF(0), "LIMIT ROWS EXAMINED inside views");
 | 
						|
    res= TRUE;
 | 
						|
    goto err;
 | 
						|
  }
 | 
						|
 | 
						|
  sp_cache_invalidate();
 | 
						|
  if (sp_process_definer(thd))
 | 
						|
    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 &&
 | 
						|
        cmp(&tbl->view_db, &view->db) == 0 &&
 | 
						|
        cmp(&tbl->view_name, &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 && !tbl->table_function)
 | 
						|
      {
 | 
						|
        my_error(ER_VIEW_SELECT_TMPTABLE, MYF(0), tbl->alias.str);
 | 
						|
        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(unit->derived, 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_CSTRING> nm(lex->view_list);
 | 
						|
    Item *item;
 | 
						|
    LEX_CSTRING *name;
 | 
						|
 | 
						|
    if (lex->view_list.elements != select_lex->item_list.elements)
 | 
						|
    {
 | 
						|
      my_message(ER_VIEW_WRONG_LIST, ER_THD(thd, ER_VIEW_WRONG_LIST), MYF(0));
 | 
						|
      res= TRUE;
 | 
						|
      goto err;
 | 
						|
    }
 | 
						|
    while ((item= it++, name= nm++))
 | 
						|
    {
 | 
						|
      item->set_name(thd, *name);
 | 
						|
      item->base_flags|= item_base_t::IS_EXPLICIT_NAME;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /* Check if the auto generated column names are conforming. */
 | 
						|
  for (sl= select_lex; sl; sl= sl->next_select())
 | 
						|
    make_valid_column_names(thd, sl->item_list);
 | 
						|
 | 
						|
  if (check_duplicate_names(thd, 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.str,
 | 
						|
                                  view->table_name.str);
 | 
						|
 | 
						|
  /*
 | 
						|
    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.
 | 
						|
     */
 | 
						|
    privilege_t final_priv(VIEW_ANY_ACL);
 | 
						|
    
 | 
						|
    for (sl= select_lex; sl; sl= sl->next_select())
 | 
						|
    {
 | 
						|
      DBUG_ASSERT(view->db.str);       /* Must be set in the parser */
 | 
						|
      List_iterator_fast<Item> it(sl->item_list);
 | 
						|
      Item *item;
 | 
						|
      while ((item= it++))
 | 
						|
      {
 | 
						|
        Item_field *fld= item->field_for_view_update();
 | 
						|
        privilege_t priv(get_column_grant(thd, &view->grant, view->db.str,
 | 
						|
                                          view->table_name.str,
 | 
						|
                                          item->name.str) &
 | 
						|
                    VIEW_ANY_ACL);
 | 
						|
 | 
						|
        if (!fld)
 | 
						|
          continue;
 | 
						|
        TABLE_SHARE *s= fld->field->table->s;
 | 
						|
        const Lex_ident field_name= fld->field->field_name;
 | 
						|
        if (s->tmp_table ||
 | 
						|
            (s->versioned &&
 | 
						|
             (field_name.streq(s->vers_start_field()->field_name) ||
 | 
						|
              field_name.streq(s->vers_end_field()->field_name))))
 | 
						|
        {
 | 
						|
          continue;
 | 
						|
        }
 | 
						|
 | 
						|
        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.str,
 | 
						|
               view->table_name.str);
 | 
						|
      res= TRUE;
 | 
						|
      goto err;
 | 
						|
    }
 | 
						|
  }
 | 
						|
#endif
 | 
						|
 | 
						|
  res= mysql_register_view(thd, &ddl_log_state, view, mode, backup_file_name);
 | 
						|
 | 
						|
  /*
 | 
						|
    View TABLE_SHARE must be removed from the table definition cache in order to
 | 
						|
    make ALTER VIEW work properly. Otherwise, we would not be able to detect
 | 
						|
    meta-data changes after ALTER VIEW.
 | 
						|
  */
 | 
						|
 | 
						|
  if (!res)
 | 
						|
    tdc_remove_table(thd, view->db.str, view->table_name.str);
 | 
						|
 | 
						|
  if (!res && mysql_bin_log.is_open())
 | 
						|
  {
 | 
						|
    StringBuffer<128> buff(thd->variables.character_set_client);
 | 
						|
    DBUG_ASSERT(buff.charset()->mbminlen == 1);
 | 
						|
    const LEX_CSTRING command[3]=
 | 
						|
      {{ STRING_WITH_LEN("CREATE ") },
 | 
						|
       { STRING_WITH_LEN("ALTER ") },
 | 
						|
       { STRING_WITH_LEN("CREATE OR REPLACE ") }};
 | 
						|
 | 
						|
    buff.append(&command[thd->lex->create_view->mode]);
 | 
						|
    view_store_options(thd, views, &buff);
 | 
						|
    buff.append(STRING_WITH_LEN("VIEW "));
 | 
						|
 | 
						|
    /* Appending IF NOT EXISTS if present in the query */
 | 
						|
    if (lex->create_info.if_not_exists())
 | 
						|
      buff.append(STRING_WITH_LEN("IF NOT EXISTS "));
 | 
						|
 | 
						|
    /* Test if user supplied a db (ie: we did not use thd->db) */
 | 
						|
    if (views->db.str && views->db.str[0] &&
 | 
						|
        (thd->db.str == NULL || cmp(&views->db, &thd->db)))
 | 
						|
    {
 | 
						|
      append_identifier(thd, &buff, &views->db);
 | 
						|
      buff.append('.');
 | 
						|
    }
 | 
						|
    append_identifier(thd, &buff, &views->table_name);
 | 
						|
    if (lex->view_list.elements)
 | 
						|
    {
 | 
						|
      List_iterator_fast<LEX_CSTRING> names(lex->view_list);
 | 
						|
 | 
						|
      buff.append('(');
 | 
						|
      while (LEX_CSTRING *name= names++)
 | 
						|
      {
 | 
						|
        append_identifier(thd, &buff, name);
 | 
						|
        buff.append(", ", 2);
 | 
						|
      }
 | 
						|
      buff.length(buff.length()-2);
 | 
						|
      buff.append(')');
 | 
						|
    }
 | 
						|
    buff.append(STRING_WITH_LEN(" AS "));
 | 
						|
    /* views->source doesn't end with \0 */
 | 
						|
    buff.append(views->source.str, views->source.length);
 | 
						|
 | 
						|
    int errcode= query_error_code(thd, TRUE);
 | 
						|
    /*
 | 
						|
      Don't log any unsafe warnings for CREATE VIEW as it's safely replicated
 | 
						|
      with statement based replication
 | 
						|
    */
 | 
						|
    thd->reset_unsafe_warnings();
 | 
						|
    thd->binlog_xid= thd->query_id;
 | 
						|
    ddl_log_update_xid(&ddl_log_state, thd->binlog_xid);
 | 
						|
    if (backup_file_name[0])
 | 
						|
    {
 | 
						|
      LEX_CSTRING cpath= {backup_file_name, strlen(backup_file_name) };
 | 
						|
      ddl_log_delete_tmp_file(&ddl_log_state_tmp_file, &cpath,
 | 
						|
                              &ddl_log_state);
 | 
						|
    }
 | 
						|
    debug_crash_here("ddl_log_create_before_binlog");
 | 
						|
    if (thd->binlog_query(THD::STMT_QUERY_TYPE,
 | 
						|
                          buff.ptr(), buff.length(), FALSE, FALSE, FALSE,
 | 
						|
                          errcode) > 0)
 | 
						|
      res= TRUE;
 | 
						|
    thd->binlog_xid= 0;
 | 
						|
    debug_crash_here("ddl_log_create_after_binlog");
 | 
						|
  }
 | 
						|
  if (!res)
 | 
						|
  {
 | 
						|
    backup_log_info ddl_log;
 | 
						|
    bzero(&ddl_log, sizeof(ddl_log));
 | 
						|
    ddl_log.query= { C_STRING_WITH_LEN("CREATE") };
 | 
						|
    ddl_log.org_storage_engine_name= { C_STRING_WITH_LEN("VIEW") };
 | 
						|
    ddl_log.org_database=     view->db;
 | 
						|
    ddl_log.org_table=        view->table_name;
 | 
						|
    backup_log_ddl(&ddl_log);
 | 
						|
  }
 | 
						|
 | 
						|
  if (mode != VIEW_CREATE_NEW)
 | 
						|
    query_cache_invalidate3(thd, view, 0);
 | 
						|
  if (res)
 | 
						|
    goto err;
 | 
						|
 | 
						|
  if (backup_file_name[0] &&
 | 
						|
      mysql_file_delete(key_file_fileparser, backup_file_name, MYF(MY_WME)))
 | 
						|
    goto err;                                   // Should be impossible
 | 
						|
 | 
						|
  my_ok(thd);
 | 
						|
  lex->link_first_table_back(view, link_to_local);
 | 
						|
  ddl_log_complete(&ddl_log_state);
 | 
						|
  ddl_log_complete(&ddl_log_state_tmp_file);
 | 
						|
  DBUG_RETURN(0);
 | 
						|
 | 
						|
#ifdef WITH_WSREP
 | 
						|
wsrep_error_label:
 | 
						|
  res= true;
 | 
						|
  goto err_no_relink;
 | 
						|
#endif
 | 
						|
 | 
						|
err:
 | 
						|
  lex->link_first_table_back(view, link_to_local);
 | 
						|
err_no_relink:
 | 
						|
  unit->cleanup();
 | 
						|
  if (backup_file_name[0])
 | 
						|
    mysql_file_delete(key_file_fileparser, backup_file_name, MYF(MY_WME));
 | 
						|
  ddl_log_complete(&ddl_log_state);
 | 
						|
  ddl_log_complete(&ddl_log_state_tmp_file);
 | 
						|
  DBUG_RETURN(res || thd->is_error());
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void make_view_filename(LEX_CSTRING *dir, char *dir_buff,
 | 
						|
                               size_t dir_buff_len,
 | 
						|
                               LEX_CSTRING *path, char *path_buff,
 | 
						|
                               size_t path_buff_len,
 | 
						|
                               LEX_CSTRING *file,
 | 
						|
                               TABLE_LIST *view)
 | 
						|
{
 | 
						|
  /* print file name */
 | 
						|
  dir->length= build_table_filename(dir_buff, dir_buff_len - 1,
 | 
						|
                                   view->db.str, "", "", 0);
 | 
						|
  dir->str= dir_buff;
 | 
						|
 | 
						|
  path->length= build_table_filename(path_buff, path_buff_len - 1,
 | 
						|
                                     view->db.str, view->table_name.str, reg_ext, 0);
 | 
						|
  path->str= path_buff;
 | 
						|
 | 
						|
  file->str= path->str + dir->length;
 | 
						|
  file->length= path->length - dir->length;
 | 
						|
}
 | 
						|
 | 
						|
/* number of required parameters for making view */
 | 
						|
static const int required_view_parameters= 15;
 | 
						|
 | 
						|
/*
 | 
						|
  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[]=
 | 
						|
{{{ STRING_WITH_LEN("query")},
 | 
						|
  my_offsetof(TABLE_LIST, select_stmt),
 | 
						|
  FILE_OPTIONS_ESTRING},
 | 
						|
 {{ STRING_WITH_LEN("md5")},
 | 
						|
  my_offsetof(TABLE_LIST, md5),
 | 
						|
  FILE_OPTIONS_STRING},
 | 
						|
 {{ STRING_WITH_LEN("updatable")},
 | 
						|
  my_offsetof(TABLE_LIST, updatable_view),
 | 
						|
  FILE_OPTIONS_ULONGLONG},
 | 
						|
 {{ STRING_WITH_LEN("algorithm")},
 | 
						|
  my_offsetof(TABLE_LIST, algorithm),
 | 
						|
  FILE_OPTIONS_VIEW_ALGO},
 | 
						|
 {{ STRING_WITH_LEN("definer_user")},
 | 
						|
  my_offsetof(TABLE_LIST, definer.user),
 | 
						|
  FILE_OPTIONS_STRING},
 | 
						|
 {{ STRING_WITH_LEN("definer_host")},
 | 
						|
  my_offsetof(TABLE_LIST, definer.host),
 | 
						|
  FILE_OPTIONS_STRING},
 | 
						|
 {{ STRING_WITH_LEN("suid")},
 | 
						|
  my_offsetof(TABLE_LIST, view_suid),
 | 
						|
  FILE_OPTIONS_ULONGLONG},
 | 
						|
 {{ STRING_WITH_LEN("with_check_option")},
 | 
						|
  my_offsetof(TABLE_LIST, with_check),
 | 
						|
  FILE_OPTIONS_ULONGLONG},
 | 
						|
 {{ STRING_WITH_LEN("timestamp")},
 | 
						|
  my_offsetof(TABLE_LIST, hr_timestamp),
 | 
						|
  FILE_OPTIONS_TIMESTAMP},
 | 
						|
 {{ STRING_WITH_LEN("create-version")},
 | 
						|
  my_offsetof(TABLE_LIST, file_version),
 | 
						|
  FILE_OPTIONS_ULONGLONG},
 | 
						|
 {{ 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},
 | 
						|
 {{ STRING_WITH_LEN("mariadb-version")},
 | 
						|
  my_offsetof(TABLE_LIST, mariadb_version),
 | 
						|
  FILE_OPTIONS_ULONGLONG},
 | 
						|
 {{NullS, 0},			0,
 | 
						|
  FILE_OPTIONS_STRING}
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
static File_option view_timestamp_parameters[]=
 | 
						|
{
 | 
						|
 | 
						|
 {{ C_STRING_WITH_LEN("timestamp")}, 0, FILE_OPTIONS_TIMESTAMP},
 | 
						|
 {{NullS, 0}, 0, FILE_OPTIONS_STRING}
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
static LEX_CSTRING view_file_type[]= {{STRING_WITH_LEN("VIEW") }};
 | 
						|
 | 
						|
 | 
						|
int mariadb_fix_view(THD *thd, TABLE_LIST *view, bool wrong_checksum,
 | 
						|
                     bool swap_alg)
 | 
						|
{
 | 
						|
  char dir_buff[FN_REFLEN + 1], path_buff[FN_REFLEN + 1];
 | 
						|
  LEX_CSTRING dir, file, path;
 | 
						|
  DBUG_ENTER("mariadb_fix_view");
 | 
						|
 | 
						|
  if (!wrong_checksum && view->mariadb_version)
 | 
						|
    DBUG_RETURN(HA_ADMIN_OK);
 | 
						|
 | 
						|
  make_view_filename(&dir, dir_buff, sizeof(dir_buff),
 | 
						|
                     &path, path_buff, sizeof(path_buff),
 | 
						|
                     &file, view);
 | 
						|
  /* init timestamp */
 | 
						|
  if (!view->hr_timestamp.str)
 | 
						|
    view->hr_timestamp.str= view->timestamp_buffer;
 | 
						|
 | 
						|
  if (swap_alg && view->algorithm != VIEW_ALGORITHM_UNDEFINED)
 | 
						|
  {
 | 
						|
    DBUG_ASSERT(view->algorithm == VIEW_ALGORITHM_MERGE ||
 | 
						|
                view->algorithm == VIEW_ALGORITHM_TMPTABLE);
 | 
						|
    if (view->algorithm == VIEW_ALGORITHM_MERGE)
 | 
						|
      view->algorithm= VIEW_ALGORITHM_TMPTABLE;
 | 
						|
    else
 | 
						|
      view->algorithm= VIEW_ALGORITHM_MERGE;
 | 
						|
  }
 | 
						|
  else
 | 
						|
    swap_alg= 0;
 | 
						|
  if (wrong_checksum)
 | 
						|
  {
 | 
						|
    if (view->md5.length != VIEW_MD5_LEN)
 | 
						|
    {
 | 
						|
       if ((view->md5.str= (char *)thd->alloc(VIEW_MD5_LEN + 1)) == NULL)
 | 
						|
         DBUG_RETURN(HA_ADMIN_FAILED);
 | 
						|
    }
 | 
						|
    view->calc_md5(const_cast<char*>(view->md5.str));
 | 
						|
    view->md5.length= VIEW_MD5_LEN;
 | 
						|
  }
 | 
						|
  view->mariadb_version= MYSQL_VERSION_ID;
 | 
						|
 | 
						|
  if (sql_create_definition_file(&dir, &file, view_file_type,
 | 
						|
                                (uchar*)view, view_parameters))
 | 
						|
  {
 | 
						|
    sql_print_error("View '%-.192s'.'%-.192s': algorithm swap error.",
 | 
						|
                    view->db.str, view->table_name.str);
 | 
						|
    DBUG_RETURN(HA_ADMIN_INTERNAL_ERROR);
 | 
						|
  }
 | 
						|
  sql_print_information("View %`s.%`s: the version is set to %llu%s%s",
 | 
						|
                        view->db.str, view->table_name.str,
 | 
						|
                        view->mariadb_version,
 | 
						|
                        (wrong_checksum ? ", checksum corrected" : ""),
 | 
						|
                        (swap_alg ?
 | 
						|
                          ((view->algorithm == VIEW_ALGORITHM_MERGE) ?
 | 
						|
                            ", algorithm restored to be MERGE"
 | 
						|
                           : ", algorithm restored to be TEMPTABLE")
 | 
						|
                         : ""));
 | 
						|
 | 
						|
 | 
						|
  DBUG_RETURN(HA_ADMIN_OK);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
  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
 | 
						|
    backup_file_name  - Store name for backup of old view definition here
 | 
						|
 | 
						|
  RETURN
 | 
						|
     0	OK
 | 
						|
    -1	Error
 | 
						|
     1	Error and error message given
 | 
						|
*/
 | 
						|
 | 
						|
static int mysql_register_view(THD *thd, DDL_LOG_STATE *ddl_log_state,
 | 
						|
                               TABLE_LIST *view, enum_view_create_mode mode,
 | 
						|
                               char *backup_file_name)
 | 
						|
{
 | 
						|
  LEX *lex= thd->lex;
 | 
						|
 | 
						|
  /*
 | 
						|
    Ensure character set number != 17 (character set = filename) and mbminlen=1
 | 
						|
    because these character sets are not parser friendly, which can give weird
 | 
						|
    sequence in .frm file of view and later give parsing error.
 | 
						|
  */
 | 
						|
  DBUG_ASSERT(thd->charset()->mbminlen == 1 && thd->charset()->number != 17);
 | 
						|
 | 
						|
  /*
 | 
						|
    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.
 | 
						|
  */
 | 
						|
  StringBuffer<4096> view_query(thd->charset());
 | 
						|
  StringBuffer<4096> is_query(system_charset_info);
 | 
						|
 | 
						|
  char md5[MD5_BUFF_LENGTH];
 | 
						|
  bool can_be_merged;
 | 
						|
  char dir_buff[FN_REFLEN + 1], path_buff[FN_REFLEN + 1];
 | 
						|
  LEX_CSTRING dir, file, path;
 | 
						|
  int error= 0;
 | 
						|
  bool old_view_exists= 0;
 | 
						|
  DBUG_ENTER("mysql_register_view");
 | 
						|
 | 
						|
  /* Generate view definition and IS queries. */
 | 
						|
  view_query.length(0);
 | 
						|
  is_query.length(0);
 | 
						|
  backup_file_name[0]= 0;
 | 
						|
  {
 | 
						|
    Sql_mode_save_for_frm_handling sql_mode_save(thd);
 | 
						|
 | 
						|
    lex->unit.print(&view_query, enum_query_type(QT_FOR_FRM |
 | 
						|
                                                 QT_VIEW_INTERNAL |
 | 
						|
                                                 QT_ITEM_ORIGINAL_FUNC_NULLIF |
 | 
						|
                                                 QT_NO_WRAPPERS_FOR_TVC_IN_VIEW));
 | 
						|
    lex->unit.print(&is_query, enum_query_type(QT_TO_SYSTEM_CHARSET |
 | 
						|
                                               QT_WITHOUT_INTRODUCERS |
 | 
						|
                                               QT_ITEM_ORIGINAL_FUNC_NULLIF |
 | 
						|
                                               QT_NO_WRAPPERS_FOR_TVC_IN_VIEW));
 | 
						|
  }
 | 
						|
  DBUG_PRINT("info", ("View: %.*s", view_query.length(), 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()))
 | 
						|
  {
 | 
						|
    my_error(ER_OUT_OF_RESOURCES, MYF(0));
 | 
						|
    error= -1;
 | 
						|
    goto err;   
 | 
						|
  }
 | 
						|
 | 
						|
  /*
 | 
						|
    version 1 - before 10.0.5
 | 
						|
    version 2 - empty definer_host means a role
 | 
						|
  */
 | 
						|
  view->file_version= 2;
 | 
						|
  view->mariadb_version= MYSQL_VERSION_ID;
 | 
						|
  view->calc_md5(md5);
 | 
						|
  if (!(view->md5.str= (char*) thd->memdup(md5, VIEW_MD5_LEN)))
 | 
						|
  {
 | 
						|
    my_error(ER_OUT_OF_RESOURCES, MYF(0));
 | 
						|
    error= -1;
 | 
						|
    goto err;   
 | 
						|
  }
 | 
						|
  view->md5.length= VIEW_MD5_LEN;
 | 
						|
  can_be_merged= lex->can_be_merged();
 | 
						|
  if (lex->create_view->algorithm == VIEW_ALGORITHM_MERGE &&
 | 
						|
      !lex->can_be_merged())
 | 
						|
  {
 | 
						|
    push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_WARN_VIEW_MERGE,
 | 
						|
                 ER_THD(thd, ER_WARN_VIEW_MERGE));
 | 
						|
    lex->create_view->algorithm= DTYPE_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;
 | 
						|
 | 
						|
  DBUG_EXECUTE_IF("simulate_register_view_failure",
 | 
						|
                  {
 | 
						|
                    my_error(ER_OUT_OF_RESOURCES, MYF(0));
 | 
						|
                    error= -1;
 | 
						|
                    goto err;
 | 
						|
                  });
 | 
						|
 | 
						|
  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->first_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 */
 | 
						|
  make_view_filename(&dir, dir_buff, sizeof(dir_buff),
 | 
						|
                     &path, path_buff, sizeof(path_buff),
 | 
						|
                     &file, view);
 | 
						|
  /* init timestamp */
 | 
						|
  if (!view->hr_timestamp.str)
 | 
						|
    view->hr_timestamp.str= view->timestamp_buffer;
 | 
						|
 | 
						|
  /* check old .frm */
 | 
						|
  {
 | 
						|
    char path_buff[FN_REFLEN];
 | 
						|
    LEX_CSTRING 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 (ha_table_exists(thd, &view->db, &view->table_name))
 | 
						|
    {
 | 
						|
      old_view_exists= 1;
 | 
						|
      if (lex->create_info.if_not_exists())
 | 
						|
      {
 | 
						|
        push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | 
						|
                            ER_TABLE_EXISTS_ERROR,
 | 
						|
                            ER_THD(thd, ER_TABLE_EXISTS_ERROR),
 | 
						|
                            view->table_name.str);
 | 
						|
        DBUG_RETURN(0);
 | 
						|
      }
 | 
						|
      else if (mode == VIEW_CREATE_NEW)
 | 
						|
      {
 | 
						|
	my_error(ER_TABLE_EXISTS_ERROR, MYF(0), view->alias.str);
 | 
						|
        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.str, view->table_name.str,
 | 
						|
                 "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.str, view->alias.str);
 | 
						|
        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.
 | 
						|
  */
 | 
						|
 | 
						|
  view->view_client_cs_name= view->view_creation_ctx->get_client_cs()->cs_name;
 | 
						|
 | 
						|
  view->view_connection_cl_name=
 | 
						|
    view->view_creation_ctx->get_connection_cl()->coll_name;
 | 
						|
 | 
						|
  if (!thd->make_lex_string(&view->view_body_utf8, is_query.ptr(),
 | 
						|
                            is_query.length()))
 | 
						|
  {
 | 
						|
    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->first_select_lex()->master_unit()->is_unit_op() &&
 | 
						|
      !(lex->first_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.str, view->table_name.str);
 | 
						|
    error= -1;
 | 
						|
    goto err;
 | 
						|
  }
 | 
						|
 | 
						|
  ddl_log_create_view(ddl_log_state, &path, old_view_exists ?
 | 
						|
                      DDL_CREATE_VIEW_PHASE_DELETE_VIEW_COPY :
 | 
						|
                      DDL_CREATE_VIEW_PHASE_NO_OLD_VIEW);
 | 
						|
 | 
						|
  debug_crash_here("ddl_log_create_before_copy_view");
 | 
						|
 | 
						|
  if (old_view_exists)
 | 
						|
  {
 | 
						|
    LEX_CSTRING backup_name= { backup_file_name, 0 };
 | 
						|
    if (sql_backup_definition_file(&path, &backup_name))
 | 
						|
    {
 | 
						|
      error= 1;
 | 
						|
      goto err;
 | 
						|
    }
 | 
						|
    ddl_log_update_phase(ddl_log_state, DDL_CREATE_VIEW_PHASE_OLD_VIEW_COPIED);
 | 
						|
  }
 | 
						|
 | 
						|
  debug_crash_here("ddl_log_create_before_create_view");
 | 
						|
  if (sql_create_definition_file(&dir, &file, view_file_type,
 | 
						|
				 (uchar*)view, view_parameters))
 | 
						|
  {
 | 
						|
    error= thd->is_error() ? -1 : 1;
 | 
						|
    goto err;
 | 
						|
  }
 | 
						|
  debug_crash_here("ddl_log_create_after_create_view");
 | 
						|
 | 
						|
  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);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Reads view definition "version"
 | 
						|
 | 
						|
  @param[in]  share               Share object of view
 | 
						|
 | 
						|
  @return true on error, otherwise false
 | 
						|
*/
 | 
						|
 | 
						|
bool mariadb_view_version_get(TABLE_SHARE *share)
 | 
						|
{
 | 
						|
  DBUG_ASSERT(share->is_view);
 | 
						|
  DBUG_ASSERT(share->tabledef_version.length == 0);
 | 
						|
 | 
						|
  if (!(share->tabledef_version.str=
 | 
						|
        (uchar*) alloc_root(&share->mem_root,
 | 
						|
                            MICROSECOND_TIMESTAMP_BUFFER_SIZE)))
 | 
						|
    return TRUE;
 | 
						|
 | 
						|
  DBUG_ASSERT(share->view_def != NULL);
 | 
						|
  if (share->view_def->parse((uchar *) &share->tabledef_version, NULL,
 | 
						|
                             view_timestamp_parameters, 1,
 | 
						|
                             &file_parser_dummy_hook))
 | 
						|
  {
 | 
						|
    // safety if the definition file is brocken
 | 
						|
    share->tabledef_version.length= 0;
 | 
						|
    my_error(ER_TABLE_CORRUPT, MYF(0),
 | 
						|
             share->db.str, share->table_name.str);
 | 
						|
    return TRUE;
 | 
						|
  }
 | 
						|
  DBUG_ASSERT(share->tabledef_version.length == MICROSECOND_TIMESTAMP_BUFFER_SIZE-1);
 | 
						|
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  read VIEW .frm and create structures
 | 
						|
 | 
						|
  @param[in]  thd                 Thread handler
 | 
						|
  @param[in]  share               Share object of view
 | 
						|
  @param[in]  table               TABLE_LIST structure for filling
 | 
						|
  @param[in]  open_view_no_parse  Flag to indicate open view but
 | 
						|
                                  do not parse.
 | 
						|
 | 
						|
  @return false-in case of success, true-in case of error.
 | 
						|
*/
 | 
						|
bool mysql_make_view(THD *thd, TABLE_SHARE *share, TABLE_LIST *table,
 | 
						|
                     bool open_view_no_parse)
 | 
						|
{
 | 
						|
  SELECT_LEX_NODE *end;
 | 
						|
  SELECT_LEX *UNINIT_VAR(view_select);
 | 
						|
  LEX *old_lex, *lex;
 | 
						|
  Query_arena *arena, backup;
 | 
						|
  TABLE_LIST *top_view= table->top_table();
 | 
						|
  bool UNINIT_VAR(parse_status);
 | 
						|
  bool result, view_is_mergeable;
 | 
						|
  TABLE_LIST *UNINIT_VAR(view_main_select_tables);
 | 
						|
  DBUG_ENTER("mysql_make_view");
 | 
						|
  DBUG_PRINT("info", ("table: %p (%s)", table, table->table_name.str));
 | 
						|
 | 
						|
  if (table->required_type == TABLE_TYPE_NORMAL)
 | 
						|
  {
 | 
						|
    my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str,
 | 
						|
             "BASE TABLE");
 | 
						|
    DBUG_RETURN(true);
 | 
						|
  }
 | 
						|
 | 
						|
  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));
 | 
						|
 | 
						|
    /*
 | 
						|
      Clear old variables in the TABLE_LIST that could be left from an old view
 | 
						|
      This is only needed if there was an error at last usage of view,
 | 
						|
      in which case the reinit call wasn't done.
 | 
						|
      See MDEV-6668 for details.
 | 
						|
    */
 | 
						|
    mysql_handle_single_derived(thd->lex, table, DT_REINIT);
 | 
						|
 | 
						|
    DEBUG_SYNC(thd, "after_cached_view_opened");
 | 
						|
    DBUG_ASSERT(share->tabledef_version.length);
 | 
						|
    DBUG_RETURN(0);
 | 
						|
  }
 | 
						|
 | 
						|
  if (table->index_hints && table->index_hints->elements)
 | 
						|
  {
 | 
						|
    my_error(ER_KEY_DOES_NOT_EXISTS, MYF(0),
 | 
						|
             table->index_hints->head()->key_name.str, table->table_name.str);
 | 
						|
    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.str) == 0 &&
 | 
						|
        my_strcasecmp(system_charset_info,
 | 
						|
                      precedent->view_db.str, table->db.str) == 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->activate_stmt_arena_if_needed(&backup);
 | 
						|
 | 
						|
  /* init timestamp */
 | 
						|
  if (!table->hr_timestamp.str)
 | 
						|
    table->hr_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 (not only parser),
 | 
						|
    table mem_root should be used here
 | 
						|
  */
 | 
						|
  DBUG_ASSERT(share->view_def != NULL);
 | 
						|
  if ((result= share->view_def->parse((uchar*)table, thd->mem_root,
 | 
						|
                                      view_parameters,
 | 
						|
                                      required_view_parameters,
 | 
						|
                                      &file_parser_dummy_hook)))
 | 
						|
    goto end;
 | 
						|
  DBUG_ASSERT(share->tabledef_version.length);
 | 
						|
  if (!table->tabledef_version.length)
 | 
						|
  {
 | 
						|
    table->set_view_def_version(&table->hr_timestamp);
 | 
						|
  }
 | 
						|
 | 
						|
  /*
 | 
						|
    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, Sql_condition::WARN_LEVEL_WARN,
 | 
						|
                        ER_VIEW_FRM_NO_USER, ER_THD(thd, ER_VIEW_FRM_NO_USER),
 | 
						|
                        table->db.str, table->table_name.str);
 | 
						|
    get_default_definer(thd, &table->definer, false);
 | 
						|
  }
 | 
						|
  
 | 
						|
  /*
 | 
						|
    since 10.0.5 definer.host can never be "" for a User, but it's
 | 
						|
    always "" for a Role. Before 10.0.5 it could be "" for a User,
 | 
						|
    but roles didn't exist. file_version helps.
 | 
						|
  */
 | 
						|
  if (!table->definer.host.str[0] && table->file_version < 2)
 | 
						|
    table->definer.host= host_not_specified; // User, not Role
 | 
						|
 | 
						|
  /*
 | 
						|
    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);
 | 
						|
 | 
						|
  if (open_view_no_parse)
 | 
						|
  {
 | 
						|
    if (arena)
 | 
						|
      thd->restore_active_arena(arena, &backup);
 | 
						|
    DBUG_RETURN(FALSE);
 | 
						|
  }
 | 
						|
 | 
						|
  /*
 | 
						|
    Save VIEW parameters, which will be wiped out by derived table
 | 
						|
    processing
 | 
						|
  */
 | 
						|
  table->view_db= table->db;
 | 
						|
  table->view_name= table->table_name;
 | 
						|
  /*
 | 
						|
    We don't invalidate a prepared statement when a view changes,
 | 
						|
    or when someone creates a temporary table.
 | 
						|
    Instead, the view is inlined into the body of the statement
 | 
						|
    upon the first execution. Below, make sure that on
 | 
						|
    re-execution of a prepared statement we don't prefer
 | 
						|
    a temporary table to the view, if the view name was shadowed
 | 
						|
    with a temporary table with the same name.
 | 
						|
    This assignment ensures that on re-execution open_table() will
 | 
						|
    not try to call find_temporary_table() for this TABLE_LIST,
 | 
						|
    but will invoke open_table_from_share(), which will
 | 
						|
    eventually call this function.
 | 
						|
  */
 | 
						|
  table->open_type= OT_BASE_ONLY;
 | 
						|
 | 
						|
  /*
 | 
						|
    Clear old variables in the TABLE_LIST that could be left from an old view
 | 
						|
  */
 | 
						|
  table->merged_for_insert= FALSE;
 | 
						|
 | 
						|
  /*TODO: md5 test here and warning if it is differ */
 | 
						|
 | 
						|
 | 
						|
  /*
 | 
						|
    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;
 | 
						|
  if (!table->view)
 | 
						|
  {
 | 
						|
    result= true;
 | 
						|
    goto end;
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    char old_db_buf[SAFE_NAME_LEN+1];
 | 
						|
    LEX_CSTRING old_db= { old_db_buf, sizeof(old_db_buf) };
 | 
						|
    bool dbchanged;
 | 
						|
    Parser_state parser_state;
 | 
						|
    if (parser_state.init(thd, table->select_stmt.str,
 | 
						|
                          (uint)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,
 | 
						|
                                     (LEX_STRING*) &old_db, 1,
 | 
						|
                                     &dbchanged)))
 | 
						|
      goto end;
 | 
						|
 | 
						|
    lex_start(thd);
 | 
						|
    lex->stmt_lex= old_lex;
 | 
						|
 | 
						|
    Sql_mode_save_for_frm_handling sql_mode_save(thd);
 | 
						|
    /* Parse the query. */
 | 
						|
 | 
						|
    parse_status= parse_sql(thd, & parser_state, table->view_creation_ctx);
 | 
						|
 | 
						|
    view_select= lex->first_select_lex();
 | 
						|
 | 
						|
    /* Restore environment. */
 | 
						|
 | 
						|
    if ((old_lex->sql_command == SQLCOM_SHOW_FIELDS) ||
 | 
						|
        (old_lex->sql_command == SQLCOM_SHOW_CREATE))
 | 
						|
        lex->sql_command= old_lex->sql_command;
 | 
						|
 | 
						|
    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= 0;
 | 
						|
 | 
						|
    /*
 | 
						|
      Check rights to run commands which show underlying tables.
 | 
						|
      In the optimizer trace we would not like to show trace for
 | 
						|
      cases when the current user does not have rights for the
 | 
						|
      underlying tables.
 | 
						|
    */
 | 
						|
    if (!table->prelocking_placeholder)
 | 
						|
      opt_trace_disable_if_no_view_access(thd, table, view_tables);
 | 
						|
 | 
						|
    /*
 | 
						|
      Check rights to run commands (ANALYZE SELECT, 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->describe ||
 | 
						|
                                           old_lex->analyze_stmt))
 | 
						|
    {
 | 
						|
      /*
 | 
						|
        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,
 | 
						|
                             FALSE, UINT_MAX, TRUE) ||
 | 
						|
          check_table_access(thd, SHOW_VIEW_ACL, &view_no_suid,
 | 
						|
                             FALSE, UINT_MAX, TRUE))
 | 
						|
      {
 | 
						|
        my_message(ER_VIEW_NO_EXPLAIN, ER_THD(thd, 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, FALSE, 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->open_type= OT_BASE_ONLY;
 | 
						|
      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.
 | 
						|
    */
 | 
						|
    old_lex->set_stmt_unsafe_flags(lex->get_stmt_unsafe_flags());
 | 
						|
 | 
						|
    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->first_select_lex()->table_list.first;
 | 
						|
      /*
 | 
						|
        Mergeable view can be used for inserting, so we move the flag down
 | 
						|
      */
 | 
						|
      if (table->for_insert_data)
 | 
						|
      {
 | 
						|
        for (TABLE_LIST *t= view_main_select_tables;
 | 
						|
             t;
 | 
						|
             t= t->next_local)
 | 
						|
        {
 | 
						|
          t->for_insert_data= TRUE;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      /*
 | 
						|
        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)
 | 
						|
      {
 | 
						|
        /* We have to keep the lock type for sequence tables */
 | 
						|
        if (!tbl->sequence)
 | 
						|
	  tbl->lock_type= table->lock_type;
 | 
						|
        tbl->mdl_request.set_type(table->mdl_request.type);
 | 
						|
        tbl->updating= table->updating;
 | 
						|
      }
 | 
						|
      /*
 | 
						|
        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;
 | 
						|
 | 
						|
      /* Fields in this view can be used in upper select in case of merge.  */
 | 
						|
      if (table->select_lex)
 | 
						|
        table->select_lex->add_where_field(lex->first_select_lex());
 | 
						|
    }
 | 
						|
    /*
 | 
						|
      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 (lex->first_select_lex()->options & OPTION_TO_QUERY_CACHE)
 | 
						|
      old_lex->first_select_lex()->options|= OPTION_TO_QUERY_CACHE;
 | 
						|
 | 
						|
#ifndef NO_EMBEDDED_ACCESS_CHECKS
 | 
						|
    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->active_stmt_arena_to_use()->calloc(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;
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
    /* 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;
 | 
						|
    }
 | 
						|
 | 
						|
    view_select->master_unit()->is_view= true;
 | 
						|
 | 
						|
    /*
 | 
						|
      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->derived_type= 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->first_select_lex()->
 | 
						|
        context.resolve_in_table_list_only(view_main_select_tables);
 | 
						|
      lex->first_select_lex()->context.outer_context= 0;
 | 
						|
      lex->first_select_lex()->select_n_having_items+=
 | 
						|
        table->select_lex->select_n_having_items;
 | 
						|
 | 
						|
      table->where= view_select->where;
 | 
						|
 | 
						|
      /* 
 | 
						|
        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_unit_op() &&
 | 
						|
          table->select_lex->order_list.elements == 0)
 | 
						|
      {
 | 
						|
        table->select_lex->order_list.
 | 
						|
          push_back(&lex->first_select_lex()->order_list);
 | 
						|
        lex->first_select_lex()->order_list.empty();
 | 
						|
      }
 | 
						|
      else
 | 
						|
      {
 | 
						|
        if (old_lex->sql_command == SQLCOM_SELECT &&
 | 
						|
            (old_lex->describe & DESCRIBE_EXTENDED) &&
 | 
						|
            lex->first_select_lex()->order_list.elements &&
 | 
						|
            !table->select_lex->master_unit()->is_unit_op())
 | 
						|
        {
 | 
						|
          push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | 
						|
                              ER_VIEW_ORDERBY_IGNORED,
 | 
						|
                              ER_THD(thd, ER_VIEW_ORDERBY_IGNORED),
 | 
						|
                              table->db.str, table->table_name.str);
 | 
						|
        }
 | 
						|
      }
 | 
						|
      /*
 | 
						|
	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->derived_type= 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;
 | 
						|
 | 
						|
    table->derived= &lex->unit;
 | 
						|
  }
 | 
						|
  else
 | 
						|
    goto err;
 | 
						|
 | 
						|
ok:
 | 
						|
  /* SELECT tree link */
 | 
						|
  lex->unit.include_down(table->select_lex);
 | 
						|
  lex->unit.slave= view_select; // fix include_down initialisation
 | 
						|
  /* global SELECT list linking */
 | 
						|
  /*
 | 
						|
    The primary SELECT_LEX is always last (because parsed first) if WITH not
 | 
						|
    used, otherwise it is good start point for last element finding
 | 
						|
  */
 | 
						|
  for (end= view_select; end->link_next; end= end->link_next);
 | 
						|
  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;
 | 
						|
  status_var_increment(thd->status_var.opened_views);
 | 
						|
  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 handle
 | 
						|
    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;
 | 
						|
  bool delete_error= FALSE, wrong_object_name= FALSE;
 | 
						|
  bool some_views_deleted= FALSE;
 | 
						|
  bool something_wrong= FALSE;
 | 
						|
  uint not_exists_count= 0, view_count= 0;
 | 
						|
  DDL_LOG_STATE ddl_log_state;
 | 
						|
  DBUG_ENTER("mysql_drop_view");
 | 
						|
 | 
						|
  bzero(&ddl_log_state, sizeof(ddl_log_state));
 | 
						|
 | 
						|
  /*
 | 
						|
    We can't allow dropping of unlocked view under LOCK TABLES since this
 | 
						|
    might lead to deadlock. But since we can't really lock view with LOCK
 | 
						|
    TABLES we have to simply prohibit dropping of views.
 | 
						|
  */
 | 
						|
 | 
						|
  if (unlikely(thd->locked_tables_mode))
 | 
						|
  {
 | 
						|
    my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
 | 
						|
    DBUG_RETURN(TRUE);
 | 
						|
  }
 | 
						|
 | 
						|
  if (unlikely(lock_table_names(thd, views, 0,
 | 
						|
                                thd->variables.lock_wait_timeout, 0)))
 | 
						|
    DBUG_RETURN(TRUE);
 | 
						|
 | 
						|
  for (view= views; view; view= view->next_local)
 | 
						|
  {
 | 
						|
    LEX_CSTRING cpath;
 | 
						|
    bool not_exist;
 | 
						|
    size_t length;
 | 
						|
    length= build_table_filename(path, sizeof(path) - 1,
 | 
						|
                                 view->db.str, view->table_name.str, reg_ext, 0);
 | 
						|
    lex_string_set3(&cpath, path, length);
 | 
						|
 | 
						|
    if ((not_exist= my_access(path, F_OK)) || !dd_frm_is_view(thd, path))
 | 
						|
    {
 | 
						|
      char name[FN_REFLEN];
 | 
						|
      size_t length= my_snprintf(name, sizeof(name), "%s.%s", view->db.str,
 | 
						|
                                 view->table_name.str);
 | 
						|
      if (non_existant_views.length())
 | 
						|
        non_existant_views.append(',');
 | 
						|
      non_existant_views.append(name, length);
 | 
						|
 | 
						|
      if (!not_exist)
 | 
						|
      {
 | 
						|
        wrong_object_name= 1;
 | 
						|
        my_error(ER_WRONG_OBJECT, MYF(ME_WARNING), view->db.str,
 | 
						|
                 view->table_name.str, "VIEW");
 | 
						|
      }
 | 
						|
      else
 | 
						|
        not_exists_count++;
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
    if (!view_count++)
 | 
						|
    {
 | 
						|
      if (ddl_log_drop_view_init(&ddl_log_state, &thd->db))
 | 
						|
        DBUG_RETURN(TRUE);
 | 
						|
    }
 | 
						|
    if (ddl_log_drop_view(&ddl_log_state, &cpath, &view->db,
 | 
						|
                          &view->table_name))
 | 
						|
      DBUG_RETURN(TRUE);
 | 
						|
    debug_crash_here("ddl_log_drop_before_delete_view");
 | 
						|
    if (unlikely(mysql_file_delete(key_file_frm, path, MYF(MY_WME))))
 | 
						|
      delete_error= TRUE;
 | 
						|
    debug_crash_here("ddl_log_drop_after_delete_view");
 | 
						|
 | 
						|
    some_views_deleted= TRUE;
 | 
						|
 | 
						|
    /*
 | 
						|
      For a view, there is a TABLE_SHARE object.
 | 
						|
      Remove it from the table definition cache, in case the view was cached.
 | 
						|
    */
 | 
						|
    tdc_remove_table(thd, view->db.str, view->table_name.str);
 | 
						|
    query_cache_invalidate3(thd, view, 0);
 | 
						|
    sp_cache_invalidate();
 | 
						|
 | 
						|
    backup_log_info ddl_log;
 | 
						|
    bzero(&ddl_log, sizeof(ddl_log));
 | 
						|
    ddl_log.query= { C_STRING_WITH_LEN("DROP") };
 | 
						|
    ddl_log.org_storage_engine_name= { C_STRING_WITH_LEN("VIEW") };
 | 
						|
    ddl_log.org_database=     view->db;
 | 
						|
    ddl_log.org_table=        view->table_name;
 | 
						|
    backup_log_ddl(&ddl_log);
 | 
						|
  }
 | 
						|
 | 
						|
  something_wrong= (delete_error ||
 | 
						|
                    (!thd->lex->if_exists() && (not_exists_count ||
 | 
						|
                                                wrong_object_name)));
 | 
						|
 | 
						|
  if (unlikely(non_existant_views.length()))
 | 
						|
  {
 | 
						|
    my_error(ER_UNKNOWN_VIEW, MYF(something_wrong ? 0 : ME_NOTE),
 | 
						|
             non_existant_views.c_ptr_safe());
 | 
						|
  }
 | 
						|
 | 
						|
  if (some_views_deleted || !something_wrong)
 | 
						|
  {
 | 
						|
    /* if something goes wrong, bin-log with possible error code,
 | 
						|
       otherwise bin-log with error code cleared.
 | 
						|
     */
 | 
						|
    debug_crash_here("ddl_log_drop_before_binlog");
 | 
						|
    thd->binlog_xid= thd->query_id;
 | 
						|
    ddl_log_update_xid(&ddl_log_state, thd->binlog_xid);
 | 
						|
    if (unlikely(write_bin_log(thd, !something_wrong, thd->query(),
 | 
						|
                               thd->query_length())))
 | 
						|
      something_wrong= 1;
 | 
						|
    thd->binlog_xid= 0;
 | 
						|
    debug_crash_here("ddl_log_drop_after_binlog");
 | 
						|
  }
 | 
						|
  ddl_log_complete(&ddl_log_state);
 | 
						|
 | 
						|
  if (unlikely(something_wrong))
 | 
						|
  {
 | 
						|
    DBUG_RETURN(TRUE);
 | 
						|
  }
 | 
						|
  my_ok(thd);
 | 
						|
  DBUG_RETURN(FALSE);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
  check of key (primary or unique) presence in updatable view
 | 
						|
 | 
						|
  SYNOPSIS
 | 
						|
    check_key_in_view()
 | 
						|
    thd     thread handle
 | 
						|
    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->first_select_lex()->limit_params.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_column_usage saved_column_usage= thd->column_usage;
 | 
						|
    thd->column_usage= COLUMNS_WRITE;
 | 
						|
    DBUG_PRINT("info", ("thd->column_usage: %d", thd->column_usage));
 | 
						|
    for (Field_translator *fld= trans; fld < end_of_trans; fld++)
 | 
						|
    {
 | 
						|
      if (fld->item->fix_fields_if_needed(thd, &fld->item))
 | 
						|
      {
 | 
						|
        thd->column_usage= saved_column_usage;
 | 
						|
        DBUG_RETURN(TRUE);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    thd->column_usage= saved_column_usage;
 | 
						|
    DBUG_PRINT("info", ("thd->column_usage: %d", thd->column_usage));
 | 
						|
  }
 | 
						|
  /* 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->user_defined_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->field_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->field_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, Sql_condition::WARN_LEVEL_NOTE,
 | 
						|
                       ER_WARN_VIEW_WITHOUT_KEY,
 | 
						|
                       ER_THD(thd, 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 client)
 | 
						|
*/
 | 
						|
 | 
						|
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->field_for_view_update()))
 | 
						|
    {
 | 
						|
      TABLE_SHARE *s= fld->context->table_list->table->s;
 | 
						|
      Lex_ident field_name= fld->field_name;
 | 
						|
      if (s->versioned &&
 | 
						|
          (field_name.streq(s->vers_start_field()->field_name) ||
 | 
						|
           field_name.streq(s->vers_end_field()->field_name)))
 | 
						|
        continue;
 | 
						|
      list->push_back(fld, thd->mem_root);
 | 
						|
    }
 | 
						|
    else
 | 
						|
    {
 | 
						|
      my_error(ER_NON_INSERTABLE_TABLE, MYF(0), view->alias.str, "INSERT");
 | 
						|
      DBUG_RETURN(TRUE);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  DBUG_RETURN(FALSE);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
  checking view md5 check sum
 | 
						|
 | 
						|
  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 != VIEW_MD5_LEN)
 | 
						|
    return HA_ADMIN_NOT_IMPLEMENTED;
 | 
						|
  view->calc_md5(md5);
 | 
						|
  return (strncmp(md5, view->md5.str, VIEW_MD5_LEN) ?
 | 
						|
          HA_ADMIN_WRONG_CHECKSUM :
 | 
						|
          HA_ADMIN_OK);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
  Check view
 | 
						|
 | 
						|
  @param thd             thread handle
 | 
						|
  @param view            view for check
 | 
						|
  @param check_opt       check options
 | 
						|
 | 
						|
  @retval HA_ADMIN_OK               OK
 | 
						|
  @retval HA_ADMIN_NOT_IMPLEMENTED  it is not VIEW
 | 
						|
  @retval HA_ADMIN_WRONG_CHECKSUM   check sum is wrong
 | 
						|
*/
 | 
						|
int view_check(THD *thd, TABLE_LIST *view, HA_CHECK_OPT *check_opt)
 | 
						|
{
 | 
						|
  DBUG_ENTER("view_check");
 | 
						|
 | 
						|
  int res= view_checksum(thd, view);
 | 
						|
  if (res != HA_ADMIN_OK)
 | 
						|
    DBUG_RETURN(res);
 | 
						|
 | 
						|
  if (((check_opt->sql_flags & TT_FOR_UPGRADE) && !view->mariadb_version))
 | 
						|
    DBUG_RETURN(HA_ADMIN_NEEDS_UPGRADE);
 | 
						|
 | 
						|
  DBUG_RETURN(HA_ADMIN_OK);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
  Repair view
 | 
						|
 | 
						|
  @param thd             thread handle
 | 
						|
  @param view            view for check
 | 
						|
  @param check_opt       check options
 | 
						|
 | 
						|
  @retval HA_ADMIN_OK               OK
 | 
						|
  @retval HA_ADMIN_NOT_IMPLEMENTED  it is not VIEW
 | 
						|
  @retval HA_ADMIN_WRONG_CHECKSUM   check sum is wrong
 | 
						|
*/
 | 
						|
 | 
						|
int view_repair(THD *thd, TABLE_LIST *view, HA_CHECK_OPT *check_opt)
 | 
						|
{
 | 
						|
  DBUG_ENTER("view_repair");
 | 
						|
  bool swap_alg= (check_opt->sql_flags & TT_FROM_MYSQL);
 | 
						|
  bool wrong_checksum= view_checksum(thd, view) != HA_ADMIN_OK;
 | 
						|
  int ret;
 | 
						|
  if (wrong_checksum || !view->mariadb_version)
 | 
						|
  {
 | 
						|
    ret= mariadb_fix_view(thd, view, wrong_checksum, swap_alg);
 | 
						|
    DBUG_RETURN(ret);
 | 
						|
  }
 | 
						|
  DBUG_RETURN(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 LEX_CSTRING *new_db,
 | 
						|
                  const LEX_CSTRING *new_name,
 | 
						|
                  const LEX_CSTRING *old_db,
 | 
						|
                  const LEX_CSTRING *old_name)
 | 
						|
{
 | 
						|
  LEX_CSTRING 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,
 | 
						|
                                       old_db->str, old_name->str,
 | 
						|
                                       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_CSTRING 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.
 | 
						|
    */
 | 
						|
    view_def.reset();
 | 
						|
    view_def.hr_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, old_db->str, old_name->str,
 | 
						|
                              new_db->str, new_name->str))
 | 
						|
      goto err;
 | 
						|
    debug_crash_here("rename_view_after_rename_schema_file");
 | 
						|
 | 
						|
    dir.str= dir_buff;
 | 
						|
    dir.length= build_table_filename(dir_buff, sizeof(dir_buff) - 1,
 | 
						|
                                     new_db->str, "", "", 0);
 | 
						|
 | 
						|
    pathstr.str= path_buff;
 | 
						|
    pathstr.length= build_table_filename(path_buff, sizeof(path_buff) - 1,
 | 
						|
                                         new_db->str, new_name->str, 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->str, new_name->str, old_db->str,
 | 
						|
                            old_name->str);
 | 
						|
      goto err;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  else
 | 
						|
    DBUG_RETURN(1);  
 | 
						|
 | 
						|
  /* remove cache entries */
 | 
						|
  {
 | 
						|
    char key[NAME_LEN*2+1], *ptr;
 | 
						|
    memcpy(key, old_db->str, old_db->length);
 | 
						|
    ptr= key+ old_db->length;
 | 
						|
    *ptr++= 0;
 | 
						|
    memcpy(key, old_name->str, old_name->length);
 | 
						|
    ptr= key+ old_db->length;
 | 
						|
    *ptr++= 0;
 | 
						|
    query_cache.invalidate(thd, key, (size_t) (ptr-key), 0);
 | 
						|
  }
 | 
						|
  error= FALSE;
 | 
						|
 | 
						|
err:
 | 
						|
  DBUG_RETURN(error);
 | 
						|
}
 |