Commit 98896697 authored by tomas@mc05.(none)'s avatar tomas@mc05.(none)

Merge tulin@bk-internal.mysql.com:/home/bk/mysql-4.1

into mc05.(none):/space2/tomas/mysql-4.1-ndb-test
parents f90ea998 d04966e3
...@@ -54,7 +54,10 @@ CREATE TABLE `t1` ( ...@@ -54,7 +54,10 @@ CREATE TABLE `t1` (
); );
INSERT INTO `t1` VALUES ('1.23450',2.3456),('1.23450',2.3456),('1.23450',2.3456),('1.23450',2.3456),('1.23450',2.3456); INSERT INTO `t1` VALUES ('1.23450',2.3456),('1.23450',2.3456),('1.23450',2.3456),('1.23450',2.3456),('1.23450',2.3456);
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT, CHARACTER_SET_CLIENT=utf8 */; /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE="NO_AUTO_VALUE_ON_ZERO" */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE="NO_AUTO_VALUE_ON_ZERO" */;
...@@ -75,6 +78,8 @@ UNLOCK TABLES; ...@@ -75,6 +78,8 @@ UNLOCK TABLES;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
...@@ -138,7 +143,10 @@ DROP TABLE t1; ...@@ -138,7 +143,10 @@ DROP TABLE t1;
CREATE TABLE t1 (a VARCHAR(255)) DEFAULT CHARSET koi8r; CREATE TABLE t1 (a VARCHAR(255)) DEFAULT CHARSET koi8r;
INSERT INTO t1 VALUES (_koi8r x'C1C2C3C4C5'); INSERT INTO t1 VALUES (_koi8r x'C1C2C3C4C5');
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT, CHARACTER_SET_CLIENT=utf8 */; /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE="NO_AUTO_VALUE_ON_ZERO" */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE="NO_AUTO_VALUE_ON_ZERO" */;
...@@ -158,6 +166,8 @@ UNLOCK TABLES; ...@@ -158,6 +166,8 @@ UNLOCK TABLES;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
DROP TABLE t1; DROP TABLE t1;
CREATE TABLE t1 (a int) ENGINE=MYISAM; CREATE TABLE t1 (a int) ENGINE=MYISAM;
...@@ -208,7 +218,10 @@ CREATE TABLE ```a` ( ...@@ -208,7 +218,10 @@ CREATE TABLE ```a` (
drop table ```a`; drop table ```a`;
create table t1(a int); create table t1(a int);
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT, CHARACTER_SET_CLIENT=utf8 */; /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE="NO_AUTO_VALUE_ON_ZERO" */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE="NO_AUTO_VALUE_ON_ZERO" */;
...@@ -227,6 +240,8 @@ UNLOCK TABLES; ...@@ -227,6 +240,8 @@ UNLOCK TABLES;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
...@@ -248,7 +263,10 @@ UNLOCK TABLES; ...@@ -248,7 +263,10 @@ UNLOCK TABLES;
set global sql_mode='ANSI_QUOTES'; set global sql_mode='ANSI_QUOTES';
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT, CHARACTER_SET_CLIENT=utf8 */; /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE="NO_AUTO_VALUE_ON_ZERO" */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE="NO_AUTO_VALUE_ON_ZERO" */;
...@@ -267,6 +285,8 @@ UNLOCK TABLES; ...@@ -267,6 +285,8 @@ UNLOCK TABLES;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
...@@ -291,7 +311,10 @@ drop table t1; ...@@ -291,7 +311,10 @@ drop table t1;
create table t1(a int); create table t1(a int);
insert into t1 values (1),(2),(3); insert into t1 values (1),(2),(3);
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT, CHARACTER_SET_CLIENT=utf8 */; /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE="" */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE="" */;
DROP TABLE IF EXISTS `t1`; DROP TABLE IF EXISTS `t1`;
CREATE TABLE `t1` ( CREATE TABLE `t1` (
...@@ -301,6 +324,8 @@ CREATE TABLE `t1` ( ...@@ -301,6 +324,8 @@ CREATE TABLE `t1` (
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
1 1
2 2
......
drop table if exists t1,t2;
create table t1
(
a int primary key,
b char(10)
);
insert into t1 values (1,'one');
insert into t1 values (2,'two');
insert into t1 values (3,'three');
insert into t1 values (4,'four');
set @a=2;
prepare stmt1 from 'select * from t1 where a <= ?';
execute stmt1 using @a;
a b
1 one
2 two
set @a=3;
execute stmt1 using @a;
a b
1 one
2 two
3 three
deallocate prepare no_such_statement;
ERROR HY000: Unknown prepared statement handler (no_such_statement) given to DEALLOCATE PREPARE
execute stmt1;
ERROR HY000: Wrong arguments to EXECUTE
prepare stmt2 from 'prepare nested_stmt from "select 1"';
ERROR 42000: You have an error in your SQL syntax. Check the manual that corresponds to your MySQL server version for the right syntax to use near '"select 1"' at line 1
prepare stmt2 from 'execute stmt1';
ERROR 42000: You have an error in your SQL syntax. Check the manual that corresponds to your MySQL server version for the right syntax to use near 'stmt1' at line 1
prepare stmt2 from 'deallocate prepare z';
ERROR 42000: You have an error in your SQL syntax. Check the manual that corresponds to your MySQL server version for the right syntax to use near 'z' at line 1
prepare stmt3 from 'insert into t1 values (?,?)';
set @arg1=5, @arg2='five';
execute stmt3 using @arg1, @arg2;
select * from t1 where a>3;
a b
4 four
5 five
prepare stmt4 from 'update t1 set a=? where b=?';
set @arg1=55, @arg2='five';
execute stmt4 using @arg1, @arg2;
select * from t1 where a>3;
a b
4 four
55 five
prepare stmt4 from 'create table t2 (a int)';
execute stmt4;
prepare stmt4 from 'drop table t2';
execute stmt4;
execute stmt4;
ERROR 42S02: Unknown table 't2'
prepare stmt5 from 'select ? + a from t1';
set @a=1;
execute stmt5 using @a;
? + a
2
3
4
5
56
execute stmt5 using @no_such_var;
? + a
NULL
NULL
NULL
NULL
NULL
set @nullvar=1;
set @nullvar=NULL;
execute stmt5 using @nullvar;
? + a
NULL
NULL
NULL
NULL
NULL
set @nullvar2=NULL;
execute stmt5 using @nullvar2;
? + a
NULL
NULL
NULL
NULL
NULL
prepare stmt6 from 'select 1; select2';
ERROR 42000: You have an error in your SQL syntax. Check the manual that corresponds to your MySQL server version for the right syntax to use near '; select2' at line 1
prepare stmt6 from 'insert into t1 values (5,"five"); select2';
ERROR 42000: You have an error in your SQL syntax. Check the manual that corresponds to your MySQL server version for the right syntax to use near '; select2' at line 1
explain prepare stmt6 from 'insert into t1 values (5,"five"); select2';
ERROR 42000: You have an error in your SQL syntax. Check the manual that corresponds to your MySQL server version for the right syntax to use near 'from 'insert into t1 values (5,"five"); select2'' at line 1
create table t2
(
a int
);
insert into t2 values (0);
set @arg00=NULL ;
prepare stmt1 from 'select 1 FROM t2 where a=?' ;
execute stmt1 using @arg00 ;
1
prepare stmt1 from @nosuchvar;
ERROR 42000: You have an error in your SQL syntax. Check the manual that corresponds to your MySQL server version for the right syntax to use near 'NULL' at line 1
set @ivar= 1234;
prepare stmt1 from @ivar;
ERROR 42000: You have an error in your SQL syntax. Check the manual that corresponds to your MySQL server version for the right syntax to use near '1234' at line 1
set @fvar= 123.4567;
prepare stmt1 from @fvar;
ERROR 42000: You have an error in your SQL syntax. Check the manual that corresponds to your MySQL server version for the right syntax to use near '123.4567' at line 1
set @str1 = 'select ?';
set @str2 = convert(@str1 using ucs2);
prepare stmt1 from @str2;
execute stmt1 using @ivar;
?
1234
drop table t1,t2;
stop slave;
drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
reset master;
reset slave;
drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
start slave;
drop table if exists t1;
create table t1(n char(30));
prepare stmt1 from 'insert into t1 values (?)';
set @var1= "from-master-1";
execute stmt1 using @var1;
set @var1= "from-master-2-'',";
execute stmt1 using @var1;
select * from t1;
n
from-master-1
from-master-2-'',
set @var2= 'insert into t1 values (concat("from-var-", ?))';
prepare stmt2 from @var2;
set @var1='from-master-3';
execute stmt2 using @var1;
select * from t1;
n
from-master-1
from-master-2-'',
from-var-from-master-3
drop table t1;
stop slave;
#
# SQL Syntax for Prepared Statements test
#
--disable_warnings
drop table if exists t1,t2;
--enable_warnings
create table t1
(
a int primary key,
b char(10)
);
insert into t1 values (1,'one');
insert into t1 values (2,'two');
insert into t1 values (3,'three');
insert into t1 values (4,'four');
# basic functionality
set @a=2;
prepare stmt1 from 'select * from t1 where a <= ?';
execute stmt1 using @a;
set @a=3;
execute stmt1 using @a;
# non-existant statement
--error 1243
deallocate prepare no_such_statement;
--error 1210
execute stmt1;
# Nesting ps commands is not allowed:
--error 1064
prepare stmt2 from 'prepare nested_stmt from "select 1"';
--error 1064
prepare stmt2 from 'execute stmt1';
--error 1064
prepare stmt2 from 'deallocate prepare z';
# PS insert
prepare stmt3 from 'insert into t1 values (?,?)';
set @arg1=5, @arg2='five';
execute stmt3 using @arg1, @arg2;
select * from t1 where a>3;
# PS update
prepare stmt4 from 'update t1 set a=? where b=?';
set @arg1=55, @arg2='five';
execute stmt4 using @arg1, @arg2;
select * from t1 where a>3;
# PS create/delete
prepare stmt4 from 'create table t2 (a int)';
execute stmt4;
prepare stmt4 from 'drop table t2';
execute stmt4;
# Do something that will cause error
--error 1051
execute stmt4;
# placeholders in result field names.
prepare stmt5 from 'select ? + a from t1';
set @a=1;
execute stmt5 using @a;
execute stmt5 using @no_such_var;
set @nullvar=1;
set @nullvar=NULL;
execute stmt5 using @nullvar;
set @nullvar2=NULL;
execute stmt5 using @nullvar2;
# Check that multiple SQL statements are disabled inside PREPARE
--error 1064
prepare stmt6 from 'select 1; select2';
--error 1064
prepare stmt6 from 'insert into t1 values (5,"five"); select2';
# This shouldn't parse
--error 1064
explain prepare stmt6 from 'insert into t1 values (5,"five"); select2';
create table t2
(
a int
);
insert into t2 values (0);
# parameter is NULL
set @arg00=NULL ;
prepare stmt1 from 'select 1 FROM t2 where a=?' ;
execute stmt1 using @arg00 ;
# prepare using variables:
--error 1064
prepare stmt1 from @nosuchvar;
set @ivar= 1234;
--error 1064
prepare stmt1 from @ivar;
set @fvar= 123.4567;
--error 1064
prepare stmt1 from @fvar;
set @str1 = 'select ?';
set @str2 = convert(@str1 using ucs2);
prepare stmt1 from @str2;
execute stmt1 using @ivar;
drop table t1,t2;
#
# Test of replicating user variables
#
source include/master-slave.inc;
#save_master_pos;
#connection slave;
#sync_with_master;
#reset master;
#connection master;
--disable_warnings
drop table if exists t1;
--enable_warnings
create table t1(n char(30));
prepare stmt1 from 'insert into t1 values (?)';
set @var1= "from-master-1";
execute stmt1 using @var1;
set @var1= "from-master-2-'',";
execute stmt1 using @var1;
select * from t1;
set @var2= 'insert into t1 values (concat("from-var-", ?))';
prepare stmt2 from @var2;
set @var1='from-master-3';
execute stmt2 using @var1;
save_master_pos;
connection slave;
sync_with_master;
select * from t1;
connection master;
drop table t1;
save_master_pos;
connection slave;
sync_with_master;
stop slave;
...@@ -33,6 +33,12 @@ char NEAR errbuff[NRERRBUFFS][ERRMSGSIZE]; ...@@ -33,6 +33,12 @@ char NEAR errbuff[NRERRBUFFS][ERRMSGSIZE];
nr Errno nr Errno
MyFlags Flags MyFlags Flags
... variable list ... variable list
NOTE
The following subset of printf format is supported:
"%[0-9.-]*l?[sdu]", where all length flags are parsed but ignored.
Additionally "%.*s" is supported and "%.*[ud]" is correctly parsed but
the length value is ignored.
*/ */
int my_error(int nr,myf MyFlags, ...) int my_error(int nr,myf MyFlags, ...)
...@@ -43,7 +49,10 @@ int my_error(int nr,myf MyFlags, ...) ...@@ -43,7 +49,10 @@ int my_error(int nr,myf MyFlags, ...)
reg2 char *endpos; reg2 char *endpos;
char * par; char * par;
char ebuff[ERRMSGSIZE+20]; char ebuff[ERRMSGSIZE+20];
int prec_chars; /* output precision */
my_bool prec_supplied;
DBUG_ENTER("my_error"); DBUG_ENTER("my_error");
LINT_INIT(prec_chars); /* protected by prec_supplied */
va_start(ap,MyFlags); va_start(ap,MyFlags);
DBUG_PRINT("my", ("nr: %d MyFlags: %d errno: %d", nr, MyFlags, errno)); DBUG_PRINT("my", ("nr: %d MyFlags: %d errno: %d", nr, MyFlags, errno));
...@@ -59,7 +68,6 @@ int my_error(int nr,myf MyFlags, ...) ...@@ -59,7 +68,6 @@ int my_error(int nr,myf MyFlags, ...)
if (tpos[0] != '%') if (tpos[0] != '%')
{ {
*endpos++= *tpos++; /* Copy ordinary char */ *endpos++= *tpos++; /* Copy ordinary char */
olen++;
continue; continue;
} }
if (*++tpos == '%') /* test if %% */ if (*++tpos == '%') /* test if %% */
...@@ -68,43 +76,71 @@ int my_error(int nr,myf MyFlags, ...) ...@@ -68,43 +76,71 @@ int my_error(int nr,myf MyFlags, ...)
} }
else else
{ {
/* Skip if max size is used (to be compatible with printf) */ /*
while (my_isdigit(&my_charset_latin1, *tpos) || *tpos == '.' || *tpos == '-') Skip size/precision flags to be compatible with printf.
The only size/precision flag supported is "%.*s".
If "%.*u" or "%.*d" are encountered, the precision number is read
from the variable argument list but its value is ignored.
*/
prec_supplied= 0;
if (*tpos== '.')
{
tpos++;
olen--;
if (*tpos == '*')
{
tpos++;
olen--;
prec_chars= va_arg(ap, int); /* get length parameter */
prec_supplied= 1;
}
}
if (!prec_supplied)
{
while (my_isdigit(&my_charset_latin1, *tpos) || *tpos == '.' ||
*tpos == '-')
tpos++; tpos++;
if (*tpos == 'l') /* Skip 'l' argument */ if (*tpos == 'l') /* Skip 'l' argument */
tpos++; tpos++;
}
if (*tpos == 's') /* String parameter */ if (*tpos == 's') /* String parameter */
{ {
par = va_arg(ap, char *); par= va_arg(ap, char *);
plen = (uint) strlen(par); plen= (uint) strlen(par);
if (prec_supplied && prec_chars > 0)
plen= min((uint)prec_chars, plen);
if (olen + plen < ERRMSGSIZE+2) /* Replace if possible */ if (olen + plen < ERRMSGSIZE+2) /* Replace if possible */
{ {
endpos=strmov(endpos,par); strmake(endpos, par, plen);
endpos+= plen;
tpos++; tpos++;
olen+=plen-2; olen+= plen-2;
continue; continue;
} }
} }
else if (*tpos == 'd' || *tpos == 'u') /* Integer parameter */ else if (*tpos == 'd' || *tpos == 'u') /* Integer parameter */
{ {
register int iarg; register int iarg;
iarg = va_arg(ap, int); iarg= va_arg(ap, int);
if (*tpos == 'd') if (*tpos == 'd')
plen= (uint) (int10_to_str((long) iarg, endpos, -10) - endpos); plen= (uint) (int10_to_str((long) iarg, endpos, -10) - endpos);
else else
plen= (uint) (int10_to_str((long) (uint) iarg, endpos, 10) - endpos); plen= (uint) (int10_to_str((long) (uint) iarg, endpos, 10) - endpos);
if (olen + plen < ERRMSGSIZE+2) /* Replace parameter if possible */ if (olen + plen < ERRMSGSIZE+2) /* Replace parameter if possible */
{ {
endpos+=plen; endpos+= plen;
tpos++; tpos++;
olen+=plen-2; olen+= plen-2;
continue; continue;
} }
} }
} }
*endpos++='%'; /* % used as % or unknown code */ *endpos++= '%'; /* % used as % or unknown code */
} }
*endpos='\0'; /* End of errmessage */ *endpos= '\0'; /* End of errmessage */
va_end(ap); va_end(ap);
DBUG_RETURN((*error_handler_hook)(nr, ebuff, MyFlags)); DBUG_RETURN((*error_handler_hook)(nr, ebuff, MyFlags));
} }
......
...@@ -255,7 +255,7 @@ bool Item::get_time(TIME *ltime) ...@@ -255,7 +255,7 @@ bool Item::get_time(TIME *ltime)
return 0; return 0;
} }
CHARSET_INFO * Item::default_charset() const CHARSET_INFO *Item::default_charset()
{ {
return current_thd->variables.collation_connection; return current_thd->variables.collation_connection;
} }
...@@ -735,6 +735,70 @@ bool Item_param::set_longdata(const char *str, ulong length) ...@@ -735,6 +735,70 @@ bool Item_param::set_longdata(const char *str, ulong length)
} }
/*
Set parameter value from user variable value.
SYNOPSIS
set_from_user_var
thd Current thread
entry User variable structure (NULL means use NULL value)
RETURN
0 OK
1 Out of memort
*/
bool Item_param::set_from_user_var(THD *thd, const user_var_entry *entry)
{
DBUG_ENTER("Item_param::set_from_user_var");
if (entry && entry->value)
{
item_result_type= entry->type;
switch (entry->type)
{
case REAL_RESULT:
set_double(*(double*)entry->value);
break;
case INT_RESULT:
set_int(*(longlong*)entry->value, 21);
break;
case STRING_RESULT:
{
CHARSET_INFO *fromcs= entry->collation.collation;
CHARSET_INFO *tocs= thd->variables.collation_connection;
uint32 dummy_offset;
value.cs_info.character_set_client= fromcs;
/*
Setup source and destination character sets so that they
are different only if conversion is necessary: this will
make later checks easier.
*/
value.cs_info.final_character_set_of_str_value=
String::needs_conversion(0, fromcs, tocs, &dummy_offset) ?
tocs : fromcs;
/*
Exact value of max_length is not known unless data is converted to
charset of connection, so we have to set it later.
*/
item_type= Item::STRING_ITEM;
item_result_type= STRING_RESULT;
if (set_str((const char *)entry->value, entry->length))
DBUG_RETURN(1);
}
break;
default:
DBUG_ASSERT(0);
set_null();
}
}
else
set_null();
DBUG_RETURN(0);
}
/* /*
Resets parameter after execution. Resets parameter after execution.
...@@ -767,8 +831,6 @@ void Item_param::reset() ...@@ -767,8 +831,6 @@ void Item_param::reset()
int Item_param::save_in_field(Field *field, bool no_conversions) int Item_param::save_in_field(Field *field, bool no_conversions)
{ {
DBUG_ASSERT(current_thd->command == COM_EXECUTE);
field->set_notnull(); field->set_notnull();
switch (state) { switch (state) {
......
...@@ -245,7 +245,7 @@ public: ...@@ -245,7 +245,7 @@ public:
virtual Item *real_item() { return this; } virtual Item *real_item() { return this; }
virtual Item *get_tmp_table_item(THD *thd) { return copy_or_same(thd); } virtual Item *get_tmp_table_item(THD *thd) { return copy_or_same(thd); }
CHARSET_INFO *default_charset() const; static CHARSET_INFO *default_charset();
virtual CHARSET_INFO *compare_collation() { return NULL; } virtual CHARSET_INFO *compare_collation() { return NULL; }
virtual bool walk(Item_processor processor, byte *arg) virtual bool walk(Item_processor processor, byte *arg)
...@@ -489,6 +489,7 @@ public: ...@@ -489,6 +489,7 @@ public:
bool set_str(const char *str, ulong length); bool set_str(const char *str, ulong length);
bool set_longdata(const char *str, ulong length); bool set_longdata(const char *str, ulong length);
void set_time(TIME *tm, timestamp_type type, uint32 max_length_arg); void set_time(TIME *tm, timestamp_type type, uint32 max_length_arg);
bool set_from_user_var(THD *thd, const user_var_entry *entry);
void reset(); void reset();
/* /*
Assign placeholder value from bind data. Assign placeholder value from bind data.
......
...@@ -2583,27 +2583,39 @@ longlong Item_func_get_user_var::val_int() ...@@ -2583,27 +2583,39 @@ longlong Item_func_get_user_var::val_int()
/* /*
Get variable by name and, if necessary, put the record of variable
use into the binary log.
SYNOPSIS
get_var_with_binlog()
thd Current thread
name Variable name
out_entry [out] variable structure or NULL. The pointer is set
regardless of whether function succeeded or not.
When a user variable is invoked from an update query (INSERT, UPDATE etc), When a user variable is invoked from an update query (INSERT, UPDATE etc),
stores this variable and its value in thd->user_var_events, so that it can be stores this variable and its value in thd->user_var_events, so that it can be
written to the binlog (will be written just before the query is written, see written to the binlog (will be written just before the query is written, see
log.cc). log.cc).
RETURN
0 OK
1 Failed to put appropiate record into binary log
*/ */
void Item_func_get_user_var::fix_length_and_dec() int get_var_with_binlog(THD *thd, LEX_STRING &name,
user_var_entry **out_entry)
{ {
THD *thd=current_thd;
BINLOG_USER_VAR_EVENT *user_var_event; BINLOG_USER_VAR_EVENT *user_var_event;
maybe_null=1; user_var_entry *var_entry;
decimals=NOT_FIXED_DEC; var_entry= get_variable(&thd->user_vars, name, 0);
max_length=MAX_BLOB_WIDTH;
if (!(var_entry= get_variable(&thd->user_vars, name, 0)))
null_value= 1;
else
collation.set(var_entry->collation);
if (!(opt_bin_log && is_update_query(thd->lex->sql_command))) if (!(opt_bin_log && is_update_query(thd->lex->sql_command)))
return; {
*out_entry= var_entry;
return 0;
}
if (!var_entry) if (!var_entry)
{ {
...@@ -2630,13 +2642,16 @@ void Item_func_get_user_var::fix_length_and_dec() ...@@ -2630,13 +2642,16 @@ void Item_func_get_user_var::fix_length_and_dec()
if (!(var_entry= get_variable(&thd->user_vars, name, 0))) if (!(var_entry= get_variable(&thd->user_vars, name, 0)))
goto err; goto err;
} }
else if (var_entry->used_query_id == thd->query_id)
{
/* /*
If this variable was already stored in user_var_events by this query If this variable was already stored in user_var_events by this query
(because it's used in more than one place in the query), don't store (because it's used in more than one place in the query), don't store
it. it.
*/ */
else if (var_entry->used_query_id == thd->query_id) *out_entry= var_entry;
return; return 0;
}
uint size; uint size;
/* /*
...@@ -2672,10 +2687,33 @@ void Item_func_get_user_var::fix_length_and_dec() ...@@ -2672,10 +2687,33 @@ void Item_func_get_user_var::fix_length_and_dec()
if (insert_dynamic(&thd->user_var_events, (gptr) &user_var_event)) if (insert_dynamic(&thd->user_var_events, (gptr) &user_var_event))
goto err; goto err;
return; *out_entry= var_entry;
return 0;
err: err:
*out_entry= var_entry;
return 1;
}
void Item_func_get_user_var::fix_length_and_dec()
{
THD *thd=current_thd;
int error;
maybe_null=1;
decimals=NOT_FIXED_DEC;
max_length=MAX_BLOB_WIDTH;
error= get_var_with_binlog(thd, name, &var_entry);
if (var_entry)
collation.set(var_entry->collation);
else
null_value= 1;
if (error)
thd->fatal_error(); thd->fatal_error();
return; return;
} }
......
...@@ -133,6 +133,7 @@ static SYMBOL symbols[] = { ...@@ -133,6 +133,7 @@ static SYMBOL symbols[] = {
{ "DAY_MICROSECOND", SYM(DAY_MICROSECOND_SYM)}, { "DAY_MICROSECOND", SYM(DAY_MICROSECOND_SYM)},
{ "DAY_MINUTE", SYM(DAY_MINUTE_SYM)}, { "DAY_MINUTE", SYM(DAY_MINUTE_SYM)},
{ "DAY_SECOND", SYM(DAY_SECOND_SYM)}, { "DAY_SECOND", SYM(DAY_SECOND_SYM)},
{ "DEALLOCATE", SYM(DEALLOCATE_SYM)},
{ "DEC", SYM(DECIMAL_SYM)}, { "DEC", SYM(DECIMAL_SYM)},
{ "DECIMAL", SYM(DECIMAL_SYM)}, { "DECIMAL", SYM(DECIMAL_SYM)},
{ "DEFAULT", SYM(DEFAULT)}, { "DEFAULT", SYM(DEFAULT)},
...@@ -325,6 +326,7 @@ static SYMBOL symbols[] = { ...@@ -325,6 +326,7 @@ static SYMBOL symbols[] = {
{ "POINT", SYM(POINT_SYM)}, { "POINT", SYM(POINT_SYM)},
{ "POLYGON", SYM(POLYGON)}, { "POLYGON", SYM(POLYGON)},
{ "PRECISION", SYM(PRECISION)}, { "PRECISION", SYM(PRECISION)},
{ "PREPARE", SYM(PREPARE_SYM)},
{ "PREV", SYM(PREV_SYM)}, { "PREV", SYM(PREV_SYM)},
{ "PRIMARY", SYM(PRIMARY_SYM)}, { "PRIMARY", SYM(PRIMARY_SYM)},
{ "PRIVILEGES", SYM(PRIVILEGES)}, { "PRIVILEGES", SYM(PRIVILEGES)},
......
...@@ -348,6 +348,7 @@ inline THD *_current_thd(void) ...@@ -348,6 +348,7 @@ inline THD *_current_thd(void)
#include "field.h" /* Field definitions */ #include "field.h" /* Field definitions */
#include "protocol.h" #include "protocol.h"
#include "sql_udf.h" #include "sql_udf.h"
class user_var_entry;
#include "item.h" #include "item.h"
typedef Comp_creator* (*chooser_compare_func_creator)(bool invert); typedef Comp_creator* (*chooser_compare_func_creator)(bool invert);
/* sql_parse.cc */ /* sql_parse.cc */
...@@ -639,8 +640,10 @@ int mysqld_show_column_types(THD *thd); ...@@ -639,8 +640,10 @@ int mysqld_show_column_types(THD *thd);
int mysqld_help (THD *thd, const char *text); int mysqld_help (THD *thd, const char *text);
/* sql_prepare.cc */ /* sql_prepare.cc */
void mysql_stmt_prepare(THD *thd, char *packet, uint packet_length); int mysql_stmt_prepare(THD *thd, char *packet, uint packet_length,
LEX_STRING *name=NULL);
void mysql_stmt_execute(THD *thd, char *packet, uint packet_length); void mysql_stmt_execute(THD *thd, char *packet, uint packet_length);
void mysql_sql_stmt_execute(THD *thd, LEX_STRING *stmt_name);
void mysql_stmt_free(THD *thd, char *packet); void mysql_stmt_free(THD *thd, char *packet);
void mysql_stmt_reset(THD *thd, char *packet); void mysql_stmt_reset(THD *thd, char *packet);
void mysql_stmt_get_longdata(THD *thd, char *pos, ulong packet_length); void mysql_stmt_get_longdata(THD *thd, char *pos, ulong packet_length);
...@@ -1069,6 +1072,9 @@ Item *get_system_var(THD *thd, enum_var_type var_type, LEX_STRING name, ...@@ -1069,6 +1072,9 @@ Item *get_system_var(THD *thd, enum_var_type var_type, LEX_STRING name,
LEX_STRING component); LEX_STRING component);
Item *get_system_var(THD *thd, enum_var_type var_type, const char *var_name, Item *get_system_var(THD *thd, enum_var_type var_type, const char *var_name,
uint length, const char *item_name); uint length, const char *item_name);
/* item_func.cc */
int get_var_with_binlog(THD *thd, LEX_STRING &name,
user_var_entry **out_entry);
/* log.cc */ /* log.cc */
bool flush_error_log(void); bool flush_error_log(void);
......
...@@ -5023,6 +5023,12 @@ struct show_var_st status_vars[]= { ...@@ -5023,6 +5023,12 @@ struct show_var_st status_vars[]= {
{"Com_unlock_tables", (char*) (com_stat+(uint) SQLCOM_UNLOCK_TABLES),SHOW_LONG}, {"Com_unlock_tables", (char*) (com_stat+(uint) SQLCOM_UNLOCK_TABLES),SHOW_LONG},
{"Com_update", (char*) (com_stat+(uint) SQLCOM_UPDATE),SHOW_LONG}, {"Com_update", (char*) (com_stat+(uint) SQLCOM_UPDATE),SHOW_LONG},
{"Com_update_multi", (char*) (com_stat+(uint) SQLCOM_UPDATE_MULTI),SHOW_LONG}, {"Com_update_multi", (char*) (com_stat+(uint) SQLCOM_UPDATE_MULTI),SHOW_LONG},
{"Com_prepare_sql", (char*) (com_stat+(uint) SQLCOM_PREPARE),
SHOW_LONG},
{"Com_execute_sql", (char*) (com_stat+(uint) SQLCOM_EXECUTE),
SHOW_LONG},
{"Com_dealloc_sql", (char*) (com_stat+(uint)
SQLCOM_DEALLOCATE_PREPARE), SHOW_LONG},
{"Connections", (char*) &thread_id, SHOW_LONG_CONST}, {"Connections", (char*) &thread_id, SHOW_LONG_CONST},
{"Created_tmp_disk_tables", (char*) &created_tmp_disk_tables,SHOW_LONG}, {"Created_tmp_disk_tables", (char*) &created_tmp_disk_tables,SHOW_LONG},
{"Created_tmp_files", (char*) &my_tmp_file_created, SHOW_LONG}, {"Created_tmp_files", (char*) &my_tmp_file_created, SHOW_LONG},
......
...@@ -255,7 +255,7 @@ character-set=latin2 ...@@ -255,7 +255,7 @@ character-set=latin2
"Key reference and table reference doesn't match", "Key reference and table reference doesn't match",
"Operand should contain %d column(s)", "Operand should contain %d column(s)",
"Subquery returns more than 1 row", "Subquery returns more than 1 row",
"Unknown prepared statement handler (%ld) given to %s", "Unknown prepared statement handler (%.*s) given to %s",
"Help database is corrupt or does not exist", "Help database is corrupt or does not exist",
"Cyclic reference on subqueries", "Cyclic reference on subqueries",
"Converting column '%s' from %s to %s", "Converting column '%s' from %s to %s",
......
...@@ -257,7 +257,7 @@ character-set=latin1 ...@@ -257,7 +257,7 @@ character-set=latin1
"Key reference and table reference doesn't match", "Key reference and table reference doesn't match",
"Operand should contain %d column(s)", "Operand should contain %d column(s)",
"Subquery returns more than 1 row", "Subquery returns more than 1 row",
"Unknown prepared statement handler (%ld) given to %s", "Unknown prepared statement handler (%.*s) given to %s",
"Help database is corrupt or does not exist", "Help database is corrupt or does not exist",
"Cyclic reference on subqueries", "Cyclic reference on subqueries",
"Converting column '%s' from %s to %s", "Converting column '%s' from %s to %s",
......
...@@ -246,7 +246,7 @@ character-set=latin1 ...@@ -246,7 +246,7 @@ character-set=latin1
"Key reference and table reference doesn't match", "Key reference and table reference doesn't match",
"Operand should contain %d column(s)", "Operand should contain %d column(s)",
"Subquery returns more than 1 row", "Subquery returns more than 1 row",
"Unknown prepared statement handler (%ld) given to %s", "Unknown prepared statement handler (%.*s) given to %s",
"Help database is corrupt or does not exist", "Help database is corrupt or does not exist",
"Cyclic reference on subqueries", "Cyclic reference on subqueries",
"Converting column '%s' from %s to %s", "Converting column '%s' from %s to %s",
......
...@@ -251,7 +251,7 @@ character-set=latin7 ...@@ -251,7 +251,7 @@ character-set=latin7
"Key reference and table reference doesn't match", "Key reference and table reference doesn't match",
"Operand should contain %d column(s)", "Operand should contain %d column(s)",
"Subquery returns more than 1 row", "Subquery returns more than 1 row",
"Unknown prepared statement handler (%ld) given to %s", "Unknown prepared statement handler (%.*s) given to %s",
"Help database is corrupt or does not exist", "Help database is corrupt or does not exist",
"Cyclic reference on subqueries", "Cyclic reference on subqueries",
"Converting column '%s' from %s to %s", "Converting column '%s' from %s to %s",
......
...@@ -246,7 +246,7 @@ character-set=latin1 ...@@ -246,7 +246,7 @@ character-set=latin1
"Key reference and table reference doesn't match", "Key reference and table reference doesn't match",
"Operand should contain %d column(s)", "Operand should contain %d column(s)",
"Subquery returns more than 1 row", "Subquery returns more than 1 row",
"Unknown prepared statement handler (%ld) given to %s", "Unknown prepared statement handler (%.*s) given to %s",
"Help database is corrupt or does not exist", "Help database is corrupt or does not exist",
"Cyclic reference on subqueries", "Cyclic reference on subqueries",
"Converting column '%s' from %s to %s", "Converting column '%s' from %s to %s",
......
...@@ -258,7 +258,7 @@ character-set=latin1 ...@@ -258,7 +258,7 @@ character-set=latin1
"Schlüssel- und Tabellenverweis passen nicht zusammen", "Schlüssel- und Tabellenverweis passen nicht zusammen",
"Operand solle %d Spalte(n) enthalten", "Operand solle %d Spalte(n) enthalten",
"Unterabfrage lieferte mehr als einen Datensatz zurück", "Unterabfrage lieferte mehr als einen Datensatz zurück",
"Unbekannter Prepared-Statement-Handler (%ld) für %s angegeben", "Unbekannter Prepared-Statement-Handler (%.*s) für %s angegeben",
"Die Hilfe-Datenbank ist beschädigt oder existiert nicht", "Die Hilfe-Datenbank ist beschädigt oder existiert nicht",
"Zyklischer Verweis in Unterabfragen", "Zyklischer Verweis in Unterabfragen",
"Spalte '%s' wird von %s nach %s umgewandelt", "Spalte '%s' wird von %s nach %s umgewandelt",
......
...@@ -246,7 +246,7 @@ character-set=greek ...@@ -246,7 +246,7 @@ character-set=greek
"Key reference and table reference doesn't match", "Key reference and table reference doesn't match",
"Operand should contain %d column(s)", "Operand should contain %d column(s)",
"Subquery returns more than 1 row", "Subquery returns more than 1 row",
"Unknown prepared statement handler (%ld) given to %s", "Unknown prepared statement handler (%.*s) given to %s",
"Help database is corrupt or does not exist", "Help database is corrupt or does not exist",
"Cyclic reference on subqueries", "Cyclic reference on subqueries",
"Converting column '%s' from %s to %s", "Converting column '%s' from %s to %s",
......
...@@ -248,7 +248,7 @@ character-set=latin2 ...@@ -248,7 +248,7 @@ character-set=latin2
"Key reference and table reference doesn't match", "Key reference and table reference doesn't match",
"Operand should contain %d column(s)", "Operand should contain %d column(s)",
"Subquery returns more than 1 row", "Subquery returns more than 1 row",
"Unknown prepared statement handler (%ld) given to %s", "Unknown prepared statement handler (%.*s) given to %s",
"Help database is corrupt or does not exist", "Help database is corrupt or does not exist",
"Cyclic reference on subqueries", "Cyclic reference on subqueries",
"Converting column '%s' from %s to %s", "Converting column '%s' from %s to %s",
......
...@@ -246,7 +246,7 @@ character-set=latin1 ...@@ -246,7 +246,7 @@ character-set=latin1
"Key reference and table reference doesn't match", "Key reference and table reference doesn't match",
"Operand should contain %d column(s)", "Operand should contain %d column(s)",
"Subquery returns more than 1 row", "Subquery returns more than 1 row",
"Unknown prepared statement handler (%ld) given to %s", "Unknown prepared statement handler (%.*s) given to %s",
"Help database is corrupt or does not exist", "Help database is corrupt or does not exist",
"Cyclic reference on subqueries", "Cyclic reference on subqueries",
"Converting column '%s' from %s to %s", "Converting column '%s' from %s to %s",
......
...@@ -248,7 +248,7 @@ character-set=ujis ...@@ -248,7 +248,7 @@ character-set=ujis
"Key reference and table reference doesn't match", "Key reference and table reference doesn't match",
"Operand should contain %d column(s)", "Operand should contain %d column(s)",
"Subquery returns more than 1 row", "Subquery returns more than 1 row",
"Unknown prepared statement handler (%ld) given to %s", "Unknown prepared statement handler (%.*s) given to %s",
"Help database is corrupt or does not exist", "Help database is corrupt or does not exist",
"Cyclic reference on subqueries", "Cyclic reference on subqueries",
"Converting column '%s' from %s to %s", "Converting column '%s' from %s to %s",
......
...@@ -246,7 +246,7 @@ character-set=euckr ...@@ -246,7 +246,7 @@ character-set=euckr
"Key reference and table reference doesn't match", "Key reference and table reference doesn't match",
"Operand should contain %d column(s)", "Operand should contain %d column(s)",
"Subquery returns more than 1 row", "Subquery returns more than 1 row",
"Unknown prepared statement handler (%ld) given to %s", "Unknown prepared statement handler (%.*s) given to %s",
"Help database is corrupt or does not exist", "Help database is corrupt or does not exist",
"Cyclic reference on subqueries", "Cyclic reference on subqueries",
"Converting column '%s' from %s to %s", "Converting column '%s' from %s to %s",
......
...@@ -248,7 +248,7 @@ character-set=latin1 ...@@ -248,7 +248,7 @@ character-set=latin1
"Key reference and table reference doesn't match", "Key reference and table reference doesn't match",
"Operand should contain %d column(s)", "Operand should contain %d column(s)",
"Subquery returns more than 1 row", "Subquery returns more than 1 row",
"Unknown prepared statement handler (%ld) given to %s", "Unknown prepared statement handler (%.*s) given to %s",
"Help database is corrupt or does not exist", "Help database is corrupt or does not exist",
"Cyclic reference on subqueries", "Cyclic reference on subqueries",
"Converting column '%s' from %s to %s", "Converting column '%s' from %s to %s",
......
...@@ -248,7 +248,7 @@ character-set=latin1 ...@@ -248,7 +248,7 @@ character-set=latin1
"Key reference and table reference doesn't match", "Key reference and table reference doesn't match",
"Operand should contain %d column(s)", "Operand should contain %d column(s)",
"Subquery returns more than 1 row", "Subquery returns more than 1 row",
"Unknown prepared statement handler (%ld) given to %s", "Unknown prepared statement handler (%.*s) given to %s",
"Help database is corrupt or does not exist", "Help database is corrupt or does not exist",
"Cyclic reference on subqueries", "Cyclic reference on subqueries",
"Converting column '%s' from %s to %s", "Converting column '%s' from %s to %s",
......
...@@ -250,7 +250,7 @@ character-set=latin2 ...@@ -250,7 +250,7 @@ character-set=latin2
"Key reference and table reference doesn't match", "Key reference and table reference doesn't match",
"Operand should contain %d column(s)", "Operand should contain %d column(s)",
"Subquery returns more than 1 row", "Subquery returns more than 1 row",
"Unknown prepared statement handler (%ld) given to %s", "Unknown prepared statement handler (%.*s) given to %s",
"Help database is corrupt or does not exist", "Help database is corrupt or does not exist",
"Cyclic reference on subqueries", "Cyclic reference on subqueries",
"Converting column '%s' from %s to %s", "Converting column '%s' from %s to %s",
......
...@@ -247,7 +247,7 @@ character-set=latin1 ...@@ -247,7 +247,7 @@ character-set=latin1
"Referência da chave e referência da tabela não coincidem", "Referência da chave e referência da tabela não coincidem",
"Operand should contain %d column(s)", "Operand should contain %d column(s)",
"Subconsulta retorna mais que 1 registro", "Subconsulta retorna mais que 1 registro",
"Desconhecido manipulador de declaração preparado (%ld) determinado para %s", "Desconhecido manipulador de declaração preparado (%.*s) determinado para %s",
"Banco de dado de ajuda corrupto ou não existente", "Banco de dado de ajuda corrupto ou não existente",
"Referência cíclica em subconsultas", "Referência cíclica em subconsultas",
"Convertendo coluna '%s' de %s para %s", "Convertendo coluna '%s' de %s para %s",
......
...@@ -250,7 +250,7 @@ character-set=latin2 ...@@ -250,7 +250,7 @@ character-set=latin2
"Key reference and table reference doesn't match", "Key reference and table reference doesn't match",
"Operand should contain %d column(s)", "Operand should contain %d column(s)",
"Subquery returns more than 1 row", "Subquery returns more than 1 row",
"Unknown prepared statement handler (%ld) given to %s", "Unknown prepared statement handler (%.*s) given to %s",
"Help database is corrupt or does not exist", "Help database is corrupt or does not exist",
"Cyclic reference on subqueries", "Cyclic reference on subqueries",
"Converting column '%s' from %s to %s", "Converting column '%s' from %s to %s",
......
...@@ -248,7 +248,7 @@ character-set=koi8r ...@@ -248,7 +248,7 @@ character-set=koi8r
"Key reference and table reference doesn't match", "Key reference and table reference doesn't match",
" %d ", " %d ",
" ", " ",
"Unknown prepared statement handler (%ld) given to %s", "Unknown prepared statement handler (%.*s) given to %s",
"Help database is corrupt or does not exist", "Help database is corrupt or does not exist",
" ", " ",
" '%s' %s %s", " '%s' %s %s",
......
...@@ -254,7 +254,7 @@ character-set=latin2 ...@@ -254,7 +254,7 @@ character-set=latin2
"Key reference and table reference doesn't match", "Key reference and table reference doesn't match",
"Operand should contain %d column(s)", "Operand should contain %d column(s)",
"Subquery returns more than 1 row", "Subquery returns more than 1 row",
"Unknown prepared statement handler (%ld) given to %s", "Unknown prepared statement handler (%.*s) given to %s",
"Help database is corrupt or does not exist", "Help database is corrupt or does not exist",
"Cyclic reference on subqueries", "Cyclic reference on subqueries",
"Converting column '%s' from %s to %s", "Converting column '%s' from %s to %s",
......
...@@ -248,7 +248,7 @@ character-set=latin1 ...@@ -248,7 +248,7 @@ character-set=latin1
"Referencia de llave y referencia de tabla no coinciden", "Referencia de llave y referencia de tabla no coinciden",
"Operando debe tener %d columna(s)", "Operando debe tener %d columna(s)",
"Subconsulta retorna mas que 1 línea", "Subconsulta retorna mas que 1 línea",
"Desconocido preparado comando handler (%ld) dado para %s", "Desconocido preparado comando handler (%.*s) dado para %s",
"Base de datos Help está corrupto o no existe", "Base de datos Help está corrupto o no existe",
"Cíclica referencia en subconsultas", "Cíclica referencia en subconsultas",
"Convirtiendo columna '%s' de %s para %s", "Convirtiendo columna '%s' de %s para %s",
......
...@@ -246,7 +246,7 @@ character-set=latin1 ...@@ -246,7 +246,7 @@ character-set=latin1
"Nyckelreferensen och tabellreferensen stämmer inte överens", "Nyckelreferensen och tabellreferensen stämmer inte överens",
"Operand should contain %d column(s)", "Operand should contain %d column(s)",
"Subquery returnerade mer än 1 rad", "Subquery returnerade mer än 1 rad",
"Okänd PREPARED STATEMENT id (%ld) var given till %s", "Okänd PREPARED STATEMENT id (%.*s) var given till %s",
"Hjälpdatabasen finns inte eller är skadad", "Hjälpdatabasen finns inte eller är skadad",
"Cyklisk referens i subqueries", "Cyklisk referens i subqueries",
"Konvertar kolumn '%s' från %s till %s", "Konvertar kolumn '%s' från %s till %s",
......
...@@ -251,7 +251,7 @@ character-set=koi8u ...@@ -251,7 +251,7 @@ character-set=koi8u
"Key reference and table reference doesn't match", "Key reference and table reference doesn't match",
" %d æ", " %d æ",
" ¦ i 1 ", " ¦ i 1 ",
"Unknown prepared statement handler (%ld) given to %s", "Unknown prepared statement handler (%.*s) given to %s",
"Help database is corrupt or does not exist", "Help database is corrupt or does not exist",
"̦ Ц", "̦ Ц",
" '%s' %s %s", " '%s' %s %s",
......
...@@ -78,7 +78,6 @@ extern "C" void free_user_var(user_var_entry *entry) ...@@ -78,7 +78,6 @@ extern "C" void free_user_var(user_var_entry *entry)
my_free((char*) entry,MYF(0)); my_free((char*) entry,MYF(0));
} }
bool key_part_spec::operator==(const key_part_spec& other) const bool key_part_spec::operator==(const key_part_spec& other) const
{ {
return length == other.length && !strcmp(field_name, other.field_name); return length == other.length && !strcmp(field_name, other.field_name);
...@@ -1302,6 +1301,7 @@ Statement::Statement(THD *thd) ...@@ -1302,6 +1301,7 @@ Statement::Statement(THD *thd)
query_length(0), query_length(0),
free_list(0) free_list(0)
{ {
name.str= NULL;
init_sql_alloc(&mem_root, init_sql_alloc(&mem_root,
thd->variables.query_alloc_block_size, thd->variables.query_alloc_block_size,
thd->variables.query_prealloc_size); thd->variables.query_prealloc_size);
...@@ -1385,17 +1385,52 @@ static void delete_statement_as_hash_key(void *key) ...@@ -1385,17 +1385,52 @@ static void delete_statement_as_hash_key(void *key)
delete (Statement *) key; delete (Statement *) key;
} }
static byte *get_stmt_name_hash_key(Statement *entry, uint *length,
my_bool not_used __attribute__((unused)))
{
*length=(uint) entry->name.length;
return (byte*) entry->name.str;
}
C_MODE_END C_MODE_END
Statement_map::Statement_map() : Statement_map::Statement_map() :
last_found_statement(0) last_found_statement(0)
{ {
enum { START_HASH_SIZE = 16 }; enum
hash_init(&st_hash, default_charset_info, START_HASH_SIZE, 0, 0, {
START_STMT_HASH_SIZE = 16,
START_NAME_HASH_SIZE = 16
};
hash_init(&st_hash, default_charset_info, START_STMT_HASH_SIZE, 0, 0,
get_statement_id_as_hash_key, get_statement_id_as_hash_key,
delete_statement_as_hash_key, MYF(0)); delete_statement_as_hash_key, MYF(0));
hash_init(&names_hash, &my_charset_bin, START_NAME_HASH_SIZE, 0, 0,
(hash_get_key) get_stmt_name_hash_key,
NULL,MYF(0));
} }
int Statement_map::insert(Statement *statement)
{
int rc= my_hash_insert(&st_hash, (byte *) statement);
if (rc == 0)
last_found_statement= statement;
if (statement->name.str)
{
/*
If there is a statement with the same name, remove it. It is ok to
remove old and fail to insert new one at the same time.
*/
Statement *old_stmt;
if ((old_stmt= find_by_name(&statement->name)))
erase(old_stmt);
if ((rc= my_hash_insert(&names_hash, (byte*)statement)))
hash_delete(&st_hash, (byte*)statement);
}
return rc;
}
bool select_dumpvar::send_data(List<Item> &items) bool select_dumpvar::send_data(List<Item> &items)
{ {
List_iterator_fast<Item_func_set_user_var> li(vars); List_iterator_fast<Item_func_set_user_var> li(vars);
......
...@@ -461,6 +461,7 @@ public: ...@@ -461,6 +461,7 @@ public:
*/ */
bool allow_sum_func; bool allow_sum_func;
LEX_STRING name; /* name for named prepared statements */
LEX *lex; // parse tree descriptor LEX *lex; // parse tree descriptor
/* /*
Points to the query associated with this statement. It's const, but Points to the query associated with this statement. It's const, but
...@@ -527,8 +528,14 @@ public: ...@@ -527,8 +528,14 @@ public:
/* /*
Used to seek all existing statements in the connection Container for all statements created/used in a connection.
Deletes all statements in destructor. Statements in Statement_map have unique Statement::id (guaranteed by id
assignment in Statement::Statement)
Non-empty statement names are unique too: attempt to insert a new statement
with duplicate name causes older statement to be deleted
Statements are auto-deleted when they are removed from the map and when the
map is deleted.
*/ */
class Statement_map class Statement_map
...@@ -536,34 +543,47 @@ class Statement_map ...@@ -536,34 +543,47 @@ class Statement_map
public: public:
Statement_map(); Statement_map();
int insert(Statement *statement) int insert(Statement *statement);
Statement *find_by_name(LEX_STRING *name)
{ {
int rc= my_hash_insert(&st_hash, (byte *) statement); Statement *stmt;
if (rc == 0) stmt= (Statement*)hash_search(&names_hash, (byte*)name->str,
last_found_statement= statement; name->length);
return rc; return stmt;
} }
Statement *find(ulong id) Statement *find(ulong id)
{ {
if (last_found_statement == 0 || id != last_found_statement->id) if (last_found_statement == 0 || id != last_found_statement->id)
last_found_statement= (Statement *) hash_search(&st_hash, (byte *) &id, {
sizeof(id)); Statement *stmt;
stmt= (Statement *) hash_search(&st_hash, (byte *) &id, sizeof(id));
if (stmt->name.str)
return NULL;
last_found_statement= stmt;
}
return last_found_statement; return last_found_statement;
} }
void erase(Statement *statement) void erase(Statement *statement)
{ {
if (statement == last_found_statement) if (statement == last_found_statement)
last_found_statement= 0; last_found_statement= 0;
if (statement->name.str)
{
hash_delete(&names_hash, (byte *) statement);
}
hash_delete(&st_hash, (byte *) statement); hash_delete(&st_hash, (byte *) statement);
} }
~Statement_map() ~Statement_map()
{ {
hash_free(&st_hash); hash_free(&st_hash);
hash_free(&names_hash);
} }
private: private:
HASH st_hash; HASH st_hash;
HASH names_hash;
Statement *last_found_statement; Statement *last_found_statement;
}; };
......
...@@ -76,6 +76,7 @@ enum enum_sql_command { ...@@ -76,6 +76,7 @@ enum enum_sql_command {
SQLCOM_SHOW_COLUMN_TYPES, SQLCOM_SHOW_STORAGE_ENGINES, SQLCOM_SHOW_PRIVILEGES, SQLCOM_SHOW_COLUMN_TYPES, SQLCOM_SHOW_STORAGE_ENGINES, SQLCOM_SHOW_PRIVILEGES,
SQLCOM_HELP, SQLCOM_DROP_USER, SQLCOM_REVOKE_ALL, SQLCOM_CHECKSUM, SQLCOM_HELP, SQLCOM_DROP_USER, SQLCOM_REVOKE_ALL, SQLCOM_CHECKSUM,
SQLCOM_PREPARE, SQLCOM_EXECUTE, SQLCOM_DEALLOCATE_PREPARE,
/* This should be the last !!! */ /* This should be the last !!! */
SQLCOM_END SQLCOM_END
}; };
...@@ -616,6 +617,17 @@ typedef struct st_lex ...@@ -616,6 +617,17 @@ typedef struct st_lex
bool derived_tables; bool derived_tables;
bool safe_to_cache_query; bool safe_to_cache_query;
ALTER_INFO alter_info; ALTER_INFO alter_info;
/* Prepared statements SQL syntax:*/
LEX_STRING prepared_stmt_name; /* Statement name (in all queries) */
/*
Prepared statement query text or name of variable that holds the
prepared statement (in PREPARE ... queries)
*/
LEX_STRING prepared_stmt_code;
/* If true, prepared_stmt_code is a name of variable that holds the query */
bool prepared_stmt_code_is_varref;
/* Names of user variables holding parameters (in EXECUTE) */
List<LEX_STRING> prepared_stmt_params;
st_lex() {} st_lex() {}
inline void uncacheable(uint8 cause) inline void uncacheable(uint8 cause)
{ {
......
...@@ -1424,7 +1424,6 @@ bool dispatch_command(enum enum_server_command command, THD *thd, ...@@ -1424,7 +1424,6 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
} }
case COM_EXECUTE: case COM_EXECUTE:
{ {
thd->free_list= NULL;
mysql_stmt_execute(thd, packet, packet_length); mysql_stmt_execute(thd, packet, packet_length);
break; break;
} }
...@@ -1976,7 +1975,99 @@ mysql_execute_command(THD *thd) ...@@ -1976,7 +1975,99 @@ mysql_execute_command(THD *thd)
} }
break; break;
} }
case SQLCOM_PREPARE:
{
char *query_str;
uint query_len;
if (lex->prepared_stmt_code_is_varref)
{
/* This is PREPARE stmt FROM @var. */
String str;
String *pstr;
CHARSET_INFO *to_cs= thd->variables.collation_connection;
bool need_conversion;
user_var_entry *entry;
uint32 unused;
/*
Convert @var contents to string in connection character set. Although
it is known that int/real/NULL value cannot be a valid query we still
convert it for error messages to uniform.
*/
if ((entry=
(user_var_entry*)hash_search(&thd->user_vars,
(byte*)lex->prepared_stmt_code.str,
lex->prepared_stmt_code.length))
&& entry->value)
{
String *pstr;
my_bool is_var_null;
pstr= entry->val_str(&is_var_null, &str, NOT_FIXED_DEC);
DBUG_ASSERT(!is_var_null);
if (!pstr)
send_error(thd, ER_OUT_OF_RESOURCES);
DBUG_ASSERT(pstr == &str);
}
else
str.set("NULL", 4, &my_charset_latin1);
need_conversion=
String::needs_conversion(str.length(), str.charset(), to_cs, &unused);
query_len= need_conversion? (str.length() * to_cs->mbmaxlen) :
str.length();
if (!(query_str= alloc_root(&thd->mem_root, query_len+1)))
send_error(thd, ER_OUT_OF_RESOURCES);
if (need_conversion)
query_len= copy_and_convert(query_str, query_len, to_cs, str.ptr(),
str.length(), str.charset());
else
memcpy(query_str, str.ptr(), str.length());
query_str[query_len]= 0;
}
else
{
query_str= lex->prepared_stmt_code.str;
query_len= lex->prepared_stmt_code.length;
DBUG_PRINT("info", ("PREPARE: %.*s FROM '%.*s' \n",
lex->prepared_stmt_name.length,
lex->prepared_stmt_name.str,
query_len, query_str));
}
thd->command= COM_PREPARE;
if (!mysql_stmt_prepare(thd, query_str, query_len + 1,
&lex->prepared_stmt_name))
send_ok(thd, 0L, 0L, "Statement prepared");
break;
}
case SQLCOM_EXECUTE:
{
DBUG_PRINT("info", ("EXECUTE: %.*s\n",
lex->prepared_stmt_name.length,
lex->prepared_stmt_name.str));
mysql_sql_stmt_execute(thd, &lex->prepared_stmt_name);
lex->prepared_stmt_params.empty();
break;
}
case SQLCOM_DEALLOCATE_PREPARE:
{
Statement* stmt;
DBUG_PRINT("info", ("DEALLOCATE PREPARE: %.*s\n",
lex->prepared_stmt_name.length,
lex->prepared_stmt_name.str));
if ((stmt= thd->stmt_map.find_by_name(&lex->prepared_stmt_name)))
{
thd->stmt_map.erase(stmt);
send_ok(thd);
}
else
{
res= -1;
my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0),
lex->prepared_stmt_name.length, lex->prepared_stmt_name.str,
"DEALLOCATE PREPARE");
}
break;
}
case SQLCOM_DO: case SQLCOM_DO:
if (tables && ((res= check_table_access(thd, SELECT_ACL, tables,0)) || if (tables && ((res= check_table_access(thd, SELECT_ACL, tables,0)) ||
(res= open_and_lock_tables(thd,tables)))) (res= open_and_lock_tables(thd,tables))))
...@@ -3441,7 +3532,6 @@ error: ...@@ -3441,7 +3532,6 @@ error:
*/ */
int check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *tables) int check_one_table_access(THD *thd, ulong privilege, TABLE_LIST *tables)
{ {
if (check_access(thd, privilege, tables->db, &tables->grant.privilege,0,0)) if (check_access(thd, privilege, tables->db, &tables->grant.privilege,0,0))
return 1; return 1;
......
This diff is collapsed.
...@@ -433,6 +433,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize); ...@@ -433,6 +433,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize);
%token MEDIUMTEXT %token MEDIUMTEXT
%token NUMERIC_SYM %token NUMERIC_SYM
%token PRECISION %token PRECISION
%token PREPARE_SYM
%token DEALLOCATE_SYM
%token QUICK %token QUICK
%token REAL %token REAL
%token SIGNED_SYM %token SIGNED_SYM
...@@ -725,6 +727,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize); ...@@ -725,6 +727,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b,int *yystacksize);
precision subselect_start opt_and charset precision subselect_start opt_and charset
subselect_end select_var_list select_var_list_init help opt_len subselect_end select_var_list select_var_list_init help opt_len
opt_extended_describe opt_extended_describe
prepare prepare_src execute deallocate
END_OF_INPUT END_OF_INPUT
%type <NONE> %type <NONE>
...@@ -761,10 +764,12 @@ verb_clause: ...@@ -761,10 +764,12 @@ verb_clause:
| checksum | checksum
| commit | commit
| create | create
| deallocate
| delete | delete
| describe | describe
| do | do
| drop | drop
| execute
| flush | flush
| grant | grant
| handler | handler
...@@ -776,6 +781,7 @@ verb_clause: ...@@ -776,6 +781,7 @@ verb_clause:
| optimize | optimize
| keycache | keycache
| preload | preload
| prepare
| purge | purge
| rename | rename
| repair | repair
...@@ -796,6 +802,86 @@ verb_clause: ...@@ -796,6 +802,86 @@ verb_clause:
| use | use
; ;
deallocate:
DEALLOCATE_SYM PREPARE_SYM ident
{
THD *thd=YYTHD;
LEX *lex= thd->lex;
if (thd->command == COM_PREPARE)
{
yyerror(ER(ER_SYNTAX_ERROR));
YYABORT;
}
lex->sql_command= SQLCOM_DEALLOCATE_PREPARE;
lex->prepared_stmt_name= $3;
};
prepare:
PREPARE_SYM ident FROM prepare_src
{
THD *thd=YYTHD;
LEX *lex= thd->lex;
if (thd->command == COM_PREPARE)
{
yyerror(ER(ER_SYNTAX_ERROR));
YYABORT;
}
lex->sql_command= SQLCOM_PREPARE;
lex->prepared_stmt_name= $2;
};
prepare_src:
TEXT_STRING_sys
{
THD *thd=YYTHD;
LEX *lex= thd->lex;
lex->prepared_stmt_code= $1;
lex->prepared_stmt_code_is_varref= false;
}
| '@' ident_or_text
{
THD *thd=YYTHD;
LEX *lex= thd->lex;
lex->prepared_stmt_code= $2;
lex->prepared_stmt_code_is_varref= true;
};
execute:
EXECUTE_SYM ident
{
THD *thd=YYTHD;
LEX *lex= thd->lex;
if (thd->command == COM_PREPARE)
{
yyerror(ER(ER_SYNTAX_ERROR));
YYABORT;
}
lex->sql_command= SQLCOM_EXECUTE;
lex->prepared_stmt_name= $2;
}
execute_using
{}
;
execute_using:
/* nothing */
| USING execute_var_list
;
execute_var_list:
execute_var_list ',' execute_var_ident
| execute_var_ident
;
execute_var_ident: '@' ident_or_text
{
LEX *lex=Lex;
LEX_STRING *lexstr= (LEX_STRING*)sql_memdup(&$2, sizeof(LEX_STRING));
if (!lexstr || lex->prepared_stmt_params.push_back(lexstr))
YYABORT;
}
;
/* help */ /* help */
help: help:
...@@ -4912,6 +4998,7 @@ keyword: ...@@ -4912,6 +4998,7 @@ keyword:
| DATETIME {} | DATETIME {}
| DATE_SYM {} | DATE_SYM {}
| DAY_SYM {} | DAY_SYM {}
| DEALLOCATE_SYM {}
| DELAY_KEY_WRITE_SYM {} | DELAY_KEY_WRITE_SYM {}
| DES_KEY_FILE {} | DES_KEY_FILE {}
| DIRECTORY_SYM {} | DIRECTORY_SYM {}
...@@ -5010,6 +5097,7 @@ keyword: ...@@ -5010,6 +5097,7 @@ keyword:
| PASSWORD {} | PASSWORD {}
| POINT_SYM {} | POINT_SYM {}
| POLYGON {} | POLYGON {}
| PREPARE_SYM {}
| PREV_SYM {} | PREV_SYM {}
| PROCESS {} | PROCESS {}
| PROCESSLIST_SYM {} | PROCESSLIST_SYM {}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment