From 9f68c710ee453cc76f1d63d1c641c5136fb42c68 Mon Sep 17 00:00:00 2001
From: halfspawn <j.brauge@qualiac.com>
Date: Tue, 21 Feb 2017 15:57:00 +0100
Subject: [PATCH] MDEV-10598 sql_mode=ORACLE: Variable declarations can go
 after cursor declarations

---
 .../suite/compat/oracle/r/sp-cursor-decl.result    | 206 +++++++++++++++++++
 .../suite/compat/oracle/t/sp-cursor-decl.test      | 221 +++++++++++++++++++++
 sql/sp_head.cc                                     |  48 +++++
 sql/sp_head.h                                      |  19 ++
 sql/sql_lex.cc                                     |   7 +
 sql/sql_lex.h                                      |  19 ++
 sql/sql_yacc_ora.yy                                |  53 +++--
 7 files changed, 558 insertions(+), 15 deletions(-)
 create mode 100644 mysql-test/suite/compat/oracle/r/sp-cursor-decl.result
 create mode 100644 mysql-test/suite/compat/oracle/t/sp-cursor-decl.test

diff --git a/mysql-test/suite/compat/oracle/r/sp-cursor-decl.result b/mysql-test/suite/compat/oracle/r/sp-cursor-decl.result
new file mode 100644
index 0000000..9f55673
--- /dev/null
+++ b/mysql-test/suite/compat/oracle/r/sp-cursor-decl.result
@@ -0,0 +1,206 @@
+SET sql_mode=ORACLE;
+#
+# MDEV-10598 sql_mode=ORACLE: Variable declarations can go after cursor declarations
+#
+#
+# Variable after cursor declaration
+#
+CREATE OR REPLACE TABLE t1 (a INT);
+insert into t1 values (1);
+insert into t1 values (2);
+CREATE or replace PROCEDURE p1
+AS
+CURSOR c IS SELECT a FROM t1;
+var1 varchar(10);
+BEGIN
+OPEN c;
+fetch c into var1;
+SELECT c%ROWCOUNT,var1;
+close c;
+END;
+$$
+CALL p1;
+c%ROWCOUNT	var1
+1	1
+DROP PROCEDURE p1;
+drop table t1;
+#
+# Variable after condition declaration
+#
+CREATE OR REPLACE TABLE t1 (col1 INT);
+insert into t1 values (1);
+create unique index t1_col1 on t1 (col1);
+CREATE or replace PROCEDURE p1
+AS
+dup_key CONDITION FOR SQLSTATE '23000';
+var1 varchar(40);
+CONTINUE HANDLER FOR dup_key
+BEGIN
+var1:='duplicate key in index';
+END;
+BEGIN
+var1:='';
+insert into t1 values (1);
+select var1;
+END;
+$$
+CALL p1;
+var1
+duplicate key in index
+DROP PROCEDURE p1;
+drop table t1;
+#
+# Condition after cursor declaration
+#
+CREATE OR REPLACE TABLE t1 (col1 INT);
+insert into t1 values (1);
+create unique index t1_col1 on t1 (col1);
+CREATE or replace PROCEDURE p1
+AS
+var1 varchar(40);
+var2 integer;
+CURSOR c IS SELECT col1 FROM t1;
+dup_key CONDITION FOR SQLSTATE '23000';
+CONTINUE HANDLER FOR dup_key
+BEGIN
+var1:='duplicate key in index';
+END;
+BEGIN
+var1:='';
+insert into t1 values (1);
+SELECT var1;
+END;
+$$
+CALL p1;
+var1
+duplicate key in index
+DROP PROCEDURE p1;
+drop table t1;
+#
+# Cursor after handler declaration
+#
+CREATE OR REPLACE TABLE t1 (col1 INT);
+insert into t1 values (1);
+create unique index t1_col1 on t1 (col1);
+CREATE or replace PROCEDURE p1
+AS
+var1 varchar(40);
+var2 integer;
+dup_key CONDITION FOR SQLSTATE '23000';
+CONTINUE HANDLER FOR dup_key
+BEGIN
+var1:='duplicate key in index';
+END;
+CURSOR c IS SELECT col1 FROM t1;
+BEGIN
+var1:='';
+insert into t1 values (1);
+SELECT var1;
+END;
+$$
+ERROR 42000: Cursor declaration after handler declaration
+drop table t1;
+#
+# Condition after handler declaration
+#
+CREATE OR REPLACE TABLE t1 (col1 INT);
+insert into t1 values (1);
+create unique index t1_col1 on t1 (col1);
+CREATE or replace PROCEDURE p1
+AS
+var1 varchar(40);
+var2 integer;
+dup_key CONDITION FOR SQLSTATE '23000';
+CURSOR c IS SELECT col1 FROM t1;
+CONTINUE HANDLER FOR dup_key
+BEGIN
+var1:='duplicate key in index';
+END;
+divide_by_zero CONDITION FOR SQLSTATE '22012';
+BEGIN
+var1:='';
+insert into t1 values (1);
+SELECT var1;
+END;
+$$
+ERROR 42000: Variable or condition declaration after cursor or handler declaration
+drop table t1;
+#
+# Variable after handler declaration
+#
+CREATE OR REPLACE TABLE t1 (col1 INT);
+insert into t1 values (1);
+create unique index t1_col1 on t1 (col1);
+CREATE or replace PROCEDURE p1
+AS
+var1 varchar(40);
+var2 integer;
+dup_key CONDITION FOR SQLSTATE '23000';
+CURSOR c IS SELECT col1 FROM t1;
+CONTINUE HANDLER FOR dup_key
+BEGIN
+var1:='duplicate key in index';
+END;
+divide_by_zero CONDITION FOR SQLSTATE '22012';
+BEGIN
+var1:='';
+insert into t1 values (1);
+SELECT var1;
+END;
+$$
+ERROR 42000: Variable or condition declaration after cursor or handler declaration
+drop table t1;
+#
+# Variable after cursor (inner block)
+#
+CREATE OR REPLACE TABLE t1 (col1 INT);
+insert into t1 values (1);
+insert into t1 values (2);
+create unique index t1_col1 on t1 (col1);
+CREATE or replace PROCEDURE p1
+AS
+CURSOR c IS SELECT col1 FROM t1;
+var1 varchar(40);
+BEGIN
+OPEN c;
+begin
+declare
+CURSOR c IS SELECT col1 FROM t1 where col1=2;
+var2 integer;
+dup_key CONDITION FOR SQLSTATE '23000';
+CONTINUE HANDLER FOR dup_key
+BEGIN
+var1:='duplicate key in index';
+END;
+begin
+OPEN c;
+fetch c into var1;
+SELECT 'inner cursor',var1;
+insert into t1 values (2);
+close c;
+end;  
+end;
+SELECT var1;
+fetch c into var1;
+SELECT c%ROWCOUNT,var1;
+begin 
+insert into t1 values (2);
+exception when 1062 then 
+begin 
+SELECT 'dup key caugth';
+end;
+end;
+close c;
+END;
+$$
+CALL p1;
+inner cursor	var1
+inner cursor	2
+var1
+duplicate key in index
+c%ROWCOUNT	var1
+1	1
+dup key caugth
+dup key caugth
+DROP PROCEDURE p1;
+drop table t1;
diff --git a/mysql-test/suite/compat/oracle/t/sp-cursor-decl.test b/mysql-test/suite/compat/oracle/t/sp-cursor-decl.test
new file mode 100644
index 0000000..108ce0d
--- /dev/null
+++ b/mysql-test/suite/compat/oracle/t/sp-cursor-decl.test
@@ -0,0 +1,221 @@
+SET sql_mode=ORACLE;
+
+--echo #
+--echo # MDEV-10598 sql_mode=ORACLE: Variable declarations can go after cursor declarations
+--echo #
+
+--echo #
+--echo # Variable after cursor declaration
+--echo #
+
+CREATE OR REPLACE TABLE t1 (a INT);
+insert into t1 values (1);
+insert into t1 values (2);
+DELIMITER $$;
+CREATE or replace PROCEDURE p1
+AS
+  CURSOR c IS SELECT a FROM t1;
+  var1 varchar(10);
+BEGIN
+  OPEN c;
+  fetch c into var1;
+  SELECT c%ROWCOUNT,var1;
+  close c;
+END;
+$$
+DELIMITER ;$$
+CALL p1;
+DROP PROCEDURE p1;
+drop table t1;
+
+--echo #
+--echo # Variable after condition declaration
+--echo #
+
+CREATE OR REPLACE TABLE t1 (col1 INT);
+insert into t1 values (1);
+create unique index t1_col1 on t1 (col1);
+DELIMITER $$;
+CREATE or replace PROCEDURE p1
+AS
+  dup_key CONDITION FOR SQLSTATE '23000';
+  var1 varchar(40);
+  CONTINUE HANDLER FOR dup_key
+  BEGIN
+    var1:='duplicate key in index';
+  END;
+BEGIN
+  var1:='';
+  insert into t1 values (1);
+  select var1;
+END;
+$$
+DELIMITER ;$$
+CALL p1;
+DROP PROCEDURE p1;
+drop table t1;
+
+--echo #
+--echo # Condition after cursor declaration
+--echo #
+
+CREATE OR REPLACE TABLE t1 (col1 INT);
+insert into t1 values (1);
+create unique index t1_col1 on t1 (col1);
+DELIMITER $$;
+CREATE or replace PROCEDURE p1
+AS
+  var1 varchar(40);
+  var2 integer;
+  CURSOR c IS SELECT col1 FROM t1;
+  dup_key CONDITION FOR SQLSTATE '23000';
+  CONTINUE HANDLER FOR dup_key
+  BEGIN
+    var1:='duplicate key in index';
+  END;
+BEGIN
+  var1:='';
+  insert into t1 values (1);
+  SELECT var1;
+END;
+$$
+DELIMITER ;$$
+CALL p1;
+DROP PROCEDURE p1;
+drop table t1;
+
+--echo #
+--echo # Cursor after handler declaration
+--echo #
+
+CREATE OR REPLACE TABLE t1 (col1 INT);
+insert into t1 values (1);
+create unique index t1_col1 on t1 (col1);
+DELIMITER $$;
+--error ER_SP_CURSOR_AFTER_HANDLER
+CREATE or replace PROCEDURE p1
+AS
+  var1 varchar(40);
+  var2 integer;
+  dup_key CONDITION FOR SQLSTATE '23000';
+  CONTINUE HANDLER FOR dup_key
+  BEGIN
+    var1:='duplicate key in index';
+  END;
+  CURSOR c IS SELECT col1 FROM t1;
+BEGIN
+  var1:='';
+  insert into t1 values (1);
+  SELECT var1;
+END;
+$$
+DELIMITER ;$$
+drop table t1;
+
+--echo #
+--echo # Condition after handler declaration
+--echo #
+
+CREATE OR REPLACE TABLE t1 (col1 INT);
+insert into t1 values (1);
+create unique index t1_col1 on t1 (col1);
+DELIMITER $$;
+--error ER_SP_VARCOND_AFTER_CURSHNDLR
+CREATE or replace PROCEDURE p1
+AS
+  var1 varchar(40);
+  var2 integer;
+  dup_key CONDITION FOR SQLSTATE '23000';
+  CURSOR c IS SELECT col1 FROM t1;
+  CONTINUE HANDLER FOR dup_key
+  BEGIN
+    var1:='duplicate key in index';
+  END;
+  divide_by_zero CONDITION FOR SQLSTATE '22012';
+BEGIN
+  var1:='';
+  insert into t1 values (1);
+  SELECT var1;
+END;
+$$
+DELIMITER ;$$
+drop table t1;
+
+--echo #
+--echo # Variable after handler declaration
+--echo #
+
+CREATE OR REPLACE TABLE t1 (col1 INT);
+insert into t1 values (1);
+create unique index t1_col1 on t1 (col1);
+DELIMITER $$;
+--error ER_SP_VARCOND_AFTER_CURSHNDLR
+CREATE or replace PROCEDURE p1
+AS
+  var1 varchar(40);
+  var2 integer;
+  dup_key CONDITION FOR SQLSTATE '23000';
+  CURSOR c IS SELECT col1 FROM t1;
+  CONTINUE HANDLER FOR dup_key
+  BEGIN
+    var1:='duplicate key in index';
+  END;
+  divide_by_zero CONDITION FOR SQLSTATE '22012';
+BEGIN
+  var1:='';
+  insert into t1 values (1);
+  SELECT var1;
+END;
+$$
+DELIMITER ;$$
+drop table t1;
+
+--echo #
+--echo # Variable after cursor (inner block)
+--echo #
+
+CREATE OR REPLACE TABLE t1 (col1 INT);
+insert into t1 values (1);
+insert into t1 values (2);
+create unique index t1_col1 on t1 (col1);
+DELIMITER $$;
+CREATE or replace PROCEDURE p1
+AS
+  CURSOR c IS SELECT col1 FROM t1;
+  var1 varchar(40);
+BEGIN
+  OPEN c;
+  begin
+    declare
+      CURSOR c IS SELECT col1 FROM t1 where col1=2;
+      var2 integer;
+      dup_key CONDITION FOR SQLSTATE '23000';
+      CONTINUE HANDLER FOR dup_key
+      BEGIN
+        var1:='duplicate key in index';
+      END;
+    begin
+      OPEN c;
+      fetch c into var1;
+      SELECT 'inner cursor',var1;
+      insert into t1 values (2);
+      close c;
+    end;  
+  end;
+  SELECT var1;
+  fetch c into var1;
+  SELECT c%ROWCOUNT,var1;
+  begin 
+    insert into t1 values (2);
+  exception when 1062 then 
+    begin 
+      SELECT 'dup key caugth';
+    end;
+  end;
+  close c;
+END;
+$$
+DELIMITER ;$$
+CALL p1;
+DROP PROCEDURE p1;
+drop table t1;
\ No newline at end of file
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index e7ce0bc..a038edb 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -593,6 +593,7 @@ sp_head::sp_head()
   m_backpatch.empty();
   m_backpatch_goto.empty();
   m_cont_backpatch.empty();
+  m_delayed_cursors.empty();
   m_lex.empty();
   my_hash_init(&m_sptabs, system_charset_info, 0, 0, 0, sp_table_key, 0, 0);
   my_hash_init(&m_sroutines, system_charset_info, 0, 0, 0, sp_sroutine_key,
@@ -2454,6 +2455,53 @@ sp_head::do_cont_backpatch()
   }
 }
 
+int
+sp_head::delay_declare_cursor(THD *thd, sp_pcontext *spcont,
+                              const LEX_STRING name, LEX *cursor_stmt,
+                              sp_pcontext *param_ctx)
+{
+  ddc_t *ddc= (ddc_t *) thd->alloc(sizeof(ddc_t));
+  if (!ddc)
+    return 1;
+  ddc->spcont= spcont;
+  ddc->name= name ;
+  ddc->param_ctx= param_ctx;
+  ddc->cursor_stmt= cursor_stmt;
+  return m_delayed_cursors.push_back(ddc);
+}
+
+int
+sp_head::sp_push_cursors_declaration(THD *thd)
+{
+  if (m_delayed_cursors.is_empty())
+    return 0;
+
+  List_iterator<ddc_t> li(m_delayed_cursors);
+  ddc_t *c;
+  uint offp;
+  sp_instr_cpush *i;
+  while ((c= li++))
+  {
+    if (c->spcont->find_cursor(c->name, &offp, true))
+    {
+      my_error(ER_SP_DUP_CURS, MYF(0), c->name.str);
+      return true;
+    }
+    i= new (thd->mem_root)
+         sp_instr_cpush(instructions(), c->spcont, c->cursor_stmt,
+                        c->spcont->current_cursor_count());
+    if (i == NULL ||
+        add_instr(i) ||
+        c->spcont->add_cursor(c->name, c->param_ctx))
+    {
+      return 1;
+    }
+    li.remove();
+  }
+  return 0;
+}
+
+
 void
 sp_head::set_info(longlong created, longlong modified,
                   st_sp_chistics *chistics, sql_mode_t sql_mode)
diff --git a/sql/sp_head.h b/sql/sp_head.h
index 2fae49f..7fe0165 100644
--- a/sql/sp_head.h
+++ b/sql/sp_head.h
@@ -551,6 +551,12 @@ class sp_head :private Query_arena
   void
   do_cont_backpatch();
 
+  int
+  delay_declare_cursor(THD *thd, sp_pcontext *spcont, const LEX_STRING name,
+                       LEX *cursor_stmt, sp_pcontext *param_ctx);
+  int
+  sp_push_cursors_declaration(THD *thd);
+
   char *name(uint *lenp = 0) const
   {
     if (lenp)
@@ -746,6 +752,19 @@ class sp_head :private Query_arena
   */
   HASH m_sptabs;
 
+  /**
+    We need a special list to declare cursor between variables and conditions
+    declaration and handler declaration
+  */
+  typedef struct
+  {
+    sp_pcontext *spcont;
+    LEX_STRING name;
+    sp_pcontext *param_ctx;
+    LEX *cursor_stmt;
+  } ddc_t;
+  List<ddc_t> m_delayed_cursors;
+
   bool
   execute(THD *thd, bool merge_da_on_success);
 
diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc
index 8bec248..983ad33 100644
--- a/sql/sql_lex.cc
+++ b/sql/sql_lex.cc
@@ -5285,6 +5285,13 @@ bool LEX::sp_declare_cursor(THD *thd, const LEX_STRING name, LEX *cursor_stmt,
          spcont->add_cursor(name, param_ctx);
 }
 
+bool LEX::sp_delay_declare_cursor(THD *thd, const LEX_STRING name,
+                                  LEX *cursor_stmt, sp_pcontext *param_ctx)
+{
+  return sphead->delay_declare_cursor(thd, spcont, name, cursor_stmt,
+                                        param_ctx);
+}
+
 
 /**
   Generate an SP code for an "OPEN cursor_name" statement.
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
index aa34b23..65dce60 100644
--- a/sql/sql_lex.h
+++ b/sql/sql_lex.h
@@ -3125,6 +3125,8 @@ struct LEX: public Query_tables_list
 
   bool sp_declare_cursor(THD *thd, const LEX_STRING name, LEX *cursor_stmt,
                          sp_pcontext *param_ctx);
+  bool sp_delay_declare_cursor(THD *thd, const LEX_STRING name,
+                                  LEX *cursor_stmt, sp_pcontext *param_ctx);
   bool sp_open_cursor(THD *thd, const LEX_STRING name,
                       List<sp_assignment_lex> *parameters);
   Item_splocal *create_item_for_sp_var(LEX_STRING name, sp_variable *spvar,
@@ -3281,6 +3283,23 @@ struct LEX: public Query_tables_list
     res->join(b1, b2);
     return false;
   }
+  bool sp_declarations_join_ora(Lex_spblock_st *res,
+                            const Lex_spblock_st b1,
+                            const Lex_spblock_st b2) const
+  {
+    if ((b2.vars || b2.conds) && (b1.hndlrs))
+    {
+      my_error(ER_SP_VARCOND_AFTER_CURSHNDLR, MYF(0));
+      return true;
+    }
+    if (b2.curs && b1.hndlrs)
+    {
+      my_error(ER_SP_CURSOR_AFTER_HANDLER, MYF(0));
+      return true;
+    }
+    res->join(b1, b2);
+    return false;
+  }
   bool sp_block_with_exceptions_finalize_declarations(THD *thd);
   bool sp_block_with_exceptions_finalize_executable_section(THD *thd,
                                                   uint executable_section_ip);
diff --git a/sql/sql_yacc_ora.yy b/sql/sql_yacc_ora.yy
index 7b6a49d..6bf4248 100644
--- a/sql/sql_yacc_ora.yy
+++ b/sql/sql_yacc_ora.yy
@@ -1319,6 +1319,7 @@ END_OF_INPUT
 %type <num>  sp_decl_idents sp_handler_type sp_hcond_list
 %type <spcondvalue> sp_cond sp_hcond sqlstate signal_value opt_signal_value
 %type <spblock> sp_decl_body sp_decl_body_list opt_sp_decl_body_list
+%type <spblock> sp_decls_body_list sp_decl_handler
 %type <spblock_handlers> sp_block_statements_and_exceptions
 %type <sp_instr_addr> sp_instr_addr
 %type <sp_cursor_name_and_offset> sp_cursor_name_and_offset
@@ -2571,10 +2572,30 @@ opt_sp_decl_body_list:
         ;
 
 sp_decl_body_list:
+          sp_decls_body_list
+          {
+            if (Lex->sphead->sp_push_cursors_declaration(thd))
+              MYSQL_YYABORT;
+            $$=$1;
+          }
+
+sp_decls_body_list:
           sp_decl_body ';' { $$= $1; }
-        | sp_decl_body_list sp_decl_body ';'
+        | sp_decl_handler ';' { $$= $1; }
+        | sp_decls_body_list sp_decl_body ';'
           {
-            if (Lex->sp_declarations_join(&$$, $1, $2))
+            if (Lex->sp_declarations_join_ora(&$$, $1, $2))
+              MYSQL_YYABORT;
+          }
+        | sp_decls_body_list
+          {
+            // if any, push all delayed declare cursors before first handler
+            if (Lex->sphead->sp_push_cursors_declaration(thd))
+              MYSQL_YYABORT;
+          }
+          sp_decl_handler ';'
+          {
+            if (Lex->sp_declarations_join_ora(&$$, $1, $3))
               MYSQL_YYABORT;
           }
         ;
@@ -2683,18 +2704,6 @@ sp_decl_body:
             $$.vars= $$.hndlrs= $$.curs= 0;
             $$.conds= 1;
           }
-        | sp_handler_type HANDLER_SYM FOR_SYM
-          {
-            if (Lex->sp_handler_declaration_init(thd, $1))
-              MYSQL_YYABORT;
-          }
-          sp_hcond_list sp_proc_stmt
-          {
-            if (Lex->sp_handler_declaration_finalize(thd, $1))
-              MYSQL_YYABORT;
-            $$.vars= $$.conds= $$.curs= 0;
-            $$.hndlrs= 1;
-          }
         | CURSOR_SYM ident_directly_assignable
           {
             Lex->sp_block_init(thd);
@@ -2705,13 +2714,27 @@ sp_decl_body:
             sp_pcontext *param_ctx= Lex->spcont;
             if (Lex->sp_block_finalize(thd))
               MYSQL_YYABORT;
-            if (Lex->sp_declare_cursor(thd, $2, $6, param_ctx))
+            if (Lex->sp_delay_declare_cursor(thd, $2, $6, param_ctx))
               MYSQL_YYABORT;
             $$.vars= $$.conds= $$.hndlrs= 0;
             $$.curs= 1;
           }
         ;
 
+sp_decl_handler:
+          sp_handler_type HANDLER_SYM FOR_SYM
+          {
+            if (Lex->sp_handler_declaration_init(thd, $1))
+              MYSQL_YYABORT;
+          }
+          sp_hcond_list sp_proc_stmt
+          {
+            if (Lex->sp_handler_declaration_finalize(thd, $1))
+              MYSQL_YYABORT;
+            $$.vars= $$.conds= $$.curs= 0;
+            $$.hndlrs= 1;
+          }
+
 opt_parenthesized_cursor_formal_parameters:
           /* Empty */
         | '(' sp_fdparams ')'
-- 
2.6.3.windows.1

