summaryrefslogtreecommitdiffstats
path: root/sql/sql_view.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/sql_view.cc')
-rw-r--r--sql/sql_view.cc2383
1 files changed, 2383 insertions, 0 deletions
diff --git a/sql/sql_view.cc b/sql/sql_view.cc
new file mode 100644
index 00000000..08208048
--- /dev/null
+++ b/sql/sql_view.cc
@@ -0,0 +1,2383 @@
+/* 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.str))
+ 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);
+ /*
+ 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->hide_view_error(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;
+
+ WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL);
+
+ /*
+ 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
+ if(!wsrep_should_replicate_ddl_iterate(thd, static_cast<const TABLE_LIST *>(tables)))
+ {
+ res= TRUE;
+ goto err_no_relink;
+ }
+#endif
+
+ 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_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->stmt_arena->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);
+}