diff --git a/mysql-test/suite/compat/oracle/r/sp-param.result b/mysql-test/suite/compat/oracle/r/sp-param.result
index d0b8ef4..17a8273 100644
--- a/mysql-test/suite/compat/oracle/r/sp-param.result
+++ b/mysql-test/suite/compat/oracle/r/sp-param.result
@@ -130,3 +130,60 @@ t1	CREATE TABLE "t1" (
 )
 DROP TABLE t1;
 DROP FUNCTION f1;
+
+Dynamic varchar parameters length set at runtime
+
+set sql_mode= 'oracle,strict_trans_tables';
+CREATE OR REPLACE PROCEDURE p1(pinout INOUT varchar, pin IN varchar)
+AS
+BEGIN
+pinout:=pin;
+END;
+/
+call p1(@w,'0123456789')
+/
+declare w varchar(10);
+begin
+call p1(w,'0123456789');
+end;
+/
+declare w varchar(5);
+begin
+call p1(w,'0123456789');
+end;
+/
+ERROR 22001: Data too long for column 'pinout' at row 1
+declare w varchar(20);
+begin
+w:='aaa';
+call p1(w,'0123456789');
+end;
+/
+declare w varchar(8);
+begin
+w:='aaa';
+call p1(w,'0123456789');
+end;
+/
+ERROR 22001: Data too long for column 'pinout' at row 1
+declare str varchar(6000);
+pout varchar(6000);
+begin
+str:=lpad('x',6000,'y');
+call p1(pout,str);
+select length(pout);
+end;
+/
+length(pout)
+6000
+declare str varchar(6000);
+pout varchar(4000);
+begin
+str:=lpad('x',6000,'y');
+call p1(pout,str);
+select length(pout);
+end;
+/
+ERROR 22001: Data too long for column 'pinout' at row 1
+drop procedure p1
+/
diff --git a/mysql-test/suite/compat/oracle/t/sp-param.test b/mysql-test/suite/compat/oracle/t/sp-param.test
index 2320331..be929cb 100644
--- a/mysql-test/suite/compat/oracle/t/sp-param.test
+++ b/mysql-test/suite/compat/oracle/t/sp-param.test
@@ -35,3 +35,60 @@ SET sql_mode=ORACLE;
 --let type = RAW
 --let length = 4000
 --source sp-param.inc
+
+--echo
+--echo Dynamic varchar parameters length set at runtime
+--echo
+set sql_mode= 'oracle,strict_trans_tables';
+delimiter /;
+CREATE OR REPLACE PROCEDURE p1(pinout INOUT varchar, pin IN varchar)
+AS
+BEGIN
+  pinout:=pin;
+END;
+/
+call p1(@w,'0123456789')
+/
+declare w varchar(10);
+begin
+  call p1(w,'0123456789');
+end;
+/
+--error ER_DATA_TOO_LONG
+declare w varchar(5);
+begin
+  call p1(w,'0123456789');
+end;
+/
+declare w varchar(20);
+begin
+  w:='aaa';
+  call p1(w,'0123456789');
+end;
+/
+--error ER_DATA_TOO_LONG
+declare w varchar(8);
+begin
+  w:='aaa';
+  call p1(w,'0123456789');
+end;
+/
+declare str varchar(6000);
+        pout varchar(6000);
+begin
+  str:=lpad('x',6000,'y');
+  call p1(pout,str);
+  select length(pout);
+end;
+/
+--error ER_DATA_TOO_LONG
+declare str varchar(6000);
+        pout varchar(4000);
+begin
+  str:=lpad('x',6000,'y');
+  call p1(pout,str);
+  select length(pout);
+end;
+/
+drop procedure p1
+/
\ No newline at end of file
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index 2ea40e3..9c9129f 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -1438,7 +1438,8 @@ bool sp_head::check_execute_access(THD *thd) const
   @retval           NULL - error (access denided or EOM)
   @retval          !NULL - success (the invoker has rights to all %TYPE tables)
 */
-sp_rcontext *sp_head::rcontext_create(THD *thd, Field *ret_value)
+sp_rcontext *sp_head::rcontext_create(THD *thd, Field *ret_value,
+                                      List<Item> *args)
 {
   bool has_column_type_refs= m_flags & HAS_COLUMN_TYPE_REFS;
 #ifndef NO_EMBEDDED_ACCESS_CHECKS
@@ -1448,7 +1449,7 @@ sp_rcontext *sp_head::rcontext_create(THD *thd, Field *ret_value)
     return NULL;
 #endif
   sp_rcontext *res= sp_rcontext::create(thd, m_pcont, ret_value,
-                                        has_column_type_refs);
+                                        has_column_type_refs, args);
 #ifndef NO_EMBEDDED_ACCESS_CHECKS
   if (has_column_type_refs)
     m_security_ctx.restore_security_context(thd, save_security_ctx);
@@ -1555,7 +1556,7 @@ sp_head::execute_trigger(THD *thd,
   thd->set_n_backup_active_arena(&call_arena, &backup_arena);
 
   if (!(nctx= sp_rcontext::create(thd, m_pcont, NULL,
-                                  m_flags & HAS_COLUMN_TYPE_REFS)))
+                                  m_flags & HAS_COLUMN_TYPE_REFS, NULL)))
   {
     err_status= TRUE;
     goto err_with_cleanup;
@@ -1636,6 +1637,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount,
   MEM_ROOT call_mem_root;
   Query_arena call_arena(&call_mem_root, Query_arena::STMT_INITIALIZED_FOR_SP);
   Query_arena backup_arena;
+  List<Item> largs;
   DBUG_ENTER("sp_head::execute_function");
   DBUG_PRINT("info", ("function %s", m_name.str));
 
@@ -1670,7 +1672,17 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount,
   init_sql_alloc(&call_mem_root, MEM_ROOT_BLOCK_SIZE, 0, MYF(0));
   thd->set_n_backup_active_arena(&call_arena, &backup_arena);
 
-  if (!(nctx= rcontext_create(thd, return_value_fld)))
+  List<Item> *plargs= NULL;
+  if (m_pcont->use_dyn_char_param())
+  {
+    for (uint i= 0 ; i < argcount ; i++)
+    {
+      largs.push_back(argp[i]);
+    }
+    plargs= &largs;
+  }
+
+  if (!(nctx= rcontext_create(thd, return_value_fld, plargs)))
   {
     thd->restore_active_arena(&call_arena, &backup_arena);
     err_status= TRUE;
@@ -1885,7 +1897,7 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
   if (! octx)
   {
     /* Create a temporary old context. */
-    if (!(octx= rcontext_create(thd, NULL)))
+    if (!(octx= rcontext_create(thd, NULL, args)))
     {
       DBUG_PRINT("error", ("Could not create octx"));
       DBUG_RETURN(TRUE);
@@ -1900,7 +1912,7 @@ sp_head::execute_procedure(THD *thd, List<Item> *args)
     thd->spcont->callers_arena= thd;
   }
 
-  if (!(nctx= rcontext_create(thd, NULL)))
+  if (!(nctx= rcontext_create(thd, NULL, args)))
   {
     delete nctx; /* Delete nctx if it was init() that failed. */
     thd->spcont= save_spcont;
diff --git a/sql/sp_head.h b/sql/sp_head.h
index c3dab60..5c97260 100644
--- a/sql/sp_head.h
+++ b/sql/sp_head.h
@@ -215,7 +215,7 @@ class sp_head :private Query_arena,
     m_sp_cache_version= version_arg;
   }
 
-  sp_rcontext *rcontext_create(THD *thd, Field *retval);
+  sp_rcontext *rcontext_create(THD *thd, Field *retval, List<Item> *args);
 
 private:
   /**
diff --git a/sql/sp_pcontext.cc b/sql/sp_pcontext.cc
index d98f800..6531046 100644
--- a/sql/sp_pcontext.cc
+++ b/sql/sp_pcontext.cc
@@ -95,7 +95,8 @@ sp_pcontext::sp_pcontext()
   : Sql_alloc(),
   m_max_var_index(0), m_max_cursor_index(0),
   m_parent(NULL), m_pboundary(0),
-  m_scope(REGULAR_SCOPE)
+  m_scope(REGULAR_SCOPE),
+  m_dyn_varchar_param(false)
 {
   init(0, 0, 0);
 }
diff --git a/sql/sp_pcontext.h b/sql/sp_pcontext.h
index 215ebbe..776bd10 100644
--- a/sql/sp_pcontext.h
+++ b/sql/sp_pcontext.h
@@ -692,6 +692,12 @@ class sp_pcontext : public Sql_alloc
     return m_for_loop;
   }
 
+  void set_dyn_char_param()
+  { m_dyn_varchar_param= true; }
+
+  bool use_dyn_char_param() const
+  { return m_dyn_varchar_param; }
+
 private:
   /// Constructor for a tree node.
   /// @param prev the parent parsing context
@@ -786,6 +792,8 @@ class sp_pcontext : public Sql_alloc
 
   /// FOR LOOP characteristics
   Lex_for_loop m_for_loop;
+
+  bool m_dyn_varchar_param;
 }; // class sp_pcontext : public Sql_alloc
 
 
diff --git a/sql/sp_rcontext.cc b/sql/sp_rcontext.cc
index 8c598e8..3e449cd 100644
--- a/sql/sp_rcontext.cc
+++ b/sql/sp_rcontext.cc
@@ -63,7 +63,8 @@ sp_rcontext::~sp_rcontext()
 sp_rcontext *sp_rcontext::create(THD *thd,
                                  const sp_pcontext *root_parsing_ctx,
                                  Field *return_value_fld,
-                                 bool resolve_type_refs)
+                                 bool resolve_type_refs,
+                                 List<Item> *args)
 {
   sp_rcontext *ctx= new (thd->mem_root) sp_rcontext(root_parsing_ctx,
                                                     return_value_fld,
@@ -75,6 +76,34 @@ sp_rcontext *sp_rcontext::create(THD *thd,
   List<Spvar_definition> field_def_lst;
   ctx->m_root_parsing_ctx->retrieve_field_definitions(&field_def_lst);
 
+  if (ctx->m_root_parsing_ctx->use_dyn_char_param() && args)
+  {
+    List_iterator<Spvar_definition> it(field_def_lst);
+    List_iterator<Item> it_args(*args);
+    DBUG_ASSERT(field_def_lst.elements >= args->elements );
+    Spvar_definition *def;
+    Item *arg;
+    uint arg_max_length;
+    /*
+      If length is not specified for varchar arguments, set length to the
+      maximum length of the real argument. Goals are:
+      -- avoid to allocate too much inused memory for m_var_table
+      -- allow length check inside the callee rather than during copy of
+         returned values in output variables.
+      -- allow varchar parameter size greater than 4000
+      Default length has been stored in "decimal" member during parse.
+    */
+    while ((def= it++) && (arg= it_args++))
+    {
+      if (def->type_handler() == &type_handler_varchar && def->decimals)
+      {
+        arg_max_length= arg->max_char_length();
+        def->length= arg_max_length > 0 ? arg_max_length : def->decimals;
+        def->create_length_to_internal_length_string();
+      }
+    }
+  }
+
   if (ctx->alloc_arrays(thd) ||
       (resolve_type_refs && ctx->resolve_type_refs(thd, field_def_lst)) ||
       ctx->init_var_table(thd, field_def_lst) ||
diff --git a/sql/sp_rcontext.h b/sql/sp_rcontext.h
index 281a00e..9215add 100644
--- a/sql/sp_rcontext.h
+++ b/sql/sp_rcontext.h
@@ -71,7 +71,8 @@ class sp_rcontext : public Sql_alloc
   static sp_rcontext *create(THD *thd,
                              const sp_pcontext *root_parsing_ctx,
                              Field *return_value_fld,
-                             bool resolve_type_refs);
+                             bool resolve_type_refs,
+                             List<Item> *args);
 
   ~sp_rcontext();
 
diff --git a/sql/sql_yacc_ora.yy b/sql/sql_yacc_ora.yy
index 97a55df..8e8a016 100644
--- a/sql/sql_yacc_ora.yy
+++ b/sql/sql_yacc_ora.yy
@@ -1053,8 +1053,6 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
 
 %type <const_simple_string>
         field_length opt_field_length opt_field_length_default_1
-        opt_field_length_default_sp_param_varchar
-        opt_field_length_default_sp_param_char
 
 %type <string>
         text_string hex_or_bin_String opt_gconcat_separator
@@ -1218,6 +1216,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
 %type <Lex_cast_type> cast_type cast_type_numeric cast_type_temporal
 
 %type <Lex_length_and_dec> precision opt_precision float_options
+        opt_field_length_default_sp_param_varchar
+        opt_field_length_default_sp_param_char
 
 %type <symbol> keyword keyword_sp
                keyword_directly_assignable
@@ -6552,9 +6552,11 @@ opt_field_length_default_1:
 
 
 /*
-  In sql_mode=ORACLE, a VARCHAR with no length is used
-  in SP parameters and return values and it's translated to VARCHAR(4000),
-  where 4000 is the maximum possible size for VARCHAR.
+  In sql_mode=ORACLE, real size of VARCHAR and CHAR with no length
+  in SP parameters is fixed at runtime with the length of real args.
+  Let's translate VARCHAR to VARCHAR(4000) for return value.
+
+  Since Oracle 9, maximum size for VARCHAR in PL/SQL is 32767.
 
   In MariaDB the limit for VARCHAR is 65535 bytes.
   We could translate VARCHAR with no length to VARCHAR(65535), but
@@ -6565,17 +6567,20 @@ opt_field_length_default_1:
   the maximum possible length in characters in case of mbmaxlen=4
   (e.g. utf32, utf16, utf8mb4). However, we'll have character sets with
   mbmaxlen=5 soon (e.g. gb18030).
-
-  Let's translate VARCHAR to VARCHAR(4000), which covert all possible Oracle
-  values.
 */
 opt_field_length_default_sp_param_varchar:
-          /* empty */  { $$= (char*) "4000"; }
-        | field_length { $$= $1; }
+          /* empty */  {
+                         $$.set("4000", "4000");
+                         Lex->spcont->set_dyn_char_param();
+                       }
+        | field_length { $$.set($1, NULL); }
 
 opt_field_length_default_sp_param_char:
-          /* empty */  { $$= (char*) "2000"; }
-        | field_length { $$= $1; }
+          /* empty */  {
+                         $$.set("2000", "2000");
+                         Lex->spcont->set_dyn_char_param();
+                       }
+        | field_length { $$.set($1, NULL); }
 
 opt_precision:
           /* empty */    { $$.set(0, 0); }
