List:Commits« Previous MessageNext Message »
From:marc.alff Date:February 21 2008 5:11am
Subject:bk commit into 5.1 tree (malff:1.2565) BUG#12093
View as plain text  
Below is the list of changes that have just been committed into a local
5.1 repository of malff.  When malff does a push these changes
will be propagated to the main repository and, within 24 hours after the
push, to the public repository.
For information on how to access the public repository
see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html

ChangeSet@stripped, 2008-02-20 22:11:23-07:00, malff@stripped. +17 -0
  Bug#12093 (SP not found on second PS execution if another thread drops other
  SP in between)
  
  Partial solution implementing:
  - Prepared statements revalidation
  - raises ER_NEED_REPREPARE errors, without re-prepare
  This patch isolate all the validation code in a separate commit,
  to facilitate code review.
  *DOES NOT PASS THE TEST SUITE*, returns errors during EXECUTE

  sql/mysql_priv.h@stripped, 2008-02-20 21:55:28-07:00, malff@stripped. +56 -1
    Bug#12093 (SP not found on second PS execution if another thread drops other
    SP in between)

  sql/parse_file.cc@stripped, 2008-02-20 21:55:28-07:00, malff@stripped. +1 -1
    Bug#12093 (SP not found on second PS execution if another thread drops other
    SP in between)

  sql/share/errmsg.txt@stripped, 2008-02-20 21:55:30-07:00, malff@stripped. +4 -0
    Bug#12093 (SP not found on second PS execution if another thread drops other
    SP in between)

  sql/sp.cc@stripped, 2008-02-20 21:55:29-07:00, malff@stripped. +157 -52
    Bug#12093 (SP not found on second PS execution if another thread drops other
    SP in between)

  sql/sp.h@stripped, 2008-02-20 21:55:29-07:00, malff@stripped. +1 -0
    Bug#12093 (SP not found on second PS execution if another thread drops other
    SP in between)

  sql/sp_head.cc@stripped, 2008-02-20 21:55:29-07:00, malff@stripped. +66 -0
    Bug#12093 (SP not found on second PS execution if another thread drops other
    SP in between)

  sql/sp_head.h@stripped, 2008-02-20 21:55:29-07:00, malff@stripped. +8 -0
    Bug#12093 (SP not found on second PS execution if another thread drops other
    SP in between)

  sql/sql_base.cc@stripped, 2008-02-20 21:55:29-07:00, malff@stripped. +162 -36
    Bug#12093 (SP not found on second PS execution if another thread drops other
    SP in between)

  sql/sql_class.cc@stripped, 2008-02-20 21:55:29-07:00, malff@stripped. +304 -10
    Bug#12093 (SP not found on second PS execution if another thread drops other
    SP in between)

  sql/sql_class.h@stripped, 2008-02-20 21:55:29-07:00, malff@stripped. +212 -2
    Bug#12093 (SP not found on second PS execution if another thread drops other
    SP in between)

  sql/sql_insert.cc@stripped, 2008-02-20 21:55:29-07:00, malff@stripped. +4 -1
    Bug#12093 (SP not found on second PS execution if another thread drops other
    SP in between)

  sql/sql_prepare.cc@stripped, 2008-02-20 22:11:20-07:00, malff@stripped. +0 -11
    Minor cleanup

  sql/sql_prepare.cc@stripped, 2008-02-20 21:55:29-07:00, malff@stripped. +273 -1
    Bug#12093 (SP not found on second PS execution if another thread drops other
    SP in between)

  sql/sql_table.cc@stripped, 2008-02-20 21:55:30-07:00, malff@stripped. +4 -1
    Bug#12093 (SP not found on second PS execution if another thread drops other
    SP in between)

  sql/sql_view.cc@stripped, 2008-02-20 21:55:30-07:00, malff@stripped. +76 -2
    Bug#12093 (SP not found on second PS execution if another thread drops other
    SP in between)

  sql/sql_view.h@stripped, 2008-02-20 21:55:30-07:00, malff@stripped. +2 -0
    Bug#12093 (SP not found on second PS execution if another thread drops other
    SP in between)

  sql/table.cc@stripped, 2008-02-20 21:55:30-07:00, malff@stripped. +46 -0
    Bug#12093 (SP not found on second PS execution if another thread drops other
    SP in between)

  sql/table.h@stripped, 2008-02-20 21:55:30-07:00, malff@stripped. +8 -0
    Bug#12093 (SP not found on second PS execution if another thread drops other
    SP in between)

diff -Nrup a/sql/mysql_priv.h b/sql/mysql_priv.h
--- a/sql/mysql_priv.h	2008-02-08 08:55:02 -07:00
+++ b/sql/mysql_priv.h	2008-02-20 21:55:28 -07:00
@@ -657,6 +657,61 @@ const char *set_thd_proc_info(THD *thd, 
                               const char *calling_file, 
                               const unsigned int calling_line);
 
+typedef enum version_kind {
+  NULL_VERSION,
+  TABLE_VERSION,
+  TEMPORARY_TABLE_VERSION,
+  VIEW_VERSION,
+  PROCEDURE_VERSION,
+  FUNCTION_VERSION
+} VERSION_KIND;
+
+#define MD5_CHAR_LENGTH 32
+
+/**
+  A Version_id identifies a version of a given object.
+  Different objects (TABLE, VIEW, FUNCTION, PROCEDURE, ...)
+  can use version ids of different spaces (MD5, timestamp, counter, ...),
+  so that a Version_id is an heterogeneous type.
+  Versions can be compared for equality, but don't define any order.
+*/
+
+class Version_id
+{
+public:
+  Version_id();
+  Version_id(const Version_id& vid);
+  ~Version_id();
+
+  VERSION_KIND get_kind() const
+  { return m_kind; }
+
+  bool is_null() const;
+  /**
+    Test for equality.
+    Equality is defined by:
+    - having the same namespace (kind)
+    - having the same value within that namespace
+  */
+  bool is_equal(const Version_id *vid) const;
+  bool is_null_or_equal(const Version_id *vid) const;
+
+  void set_null();
+  void set(const Version_id *vid);
+  void set_table(longlong value);
+  void set_temporary_table(longlong value);
+  void set_view(const char* md5);
+  void set_procedure(const char* md5);
+  void set_function(const char* md5);
+
+  Version_id& operator = (const Version_id& vid);
+
+private:
+  VERSION_KIND m_kind;
+  longlong m_long_value;
+  char m_md5_value[MD5_CHAR_LENGTH];
+};
+
 /*
   External variables
 */
@@ -1196,7 +1251,7 @@ TABLE_SHARE *get_cached_table_share(cons
 TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update,
                    uint lock_flags);
 TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT* mem,
-		  bool *refresh, uint flags);
+		  bool *refresh, bool *invalidated, uint flags);
 bool name_lock_locked_table(THD *thd, TABLE_LIST *tables);
 bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list, bool link_in);
 TABLE *table_cache_insert_placeholder(THD *thd, const char *key,
diff -Nrup a/sql/parse_file.cc b/sql/parse_file.cc
--- a/sql/parse_file.cc	2007-10-11 12:37:42 -06:00
+++ b/sql/parse_file.cc	2008-02-20 21:55:28 -07:00
@@ -430,7 +430,7 @@ sql_parse_prepare(const LEX_STRING *file
   File file;
   DBUG_ENTER("sql_parse_prepare");
 
-  if (!my_stat(file_name->str, &stat_info, MYF(MY_WME)))
+  if (!my_stat(file_name->str, &stat_info, MYF(0)))
   {
     DBUG_RETURN(0);
   }
diff -Nrup a/sql/share/errmsg.txt b/sql/share/errmsg.txt
--- a/sql/share/errmsg.txt	2008-01-31 05:54:00 -07:00
+++ b/sql/share/errmsg.txt	2008-02-20 21:55:30 -07:00
@@ -6121,3 +6121,7 @@ ER_NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BI
   eng "The BINLOG statement of type `%s` was not preceded by a format description BINLOG statement."
 ER_SLAVE_CORRUPT_EVENT
   eng "Corrupted replication event was detected"
+
+ER_NEED_REPREPARE
+  eng "(Internal) Prepared statement needs to be re-prepared"
+
diff -Nrup a/sql/sp.cc b/sql/sp.cc
--- a/sql/sp.cc	2008-02-19 05:57:58 -07:00
+++ b/sql/sp.cc	2008-02-20 21:55:29 -07:00
@@ -1837,6 +1837,158 @@ static void sp_update_stmt_used_routines
 
 
 /**
+  This helper ensures that a given Stored Function or Stored Procedure
+  is present in the corresponding per session cache,
+  and loads the Stored Function/Procedure from storage into the cache,
+  if necessary.
+  @param [in] thd Current thread
+  @param [in] type Type of stored object to lookup/load
+  @param [in] name Name of the stored object
+  @param [out] sphp Stored object refreshed in the cache, if found
+  @return An error status
+    @retval 0 Success, sphp will contain the stored object fetched, if any
+*/
+
+static int
+sp_cache_load(THD *thd, Sroutine_hash_entry *entry, sp_head **sphp)
+{
+  sp_cache **cp;
+  sp_head *sp;
+  int ret;
+  Object_version sp_version;
+  sp_name name(thd, entry->key.str, entry->key.length);
+  int type= entry->key.str[0];
+
+  DBUG_ENTER("sp_cache_load");
+
+  DBUG_ASSERT(sphp);
+  DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE ||
+              type == TYPE_ENUM_TRIGGER ||
+              type == TYPE_ENUM_FUNCTION);
+
+  /* Triggers are cached in the stored procedure cache. */
+  cp= (type == TYPE_ENUM_FUNCTION ? &thd->sp_func_cache : &thd->sp_proc_cache);
+
+  sp= sp_cache_lookup(cp, &name);
+
+  if (! sp)
+  {
+    switch ((ret= db_find_routine(thd, type, &name, &sp)))
+    {
+    case SP_OK:
+      sp_cache_insert(cp, sp);
+
+      if (thd->m_object_observer && type != TYPE_ENUM_TRIGGER)
+      {
+        sp->make_object_version(& sp_version);
+
+        if (thd->m_object_observer->notify(thd, & sp_version))
+          DBUG_RETURN(1);
+      }
+
+      *sphp= sp;
+      ret= 0;
+      break;
+    case SP_KEY_NOT_FOUND:
+      if (thd->m_object_observer && type != TYPE_ENUM_TRIGGER)
+      {
+        sp_head::make_object_null_version(& sp_version,
+                                          type,
+                                          & name.m_db,
+                                          & name.m_name);
+
+        if (thd->m_object_observer->notify(thd, & sp_version))
+          DBUG_RETURN(1);
+      }
+
+      *sphp= NULL;
+      ret= 0;
+      break;
+    default:
+      /*
+        Any error when loading an existing routine is either some problem
+        with the mysql.proc table, or a parse error because the contents
+        has been tampered with (in which case we clear that error).
+      */
+      if (ret == SP_PARSE_ERROR)
+        thd->clear_error();
+      /*
+        If we cleared the parse error, or when db_find_routine() flagged
+        an error with it's return value without calling my_error(), we
+        set the generic "mysql.proc table corrupt" error here.
+       */
+      if (! thd->is_error())
+      {
+        /*
+          SP allows full NAME_LEN chars thus he have to allocate enough
+          size in bytes. Otherwise there is stack overrun could happen
+          if multibyte sequence is `name`. `db` is still safe because the
+          rest of the server checks against NAME_LEN bytes and not chars.
+          Hence, the overrun happens only if the name is in length > 32 and
+          uses multibyte (cyrillic, greek, etc.)
+        */
+        char n[NAME_LEN*2+2];
+
+        /* m_qname.str is not always \0 terminated */
+        memcpy(n, name.m_qname.str, name.m_qname.length);
+        n[name.m_qname.length]= '\0';
+        my_error(ER_SP_PROC_TABLE_CORRUPT, MYF(0), n, ret);
+        ret= 1;
+      }
+      break;
+    }
+  }
+  else
+  {
+    if (thd->m_object_observer && type != TYPE_ENUM_TRIGGER)
+    {
+      sp->make_object_version(& sp_version);
+
+      if (thd->m_object_observer->notify(thd, & sp_version))
+        DBUG_RETURN(1);
+    }
+
+    ret= 0;
+    *sphp= sp;
+  }
+
+  DBUG_RETURN(ret);
+}
+
+
+/**
+  Refresh all the routines used by a statement in the session cache.
+  Note that routines that can be found are reloaded,
+  while routines that can not be found are ignored.
+  @param [in] thd Current thread
+  @param [in] lex Statement semantic tree
+  @return An error status
+    @retval 0 Success
+*/
+int
+sp_refresh_routines(THD *thd, LEX *lex)
+{
+  Sroutine_hash_entry *start;
+  int ret= 0;
+
+  DBUG_ENTER("sp_refresh_routines");
+
+  start= (Sroutine_hash_entry *)lex->sroutines_list.first;
+
+  for (Sroutine_hash_entry *rt= start; rt; rt= rt->next)
+  {
+    sp_head *sp;
+
+    ret= sp_cache_load(thd, rt, &sp);
+
+    if (ret)
+      break;
+  }
+  DBUG_RETURN(ret);
+}
+
+
+/**
   Cache sub-set of routines used by statement, add tables used by these
   routines to statement table list. Do the same for all routines used
   by these routines.
@@ -1871,60 +2023,13 @@ sp_cache_routines_and_add_tables_aux(THD
 
   for (Sroutine_hash_entry *rt= start; rt; rt= rt->next)
   {
-    sp_name name(thd, rt->key.str, rt->key.length);
-    int type= rt->key.str[0];
     sp_head *sp;
 
-    if (!(sp= sp_cache_lookup((type == TYPE_ENUM_FUNCTION ?
-                              &thd->sp_func_cache : &thd->sp_proc_cache),
-                              &name)))
-    {
-      switch ((ret= db_find_routine(thd, type, &name, &sp)))
-      {
-      case SP_OK:
-        {
-          if (type == TYPE_ENUM_FUNCTION)
-            sp_cache_insert(&thd->sp_func_cache, sp);
-          else
-            sp_cache_insert(&thd->sp_proc_cache, sp);
-        }
-        break;
-      case SP_KEY_NOT_FOUND:
-        ret= SP_OK;
-        break;
-      default:
-        /*
-          Any error when loading an existing routine is either some problem
-          with the mysql.proc table, or a parse error because the contents
-          has been tampered with (in which case we clear that error).
-        */
-        if (ret == SP_PARSE_ERROR)
-          thd->clear_error();
-        /*
-          If we cleared the parse error, or when db_find_routine() flagged
-          an error with it's return value without calling my_error(), we
-          set the generic "mysql.proc table corrupt" error here.
-         */
-        if (! thd->is_error())
-        {
-          /*
-            SP allows full NAME_LEN chars thus he have to allocate enough
-            size in bytes. Otherwise there is stack overrun could happen
-            if multibyte sequence is `name`. `db` is still safe because the
-            rest of the server checks agains NAME_LEN bytes and not chars.
-            Hence, the overrun happens only if the name is in length > 32 and
-            uses multibyte (cyrillic, greek, etc.)
-          */
-          char n[NAME_LEN*2+2];
-
-          /* m_qname.str is not always \0 terminated */
-          memcpy(n, name.m_qname.str, name.m_qname.length);
-          n[name.m_qname.length]= '\0';
-          my_error(ER_SP_PROC_TABLE_CORRUPT, MYF(0), n, ret);
-        }
-        break;
-      }
-    }
+    ret= sp_cache_load(thd, rt, &sp);
+
+    if (ret)
+      break;
+
     if (sp)
     {
       if (!(first && first_no_prelock))
diff -Nrup a/sql/sp.h b/sql/sp.h
--- a/sql/sp.h	2007-10-17 02:13:53 -06:00
+++ b/sql/sp.h	2008-02-20 21:55:29 -07:00
@@ -70,6 +70,7 @@ void sp_add_used_routine(LEX *lex, Query
                          sp_name *rt, char rt_type);
 void sp_remove_not_own_routines(LEX *lex);
 void sp_update_sp_used_routines(HASH *dst, HASH *src);
+int sp_refresh_routines(THD *thd, LEX *lex);
 int sp_cache_routines_and_add_tables(THD *thd, LEX *lex,
                                      bool first_no_prelock);
 int sp_cache_routines_and_add_tables_for_view(THD *thd, LEX *lex,
diff -Nrup a/sql/sp_head.cc b/sql/sp_head.cc
--- a/sql/sp_head.cc	2008-02-19 05:57:58 -07:00
+++ b/sql/sp_head.cc	2008-02-20 21:55:29 -07:00
@@ -22,6 +22,7 @@
 #include "sp_pcontext.h"
 #include "sp_rcontext.h"
 #include "sp_cache.h"
+#include "my_md5.h"
 
 /*
   Sufficient max length of printed destinations and frame offsets (all uints).
@@ -535,6 +536,55 @@ sp_head::sp_head()
   DBUG_VOID_RETURN;
 }
 
+int sp_head::make_object_version(Object_version *version)
+{
+  int rc;
+
+  switch(m_type)
+  {
+  case TYPE_ENUM_FUNCTION:
+    rc= version->m_oid.set_function(& m_db, & m_name);
+  version->m_vid.set_function(m_md5_value);
+    break;
+  case TYPE_ENUM_PROCEDURE:
+    rc= version->m_oid.set_procedure(& m_db, & m_name);
+  version->m_vid.set_procedure(m_md5_value);
+    break;
+  case TYPE_ENUM_TRIGGER:
+  default:
+    DBUG_ASSERT(FALSE);
+    rc= 1;
+  }
+
+  return rc;
+}
+
+int sp_head::make_object_null_version(
+  Object_version *version,
+  int type,
+  const LEX_STRING *db,
+  const LEX_STRING *name)
+{
+  int rc;
+
+  switch(type)
+  {
+  case TYPE_ENUM_FUNCTION:
+    rc= version->m_oid.set_function(db, name);
+    break;
+  case TYPE_ENUM_PROCEDURE:
+    rc= version->m_oid.set_procedure(db, name);
+    break;
+  case TYPE_ENUM_TRIGGER:
+  default:
+    DBUG_ASSERT(false);
+    rc= 1;
+  }
+
+  version->m_vid.set_null();
+
+  return rc;
+}
 
 void
 sp_head::init(LEX *lex)
@@ -632,6 +682,8 @@ sp_head::set_stmt_end(THD *thd)
 {
   Lex_input_stream *lip= thd->m_lip; /* shortcut */
   const char *end_ptr= lip->get_cpp_ptr(); /* shortcut */
+  my_MD5_CTX context;
+  uchar digest[16];
 
   /* Make the string of parameters. */
 
@@ -667,6 +719,20 @@ sp_head::set_stmt_end(THD *thd)
   m_defstr.length= end_ptr - lip->get_cpp_buf();
   m_defstr.str= thd->strmake(lip->get_cpp_buf(), m_defstr.length);
   trim_whitespace(thd->charset(), & m_defstr);
+
+  /*
+    Compute MD5(definition string), store the result in sp_head::m_md5_value.
+  */
+  my_MD5Init(&context);
+  my_MD5Update(&context,(uchar *) m_defstr.str, m_defstr.length);
+  my_MD5Final(digest, &context);
+
+  sprintf(m_md5_value,
+          "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
+          digest[0], digest[1], digest[2], digest[3],
+          digest[4], digest[5], digest[6], digest[7],
+          digest[8], digest[9], digest[10], digest[11],
+          digest[12], digest[13], digest[14], digest[15]);
 }
 
 
diff -Nrup a/sql/sp_head.h b/sql/sp_head.h
--- a/sql/sp_head.h	2008-01-25 10:14:58 -07:00
+++ b/sql/sp_head.h	2008-02-20 21:55:29 -07:00
@@ -194,6 +194,14 @@ public:
   LEX_STRING m_defstr;
   LEX_STRING m_definer_user;
   LEX_STRING m_definer_host;
+  char m_md5_value[MD5_CHAR_LENGTH + 1];
+
+  int make_object_version(Object_version * version);
+  static int make_object_null_version(
+    Object_version * version,
+    int type,
+    const LEX_STRING *db,
+    const LEX_STRING *name);
 
 private:
   Stored_program_creation_ctx *m_creation_ctx;
diff -Nrup a/sql/sql_base.cc b/sql/sql_base.cc
--- a/sql/sql_base.cc	2008-02-19 04:42:58 -07:00
+++ b/sql/sql_base.cc	2008-02-20 21:55:29 -07:00
@@ -2569,7 +2569,7 @@ bool check_if_table_exists(THD *thd, TAB
 
 
 TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
-		  bool *refresh, uint flags)
+                  bool *refresh, bool *invalidated, uint flags)
 {
   reg1	TABLE *table;
   char	key[MAX_DBKEY_LENGTH];
@@ -2581,9 +2581,12 @@ TABLE *open_table(THD *thd, TABLE_LIST *
   /* Parsing of partitioning information from .frm needs thd->lex set up. */
   DBUG_ASSERT(thd->lex->is_lex_started);
 
+  DBUG_ASSERT(refresh);
+  DBUG_ASSERT(invalidated);
+  *invalidated= FALSE;
+
   /* find a unused table in the open table cache */
-  if (refresh)
-    *refresh=0;
+  *refresh=0;
 
   /* an open table operation needs a lot of the stack space */
   if (check_stack_overrun(thd, STACK_MIN_SIZE_FOR_OPEN, (uchar *)&alias))
@@ -2624,7 +2627,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
                       (ulong) table->query_id, (uint) thd->server_id,
                       (ulong) thd->variables.pseudo_thread_id));
 	  my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias);
-	  DBUG_RETURN(0);
+          goto end_null;
 	}
 	table->query_id= thd->query_id;
 	thd->thread_specific_used= TRUE;
@@ -2637,7 +2640,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
   if (flags & MYSQL_OPEN_TEMPORARY_ONLY)
   {
     my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->table_name);
-    DBUG_RETURN(0);
+    goto end_null;
   }
 
   /*
@@ -2670,7 +2673,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
           */
           my_error(ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG, MYF(0),
                    table->s->table_name.str);
-          DBUG_RETURN(0);
+          goto end_null;
         }
         /*
           When looking for a usable TABLE, ignore MERGE children, as they
@@ -2745,7 +2748,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
         {
           DBUG_ASSERT(table_list->view != 0);
           VOID(pthread_mutex_unlock(&LOCK_open));
-          DBUG_RETURN(0); // VIEW
+          goto end_null; // VIEW
         }
         VOID(pthread_mutex_unlock(&LOCK_open));
       }
@@ -2761,7 +2764,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
       my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->alias);
     else
       my_error(ER_TABLE_NOT_LOCKED, MYF(0), alias);
-    DBUG_RETURN(0);
+    goto end_null;
   }
 
   /*
@@ -2796,10 +2799,9 @@ TABLE *open_table(THD *thd, TABLE_LIST *
            ! (flags & MYSQL_LOCK_IGNORE_FLUSH))
   {
     /* Someone did a refresh while thread was opening tables */
-    if (refresh)
-      *refresh=1;
+    *refresh=1;
     VOID(pthread_mutex_unlock(&LOCK_open));
-    DBUG_RETURN(0);
+    goto end_null;
   }
 
   /*
@@ -2866,7 +2868,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
       {
 	VOID(pthread_mutex_unlock(&LOCK_open));
         my_error(ER_UPDATE_TABLE_USED, MYF(0), table->s->table_name.str);
-        DBUG_RETURN(0);
+        goto end_null;
       }
 
       /*
@@ -2911,9 +2913,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *
         There is a refresh in progress for this table.
         Signal the caller that it has to try again.
       */
-      if (refresh)
-	*refresh=1;
-      DBUG_RETURN(0);
+      *refresh=1;
+      goto end_null;
     }
   }
   if (table)
@@ -2947,7 +2948,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
       if (check_if_table_exists(thd, table_list, &exists))
       {
         VOID(pthread_mutex_unlock(&LOCK_open));
-        DBUG_RETURN(NULL);
+        goto end_null;
       }
 
       if (!exists)
@@ -2958,7 +2959,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
         if (!(table= table_cache_insert_placeholder(thd, key, key_length)))
         {
           VOID(pthread_mutex_unlock(&LOCK_open));
-          DBUG_RETURN(NULL);
+          goto end_null;
         }
         /*
           Link placeholder to the open tables list so it will be automatically
@@ -2969,7 +2970,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
         table->next= thd->open_tables;
         thd->open_tables= table;
         VOID(pthread_mutex_unlock(&LOCK_open));
-        DBUG_RETURN(table);
+        goto end_table;
       }
       /* Table exists. Let us try to open it. */
     }
@@ -2978,7 +2979,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
     if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))))
     {
       VOID(pthread_mutex_unlock(&LOCK_open));
-      DBUG_RETURN(NULL);
+      goto end_null;
     }
 
     error= open_unireg_entry(thd, table, table_list, alias, key, key_length,
@@ -2987,7 +2988,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
     {
       my_free((uchar*)table, MYF(0));
       VOID(pthread_mutex_unlock(&LOCK_open));
-      DBUG_RETURN(NULL);
+      goto end_null;
     }
     if (table_list->view || error < 0)
     {
@@ -3000,7 +3001,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
 
       my_free((uchar*)table, MYF(0));
       VOID(pthread_mutex_unlock(&LOCK_open));
-      DBUG_RETURN(0); // VIEW
+      goto end_null; // VIEW
     }
     DBUG_PRINT("info", ("inserting table '%s'.'%s' 0x%lx into the cache",
                         table->s->db.str, table->s->table_name.str,
@@ -3011,11 +3012,16 @@ TABLE *open_table(THD *thd, TABLE_LIST *
   check_unused();				// Debugging call
 
   VOID(pthread_mutex_unlock(&LOCK_open));
-  if (refresh)
-  {
-    table->next=thd->open_tables;		/* Link into simple list */
-    thd->open_tables=table;
-  }
+
+  /*
+    Link into simple list
+    Note that the table is added to the THD open tables,
+    even if it's rejected by the Object_observer later.
+    This is to avoid table leaks.
+  */
+  table->next=thd->open_tables;
+  thd->open_tables=table;
+
   table->reginfo.lock_type=TL_READ;		/* Assume read */
 
  reset:
@@ -3050,7 +3056,33 @@ TABLE *open_table(THD *thd, TABLE_LIST *
   table_list->updatable= 1; // It is not derived table nor non-updatable VIEW
   table->clear_column_bitmaps();
   DBUG_ASSERT(table->key_read == 0);
+
+end_table:
+  if (thd->m_object_observer && ! thd->in_sub_stmt)
+  {
+    Object_version table_version;
+    table->s->make_object_version(& table_version);
+    if (thd->m_object_observer->notify(thd, & table_version))
+    {
+      *refresh= FALSE;
+      *invalidated= TRUE;
+      DBUG_RETURN(NULL);
+    }
+  }
   DBUG_RETURN(table);
+
+end_null:
+  if (thd->m_object_observer && ! thd->in_sub_stmt &&
+      ! *refresh && ! table_list->view)
+  {
+    Object_version table_version;
+    TABLE_SHARE::make_object_null_version(& table_version,
+                                          table_list->db,
+                                          table_list->table_name);
+    if (thd->m_object_observer->notify(thd, & table_version))
+      *invalidated= TRUE;
+  }
+  DBUG_RETURN(NULL);
 }
 
 
@@ -4343,6 +4375,68 @@ bool fix_merge_after_open(TABLE_LIST *ol
 }
 
 
+int recheck_all_views(THD *thd, TABLE_LIST *start)
+{
+  Object_observer *observer;
+  TABLE_LIST *tables;
+  TABLE_LIST dummy;
+  int rc;
+  Object_version view_version;
+
+  observer= thd->m_object_observer;
+
+  if (observer == NULL)
+    return 0;
+
+  /*
+    FIXME: There is no equivalent of sroutines_list for views,
+    so we have to iterate in TABLE_LIST.
+  */
+  for (tables= start; tables; tables= tables->next_global)
+  {
+    if (tables->view)
+    {
+      /*
+        WARNING: [1] and [2] are due to the implementation of views,
+        where the view logical name is found:
+        - sometime in (db, table_name) if the view has not be processed,
+        - sometime in (view_db, view_name) if the view has been processed.
+        See the comments in mysql_make_view()
+      */
+
+      /* [1] mysql_load_view_md5 uses (db, table_name) */
+      dummy.db= tables->view_db.str;
+      dummy.table_name= tables->view_name.str;
+      rc= mysql_load_view_md5(thd, &dummy);
+
+      if (rc == 0)
+      {
+        /* [2] TABLE_LIST::make_object_version uses (view_db, view_name) */
+        LEX fake_lex;
+        dummy.view_db= tables->view_db;
+        dummy.view_name= tables->view_name;
+        dummy.view= & fake_lex;
+        rc= dummy.make_object_version(& view_version);
+      }
+      else
+      {
+        rc= TABLE_SHARE::make_object_null_version(& view_version,
+                                                  tables->db,
+                                                  tables->table_name);
+      }
+
+      if (rc != 0)
+        return 1;
+
+      if (observer->notify(thd, & view_version))
+        return 1;
+    }
+  }
+
+  return 0;
+}
+
+
 /*
   Open all tables in list
 
@@ -4379,6 +4473,7 @@ int open_tables(THD *thd, TABLE_LIST **s
   MEM_ROOT new_frm_mem;
   /* Also used for indicating that prelocking is need */
   TABLE_LIST **query_tables_last_own;
+  bool invalidated;
   bool safe_to_ignore_table;
 
   DBUG_ENTER("open_tables");
@@ -4394,6 +4489,15 @@ int open_tables(THD *thd, TABLE_LIST **s
   query_tables_last_own= 0;
   thd_proc_info(thd, "Opening tables");
 
+  if (! thd->in_sub_stmt)
+  {
+    if (recheck_all_views(thd, *start))
+    {
+      result= -1;
+      goto err;
+    }
+  }
+
   /*
     If we are not already executing prelocked statement and don't have
     statement for which table list for prelocking is already built, let
@@ -4426,6 +4530,17 @@ int open_tables(THD *thd, TABLE_LIST **s
       *start= thd->lex->query_tables;
     }
   }
+  else
+  {
+    if (! thd->in_sub_stmt)
+    {
+      if (sp_refresh_routines(thd, thd->lex))
+      {
+        result= -1;
+        goto err;
+      }
+    }
+  }
 
   /*
     For every table in the list of tables to open, try to find or open
@@ -4436,6 +4551,7 @@ int open_tables(THD *thd, TABLE_LIST **s
     DBUG_PRINT("tcache", ("opening table: '%s'.'%s'  item: 0x%lx",
                           tables->db, tables->table_name, (long) tables));
 
+    invalidated= FALSE;
     safe_to_ignore_table= FALSE;
 
     /*
@@ -4480,12 +4596,14 @@ int open_tables(THD *thd, TABLE_LIST **s
         */
         Prelock_error_handler prelock_handler;
         thd->push_internal_handler(& prelock_handler);
-        tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags);
+        tables->table= open_table(thd, tables, &new_frm_mem,
+                                  &refresh, &invalidated, flags);
         thd->pop_internal_handler();
         safe_to_ignore_table= prelock_handler.safely_trapped_errors();
       }
       else
-        tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags);
+        tables->table= open_table(thd, tables, &new_frm_mem,
+                                  &refresh, &invalidated, flags);
     }
     else
       DBUG_PRINT("tcache", ("referenced table: '%s'.'%s' 0x%lx",
@@ -4496,10 +4614,13 @@ int open_tables(THD *thd, TABLE_LIST **s
     {
       free_root(&new_frm_mem, MYF(MY_KEEP_PREALLOC));
 
+      if (! invalidated)
+      {
+/* TODO: tabulate the code after review */
       if (tables->view)
       {
         /* VIEW placeholder */
-	(*counter)--;
+        (*counter)--;
 
         /*
           tables->next_global list consists of two parts:
@@ -4519,7 +4640,7 @@ int open_tables(THD *thd, TABLE_LIST **s
           call destructor for this LEX.
         */
         hash_free(&tables->view->sroutines);
-	goto process_view_routines;
+        goto process_view_routines;
       }
 
       /*
@@ -4535,7 +4656,7 @@ int open_tables(THD *thd, TABLE_LIST **s
         parent_l->next_global= *parent_l->table->child_last_l;
       }
 
-      if (refresh)				// Refresh in progress
+      if (refresh)                            // Refresh in progress
       {
         /*
           We have met name-locked or old version of table. Now we have
@@ -4554,7 +4675,7 @@ int open_tables(THD *thd, TABLE_LIST **s
         if (query_tables_last_own)
           thd->lex->mark_as_requiring_prelocking(query_tables_last_own);
         close_tables_for_reopen(thd, start);
-	goto restart;
+        goto restart;
       }
 
       if (safe_to_ignore_table)
@@ -4563,6 +4684,7 @@ int open_tables(THD *thd, TABLE_LIST **s
                             tables->db, tables->alias));
         continue;
       }
+      }
 
       result= -1;				// Fatal error
       break;
@@ -4790,13 +4912,15 @@ TABLE *open_ltable(THD *thd, TABLE_LIST 
 {
   TABLE *table;
   bool refresh;
+  bool invalidated;
   DBUG_ENTER("open_ltable");
 
   thd_proc_info(thd, "Opening table");
   thd->current_tablenr= 0;
   /* open_ltable can be used only for BASIC TABLEs */
   table_list->required_type= FRMTYPE_TABLE;
-  while (!(table= open_table(thd, table_list, thd->mem_root, &refresh, 0)) &&
+  while (!(table= open_table(thd, table_list, thd->mem_root,
+                             &refresh, &invalidated, 0)) &&
          refresh)
     ;
 
@@ -8674,10 +8798,12 @@ open_system_tables_for_read(THD *thd, TA
   thd->reset_n_backup_open_tables_state(backup);
 
   uint count= 0;
-  bool not_used;
+  bool refresh_not_used;
+  bool invalidated_not_used;
   for (TABLE_LIST *tables= table_list; tables; tables= tables->next_global)
   {
-    TABLE *table= open_table(thd, tables, thd->mem_root, &not_used,
+    TABLE *table= open_table(thd, tables, thd->mem_root,
+                             &refresh_not_used, &invalidated_not_used,
                              MYSQL_LOCK_IGNORE_FLUSH);
     if (!table)
       goto error;
@@ -8697,7 +8823,7 @@ open_system_tables_for_read(THD *thd, TA
       *(ptr++)= tables->table;
 
     thd->lock= mysql_lock_tables(thd, list, count,
-                                 MYSQL_LOCK_IGNORE_FLUSH, &not_used);
+                                 MYSQL_LOCK_IGNORE_FLUSH, &refresh_not_used);
   }
   if (thd->lock)
     DBUG_RETURN(FALSE);
diff -Nrup a/sql/sql_class.cc b/sql/sql_class.cc
--- a/sql/sql_class.cc	2008-02-19 05:57:59 -07:00
+++ b/sql/sql_class.cc	2008-02-20 21:55:29 -07:00
@@ -50,6 +50,298 @@ char empty_c_string[1]= {0};    /* used 
 
 const char * const THD::DEFAULT_WHERE= "field list";
 
+Object_id::Object_id()
+{
+  m_type= NO_OBJECT_TYPE;
+  m_key[0]= '\0';
+  m_key_length= 0;
+}
+
+Object_id::Object_id(const Object_id& oid)
+{
+  m_type= oid.m_type;
+  memcpy(m_key, oid.m_key, sizeof(m_key));
+  m_key_length= oid.m_key_length;
+}
+
+bool Object_id::is_equal(const Object_id& oid) const
+{
+  return ((m_type == oid.m_type) &&
+         (m_key_length == oid.m_key_length) &&
+         (memcmp(m_key, oid.m_key, m_key_length) == 0));
+}
+
+int
+Object_id::set(const Object_id *oid)
+{
+  m_type= oid->m_type;
+  m_key_length= oid->m_key_length;
+  memcpy(m_key, oid->m_key, m_key_length);
+  return 0;
+}
+
+int
+Object_id::set_table(const char* db, const char* name)
+{
+  /* FIXME: Fix TABLE_SHARE::db, TABLE_SHARE::table_name */
+  LEX_STRING lex_db;
+  LEX_STRING lex_name;
+  lex_db.str= (char*) db;
+  lex_db.length= strlen(lex_db.str);
+  lex_name.str= (char*) name;
+  lex_name.length= strlen(lex_name.str);
+
+  return set_table(& lex_db, & lex_name);
+}
+
+int
+Object_id::set_key(
+  char kind, const LEX_STRING *db, const LEX_STRING *name)
+{
+  DBUG_ASSERT(db->length <= NAME_LEN);
+  DBUG_ASSERT(name->length <= NAME_LEN);
+
+  /* format: <type> <db> . <name> \0 */
+  m_key_length= sprintf(m_key, "%c%.*s.%.*s",
+                        kind,
+                        (int) db->length, db->str,
+                        (int) name->length, name->str);
+  DBUG_ASSERT(m_key_length <= sizeof(m_key));
+
+  return 0;
+}
+
+Version_id::Version_id()
+  : m_kind(NULL_VERSION),
+    m_long_value(0)
+{
+  m_md5_value[0]= 0;
+}
+
+Version_id::Version_id(const Version_id& vid)
+{
+  m_kind= vid.m_kind;
+  switch(m_kind)
+  {
+  case NULL_VERSION:
+    break;
+  case TABLE_VERSION:
+  case TEMPORARY_TABLE_VERSION:
+    m_long_value= vid.m_long_value;
+    break;
+  case VIEW_VERSION:
+  case PROCEDURE_VERSION:
+  case FUNCTION_VERSION:
+    memcpy(m_md5_value, vid.m_md5_value, sizeof(m_md5_value));
+    break;
+  }
+}
+
+Version_id::~Version_id()
+{}
+
+bool
+Version_id::is_equal(const Version_id *vid) const
+{
+  if (m_kind != vid->m_kind)
+    return FALSE;
+
+  switch(m_kind)
+  {
+    case NULL_VERSION:
+      return TRUE;
+    case TABLE_VERSION:
+    case TEMPORARY_TABLE_VERSION:
+      return (m_long_value == vid->m_long_value);
+    case VIEW_VERSION:
+    case PROCEDURE_VERSION:
+    case FUNCTION_VERSION:
+      return (memcmp(m_md5_value, vid->m_md5_value, sizeof(m_md5_value)) == 0);
+    default:
+      DBUG_ASSERT(FALSE);
+  }
+}
+
+bool
+Version_id::is_null_or_equal(const Version_id *vid) const
+{
+  return is_null() || vid->is_null() || is_equal(vid);
+}
+
+bool
+Version_id::is_null() const
+{
+  return (m_kind == NULL_VERSION);
+}
+
+void
+Version_id::set_null()
+{
+  m_kind= NULL_VERSION;
+}
+
+void
+Version_id::set(const Version_id *vid)
+{
+  m_kind= vid->m_kind;
+
+  switch(m_kind)
+  {
+    case NULL_VERSION:
+      return;
+    case TABLE_VERSION:
+    case TEMPORARY_TABLE_VERSION:
+      m_long_value= vid->m_long_value;
+      return;
+    case VIEW_VERSION:
+    case PROCEDURE_VERSION:
+    case FUNCTION_VERSION:
+      memcpy(m_md5_value, vid->m_md5_value, sizeof(m_md5_value));
+      return;
+    default:
+      DBUG_ASSERT(FALSE);
+  }
+}
+
+Version_id& Version_id::operator = (const Version_id& vid)
+{
+  set(& vid);
+  return *this;
+}
+
+void
+Version_id::set_table(longlong value)
+{
+  m_kind= TABLE_VERSION;
+  m_long_value= value;
+}
+
+void
+Version_id::set_temporary_table(longlong value)
+{
+  m_kind= TEMPORARY_TABLE_VERSION;
+  m_long_value= value;
+}
+
+void
+Version_id::set_view(const char* md5)
+{
+  m_kind= VIEW_VERSION;
+  memcpy(m_md5_value, md5, sizeof(m_md5_value));
+}
+
+void
+Version_id::set_procedure(const char* md5)
+{
+  m_kind= PROCEDURE_VERSION;
+  memcpy(m_md5_value, md5, sizeof(m_md5_value));
+}
+
+void
+Version_id::set_function(const char* md5)
+{
+  m_kind= FUNCTION_VERSION;
+  memcpy(m_md5_value, md5, sizeof(m_md5_value));
+}
+
+extern "C" uchar*
+get_obj_version_hash_key(const uchar *buff, size_t *length,
+                         my_bool /* unused */)
+{
+  Object_version *over= (Object_version*) buff;
+  return over->m_oid.get_hash_key(length);
+}
+
+void* Object_version::operator new(size_t size, MEM_ROOT* mem_root) throw()
+{
+  void *ptr;
+  DBUG_ENTER("Object_version::operator new");
+  ptr= alloc_root(mem_root, size);
+  DBUG_RETURN(ptr);
+}
+
+void
+Object_version::operator delete(void *ptr, MEM_ROOT* mem_root) throw()
+{
+}
+
+int
+Object_version_collection::init(MEM_ROOT *mem_root)
+{
+  DBUG_ENTER("Object_version_collection::init");
+  int rc;
+
+  DBUG_ASSERT(m_mem_root == NULL);
+  m_mem_root= mem_root;
+
+  rc= hash_init(& m_hash,
+                & my_charset_bin,
+                0,                             /* initial size */
+                0,
+                0,
+                (hash_get_key) get_obj_version_hash_key,
+                NULL,                          /* Nothing to free */
+                MYF(0));
+
+  DBUG_RETURN(rc);
+}
+
+Object_version_collection::~Object_version_collection()
+{
+  if (m_mem_root)
+  {
+    (void) hash_free(& m_hash);
+    m_mem_root= NULL;
+  }
+}
+
+int
+Object_version_collection::add(
+  const Object_id& oid,
+  const Version_id& vid)
+{
+  Object_version *over;
+  size_t hash_key_length;
+  uchar* hash_key;
+
+  DBUG_ENTER("Object_version_collection::add()");
+
+  hash_key= oid.get_hash_key(& hash_key_length);
+  over= (Object_version*) hash_search(& m_hash, hash_key, hash_key_length);
+
+  if (over)
+  {
+    if (! over->m_vid.is_equal(& vid))
+      DBUG_RETURN(1);
+  }
+  else
+  {
+    over= new (m_mem_root) Object_version(oid, vid);
+    if (my_hash_insert(& m_hash, (uchar*) over))
+      DBUG_RETURN(2);
+  }
+
+  DBUG_PRINT("info",("key: %s  length: %d",
+                    hash_key, (int) hash_key_length));
+  DBUG_RETURN(0);
+}
+
+int
+Object_version_collection::find(
+  const Object_id& oid,
+  const Object_version ** over) const
+{
+  Object_version *v;
+  size_t hash_key_length;
+  uchar* hash_key;
+
+  hash_key= oid.get_hash_key(& hash_key_length);
+  v= (Object_version*) hash_search(& m_hash, hash_key, hash_key_length);
+
+  *over= v;
+  return 0;
+}
+
 
 /*****************************************************************************
 ** Instansiate templates
@@ -500,7 +792,8 @@ THD::THD()
    bootstrap(0),
    derived_tables_processing(FALSE),
    spcont(NULL),
-   m_lip(NULL)
+   m_lip(NULL),
+   m_object_observer(NULL)
 {
   ulong tmp;
 
@@ -616,11 +909,7 @@ THD::THD()
 
 void THD::push_internal_handler(Internal_error_handler *handler)
 {
-  /*
-    TODO: The current implementation is limited to 1 handler at a time only.
-    THD and sp_rcontext need to be modified to use a common handler stack.
-  */
-  DBUG_ASSERT(m_internal_handler == NULL);
+  handler->m_next= m_internal_handler;
   m_internal_handler= handler;
 }
 
@@ -628,19 +917,24 @@ void THD::push_internal_handler(Internal
 bool THD::handle_error(uint sql_errno, const char *message,
                        MYSQL_ERROR::enum_warning_level level)
 {
-  if (m_internal_handler)
+  Internal_error_handler *current= m_internal_handler;
+
+  while (current != NULL)
   {
-    return m_internal_handler->handle_error(sql_errno, message, level, this);
+    if (current->handle_error(sql_errno, message, level, this))
+      return TRUE;
+
+    current= current->m_next;
   }
 
-  return FALSE;                                 // 'FALSE', as per coding style
+  return FALSE;
 }
 
 
 void THD::pop_internal_handler()
 {
   DBUG_ASSERT(m_internal_handler != NULL);
-  m_internal_handler= NULL;
+  m_internal_handler= m_internal_handler->m_next;
 }
 
 extern "C"
diff -Nrup a/sql/sql_class.h b/sql/sql_class.h
--- a/sql/sql_class.h	2008-02-19 05:57:59 -07:00
+++ b/sql/sql_class.h	2008-02-20 21:55:29 -07:00
@@ -23,6 +23,207 @@
 #include "log.h"
 #include "rpl_tblmap.h"
 
+/**
+  The type of a MySQL object.
+*/
+enum Object_type
+{
+  NO_OBJECT_TYPE= 0,
+  /**
+    A table in the general sense: a temporary table,
+    a base table, or a view.
+  */
+  OBJECT_TYPE_TABLE= 1,
+  /** A stored procedure. */
+  OBJECT_TYPE_PROCEDURE= 2,
+  /** A stored function. */
+  OBJECT_TYPE_FUNCTION= 3
+
+  /*
+    TODO: To be extended:
+    - OBJECT_TYPE_TRIGGER,
+    - OBJECT_TYPE_UDF,
+    - OBJECT_TYPE_EVENT,
+    - OBJECT_TYPE_USER,
+    - OBJECT_TYPE_TABLESPACE,
+    - OBJECT_TYPE_PARTITION,
+    etc, as needed.
+  */
+};
+
+/*
+  Format: <type> <NAME> <.> <NAME> <\0>
+*/
+#define MAX_OBJ_ID_KEY_LEN (2*NAME_LEN + 3)
+
+/**
+  An Object Identifier.
+  An Object_id can represent "TABLE db.t1", "PROCEDURE db.proc" or
+  "FUNCTION db.func", and contain both a type and a fully qualified name.
+  This class is mainly used to provide a HASH friendly key representation.
+*/
+class Object_id
+{
+public:
+  Object_id();
+  Object_id(const Object_id& oid);
+  ~Object_id() {}
+
+  bool is_equal(const Object_id& oid) const;
+
+  Object_type get_type() const
+  { return m_type; }
+
+  int set(const Object_id *oid);
+
+  int set_table(const char* db, const char* name);
+
+  int set_table(const LEX_STRING *db, const LEX_STRING *name)
+  {
+    m_type= OBJECT_TYPE_TABLE;
+    return set_key('T', db, name);
+  }
+
+  int set_procedure(const LEX_STRING *db, const LEX_STRING *name)
+  {
+    m_type= OBJECT_TYPE_PROCEDURE;
+    return set_key('P', db, name);
+  }
+
+  int set_function(const LEX_STRING *db, const LEX_STRING *name)
+  {
+    m_type= OBJECT_TYPE_FUNCTION;
+    return set_key('F', db, name);
+  }
+
+  uchar* get_hash_key(size_t *length) const
+  {
+    *length= m_key_length;
+    return (uchar*) & m_key[0];
+  }
+
+  const char* get_printable_key() const
+  {
+    return & m_key[0];
+  }
+
+private:
+  int set_key(char kind, const LEX_STRING *db, const LEX_STRING *name);
+
+private:
+  /** Type of this object. */
+  Object_type m_type;
+  /**
+    Object key, expressed as a HASH friendly string.
+    The key consist of the type, followed by a fully qualified name.
+  */
+  char m_key[MAX_OBJ_ID_KEY_LEN];
+  /** Length of m_key. */
+  uint m_key_length;
+
+private: // Not implemented, use set()
+  Object_id& operator = (const Object_id&);
+};
+
+/**
+  An Object Version.
+  An object version represent a specific version of a given object,
+  like "TABLE db.t1, version 1".
+  Object_version is primarily used to build collections,
+  see Object_version_collection.
+*/
+class Object_version
+{
+public:
+  static void* operator new(size_t size, MEM_ROOT* mem_root) throw();
+  static void operator delete(void *ptr, MEM_ROOT* mem_root) throw();
+
+  Object_version()
+  {}
+
+  Object_version(const Object_id& oid, const Version_id& vid)
+   : m_oid(oid), m_vid(vid)
+  {}
+
+  ~Object_version() {}
+
+  Object_id m_oid;
+  Version_id m_vid;
+
+private: // Non implemented
+  Object_version(const Object_version&);
+  Object_version& operator = (const Object_version&);
+};
+
+
+/**
+  A collection of object versions.
+  This class is a basic container of Object_version,
+  and enforces that at most one version of a given object can exist in the
+  collection.
+  For example, this collection can contain:
+  - "TABLE db.t1, version 1"
+  - "TABLE db.t2, version 2",
+  - "PROCEDURE db.proc, version AA",
+  - "TABLE db.t3, version 1"
+  but can not contain both:
+  - "TABLE db.t1, version 1"
+  - "TABLE db.t1, version 2"
+  The collection itself is implemented by a HASH.
+*/
+
+class Object_version_collection
+{
+public:
+  Object_version_collection()
+    : m_mem_root(NULL)
+  {}
+
+  ~Object_version_collection();
+
+  int init(MEM_ROOT *mem_root);
+  int add(const Object_id& oid, const Version_id & vid);
+  int find(const Object_id& oid, const Object_version ** over) const;
+
+private:
+  HASH m_hash;
+  MEM_ROOT *m_mem_root;
+
+private: // Non implemented
+  Object_version_collection(const Object_version_collection&);
+  Object_version_collection& operator=(const Object_version_collection&);
+};
+
+/**
+  Interface class for observers, used to inspect objects referenced
+  by a statement.
+  An Object_observer can inspect existing objects,
+  as well as get notified that some objects used in a statement are missing.
+  Observers can cause the current operation to fail, by returning an error
+  from the notification methods.
+*/
+
+class Object_observer
+{
+protected:
+  Object_observer() {}
+  virtual ~Object_observer() {}
+
+public:
+  /**
+    Notify that an object is used in a statement.
+    @param [in] thd Current thread
+    @param [in] found object version found during open
+    @return An error status
+      @retval 0 Success
+  */
+  virtual int notify(THD *thd, const Object_version *found) = 0;
+
+private: // Non implemented
+  Object_observer(const Object_observer&);
+  Object_observer& operator = (const Object_observer&);
+};
+
 class Relay_log_info;
 
 class Query_log_event;
@@ -454,7 +655,7 @@ public:
   bool is_backup_arena; /* True if this arena is used for backup. */
 #endif
   /*
-    The states relfects three diffrent life cycles for three
+    The states reflects three different life cycles for three
     different types of statements:
     Prepared statement: INITIALIZED -> PREPARED -> EXECUTED.
     Stored procedure:   INITIALIZED_FOR_SP -> EXECUTED.
@@ -943,7 +1144,10 @@ enum enum_thread_type
 class Internal_error_handler
 {
 protected:
-  Internal_error_handler() {}
+  Internal_error_handler()
+   : m_next(NULL)
+  {}
+
   virtual ~Internal_error_handler() {}
 
 public:
@@ -976,6 +1180,10 @@ public:
                             const char *message,
                             MYSQL_ERROR::enum_warning_level level,
                             THD *thd) = 0;
+
+private:
+  friend class THD;
+  Internal_error_handler *m_next;
 };
 
 
@@ -1716,6 +1924,8 @@ public:
     and may point to invalid memory after that.
   */
   Lex_input_stream *m_lip;
+
+  Object_observer *m_object_observer;
 
 #ifdef WITH_PARTITION_STORAGE_ENGINE
   partition_info *work_part_info;
diff -Nrup a/sql/sql_insert.cc b/sql/sql_insert.cc
--- a/sql/sql_insert.cc	2008-02-19 05:45:16 -07:00
+++ b/sql/sql_insert.cc	2008-02-20 21:55:29 -07:00
@@ -3392,7 +3392,10 @@ static TABLE *create_table_from_items(TH
       }
       else
       {
-        if (!(table= open_table(thd, create_table, thd->mem_root, (bool*) 0,
+        bool refresh_not_used;
+        bool invalidated_not_used;
+        if (!(table= open_table(thd, create_table, thd->mem_root,
+                                &refresh_not_used, &invalidated_not_used,
                                 MYSQL_OPEN_TEMPORARY_ONLY)) &&
             !create_info->table_existed)
         {
diff -Nrup a/sql/sql_prepare.cc b/sql/sql_prepare.cc
--- a/sql/sql_prepare.cc	2008-02-19 05:58:00 -07:00
+++ b/sql/sql_prepare.cc	2008-02-20 22:11:20 -07:00
@@ -116,6 +116,242 @@ public:
 #endif
 };
 
+/**
+  Collect the version number of all the objects seen during PREPARE.
+  The list of object versions recorded is stored with a prepared statement,
+  and is used during EXECUTE to re-validate the statement.
+*/
+class Prepare_observer : public Object_observer
+{
+public:
+  Prepare_observer(Object_version_collection* coll)
+    : m_coll(coll)
+  {}
+
+  virtual ~Prepare_observer() {}
+
+  virtual int notify(THD *thd, const Object_version* found);
+
+private:
+  Object_version_collection *m_coll;
+};
+
+/**
+  Enforce that the objects used during EXECUTE are valid.
+  This class enforces integrity constraints, and guarantees that the content
+  of a prepared statement is still valid and can be used to EXECUTE the
+  statement.
+*/
+class Execute_observer : public Object_observer
+{
+public:
+  Execute_observer(Object_version_collection* coll)
+    : m_coll(coll)
+  {}
+
+  virtual ~Execute_observer() {}
+
+  virtual int notify(THD *thd, const Object_version* found);
+
+private:
+  bool is_safe_table_operation(LEX *lex) const;
+  bool is_safe_sp_operation(LEX *lex) const;
+  int notify_table_or_view(THD *thd, const Object_version *found);
+  int notify_function_or_procedure(THD *thd, const Object_version *found);
+
+private:
+  Object_version_collection *m_coll;
+};
+
+int
+Prepare_observer::notify(THD *thd, const Object_version *found)
+{
+  int rc;
+
+  DBUG_ENTER("Prepare_observer::notify");
+
+  DBUG_PRINT("info", ("key= %s",
+                      found->m_oid.get_printable_key()));
+
+  switch(found->m_oid.get_type())
+  {
+  case OBJECT_TYPE_TABLE:
+  case OBJECT_TYPE_PROCEDURE:
+  case OBJECT_TYPE_FUNCTION:
+    rc= m_coll->add(found->m_oid, found->m_vid);
+  default:
+    rc= 0;
+  }
+
+  DBUG_RETURN(rc);
+}
+
+/**
+  Indicate if DDL performed on a table is safe for a prepared statement.
+  Some statements can be applied to different tables at each execution,
+  and the re-validation code should allow them.
+*/
+bool
+Execute_observer::is_safe_table_operation(LEX *lex) const
+{
+  switch(lex->sql_command)
+  {
+  case SQLCOM_ALTER_TABLE:
+  case SQLCOM_REPAIR:
+  case SQLCOM_ANALYZE:
+  case SQLCOM_OPTIMIZE:
+    return TRUE;
+
+  case SQLCOM_SHOW_CREATE: // safe for tables, not views
+  default:
+    return FALSE;
+  }
+}
+
+/**
+  Indicate if DDL performed on a procedure or function is safe for a
+  prepared statement.
+  Some statements can be applied to different SP/SF at each execution,
+  and the re-validation code should allow them.
+*/
+bool
+Execute_observer::is_safe_sp_operation(LEX *lex) const
+{
+  switch(lex->sql_command)
+  {
+  case SQLCOM_SHOW_CREATE_PROC:
+  case SQLCOM_SHOW_CREATE_FUNC:
+    return TRUE;
+
+  default:
+    return FALSE;
+  }
+}
+
+int
+Execute_observer::notify(THD *thd, const Object_version *found)
+{
+  int rc;
+
+  DBUG_ENTER("Execute_observer::notify()");
+
+  DBUG_PRINT("info", ("key= %s",
+                      found->m_oid.get_printable_key()));
+
+  DBUG_ASSERT(! thd->in_sub_stmt);
+
+  switch(found->m_oid.get_type())
+  {
+  case OBJECT_TYPE_TABLE:
+    rc= notify_table_or_view(thd, found);
+    break;
+  case OBJECT_TYPE_PROCEDURE:
+  case OBJECT_TYPE_FUNCTION:
+    rc= notify_function_or_procedure(thd, found);
+    break;
+  case NO_OBJECT_TYPE:
+  default:
+    rc= 0;
+    break;
+  }
+
+  DBUG_RETURN(rc);
+}
+
+int
+Execute_observer::notify_table_or_view(THD *thd, const Object_version *found)
+{
+  const Object_version *cached;
+  int rc;
+
+  DBUG_ENTER("Execute_observer::notify_table_or_view");
+
+  rc= m_coll->find(found->m_oid, & cached);
+  if (rc != 0)
+    goto err;
+
+  if (cached)
+  {
+    if (! cached->m_vid.is_equal(& found->m_vid))
+    {
+      if (! is_safe_table_operation(thd->lex))
+      {
+        my_error(ER_NEED_REPREPARE, MYF(0));
+        rc= 1;
+      }
+    }
+  }
+  else
+  {
+    /*
+      Here, the Prepared statement execute code is discovering
+      an object that was never found during the prepare phase.
+      For some statements, the prepare phase does not open and lock
+      tables (which is ok, performances), so that this is a normal case.
+      Example of such statements are:
+      - SHOW COLUMNS FROM <table>
+        --> open the table <table>
+      - INSTALL PLUGIN ... SONAME ...
+        --> open the table mysql.plugin
+      - SHOW CREATE TRIGGER t1_bi
+        --> open the table t1 (that the trigger relates to)
+      - SHOW COLUMNS FROM <view>
+        --> open the view <view>
+      Once a real object (version not null) is found by the first
+      execute(), it's added to the prepared statement internal state.
+      This is to properly detect further DDL changes for these late found
+      dependencies.
+      This is critical, since until Bug#27011 is fixed, the internal
+      state of the statement can contain a copy of view DDL,
+      which needs to be checked for changes later.
+    */
+    if (! found->m_vid.is_null())
+      rc= m_coll->add(found->m_oid, found->m_vid);
+  }
+
+err:
+  DBUG_RETURN(rc);
+}
+
+int
+Execute_observer::notify_function_or_procedure(THD *thd,
+                                               const Object_version *found)
+{
+  const Object_version *cached;
+  int rc;
+
+  DBUG_ENTER("Execute_observer::notify_function_or_procedure");
+
+  rc= m_coll->find(found->m_oid, & cached);
+  if (rc != 0)
+    goto err;
+
+  if (cached)
+  {
+    if (! cached->m_vid.is_equal(& found->m_vid))
+    {
+      if (! is_safe_sp_operation(thd->lex))
+      {
+        /*
+          For these objects, we do require an exact match:
+          - Version_id::is_equal() means that:
+            - either the object did not exist, and still does not exist
+            - or the object existed and is unchanged.
+        */
+        my_error(ER_NEED_REPREPARE, MYF(0));
+        rc= 1;
+      }
+    }
+  }
+  else
+  {
+    rc= m_coll->add(found->m_oid, found->m_vid);
+  }
+
+err:
+  DBUG_RETURN(rc);
+}
+
 /****************************************************************************/
 
 /**
@@ -170,6 +406,15 @@ private:
     SELECT_LEX and other classes).
   */
   MEM_ROOT main_mem_root;
+
+  /**
+    Collection of all objects versions this prepared statement depends on.
+    The prepared statement internal state contains optimizations based on
+    the objects seen during prepare. Should any of the objects listed change
+    between prepare() and execute(), the prepared statement will be declared
+    invalidated (with some exceptions, see Execute_observer).
+  */
+  Object_version_collection m_versions;
 };
 
 
@@ -2692,7 +2937,8 @@ Prepared_statement::Prepared_statement(T
   param_array(0),
   param_count(0),
   last_errno(0),
-  flags((uint) IS_IN_USE)
+  flags((uint) IS_IN_USE),
+  m_versions()
 {
   init_sql_alloc(&main_mem_root, thd_arg->variables.query_alloc_block_size,
                   thd_arg->variables.query_prealloc_size);
@@ -2832,6 +3078,9 @@ bool Prepared_statement::prepare(const c
   */
   status_var_increment(thd->status_var.com_stmt_prepare);
 
+  if (m_versions.init(& main_mem_root))
+    DBUG_RETURN(TRUE);
+
   /*
     alloc_query() uses thd->memroot && thd->query, so we should call
     both of backup_statement() and backup_query_arena() here.
@@ -2888,6 +3137,10 @@ bool Prepared_statement::prepare(const c
   */
   DBUG_ASSERT(thd->change_list.is_empty());
 
+  Prepare_observer observer(& m_versions);
+  Object_observer *save_observer= thd->m_object_observer;
+  thd->m_object_observer= & observer;
+
   /* 
    The only case where we should have items in the thd->free_list is
    after stmt->set_params_from_vars(), which may in some cases create
@@ -2897,6 +3150,8 @@ bool Prepared_statement::prepare(const c
   if (error == 0)
     error= check_prepared_statement(this, name.str != 0);
 
+  thd->m_object_observer= save_observer;
+
   /*
     Currently CREATE PROCEDURE/TRIGGER/EVENT are prohibited in prepared
     statements: ensure we have no memory leak here if by someone tries
@@ -3083,7 +3338,13 @@ bool Prepared_statement::execute(String 
     if (query_cache_send_result_to_client(thd, thd->query,
                                           thd->query_length) <= 0)
     {
+      Execute_observer observer(& m_versions);
+      Object_observer *save_observer= thd->m_object_observer;
+      thd->m_object_observer= & observer;
+
       error= mysql_execute_command(thd);
+
+      thd->m_object_observer= save_observer;
     }
   }
 
diff -Nrup a/sql/sql_table.cc b/sql/sql_table.cc
--- a/sql/sql_table.cc	2008-02-19 05:58:01 -07:00
+++ b/sql/sql_table.cc	2008-02-20 21:55:30 -07:00
@@ -6508,12 +6508,15 @@ view_err:
   {
     if (table->s->tmp_table)
     {
+      bool refresh_not_used;
+      bool invalidated_not_used;
       TABLE_LIST tbl;
       bzero((void*) &tbl, sizeof(tbl));
       tbl.db= new_db;
       tbl.table_name= tbl.alias= tmp_name;
       /* Table is in thd->temporary_tables */
-      new_table= open_table(thd, &tbl, thd->mem_root, (bool*) 0,
+      new_table= open_table(thd, &tbl, thd->mem_root,
+                            &refresh_not_used, &invalidated_not_used,
                             MYSQL_LOCK_IGNORE_FLUSH);
     }
     else
diff -Nrup a/sql/sql_view.cc b/sql/sql_view.cc
--- a/sql/sql_view.cc	2008-02-20 13:30:10 -07:00
+++ b/sql/sql_view.cc	2008-02-20 21:55:30 -07:00
@@ -178,7 +178,8 @@ static bool
 fill_defined_view_parts (THD *thd, TABLE_LIST *view)
 {
   LEX *lex= thd->lex;
-  bool not_used;
+  bool refresh_not_used;
+  bool invalidated_not_used;
   TABLE_LIST decoy;
 
   memcpy (&decoy, view, sizeof (TABLE_LIST));
@@ -203,7 +204,8 @@ fill_defined_view_parts (THD *thd, TABLE
     since the return value itself does not mean anything.
   */
 
-  open_table(thd, &decoy, thd->mem_root, &not_used, OPEN_VIEW_NO_PARSE);
+  open_table(thd, &decoy, thd->mem_root,
+             &refresh_not_used, &invalidated_not_used, OPEN_VIEW_NO_PARSE);
 
   if (!decoy.view)
   {
@@ -697,6 +699,19 @@ static File_option view_parameters[]=
   FILE_OPTIONS_STRING}
 };
 
+/* Short parsing, only to read MD5 */
+static File_option view_md5_parameters[]=
+{{{ C_STRING_WITH_LEN("query")},
+  my_offsetof(TABLE_LIST, select_stmt),
+  FILE_OPTIONS_ESTRING},
+ {{ C_STRING_WITH_LEN("md5")},
+  my_offsetof(TABLE_LIST, md5),
+  FILE_OPTIONS_STRING},
+ {{NullS, 0},			0,
+  FILE_OPTIONS_STRING}
+};
+
+
 static LEX_STRING view_file_type[]= {{(char*) STRING_WITH_LEN("VIEW") }};
 
 
@@ -963,6 +978,7 @@ bool mysql_make_view(THD *thd, File_pars
     {
       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));
@@ -1020,6 +1036,26 @@ bool mysql_make_view(THD *thd, File_pars
                     required_view_parameters, &file_parser_dummy_hook))
     goto err;
 
+
+  if (thd->m_object_observer && ! thd->in_sub_stmt)
+  {
+    Object_version version;
+
+    if (version.m_oid.set_table(table->db, table->table_name))
+    {
+      result= TRUE;
+      goto end;
+    }
+    version.m_vid.set_view(table->md5.str);
+
+    if (thd->m_object_observer->notify(thd, & version))
+    {
+      result= TRUE;
+      goto end;
+    }
+  }
+
+
   /*
     check old format view .frm
   */
@@ -1459,6 +1495,44 @@ err:
   table->view= 0;	// now it is not VIEW placeholder
   result= 1;
   goto end;
+}
+
+int mysql_load_view_md5(THD *thd, TABLE_LIST *view)
+{
+  int rc;
+  LEX_STRING pathstr;
+  File_parser *parser;
+  char path_buff[FN_REFLEN];
+  TABLE_LIST dummy;
+
+  DBUG_ENTER("mysql_load_view_md5");
+  DBUG_PRINT("info", ("view: (%s.%s)", view->db, view->table_name));
+
+  bzero(& dummy, sizeof(dummy));
+
+  pathstr.str= (char *) path_buff;
+  pathstr.length= build_table_filename(path_buff, sizeof(path_buff) - 1,
+                                       view->db, view->table_name,
+                                       reg_ext, 0);
+
+  parser= sql_parse_prepare(&pathstr, thd->mem_root, 0);
+  if (parser &&
+      parser->ok() &&
+      is_equal(&view_type, parser->type()) &&
+      parser->parse((uchar*) &dummy,
+                    thd->mem_root,
+                    view_md5_parameters,
+                    array_elements(view_md5_parameters)-1,
+                    &file_parser_dummy_hook) == 0)
+  {
+    view->md5.str= dummy.md5.str;
+    view->md5.length= dummy.md5.length;
+    rc= 0;
+  }
+  else
+    rc= 1;
+
+  DBUG_RETURN(rc);
 }
 
 
diff -Nrup a/sql/sql_view.h b/sql/sql_view.h
--- a/sql/sql_view.h	2006-12-23 12:19:56 -07:00
+++ b/sql/sql_view.h	2008-02-20 21:55:30 -07:00
@@ -31,6 +31,8 @@ frm_type_enum mysql_frm_type(THD *thd, c
 
 int view_checksum(THD *thd, TABLE_LIST *view);
 
+int mysql_load_view_md5(THD *thd, TABLE_LIST *view);
+
 extern TYPELIB updatable_views_with_limit_typelib;
 
 bool check_duplicate_names(List<Item>& item_list, bool gen_unique_view_names);
diff -Nrup a/sql/table.cc b/sql/table.cc
--- a/sql/table.cc	2007-12-17 06:28:05 -07:00
+++ b/sql/table.cc	2008-02-20 21:55:30 -07:00
@@ -250,6 +250,41 @@ TABLE_CATEGORY get_table_category(const 
   return TABLE_CATEGORY_USER;
 }
 
+int TABLE_SHARE::make_object_version(Object_version *version)
+{
+  int rc;
+
+  rc= version->m_oid.set_table(& db, & table_name);
+
+  if ((tmp_table == INTERNAL_TMP_TABLE) ||
+      (tmp_table == NON_TRANSACTIONAL_TMP_TABLE))
+  {
+    /*
+      table_map_id is THD::query_id.
+      The version number is not persistent,
+      but the table is not persistent either,
+      so using table_map_id is ok here.
+    */
+    version->m_vid.set_temporary_table(table_map_id);
+  }
+  else
+  {
+    version->m_vid.set_table(table_map_id);
+  }
+
+  return rc;
+}
+
+int TABLE_SHARE::make_object_null_version(
+  Object_version * version, const char* db, const char* name)
+{
+  int rc;
+
+  rc= version->m_oid.set_table(db, name);
+  version->m_vid.set_null();
+
+  return rc;
+}
 
 /*
   Allocate a setup TABLE_SHARE structure
@@ -2934,6 +2969,17 @@ void st_table::reset_item_list(List<Item
     DBUG_ASSERT(item_field != 0);
     item_field->reset_field(*ptr);
   }
+}
+
+int TABLE_LIST::make_object_version(Object_version *version)
+{
+  int rc;
+
+  DBUG_ASSERT(view);
+  rc= version->m_oid.set_table(& view_db, & view_name);
+  version->m_vid.set_view(md5.str);
+
+  return rc;
 }
 
 /*
diff -Nrup a/sql/table.h b/sql/table.h
--- a/sql/table.h	2007-12-20 13:24:07 -07:00
+++ b/sql/table.h	2008-02-20 21:55:30 -07:00
@@ -24,6 +24,7 @@ class st_select_lex;
 class partition_info;
 class COND_EQUAL;
 class Security_context;
+class Object_version;
 
 /*************************************************************************/
 
@@ -437,6 +438,11 @@ typedef struct st_table_share
     return table_map_id;
   }
 
+  int make_object_version(Object_version *version);
+  static int make_object_null_version(
+    Object_version * version, const char* db, const char* name);
+
+  int make_object_version(Version_id *version);
 } TABLE_SHARE;
 
 
@@ -893,6 +899,8 @@ class Index_hint;
 struct TABLE_LIST
 {
   TABLE_LIST() {}                          /* Remove gcc warning */
+
+  int make_object_version(Object_version *version);
 
   /**
     Prepare TABLE_LIST that consists of one table instance to use in
Thread
bk commit into 5.1 tree (malff:1.2565) BUG#12093marc.alff21 Feb