Skip to content

Commit

Permalink
MDEV-12666: CURRENT_ROLE() and DATABASE() does not work in a view
Browse files Browse the repository at this point in the history
The problem lies in how CURRENT_ROLE is defined. The
Item_func_current_role inherits from Item_func_sysconst, which defines
a safe_charset_converter to be a const_charset_converter.

During view creation, if there is no role previously set, the current_role()
function returns NULL.

This is captured on item instantiation and the
const_charset_converter call subsequently returns an Item_null.
In turn, the function is replaced with Item_null and the view is
then created with an Item_null instead of Item_func_current_role.

Without this patch, the first SHOW CREATE VIEW from the testcase would
have a where clause of WHERE role_name = NULL, while the second SHOW
CREATE VIEW would show a correctly created view.

The same applies for the DATABASE function, as it can change as well.

There is an additional problem with CURRENT_ROLE() when used in a
prepared statement. During prepared statement creation we used to set
the string_value of the function to the current role as well as the
null_value flag. During execution, if CURRENT_ROLE was not null, the
null_value flag was never set to not-null during fix_fields.

Item_func_current_user however can never be NULL so it did not show this
problem in a view before. At the same time, the CURRENT_USER() can not
be changed between prepared statement execution and creation so the
implementation where the value is stored during fix_fields is
sufficient.

Note also that DATABASE() function behaves differently during prepared
statements. See bug 25843 for details or commit
7e0ad09
  • Loading branch information
cvicentiu committed Jun 15, 2017
1 parent 34da3be commit f0ad934
Show file tree
Hide file tree
Showing 8 changed files with 345 additions and 9 deletions.
62 changes: 62 additions & 0 deletions mysql-test/r/view.result
Original file line number Diff line number Diff line change
Expand Up @@ -5944,6 +5944,68 @@ use_case_id InitialDeadline
10 2015-12-18
drop view v1;
drop table t1;
#
# MDEV-12666: CURRENT_ROLE() and DATABASE() does not work in a view
#
# DATABASE() fails only when the initial view creation features a NULL
# default database.
#
# CREATE, USE and DROP database so that we have no "default" database.
#
CREATE DATABASE temporary;
USE temporary;
DROP DATABASE temporary;
SELECT DATABASE();
DATABASE()
NULL
CREATE VIEW test.v_no_db AS SELECT DATABASE() = 'temporary_two';
SHOW CREATE VIEW test.v_no_db;
View Create View character_set_client collation_connection
v_no_db CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `test`.`v_no_db` AS select (database() = 'temporary_two') AS `DATABASE() = 'temporary_two'` latin1 latin1_swedish_ci
PREPARE prepared_no_database FROM "SELECT DATABASE() = 'temporary_two'";
#
# All statements should return NULL
#
EXECUTE prepared_no_database;
DATABASE() = 'temporary_two'
NULL
SELECT DATABASE() = 'temporary_two';
DATABASE() = 'temporary_two'
NULL
SELECT * FROM test.v_no_db;
DATABASE() = 'temporary_two'
NULL
CREATE DATABASE temporary_two;
USE temporary_two;
CREATE VIEW test.v_with_db AS SELECT DATABASE() = 'temporary_two';
PREPARE prepared_with_database FROM "SELECT DATABASE() = 'temporary_two'";
#
# All statements should return 1;
#
SELECT DATABASE() = 'temporary_two';
DATABASE() = 'temporary_two'
1
SELECT * FROM test.v_no_db;
DATABASE() = 'temporary_two'
1
SELECT * FROM test.v_with_db;
DATABASE() = 'temporary_two'
1
EXECUTE prepared_with_database;
DATABASE() = 'temporary_two'
1
#
# Prepared statements maintain default database to be the same
# during on creation so this should return NULL still.
# See MySQL bug #25843
#
EXECUTE prepared_no_database;
DATABASE() = 'temporary_two'
NULL
DROP DATABASE temporary_two;
DROP VIEW test.v_no_db;
DROP VIEW test.v_with_db;
USE test;
# -----------------------------------------------------------------
# -- End of 10.0 tests.
# -----------------------------------------------------------------
Expand Down
103 changes: 103 additions & 0 deletions mysql-test/suite/roles/current_role_view-12666.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
CREATE USER has_role@'localhost';
GRANT ALL PRIVILEGES ON *.* TO has_role@'localhost';
CREATE ROLE test_role;
GRANT test_role TO has_role@'localhost';
CREATE USER no_role@'localhost';
GRANT ALL PRIVILEGES ON *.* TO no_role@'localhost';
CREATE TABLE view_role_test (
id int primary key,
role_name varchar(50)
);
INSERT INTO view_role_test VALUES (1, 'test_role');
#
# Use the same logic for stored procedures.
#
PREPARE prepared_no_current_role FROM "SELECT * from view_role_test WHERE role_name = CURRENT_ROLE()";
#
# Creating a view with no CURRENT_ROLE() set and one with CURRENT_ROLE()
# set. Both should produce the same SHOW CREATE VIEW output.
#
CREATE
DEFINER = no_role@localhost
SQL SECURITY INVOKER
VIEW v_view_role_test_no_current_role
AS
SELECT * FROM view_role_test WHERE role_name = CURRENT_ROLE();
SHOW CREATE VIEW v_view_role_test_no_current_role;
View Create View character_set_client collation_connection
v_view_role_test_no_current_role CREATE ALGORITHM=UNDEFINED DEFINER=`no_role`@`localhost` SQL SECURITY INVOKER VIEW `v_view_role_test_no_current_role` AS select `view_role_test`.`id` AS `id`,`view_role_test`.`role_name` AS `role_name` from `view_role_test` where (`view_role_test`.`role_name` = current_role()) latin1 latin1_swedish_ci
#
# No values should be returned
#
EXECUTE prepared_no_current_role;
id role_name
SELECT * FROM view_role_test WHERE role_name = CURRENT_ROLE();
id role_name
SELECT * FROM v_view_role_test_no_current_role;
id role_name
#
# Now let's set the role. Create identical views as before. See if
# their behaviour is different. It should not be.
#
SET ROLE test_role;
SELECT CURRENT_USER();
CURRENT_USER()
root@localhost
SELECT CURRENT_ROLE();
CURRENT_ROLE()
test_role
#
# Create the VIEW and prepared Statement with a CURRENT_ROLE() set.
#
CREATE
DEFINER = no_role@localhost
SQL SECURITY INVOKER
VIEW v_view_role_test_with_current_role
AS
SELECT * FROM view_role_test WHERE role_name = CURRENT_ROLE();
PREPARE prepared_with_current_role FROM "SELECT * from view_role_test WHERE role_name = CURRENT_ROLE()";
SHOW CREATE VIEW v_view_role_test_with_current_role;
View Create View character_set_client collation_connection
v_view_role_test_with_current_role CREATE ALGORITHM=UNDEFINED DEFINER=`no_role`@`localhost` SQL SECURITY INVOKER VIEW `v_view_role_test_with_current_role` AS select `view_role_test`.`id` AS `id`,`view_role_test`.`role_name` AS `role_name` from `view_role_test` where (`view_role_test`.`role_name` = current_role()) latin1 latin1_swedish_ci
#
# Values should be returned for all select statements as we do have
# a CURRENT_ROLE() active;
#
EXECUTE prepared_no_current_role;
id role_name
1 test_role
EXECUTE prepared_with_current_role;
id role_name
1 test_role
SELECT * FROM view_role_test WHERE role_name = CURRENT_ROLE();
id role_name
1 test_role
SELECT * FROM v_view_role_test_no_current_role;
id role_name
1 test_role
SELECT * FROM v_view_role_test_with_current_role;
id role_name
1 test_role
SET ROLE NONE;
#
# No values should be returned for all select statements as we do not have
# a CURRENT_ROLE() active;
#
EXECUTE prepared_no_current_role;
id role_name
EXECUTE prepared_with_current_role;
id role_name
SELECT * FROM view_role_test WHERE role_name = CURRENT_ROLE();
id role_name
SELECT * FROM v_view_role_test_no_current_role;
id role_name
SELECT * FROM v_view_role_test_with_current_role;
id role_name
DROP USER has_role@'localhost';
DROP USER no_role@'localhost';
DROP ROLE test_role;
DROP table view_role_test;
DROP VIEW v_view_role_test_no_current_role;
DROP VIEW v_view_role_test_with_current_role;
DROP PREPARE prepared_no_current_role;
DROP PREPARE prepared_with_current_role;
102 changes: 102 additions & 0 deletions mysql-test/suite/roles/current_role_view-12666.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#
# MDEV-12666 CURRENT_ROLE() does not work in a view
#
--source include/not_embedded.inc

CREATE USER has_role@'localhost';
GRANT ALL PRIVILEGES ON *.* TO has_role@'localhost';

CREATE ROLE test_role;
GRANT test_role TO has_role@'localhost';

CREATE USER no_role@'localhost';
GRANT ALL PRIVILEGES ON *.* TO no_role@'localhost';

CREATE TABLE view_role_test (
id int primary key,
role_name varchar(50)
);

INSERT INTO view_role_test VALUES (1, 'test_role');

--echo #
--echo # Use the same logic for stored procedures.
--echo #
PREPARE prepared_no_current_role FROM "SELECT * from view_role_test WHERE role_name = CURRENT_ROLE()";

--echo #
--echo # Creating a view with no CURRENT_ROLE() set and one with CURRENT_ROLE()
--echo # set. Both should produce the same SHOW CREATE VIEW output.
--echo #
CREATE
DEFINER = no_role@localhost
SQL SECURITY INVOKER
VIEW v_view_role_test_no_current_role
AS
SELECT * FROM view_role_test WHERE role_name = CURRENT_ROLE();

SHOW CREATE VIEW v_view_role_test_no_current_role;


--echo #
--echo # No values should be returned
--echo #
EXECUTE prepared_no_current_role;
SELECT * FROM view_role_test WHERE role_name = CURRENT_ROLE();
SELECT * FROM v_view_role_test_no_current_role;

--echo #
--echo # Now let's set the role. Create identical views as before. See if
--echo # their behaviour is different. It should not be.
--echo #
SET ROLE test_role;

SELECT CURRENT_USER();
SELECT CURRENT_ROLE();

--echo #
--echo # Create the VIEW and prepared Statement with a CURRENT_ROLE() set.
--echo #
CREATE
DEFINER = no_role@localhost
SQL SECURITY INVOKER
VIEW v_view_role_test_with_current_role
AS
SELECT * FROM view_role_test WHERE role_name = CURRENT_ROLE();

PREPARE prepared_with_current_role FROM "SELECT * from view_role_test WHERE role_name = CURRENT_ROLE()";

SHOW CREATE VIEW v_view_role_test_with_current_role;


--echo #
--echo # Values should be returned for all select statements as we do have
--echo # a CURRENT_ROLE() active;
--echo #
EXECUTE prepared_no_current_role;
EXECUTE prepared_with_current_role;
SELECT * FROM view_role_test WHERE role_name = CURRENT_ROLE();
SELECT * FROM v_view_role_test_no_current_role;
SELECT * FROM v_view_role_test_with_current_role;

SET ROLE NONE;
--echo #
--echo # No values should be returned for all select statements as we do not have
--echo # a CURRENT_ROLE() active;
--echo #
EXECUTE prepared_no_current_role;
EXECUTE prepared_with_current_role;
SELECT * FROM view_role_test WHERE role_name = CURRENT_ROLE();
SELECT * FROM v_view_role_test_no_current_role;
SELECT * FROM v_view_role_test_with_current_role;


DROP USER has_role@'localhost';
DROP USER no_role@'localhost';
DROP ROLE test_role;

DROP table view_role_test;
DROP VIEW v_view_role_test_no_current_role;
DROP VIEW v_view_role_test_with_current_role;
DROP PREPARE prepared_no_current_role;
DROP PREPARE prepared_with_current_role;
49 changes: 49 additions & 0 deletions mysql-test/t/view.test
Original file line number Diff line number Diff line change
Expand Up @@ -5814,6 +5814,55 @@ SELECT * FROM v1 where use_case_id = 10;
drop view v1;
drop table t1;

--echo #
--echo # MDEV-12666: CURRENT_ROLE() and DATABASE() does not work in a view
--echo #
--echo # DATABASE() fails only when the initial view creation features a NULL
--echo # default database.
--echo #
--echo # CREATE, USE and DROP database so that we have no "default" database.
--echo #
CREATE DATABASE temporary;
USE temporary;
DROP DATABASE temporary;
SELECT DATABASE();

CREATE VIEW test.v_no_db AS SELECT DATABASE() = 'temporary_two';
SHOW CREATE VIEW test.v_no_db;
PREPARE prepared_no_database FROM "SELECT DATABASE() = 'temporary_two'";

--echo #
--echo # All statements should return NULL
--echo #
EXECUTE prepared_no_database;
SELECT DATABASE() = 'temporary_two';
SELECT * FROM test.v_no_db;

CREATE DATABASE temporary_two;
USE temporary_two;
CREATE VIEW test.v_with_db AS SELECT DATABASE() = 'temporary_two';
PREPARE prepared_with_database FROM "SELECT DATABASE() = 'temporary_two'";

--echo #
--echo # All statements should return 1;
--echo #
SELECT DATABASE() = 'temporary_two';
SELECT * FROM test.v_no_db;
SELECT * FROM test.v_with_db;
EXECUTE prepared_with_database;

--echo #
--echo # Prepared statements maintain default database to be the same
--echo # during on creation so this should return NULL still.
--echo # See MySQL bug #25843
--echo #
EXECUTE prepared_no_database;

DROP DATABASE temporary_two;
DROP VIEW test.v_no_db;
DROP VIEW test.v_with_db;
USE test;

--echo # -----------------------------------------------------------------
--echo # -- End of 10.0 tests.
--echo # -----------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions sql/item.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,7 @@ Item *Item::const_charset_converter(CHARSET_INFO *tocs,
DBUG_ASSERT(fixed);
StringBuffer<64>tmp;
String *s= val_str(&tmp);

if (!s)
return new Item_null((char *) func_name, tocs);

Expand Down
2 changes: 1 addition & 1 deletion sql/item.h
Original file line number Diff line number Diff line change
Expand Up @@ -1476,7 +1476,7 @@ class Item {
virtual Item *expr_cache_insert_transformer(uchar *thd_arg) { return this; }
virtual bool expr_cache_is_needed(THD *) { return FALSE; }
virtual Item *safe_charset_converter(CHARSET_INFO *tocs);
bool needs_charset_converter(uint32 length, CHARSET_INFO *tocs)
bool needs_charset_converter(uint32 length, CHARSET_INFO *tocs) const
{
/*
This will return "true" if conversion happens:
Expand Down
Loading

0 comments on commit f0ad934

Please sign in to comment.