Merge bk-internal.mysql.com:/home/bk/mysql-5.1-runtime
into ibm.opbmk:/home/alik/Documents/MySQL/devel/5.1-rt-bug25843 sql/mysql_priv.h: Auto merged sql/sp.cc: Auto merged sql/sp_head.cc: Auto merged sql/sql_class.cc: Auto merged sql/sql_class.h: Auto merged sql/sql_db.cc: Auto merged
This commit is contained in:
commit
d7463b5e1b
@ -275,5 +275,223 @@ drop table t1;
|
||||
--echo ---- disconnect connection con1 ----
|
||||
disconnect con1;
|
||||
|
||||
#
|
||||
# Bug #25843 Changing default database between PREPARE and EXECUTE of statement
|
||||
# breaks binlog.
|
||||
#
|
||||
# There were actually two problems discovered by this bug:
|
||||
#
|
||||
# 1. Default (current) database is not fixed at the creation time.
|
||||
# That leads to wrong output of DATABASE() function.
|
||||
#
|
||||
# 2. Database attributes (@@collation_database) are not fixed at the creation
|
||||
# time. That leads to wrong resultset.
|
||||
#
|
||||
# Binlog breakage and Query Cache wrong output happened because of the first
|
||||
# problem.
|
||||
#
|
||||
|
||||
--echo ########################################################################
|
||||
--echo #
|
||||
--echo # BUG#25843: Changing default database between PREPARE and EXECUTE of
|
||||
--echo # statement breaks binlog.
|
||||
--echo #
|
||||
--echo ########################################################################
|
||||
|
||||
###############################################################################
|
||||
|
||||
--echo
|
||||
--echo #
|
||||
--echo # Check that default database and its attributes are fixed at the
|
||||
--echo # creation time.
|
||||
--echo #
|
||||
|
||||
# Prepare data structures.
|
||||
|
||||
--echo
|
||||
--disable_warnings
|
||||
DROP DATABASE IF EXISTS mysqltest1;
|
||||
DROP DATABASE IF EXISTS mysqltest2;
|
||||
--enable_warnings
|
||||
|
||||
--echo
|
||||
CREATE DATABASE mysqltest1 COLLATE utf8_unicode_ci;
|
||||
CREATE DATABASE mysqltest2 COLLATE utf8_general_ci;
|
||||
|
||||
--echo
|
||||
CREATE TABLE mysqltest1.t1(msg VARCHAR(255));
|
||||
CREATE TABLE mysqltest2.t1(msg VARCHAR(255));
|
||||
|
||||
# - Create a prepared statement with mysqltest1 as default database;
|
||||
|
||||
--echo
|
||||
|
||||
use mysqltest1;
|
||||
|
||||
PREPARE stmt_a_1 FROM 'INSERT INTO t1 VALUES(DATABASE())';
|
||||
PREPARE stmt_a_2 FROM 'INSERT INTO t1 VALUES(@@collation_database)';
|
||||
|
||||
# - Execute on mysqltest1.
|
||||
|
||||
--echo
|
||||
|
||||
EXECUTE stmt_a_1;
|
||||
EXECUTE stmt_a_2;
|
||||
|
||||
# - Execute on mysqltest2.
|
||||
|
||||
--echo
|
||||
|
||||
use mysqltest2;
|
||||
|
||||
EXECUTE stmt_a_1;
|
||||
EXECUTE stmt_a_2;
|
||||
|
||||
# - Check the results;
|
||||
|
||||
--echo
|
||||
SELECT * FROM mysqltest1.t1;
|
||||
|
||||
--echo
|
||||
SELECT * FROM mysqltest2.t1;
|
||||
|
||||
# - Drop prepared statements.
|
||||
|
||||
--echo
|
||||
DROP PREPARE stmt_a_1;
|
||||
DROP PREPARE stmt_a_2;
|
||||
|
||||
###############################################################################
|
||||
|
||||
--echo
|
||||
--echo #
|
||||
--echo # The Query Cache test case.
|
||||
--echo #
|
||||
|
||||
--echo
|
||||
DELETE FROM mysqltest1.t1;
|
||||
DELETE FROM mysqltest2.t1;
|
||||
|
||||
--echo
|
||||
INSERT INTO mysqltest1.t1 VALUES('mysqltest1.t1');
|
||||
INSERT INTO mysqltest2.t1 VALUES('mysqltest2.t1');
|
||||
|
||||
--echo
|
||||
use mysqltest1;
|
||||
PREPARE stmt_b_1 FROM 'SELECT * FROM t1';
|
||||
|
||||
--echo
|
||||
use mysqltest2;
|
||||
PREPARE stmt_b_2 FROM 'SELECT * FROM t1';
|
||||
|
||||
--echo
|
||||
EXECUTE stmt_b_1;
|
||||
|
||||
--echo
|
||||
EXECUTE stmt_b_2;
|
||||
|
||||
--echo
|
||||
use mysqltest1;
|
||||
|
||||
--echo
|
||||
EXECUTE stmt_b_1;
|
||||
|
||||
--echo
|
||||
EXECUTE stmt_b_2;
|
||||
|
||||
--echo
|
||||
DROP PREPARE stmt_b_1;
|
||||
DROP PREPARE stmt_b_2;
|
||||
|
||||
# Cleanup.
|
||||
|
||||
--echo
|
||||
use test;
|
||||
|
||||
--echo
|
||||
DROP DATABASE mysqltest1;
|
||||
DROP DATABASE mysqltest2;
|
||||
|
||||
###############################################################################
|
||||
|
||||
--echo
|
||||
--echo #
|
||||
--echo # Check that prepared statements work properly when there is no current
|
||||
--echo # database.
|
||||
--echo #
|
||||
|
||||
--echo
|
||||
CREATE DATABASE mysqltest1 COLLATE utf8_unicode_ci;
|
||||
CREATE DATABASE mysqltest2 COLLATE utf8_general_ci;
|
||||
|
||||
--echo
|
||||
use mysqltest1;
|
||||
|
||||
--echo
|
||||
PREPARE stmt_c_1 FROM 'SELECT DATABASE(), @@collation_database';
|
||||
|
||||
--echo
|
||||
use mysqltest2;
|
||||
|
||||
--echo
|
||||
PREPARE stmt_c_2 FROM 'SELECT DATABASE(), @@collation_database';
|
||||
|
||||
--echo
|
||||
DROP DATABASE mysqltest2;
|
||||
|
||||
--echo
|
||||
SELECT DATABASE(), @@collation_database;
|
||||
|
||||
# -- Here we have: current db: NULL; stmt db: mysqltest1;
|
||||
--echo
|
||||
EXECUTE stmt_c_1;
|
||||
|
||||
--echo
|
||||
SELECT DATABASE(), @@collation_database;
|
||||
|
||||
# -- Here we have: current db: NULL; stmt db: mysqltest2 (non-existent);
|
||||
--echo
|
||||
EXECUTE stmt_c_2;
|
||||
|
||||
--echo
|
||||
SELECT DATABASE(), @@collation_database;
|
||||
|
||||
# -- Create prepared statement, which has no current database.
|
||||
|
||||
--echo
|
||||
PREPARE stmt_c_3 FROM 'SELECT DATABASE(), @@collation_database';
|
||||
|
||||
# -- Here we have: current db: NULL; stmt db: NULL;
|
||||
--echo
|
||||
EXECUTE stmt_c_3;
|
||||
|
||||
--echo
|
||||
use mysqltest1;
|
||||
|
||||
# -- Here we have: current db: mysqltest1; stmt db: mysqltest2 (non-existent);
|
||||
--echo
|
||||
EXECUTE stmt_c_2;
|
||||
|
||||
--echo
|
||||
SELECT DATABASE(), @@collation_database;
|
||||
|
||||
# -- Here we have: current db: mysqltest1; stmt db: NULL;
|
||||
--echo
|
||||
EXECUTE stmt_c_3;
|
||||
|
||||
--echo
|
||||
SELECT DATABASE(), @@collation_database;
|
||||
|
||||
--echo
|
||||
DROP DATABASE mysqltest1;
|
||||
|
||||
--echo
|
||||
use test;
|
||||
|
||||
--echo
|
||||
--echo ########################################################################
|
||||
|
||||
###############################################################################
|
||||
|
||||
set @@global.query_cache_size=@initial_query_cache_size;
|
||||
flush status; # reset Qcache status variables for next tests
|
||||
|
@ -371,5 +371,163 @@ Variable_name Value
|
||||
Qcache_hits 21
|
||||
drop table t1;
|
||||
---- disconnect connection con1 ----
|
||||
########################################################################
|
||||
#
|
||||
# BUG#25843: Changing default database between PREPARE and EXECUTE of
|
||||
# statement breaks binlog.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
#
|
||||
# Check that default database and its attributes are fixed at the
|
||||
# creation time.
|
||||
#
|
||||
|
||||
DROP DATABASE IF EXISTS mysqltest1;
|
||||
DROP DATABASE IF EXISTS mysqltest2;
|
||||
|
||||
CREATE DATABASE mysqltest1 COLLATE utf8_unicode_ci;
|
||||
CREATE DATABASE mysqltest2 COLLATE utf8_general_ci;
|
||||
|
||||
CREATE TABLE mysqltest1.t1(msg VARCHAR(255));
|
||||
CREATE TABLE mysqltest2.t1(msg VARCHAR(255));
|
||||
|
||||
use mysqltest1;
|
||||
PREPARE stmt_a_1 FROM 'INSERT INTO t1 VALUES(DATABASE())';
|
||||
PREPARE stmt_a_2 FROM 'INSERT INTO t1 VALUES(@@collation_database)';
|
||||
|
||||
EXECUTE stmt_a_1;
|
||||
EXECUTE stmt_a_2;
|
||||
|
||||
use mysqltest2;
|
||||
EXECUTE stmt_a_1;
|
||||
EXECUTE stmt_a_2;
|
||||
|
||||
SELECT * FROM mysqltest1.t1;
|
||||
msg
|
||||
mysqltest1
|
||||
utf8_unicode_ci
|
||||
mysqltest1
|
||||
utf8_unicode_ci
|
||||
|
||||
SELECT * FROM mysqltest2.t1;
|
||||
msg
|
||||
|
||||
DROP PREPARE stmt_a_1;
|
||||
DROP PREPARE stmt_a_2;
|
||||
|
||||
#
|
||||
# The Query Cache test case.
|
||||
#
|
||||
|
||||
DELETE FROM mysqltest1.t1;
|
||||
DELETE FROM mysqltest2.t1;
|
||||
|
||||
INSERT INTO mysqltest1.t1 VALUES('mysqltest1.t1');
|
||||
INSERT INTO mysqltest2.t1 VALUES('mysqltest2.t1');
|
||||
|
||||
use mysqltest1;
|
||||
PREPARE stmt_b_1 FROM 'SELECT * FROM t1';
|
||||
|
||||
use mysqltest2;
|
||||
PREPARE stmt_b_2 FROM 'SELECT * FROM t1';
|
||||
|
||||
EXECUTE stmt_b_1;
|
||||
msg
|
||||
mysqltest1.t1
|
||||
|
||||
EXECUTE stmt_b_2;
|
||||
msg
|
||||
mysqltest2.t1
|
||||
|
||||
use mysqltest1;
|
||||
|
||||
EXECUTE stmt_b_1;
|
||||
msg
|
||||
mysqltest1.t1
|
||||
|
||||
EXECUTE stmt_b_2;
|
||||
msg
|
||||
mysqltest2.t1
|
||||
|
||||
DROP PREPARE stmt_b_1;
|
||||
DROP PREPARE stmt_b_2;
|
||||
|
||||
use test;
|
||||
|
||||
DROP DATABASE mysqltest1;
|
||||
DROP DATABASE mysqltest2;
|
||||
|
||||
#
|
||||
# Check that prepared statements work properly when there is no current
|
||||
# database.
|
||||
#
|
||||
|
||||
CREATE DATABASE mysqltest1 COLLATE utf8_unicode_ci;
|
||||
CREATE DATABASE mysqltest2 COLLATE utf8_general_ci;
|
||||
|
||||
use mysqltest1;
|
||||
|
||||
PREPARE stmt_c_1 FROM 'SELECT DATABASE(), @@collation_database';
|
||||
|
||||
use mysqltest2;
|
||||
|
||||
PREPARE stmt_c_2 FROM 'SELECT DATABASE(), @@collation_database';
|
||||
|
||||
DROP DATABASE mysqltest2;
|
||||
|
||||
SELECT DATABASE(), @@collation_database;
|
||||
DATABASE() @@collation_database
|
||||
NULL latin1_swedish_ci
|
||||
|
||||
EXECUTE stmt_c_1;
|
||||
DATABASE() @@collation_database
|
||||
mysqltest1 utf8_unicode_ci
|
||||
|
||||
SELECT DATABASE(), @@collation_database;
|
||||
DATABASE() @@collation_database
|
||||
NULL latin1_swedish_ci
|
||||
|
||||
EXECUTE stmt_c_2;
|
||||
DATABASE() @@collation_database
|
||||
NULL latin1_swedish_ci
|
||||
Warnings:
|
||||
Note 1049 Unknown database 'mysqltest2'
|
||||
|
||||
SELECT DATABASE(), @@collation_database;
|
||||
DATABASE() @@collation_database
|
||||
NULL latin1_swedish_ci
|
||||
|
||||
PREPARE stmt_c_3 FROM 'SELECT DATABASE(), @@collation_database';
|
||||
|
||||
EXECUTE stmt_c_3;
|
||||
DATABASE() @@collation_database
|
||||
NULL latin1_swedish_ci
|
||||
|
||||
use mysqltest1;
|
||||
|
||||
EXECUTE stmt_c_2;
|
||||
DATABASE() @@collation_database
|
||||
NULL latin1_swedish_ci
|
||||
Warnings:
|
||||
Note 1049 Unknown database 'mysqltest2'
|
||||
|
||||
SELECT DATABASE(), @@collation_database;
|
||||
DATABASE() @@collation_database
|
||||
mysqltest1 utf8_unicode_ci
|
||||
|
||||
EXECUTE stmt_c_3;
|
||||
DATABASE() @@collation_database
|
||||
NULL latin1_swedish_ci
|
||||
|
||||
SELECT DATABASE(), @@collation_database;
|
||||
DATABASE() @@collation_database
|
||||
mysqltest1 utf8_unicode_ci
|
||||
|
||||
DROP DATABASE mysqltest1;
|
||||
|
||||
use test;
|
||||
|
||||
########################################################################
|
||||
set @@global.query_cache_size=@initial_query_cache_size;
|
||||
flush status;
|
||||
|
@ -371,5 +371,163 @@ Variable_name Value
|
||||
Qcache_hits 19
|
||||
drop table t1;
|
||||
---- disconnect connection con1 ----
|
||||
########################################################################
|
||||
#
|
||||
# BUG#25843: Changing default database between PREPARE and EXECUTE of
|
||||
# statement breaks binlog.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
#
|
||||
# Check that default database and its attributes are fixed at the
|
||||
# creation time.
|
||||
#
|
||||
|
||||
DROP DATABASE IF EXISTS mysqltest1;
|
||||
DROP DATABASE IF EXISTS mysqltest2;
|
||||
|
||||
CREATE DATABASE mysqltest1 COLLATE utf8_unicode_ci;
|
||||
CREATE DATABASE mysqltest2 COLLATE utf8_general_ci;
|
||||
|
||||
CREATE TABLE mysqltest1.t1(msg VARCHAR(255));
|
||||
CREATE TABLE mysqltest2.t1(msg VARCHAR(255));
|
||||
|
||||
use mysqltest1;
|
||||
PREPARE stmt_a_1 FROM 'INSERT INTO t1 VALUES(DATABASE())';
|
||||
PREPARE stmt_a_2 FROM 'INSERT INTO t1 VALUES(@@collation_database)';
|
||||
|
||||
EXECUTE stmt_a_1;
|
||||
EXECUTE stmt_a_2;
|
||||
|
||||
use mysqltest2;
|
||||
EXECUTE stmt_a_1;
|
||||
EXECUTE stmt_a_2;
|
||||
|
||||
SELECT * FROM mysqltest1.t1;
|
||||
msg
|
||||
mysqltest1
|
||||
utf8_unicode_ci
|
||||
mysqltest1
|
||||
utf8_unicode_ci
|
||||
|
||||
SELECT * FROM mysqltest2.t1;
|
||||
msg
|
||||
|
||||
DROP PREPARE stmt_a_1;
|
||||
DROP PREPARE stmt_a_2;
|
||||
|
||||
#
|
||||
# The Query Cache test case.
|
||||
#
|
||||
|
||||
DELETE FROM mysqltest1.t1;
|
||||
DELETE FROM mysqltest2.t1;
|
||||
|
||||
INSERT INTO mysqltest1.t1 VALUES('mysqltest1.t1');
|
||||
INSERT INTO mysqltest2.t1 VALUES('mysqltest2.t1');
|
||||
|
||||
use mysqltest1;
|
||||
PREPARE stmt_b_1 FROM 'SELECT * FROM t1';
|
||||
|
||||
use mysqltest2;
|
||||
PREPARE stmt_b_2 FROM 'SELECT * FROM t1';
|
||||
|
||||
EXECUTE stmt_b_1;
|
||||
msg
|
||||
mysqltest1.t1
|
||||
|
||||
EXECUTE stmt_b_2;
|
||||
msg
|
||||
mysqltest2.t1
|
||||
|
||||
use mysqltest1;
|
||||
|
||||
EXECUTE stmt_b_1;
|
||||
msg
|
||||
mysqltest1.t1
|
||||
|
||||
EXECUTE stmt_b_2;
|
||||
msg
|
||||
mysqltest2.t1
|
||||
|
||||
DROP PREPARE stmt_b_1;
|
||||
DROP PREPARE stmt_b_2;
|
||||
|
||||
use test;
|
||||
|
||||
DROP DATABASE mysqltest1;
|
||||
DROP DATABASE mysqltest2;
|
||||
|
||||
#
|
||||
# Check that prepared statements work properly when there is no current
|
||||
# database.
|
||||
#
|
||||
|
||||
CREATE DATABASE mysqltest1 COLLATE utf8_unicode_ci;
|
||||
CREATE DATABASE mysqltest2 COLLATE utf8_general_ci;
|
||||
|
||||
use mysqltest1;
|
||||
|
||||
PREPARE stmt_c_1 FROM 'SELECT DATABASE(), @@collation_database';
|
||||
|
||||
use mysqltest2;
|
||||
|
||||
PREPARE stmt_c_2 FROM 'SELECT DATABASE(), @@collation_database';
|
||||
|
||||
DROP DATABASE mysqltest2;
|
||||
|
||||
SELECT DATABASE(), @@collation_database;
|
||||
DATABASE() @@collation_database
|
||||
NULL latin1_swedish_ci
|
||||
|
||||
EXECUTE stmt_c_1;
|
||||
DATABASE() @@collation_database
|
||||
mysqltest1 utf8_unicode_ci
|
||||
|
||||
SELECT DATABASE(), @@collation_database;
|
||||
DATABASE() @@collation_database
|
||||
NULL latin1_swedish_ci
|
||||
|
||||
EXECUTE stmt_c_2;
|
||||
DATABASE() @@collation_database
|
||||
NULL latin1_swedish_ci
|
||||
Warnings:
|
||||
Note 1049 Unknown database 'mysqltest2'
|
||||
|
||||
SELECT DATABASE(), @@collation_database;
|
||||
DATABASE() @@collation_database
|
||||
NULL latin1_swedish_ci
|
||||
|
||||
PREPARE stmt_c_3 FROM 'SELECT DATABASE(), @@collation_database';
|
||||
|
||||
EXECUTE stmt_c_3;
|
||||
DATABASE() @@collation_database
|
||||
NULL latin1_swedish_ci
|
||||
|
||||
use mysqltest1;
|
||||
|
||||
EXECUTE stmt_c_2;
|
||||
DATABASE() @@collation_database
|
||||
NULL latin1_swedish_ci
|
||||
Warnings:
|
||||
Note 1049 Unknown database 'mysqltest2'
|
||||
|
||||
SELECT DATABASE(), @@collation_database;
|
||||
DATABASE() @@collation_database
|
||||
mysqltest1 utf8_unicode_ci
|
||||
|
||||
EXECUTE stmt_c_3;
|
||||
DATABASE() @@collation_database
|
||||
NULL latin1_swedish_ci
|
||||
|
||||
SELECT DATABASE(), @@collation_database;
|
||||
DATABASE() @@collation_database
|
||||
mysqltest1 utf8_unicode_ci
|
||||
|
||||
DROP DATABASE mysqltest1;
|
||||
|
||||
use test;
|
||||
|
||||
########################################################################
|
||||
set @@global.query_cache_size=@initial_query_cache_size;
|
||||
flush status;
|
||||
|
@ -26,5 +26,44 @@ from-master-2-'',
|
||||
from-var-from-master-3
|
||||
drop table t1;
|
||||
stop slave;
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# BUG#25843: Changing default database between PREPARE and EXECUTE of
|
||||
# statement breaks binlog.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
#
|
||||
# Check that binlog is filled properly.
|
||||
#
|
||||
|
||||
CREATE DATABASE mysqltest1;
|
||||
CREATE TABLE t1(c INT);
|
||||
|
||||
RESET MASTER;
|
||||
RESET SLAVE;
|
||||
|
||||
PREPARE stmt_d_1 FROM 'INSERT INTO t1 VALUES(1)';
|
||||
|
||||
EXECUTE stmt_d_1;
|
||||
|
||||
use mysqltest1;
|
||||
|
||||
EXECUTE stmt_d_1;
|
||||
|
||||
FLUSH LOGS;
|
||||
|
||||
SHOW BINLOG EVENTS FROM 106;
|
||||
Log_name Pos Event_type Server_id End_log_pos Info
|
||||
slave-bin.000001 106 Query 2 193 use `test`; INSERT INTO t1 VALUES(1)
|
||||
slave-bin.000001 193 Query 2 280 use `test`; INSERT INTO t1 VALUES(1)
|
||||
slave-bin.000001 280 Rotate 2 323 slave-bin.000002;pos=4
|
||||
|
||||
DROP DATABASE mysqltest1;
|
||||
|
||||
use test;
|
||||
|
||||
########################################################################
|
||||
reset master;
|
||||
reset slave;
|
||||
|
@ -46,6 +46,74 @@ stop slave;
|
||||
|
||||
# End of 4.1 tests
|
||||
|
||||
#
|
||||
# Bug #25843 Changing default database between PREPARE and EXECUTE of statement
|
||||
# breaks binlog.
|
||||
#
|
||||
# There were actually two problems discovered by this bug:
|
||||
#
|
||||
# 1. Default (current) database is not fixed at the creation time.
|
||||
# That leads to wrong output of DATABASE() function.
|
||||
#
|
||||
# 2. Database attributes (@@collation_database) are not fixed at the creation
|
||||
# time. That leads to wrong resultset.
|
||||
#
|
||||
# Binlog breakage and Query Cache wrong output happened because of the first
|
||||
# problem.
|
||||
#
|
||||
|
||||
--echo
|
||||
--echo ########################################################################
|
||||
--echo #
|
||||
--echo # BUG#25843: Changing default database between PREPARE and EXECUTE of
|
||||
--echo # statement breaks binlog.
|
||||
--echo #
|
||||
--echo ########################################################################
|
||||
|
||||
###############################################################################
|
||||
|
||||
--echo
|
||||
--echo #
|
||||
--echo # Check that binlog is filled properly.
|
||||
--echo #
|
||||
|
||||
--echo
|
||||
CREATE DATABASE mysqltest1;
|
||||
CREATE TABLE t1(c INT);
|
||||
|
||||
--echo
|
||||
RESET MASTER;
|
||||
RESET SLAVE;
|
||||
|
||||
--echo
|
||||
PREPARE stmt_d_1 FROM 'INSERT INTO t1 VALUES(1)';
|
||||
|
||||
--echo
|
||||
EXECUTE stmt_d_1;
|
||||
|
||||
--echo
|
||||
use mysqltest1;
|
||||
|
||||
--echo
|
||||
EXECUTE stmt_d_1;
|
||||
|
||||
--echo
|
||||
FLUSH LOGS;
|
||||
|
||||
--echo
|
||||
SHOW BINLOG EVENTS FROM 106;
|
||||
|
||||
--echo
|
||||
DROP DATABASE mysqltest1;
|
||||
|
||||
--echo
|
||||
use test;
|
||||
|
||||
--echo
|
||||
--echo ########################################################################
|
||||
|
||||
###############################################################################
|
||||
|
||||
reset master;
|
||||
reset slave;
|
||||
disconnect master;
|
||||
|
@ -937,9 +937,16 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent);
|
||||
bool do_rename(THD *thd, TABLE_LIST *ren_table, char *new_db,
|
||||
char *new_table_name, char *new_table_alias,
|
||||
bool skip_error);
|
||||
|
||||
bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name,
|
||||
bool force_switch);
|
||||
|
||||
bool mysql_opt_change_db(THD *thd,
|
||||
const LEX_STRING *new_db_name,
|
||||
LEX_STRING *saved_db_name,
|
||||
bool force_switch,
|
||||
bool *cur_db_changed);
|
||||
|
||||
void mysql_parse(THD *thd, const char *inBuf, uint length,
|
||||
const char ** semicolon);
|
||||
|
||||
|
150
sql/sp.cc
150
sql/sp.cc
@ -520,9 +520,10 @@ db_load_routine(THD *thd, int type, sp_name *name, sp_head **sphp,
|
||||
{
|
||||
LEX *old_lex= thd->lex, newlex;
|
||||
String defstr;
|
||||
char old_db_buf[NAME_LEN+1];
|
||||
LEX_STRING old_db= { old_db_buf, sizeof(old_db_buf) };
|
||||
bool dbchanged;
|
||||
char saved_cur_db_name_buf[NAME_LEN+1];
|
||||
LEX_STRING saved_cur_db_name=
|
||||
{ saved_cur_db_name_buf, sizeof(saved_cur_db_name_buf) };
|
||||
bool cur_db_changed;
|
||||
ulong old_sql_mode= thd->variables.sql_mode;
|
||||
ha_rows old_select_limit= thd->variables.select_limit;
|
||||
sp_rcontext *old_spcont= thd->spcont;
|
||||
@ -567,16 +568,16 @@ db_load_routine(THD *thd, int type, sp_name *name, sp_head **sphp,
|
||||
}
|
||||
|
||||
/*
|
||||
Change current database if needed.
|
||||
Change the current database (if needed).
|
||||
|
||||
collation_database will be updated here. However, it can be wrong,
|
||||
because it will contain the current value of the database collation.
|
||||
We need collation_database to be fixed at the creation time -- so
|
||||
we'll update it later in switch_query_ctx().
|
||||
TODO: why do we force switch here?
|
||||
*/
|
||||
|
||||
if ((ret= sp_use_new_db(thd, name->m_db, &old_db, TRUE, &dbchanged)))
|
||||
if (mysql_opt_change_db(thd, &name->m_db, &saved_cur_db_name, TRUE,
|
||||
&cur_db_changed))
|
||||
{
|
||||
goto end;
|
||||
}
|
||||
|
||||
thd->spcont= NULL;
|
||||
|
||||
@ -585,34 +586,42 @@ db_load_routine(THD *thd, int type, sp_name *name, sp_head **sphp,
|
||||
|
||||
lex_start(thd);
|
||||
|
||||
if (parse_sql(thd, &lip, creation_ctx) || newlex.sphead == NULL)
|
||||
{
|
||||
sp_head *sp= newlex.sphead;
|
||||
ret= parse_sql(thd, &lip, creation_ctx) || newlex.sphead == NULL;
|
||||
|
||||
if (dbchanged && (ret= mysql_change_db(thd, &old_db, TRUE)))
|
||||
goto end;
|
||||
delete sp;
|
||||
ret= SP_PARSE_ERROR;
|
||||
}
|
||||
else
|
||||
/*
|
||||
Force switching back to the saved current database (if changed),
|
||||
because it may be NULL. In this case, mysql_change_db() would
|
||||
generate an error.
|
||||
*/
|
||||
|
||||
if (cur_db_changed && mysql_change_db(thd, &saved_cur_db_name, TRUE))
|
||||
{
|
||||
if (dbchanged && (ret= mysql_change_db(thd, &old_db, TRUE)))
|
||||
goto end;
|
||||
*sphp= newlex.sphead;
|
||||
(*sphp)->set_definer(&definer_user_name, &definer_host_name);
|
||||
(*sphp)->set_info(created, modified, &chistics, sql_mode);
|
||||
(*sphp)->set_creation_ctx(creation_ctx);
|
||||
(*sphp)->optimize();
|
||||
/*
|
||||
Not strictly necessary to invoke this method here, since we know
|
||||
that we've parsed CREATE PROCEDURE/FUNCTION and not an
|
||||
UPDATE/DELETE/INSERT/REPLACE/LOAD/CREATE TABLE, but we try to
|
||||
maintain the invariant that this method is called for each
|
||||
distinct statement, in case its logic is extended with other
|
||||
types of analyses in future.
|
||||
*/
|
||||
newlex.set_trg_event_type_for_tables();
|
||||
delete newlex.sphead;
|
||||
ret= -1;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
{
|
||||
delete newlex.sphead;
|
||||
ret= SP_PARSE_ERROR;
|
||||
goto end;
|
||||
}
|
||||
|
||||
*sphp= newlex.sphead;
|
||||
(*sphp)->set_definer(&definer_user_name, &definer_host_name);
|
||||
(*sphp)->set_info(created, modified, &chistics, sql_mode);
|
||||
(*sphp)->set_creation_ctx(creation_ctx);
|
||||
(*sphp)->optimize();
|
||||
/*
|
||||
Not strictly necessary to invoke this method here, since we know
|
||||
that we've parsed CREATE PROCEDURE/FUNCTION and not an
|
||||
UPDATE/DELETE/INSERT/REPLACE/LOAD/CREATE TABLE, but we try to
|
||||
maintain the invariant that this method is called for each
|
||||
distinct statement, in case its logic is extended with other
|
||||
types of analyses in future.
|
||||
*/
|
||||
newlex.set_trg_event_type_for_tables();
|
||||
}
|
||||
|
||||
end:
|
||||
@ -2025,76 +2034,3 @@ create_string(THD *thd, String *buf,
|
||||
buf->append(body, bodylen);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Change the current database if needed.
|
||||
|
||||
@param[in] thd thread handle
|
||||
@param[in] new_db new database name
|
||||
@param[in, out] old_db IN: str points to a buffer where to store
|
||||
the old database, length contains the
|
||||
size of the buffer
|
||||
OUT: if old db was not NULL, its name is
|
||||
copied to the buffer pointed at by str
|
||||
and length is updated accordingly.
|
||||
Otherwise str[0] is set to '\0' and
|
||||
length is set to 0. The out parameter
|
||||
should be used only if the database name
|
||||
has been changed (see dbchangedp).
|
||||
@param[in] force_switch Flag to mysql_change_db(). For more information,
|
||||
see mysql_change_db() comment.
|
||||
@param[out] dbchangedp is set to TRUE if the current database is
|
||||
changed, FALSE otherwise. The current
|
||||
database is not changed if the old name
|
||||
is equal to the new one, both names are
|
||||
empty, or an error has occurred.
|
||||
|
||||
@return Operation status.
|
||||
@retval 0 on success
|
||||
@retval 1 access denied or out of memory
|
||||
(the error message is set in THD)
|
||||
*/
|
||||
|
||||
int
|
||||
sp_use_new_db(THD *thd,
|
||||
LEX_STRING new_db,
|
||||
LEX_STRING *old_db,
|
||||
bool force_switch,
|
||||
bool *dbchangedp)
|
||||
{
|
||||
int ret;
|
||||
DBUG_ENTER("sp_use_new_db");
|
||||
DBUG_PRINT("enter", ("newdb: %s", new_db.str));
|
||||
|
||||
/*
|
||||
A stored routine always belongs to some database. The
|
||||
old database (old_db) might be NULL, but to restore the
|
||||
old database we will use mysql_change_db.
|
||||
*/
|
||||
DBUG_ASSERT(new_db.str && new_db.length);
|
||||
|
||||
if (thd->db)
|
||||
{
|
||||
old_db->length= (strmake(old_db->str, thd->db, old_db->length) -
|
||||
old_db->str);
|
||||
}
|
||||
else
|
||||
{
|
||||
old_db->str[0]= '\0';
|
||||
old_db->length= 0;
|
||||
}
|
||||
|
||||
/* Don't change the database if the new name is the same as the old one. */
|
||||
if (my_strcasecmp(system_charset_info, old_db->str, new_db.str) == 0)
|
||||
{
|
||||
*dbchangedp= FALSE;
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
ret= mysql_change_db(thd, &new_db, force_switch);
|
||||
|
||||
*dbchangedp= ret == 0;
|
||||
DBUG_RETURN(ret);
|
||||
}
|
||||
|
11
sql/sp.h
11
sql/sp.h
@ -85,15 +85,4 @@ extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen,
|
||||
*/
|
||||
TABLE *open_proc_table_for_read(THD *thd, Open_tables_state *backup);
|
||||
|
||||
|
||||
/*
|
||||
Do a "use new_db". The current db is stored at old_db. If new_db is the
|
||||
same as the current one, nothing is changed. dbchangedp is set to true if
|
||||
the db was actually changed.
|
||||
*/
|
||||
|
||||
int
|
||||
sp_use_new_db(THD *thd, LEX_STRING new_db, LEX_STRING *old_db,
|
||||
bool no_access_check, bool *dbchangedp);
|
||||
|
||||
#endif /* _SP_H_ */
|
||||
|
@ -1016,9 +1016,10 @@ bool
|
||||
sp_head::execute(THD *thd)
|
||||
{
|
||||
DBUG_ENTER("sp_head::execute");
|
||||
char old_db_buf[NAME_LEN+1];
|
||||
LEX_STRING old_db= { old_db_buf, sizeof(old_db_buf) };
|
||||
bool dbchanged;
|
||||
char saved_cur_db_name_buf[NAME_LEN+1];
|
||||
LEX_STRING saved_cur_db_name=
|
||||
{ saved_cur_db_name_buf, sizeof(saved_cur_db_name_buf) };
|
||||
bool cur_db_changed= FALSE;
|
||||
sp_rcontext *ctx;
|
||||
bool err_status= FALSE;
|
||||
uint ip= 0;
|
||||
@ -1073,8 +1074,11 @@ sp_head::execute(THD *thd)
|
||||
*/
|
||||
|
||||
if (m_db.length &&
|
||||
(err_status= sp_use_new_db(thd, m_db, &old_db, 0, &dbchanged)))
|
||||
(err_status= mysql_opt_change_db(thd, &m_db, &saved_cur_db_name, FALSE,
|
||||
&cur_db_changed)))
|
||||
{
|
||||
goto done;
|
||||
}
|
||||
|
||||
if ((ctx= thd->spcont))
|
||||
ctx->clear_handler();
|
||||
@ -1255,14 +1259,14 @@ sp_head::execute(THD *thd)
|
||||
If the DB has changed, the pointer has changed too, but the
|
||||
original thd->db will then have been freed
|
||||
*/
|
||||
if (dbchanged)
|
||||
if (cur_db_changed && !thd->killed)
|
||||
{
|
||||
/*
|
||||
No access check when changing back to where we came from.
|
||||
(It would generate an error from mysql_change_db() when old_db=="")
|
||||
Force switching back to the saved current database, because it may be
|
||||
NULL. In this case, mysql_change_db() would generate an error.
|
||||
*/
|
||||
if (! thd->killed)
|
||||
err_status|= mysql_change_db(thd, &old_db, TRUE);
|
||||
|
||||
err_status|= mysql_change_db(thd, &saved_cur_db_name, TRUE);
|
||||
}
|
||||
m_flags&= ~IS_INVOKED;
|
||||
DBUG_PRINT("info",
|
||||
|
@ -387,7 +387,6 @@ THD::THD()
|
||||
init_sql_alloc(&main_mem_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0);
|
||||
stmt_arena= this;
|
||||
thread_stack= 0;
|
||||
db= 0;
|
||||
catalog= (char*)"std"; // the only catalog we have for now
|
||||
main_security_ctx.init();
|
||||
security_ctx= &main_security_ctx;
|
||||
@ -395,7 +394,7 @@ THD::THD()
|
||||
query_start_used= 0;
|
||||
count_cuted_fields= CHECK_FIELD_IGNORE;
|
||||
killed= NOT_KILLED;
|
||||
db_length= col_access=0;
|
||||
col_access=0;
|
||||
query_error= thread_specific_used= FALSE;
|
||||
hash_clear(&handler_tables_hash);
|
||||
tmp_table=0;
|
||||
@ -2040,7 +2039,9 @@ Statement::Statement(LEX *lex_arg, MEM_ROOT *mem_root_arg,
|
||||
lex(lex_arg),
|
||||
query(0),
|
||||
query_length(0),
|
||||
cursor(0)
|
||||
cursor(0),
|
||||
db(NULL),
|
||||
db_length(0)
|
||||
{
|
||||
name.str= NULL;
|
||||
}
|
||||
|
@ -593,6 +593,22 @@ public:
|
||||
uint32 query_length; // current query length
|
||||
Server_side_cursor *cursor;
|
||||
|
||||
/**
|
||||
Name of the current (default) database.
|
||||
|
||||
If there is the current (default) database, "db" contains its name. If
|
||||
there is no current (default) database, "db" is NULL and "db_length" is
|
||||
0. In other words, "db", "db_length" must either be NULL, or contain a
|
||||
valid database name.
|
||||
|
||||
@note this attribute is set and alloced by the slave SQL thread (for
|
||||
the THD of that thread); that thread is (and must remain, for now) the
|
||||
only responsible for freeing this member.
|
||||
*/
|
||||
|
||||
char *db;
|
||||
uint db_length;
|
||||
|
||||
public:
|
||||
|
||||
/* This constructor is called for backup statements */
|
||||
@ -1024,18 +1040,21 @@ public:
|
||||
*/
|
||||
char *thread_stack;
|
||||
|
||||
/**
|
||||
Currently selected catalog.
|
||||
*/
|
||||
char *catalog;
|
||||
|
||||
/*
|
||||
db - currently selected database
|
||||
catalog - currently selected catalog
|
||||
WARNING: some members of THD (currently 'db', 'catalog' and 'query') are
|
||||
set and alloced by the slave SQL thread (for the THD of that thread); that
|
||||
thread is (and must remain, for now) the only responsible for freeing these
|
||||
3 members. If you add members here, and you add code to set them in
|
||||
replication, don't forget to free_them_and_set_them_to_0 in replication
|
||||
properly. For details see the 'err:' label of the handle_slave_sql()
|
||||
in sql/slave.cc.
|
||||
*/
|
||||
char *db, *catalog;
|
||||
WARNING: some members of THD (currently 'Statement::db',
|
||||
'catalog' and 'query') are set and alloced by the slave SQL thread
|
||||
(for the THD of that thread); that thread is (and must remain, for now)
|
||||
the only responsible for freeing these 3 members. If you add members
|
||||
here, and you add code to set them in replication, don't forget to
|
||||
free_them_and_set_them_to_0 in replication properly. For details see
|
||||
the 'err:' label of the handle_slave_sql() in sql/slave.cc.
|
||||
*/
|
||||
|
||||
Security_context main_security_ctx;
|
||||
Security_context *security_ctx;
|
||||
|
||||
@ -1390,7 +1409,6 @@ public:
|
||||
uint tmp_table, global_read_lock;
|
||||
uint server_status,open_options;
|
||||
enum enum_thread_type system_thread;
|
||||
uint db_length;
|
||||
uint select_number; //number of select (used for EXPLAIN)
|
||||
/* variables.transaction_isolation is reset to this after each commit */
|
||||
enum_tx_isolation session_tx_isolation;
|
||||
@ -1814,11 +1832,10 @@ public:
|
||||
no current database selected (in addition to the error message set by
|
||||
malloc).
|
||||
|
||||
@note This operation just sets {thd->db, thd->db_length}. Switching the
|
||||
current database usually involves other actions, like switching other
|
||||
database attributes including security context. In the future, this
|
||||
operation will be made private and more convenient interface will be
|
||||
provided.
|
||||
@note This operation just sets {db, db_length}. Switching the current
|
||||
database usually involves other actions, like switching other database
|
||||
attributes including security context. In the future, this operation
|
||||
will be made private and more convenient interface will be provided.
|
||||
|
||||
@return Operation status
|
||||
@retval FALSE Success
|
||||
@ -1844,11 +1861,10 @@ public:
|
||||
@param new_db a pointer to the new database name.
|
||||
@param new_db_len length of the new database name.
|
||||
|
||||
@note This operation just sets {thd->db, thd->db_length}. Switching the
|
||||
current database usually involves other actions, like switching other
|
||||
database attributes including security context. In the future, this
|
||||
operation will be made private and more convenient interface will be
|
||||
provided.
|
||||
@note This operation just sets {db, db_length}. Switching the current
|
||||
database usually involves other actions, like switching other database
|
||||
attributes including security context. In the future, this operation
|
||||
will be made private and more convenient interface will be provided.
|
||||
*/
|
||||
void reset_db(char *new_db, size_t new_db_len)
|
||||
{
|
||||
|
@ -1359,8 +1359,67 @@ static void mysql_change_db_impl(THD *thd,
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@brief Change the current database and its attributes.
|
||||
Backup the current database name before switch.
|
||||
|
||||
@param[in] thd thread handle
|
||||
@param[in, out] saved_db_name IN: "str" points to a buffer where to store
|
||||
the old database name, "length" contains the
|
||||
buffer size
|
||||
OUT: if the current (default) database is
|
||||
not NULL, its name is copied to the
|
||||
buffer pointed at by "str"
|
||||
and "length" is updated accordingly.
|
||||
Otherwise "str" is set to NULL and
|
||||
"length" is set to 0.
|
||||
*/
|
||||
|
||||
static void backup_current_db_name(THD *thd,
|
||||
LEX_STRING *saved_db_name)
|
||||
{
|
||||
if (!thd->db)
|
||||
{
|
||||
/* No current (default) database selected. */
|
||||
|
||||
saved_db_name->str= NULL;
|
||||
saved_db_name->length= 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
strmake(saved_db_name->str, thd->db, saved_db_name->length);
|
||||
saved_db_name->length= thd->db_length;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Return TRUE if db1_name is equal to db2_name, FALSE otherwise.
|
||||
|
||||
The function allows to compare database names according to the MySQL
|
||||
rules. The database names db1 and db2 are equal if:
|
||||
- db1 is NULL and db2 is NULL;
|
||||
or
|
||||
- db1 is not-NULL, db2 is not-NULL, db1 is equal (ignoring case) to
|
||||
db2 in system character set (UTF8).
|
||||
*/
|
||||
|
||||
static inline bool
|
||||
cmp_db_names(const char *db1_name,
|
||||
const char *db2_name)
|
||||
{
|
||||
return
|
||||
/* db1 is NULL and db2 is NULL */
|
||||
!db1_name && !db2_name ||
|
||||
|
||||
/* db1 is not-NULL, db2 is not-NULL, db1 == db2. */
|
||||
db1_name && db2_name &&
|
||||
my_strcasecmp(system_charset_info, db1_name, db2_name) == 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@brief Change the current database and its attributes unconditionally.
|
||||
|
||||
@param thd thread handle
|
||||
@param new_db_name database name
|
||||
@ -1577,6 +1636,43 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Change the current database and its attributes if needed.
|
||||
|
||||
@param thd thread handle
|
||||
@param new_db_name database name
|
||||
@param[in, out] saved_db_name IN: "str" points to a buffer where to store
|
||||
the old database name, "length" contains the
|
||||
buffer size
|
||||
OUT: if the current (default) database is
|
||||
not NULL, its name is copied to the
|
||||
buffer pointed at by "str"
|
||||
and "length" is updated accordingly.
|
||||
Otherwise "str" is set to NULL and
|
||||
"length" is set to 0.
|
||||
@param force_switch @see mysql_change_db()
|
||||
@param[out] cur_db_changed out-flag to indicate whether the current
|
||||
database has been changed (valid only if
|
||||
the function suceeded)
|
||||
*/
|
||||
|
||||
bool mysql_opt_change_db(THD *thd,
|
||||
const LEX_STRING *new_db_name,
|
||||
LEX_STRING *saved_db_name,
|
||||
bool force_switch,
|
||||
bool *cur_db_changed)
|
||||
{
|
||||
*cur_db_changed= !cmp_db_names(thd->db, new_db_name->str);
|
||||
|
||||
if (!*cur_db_changed)
|
||||
return FALSE;
|
||||
|
||||
backup_current_db_name(thd, saved_db_name);
|
||||
|
||||
return mysql_change_db(thd, new_db_name, force_switch);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
lock_databases(THD *thd, const char *db1, uint length1,
|
||||
const char *db2, uint length2)
|
||||
|
@ -2868,6 +2868,19 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
|
||||
init_param_array(this);
|
||||
lex->set_trg_event_type_for_tables();
|
||||
|
||||
/* Remember the current database. */
|
||||
|
||||
if (thd->db && thd->db_length)
|
||||
{
|
||||
db= this->strmake(thd->db, thd->db_length);
|
||||
db_length= thd->db_length;
|
||||
}
|
||||
else
|
||||
{
|
||||
db= NULL;
|
||||
db_length= 0;
|
||||
}
|
||||
|
||||
/*
|
||||
While doing context analysis of the query (in check_prepared_statement)
|
||||
we allocate a lot of additional memory: for open tables, JOINs, derived
|
||||
@ -2974,6 +2987,13 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
|
||||
Query_arena *old_stmt_arena;
|
||||
bool error= TRUE;
|
||||
|
||||
char saved_cur_db_name_buf[NAME_LEN+1];
|
||||
LEX_STRING saved_cur_db_name=
|
||||
{ saved_cur_db_name_buf, sizeof(saved_cur_db_name_buf) };
|
||||
bool cur_db_changed;
|
||||
|
||||
LEX_STRING stmt_db_name= { db, db_length };
|
||||
|
||||
status_var_increment(thd->status_var.com_stmt_execute);
|
||||
|
||||
/* Check if we got an error when sending long data */
|
||||
@ -3022,6 +3042,21 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
|
||||
*/
|
||||
|
||||
thd->set_n_backup_statement(this, &stmt_backup);
|
||||
|
||||
/*
|
||||
Change the current database (if needed).
|
||||
|
||||
Force switching, because the database of the prepared statement may be
|
||||
NULL (prepared statements can be created while no current database
|
||||
selected).
|
||||
*/
|
||||
|
||||
if (mysql_opt_change_db(thd, &stmt_db_name, &saved_cur_db_name, TRUE,
|
||||
&cur_db_changed))
|
||||
goto error;
|
||||
|
||||
/* Allocate query. */
|
||||
|
||||
if (expanded_query->length() &&
|
||||
alloc_query(thd, (char*) expanded_query->ptr(),
|
||||
expanded_query->length()+1))
|
||||
@ -3050,6 +3085,8 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
|
||||
|
||||
thd->protocol= protocol; /* activate stmt protocol */
|
||||
|
||||
/* Go! */
|
||||
|
||||
if (open_cursor)
|
||||
error= mysql_open_cursor(thd, (uint) ALWAYS_MATERIALIZED_CURSOR,
|
||||
&result, &cursor);
|
||||
@ -3068,6 +3105,17 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Restore the current database (if changed).
|
||||
|
||||
Force switching back to the saved current database (if changed),
|
||||
because it may be NULL. In this case, mysql_change_db() would generate
|
||||
an error.
|
||||
*/
|
||||
|
||||
if (cur_db_changed)
|
||||
mysql_change_db(thd, &saved_cur_db_name, TRUE);
|
||||
|
||||
thd->protocol= &thd->protocol_text; /* use normal protocol */
|
||||
|
||||
/* Assert that if an error, no cursor is open */
|
||||
|
Loading…
x
Reference in New Issue
Block a user