diff --git a/contrib/babelfishpg_tsql/sql/sys_function_helpers.sql b/contrib/babelfishpg_tsql/sql/sys_function_helpers.sql index b10411e748c..da0aa1d21f3 100644 --- a/contrib/babelfishpg_tsql/sql/sys_function_helpers.sql +++ b/contrib/babelfishpg_tsql/sql/sys_function_helpers.sql @@ -11159,7 +11159,7 @@ DECLARE counter int; cur_pos int; BEGIN - lower_object_name = pg_catalog.lower(PG_CATALOG.rtrim(name)); + lower_object_name = pg_catalog.lower(name); counter = 1; cur_pos = babelfish_get_name_delimiter_pos(lower_object_name); @@ -11171,7 +11171,7 @@ BEGIN RETURN; END IF; - names[counter] = babelfish_remove_delimiter_pair(PG_CATALOG.rtrim(PG_CATALOG.left(lower_object_name, cur_pos - 1))); + names[counter] = babelfish_remove_delimiter_pair(PG_CATALOG.left(lower_object_name, cur_pos - 1)); -- invalid name IF names[counter] IS NULL THEN @@ -11198,7 +11198,7 @@ BEGIN END CASE; -- Assign each name accordingly - object_name = sys.babelfish_truncate_identifier(babelfish_remove_delimiter_pair(PG_CATALOG.rtrim(lower_object_name))); + object_name = sys.babelfish_truncate_identifier(babelfish_remove_delimiter_pair(lower_object_name)); END; $$ LANGUAGE plpgsql diff --git a/contrib/babelfishpg_tsql/sql/sys_functions.sql b/contrib/babelfishpg_tsql/sql/sys_functions.sql index e7909cb7f68..09223524dab 100644 --- a/contrib/babelfishpg_tsql/sql/sys_functions.sql +++ b/contrib/babelfishpg_tsql/sql/sys_functions.sql @@ -2994,7 +2994,7 @@ BEGIN -- Lower-case to avoid case issues, remove trailing whitespace to match SQL SERVER behavior -- Objects created in Babelfish are stored in lower-case in pg_class/pg_proc - cs_as_securable = pg_catalog.lower(PG_CATALOG.rtrim(cs_as_securable)); + cs_as_securable = pg_catalog.lower(cs_as_securable); cs_as_securable_class = pg_catalog.lower(PG_CATALOG.rtrim(cs_as_securable_class)); cs_as_permission = pg_catalog.lower(PG_CATALOG.rtrim(cs_as_permission)); cs_as_sub_securable = pg_catalog.lower(PG_CATALOG.rtrim(cs_as_sub_securable)); diff --git a/contrib/babelfishpg_tsql/sql/upgrades/babelfishpg_tsql--5.4.0--5.5.0.sql b/contrib/babelfishpg_tsql/sql/upgrades/babelfishpg_tsql--5.4.0--5.5.0.sql index 98e4e16f020..2da753def6d 100644 --- a/contrib/babelfishpg_tsql/sql/upgrades/babelfishpg_tsql--5.4.0--5.5.0.sql +++ b/contrib/babelfishpg_tsql/sql/upgrades/babelfishpg_tsql--5.4.0--5.5.0.sql @@ -45,3 +45,329 @@ $$; CALL sys.analyze_babelfish_catalogs(); -- Reset search_path to not affect any subsequent scripts SELECT set_config('search_path', trim(leading 'sys, ' from current_setting('search_path')), false); + + +-- valid names are db_name.schema_name.object_name or schema_name.object_name or object_name +CREATE OR REPLACE FUNCTION sys.babelfish_split_object_name( + name TEXT, + OUT db_name TEXT, + OUT schema_name TEXT, + OUT object_name TEXT) +AS $$ +DECLARE + lower_object_name text; + names text[2]; + counter int; + cur_pos int; +BEGIN + lower_object_name = pg_catalog.lower(name); + + counter = 1; + cur_pos = babelfish_get_name_delimiter_pos(lower_object_name); + + -- Parse user input into names split by '.' + WHILE cur_pos > 0 LOOP + IF counter > 3 THEN + -- Too many names provided + RETURN; + END IF; + + names[counter] = babelfish_remove_delimiter_pair(PG_CATALOG.left(lower_object_name, cur_pos - 1)); + + -- invalid name + IF names[counter] IS NULL THEN + RETURN; + END IF; + + lower_object_name = substring(lower_object_name from cur_pos + 1); + counter = counter + 1; + cur_pos = babelfish_get_name_delimiter_pos(lower_object_name); + END LOOP; + + CASE counter + WHEN 1 THEN + db_name = NULL; + schema_name = NULL; + WHEN 2 THEN + db_name = NULL; + schema_name = sys.babelfish_truncate_identifier(names[1]); + WHEN 3 THEN + db_name = sys.babelfish_truncate_identifier(names[1]); + schema_name = sys.babelfish_truncate_identifier(names[2]); + ELSE + RETURN; + END CASE; + + -- Assign each name accordingly + object_name = sys.babelfish_truncate_identifier(babelfish_remove_delimiter_pair(lower_object_name)); +END; +$$ +LANGUAGE plpgsql +STABLE; + +CREATE OR REPLACE FUNCTION sys.has_perms_by_name( + securable SYS.SYSNAME, + securable_class SYS.NVARCHAR(60), + permission SYS.SYSNAME, + sub_securable SYS.SYSNAME DEFAULT NULL, + sub_securable_class SYS.NVARCHAR(60) DEFAULT NULL +) +RETURNS integer +LANGUAGE plpgsql +STABLE +AS $$ +DECLARE + db_name text COLLATE sys.database_default; + bbf_schema_name text COLLATE sys.database_default; + pg_schema text COLLATE sys.database_default; + implied_dbo_permissions boolean; + fully_supported boolean; + is_cross_db boolean := false; + object_name text COLLATE sys.database_default; + database_id smallint; + namespace_id oid; + userid oid; + object_type text; + function_signature text; + qualified_name text; + return_value integer; + cs_as_securable text COLLATE "C" := securable; + cs_as_securable_class text COLLATE "C" := securable_class; + cs_as_permission text COLLATE "C" := permission; + cs_as_sub_securable text COLLATE "C" := sub_securable; + cs_as_sub_securable_class text COLLATE "C" := sub_securable_class; +BEGIN + return_value := NULL; + + -- Lower-case to avoid case issues, remove trailing whitespace to match SQL SERVER behavior + -- Objects created in Babelfish are stored in lower-case in pg_class/pg_proc + cs_as_securable = pg_catalog.lower(cs_as_securable); + cs_as_securable_class = pg_catalog.lower(PG_CATALOG.rtrim(cs_as_securable_class)); + cs_as_permission = pg_catalog.lower(PG_CATALOG.rtrim(cs_as_permission)); + cs_as_sub_securable = pg_catalog.lower(PG_CATALOG.rtrim(cs_as_sub_securable)); + cs_as_sub_securable_class = pg_catalog.lower(PG_CATALOG.rtrim(cs_as_sub_securable_class)); + + -- Assert that sub_securable and sub_securable_class are either both NULL or both defined + IF cs_as_sub_securable IS NOT NULL AND cs_as_sub_securable_class IS NULL THEN + RETURN NULL; + ELSIF cs_as_sub_securable IS NULL AND cs_as_sub_securable_class IS NOT NULL THEN + RETURN NULL; + -- If they are both defined, user must be evaluating column privileges. + -- Check that inputs are valid for column privileges: sub_securable_class must + -- be column, securable_class must be object, and permission cannot be any. + ELSIF cs_as_sub_securable_class IS NOT NULL + AND (cs_as_sub_securable_class != 'column' + OR cs_as_securable_class IS NULL + OR cs_as_securable_class != 'object' + OR cs_as_permission = 'any') THEN + RETURN NULL; + + -- If securable is null, securable_class must be null + ELSIF cs_as_securable IS NULL AND cs_as_securable_class IS NOT NULL THEN + RETURN NULL; + -- If securable_class is null, securable must be null + ELSIF cs_as_securable IS NOT NULL AND cs_as_securable_class IS NULL THEN + RETURN NULL; + END IF; + + IF cs_as_securable_class = 'server' THEN + -- SQL Server does not permit a securable_class value of 'server'. + -- securable_class should be NULL to evaluate server permissions. + RETURN NULL; + ELSIF cs_as_securable_class IS NULL THEN + -- NULL indicates a server permission. Set this variable so that we can + -- search for the matching entry in babelfish_has_perms_by_name_permissions + cs_as_securable_class = 'server'; + END IF; + + IF cs_as_sub_securable IS NOT NULL THEN + cs_as_sub_securable := babelfish_remove_delimiter_pair(cs_as_sub_securable); + IF cs_as_sub_securable IS NULL THEN + RETURN NULL; + END IF; + END IF; + + SELECT p.implied_dbo_permissions,p.fully_supported + INTO implied_dbo_permissions,fully_supported + FROM babelfish_has_perms_by_name_permissions p + WHERE p.securable_type = cs_as_securable_class AND p.permission_name = cs_as_permission; + + IF implied_dbo_permissions IS NULL OR fully_supported IS NULL THEN + -- Securable class or permission is not valid, or permission is not valid for given securable + RETURN NULL; + END IF; + + IF cs_as_securable_class = 'database' AND cs_as_securable IS NOT NULL THEN + db_name = babelfish_remove_delimiter_pair(cs_as_securable); + IF db_name IS NULL THEN + RETURN NULL; + ELSIF (SELECT COUNT(name) FROM sys.databases WHERE name = db_name COLLATE sys.database_default) != 1 THEN + RETURN 0; + END IF; + ELSIF cs_as_securable_class = 'schema' THEN + bbf_schema_name = babelfish_remove_delimiter_pair(cs_as_securable); + IF bbf_schema_name IS NULL THEN + RETURN NULL; + ELSIF (SELECT COUNT(nspname) FROM sys.babelfish_namespace_ext ext + WHERE ext.orig_name = bbf_schema_name COLLATE sys.database_default + AND ext.dbid = sys.db_id()) != 1 THEN + RETURN 0; + END IF; + END IF; + + IF fully_supported = 'f' AND + (SELECT orig_username FROM sys.babelfish_authid_user_ext WHERE rolname = CURRENT_USER) = 'dbo' THEN + RETURN CAST(implied_dbo_permissions AS integer); + ELSIF fully_supported = 'f' THEN + RETURN 0; + END IF; + + -- The only permissions that are fully supported belong to the OBJECT securable class. + -- The block above has dealt with all permissions that are not fully supported, so + -- if we reach this point we know the securable class is OBJECT. + SELECT s.db_name, s.schema_name, s.object_name INTO db_name, bbf_schema_name, object_name + FROM babelfish_split_object_name(cs_as_securable) s; + + -- Invalid securable name + IF object_name IS NULL OR object_name = '' THEN + RETURN NULL; + END IF; + + -- If schema was not specified, use the default + IF bbf_schema_name IS NULL OR bbf_schema_name = '' THEN + bbf_schema_name := sys.schema_name(); + END IF; + + database_id := ( + SELECT CASE + WHEN db_name IS NULL OR db_name = '' THEN (sys.db_id()) + ELSE (sys.db_id(db_name)) + END); + + IF database_id <> sys.db_id() THEN + is_cross_db = true; + END IF; + + userid := ( + SELECT CASE + WHEN is_cross_db THEN sys.suser_id() + ELSE sys.user_id() + END); + + -- Translate schema name from bbf to postgres, e.g. dbo -> master_dbo + pg_schema := (SELECT nspname + FROM sys.babelfish_namespace_ext ext + WHERE ext.orig_name = bbf_schema_name COLLATE sys.database_default + AND CAST(ext.dbid AS oid) = CAST(database_id AS oid)); + + IF pg_schema IS NULL THEN + -- Shared schemas like sys and pg_catalog do not exist in the table above. + -- These schemas do not need to be translated from Babelfish to Postgres + pg_schema := bbf_schema_name; + END IF; + + -- Surround with double-quotes to handle names that contain periods/spaces + qualified_name := PG_CATALOG.concat('"', pg_schema, '"."', object_name, '"'); + + SELECT oid INTO namespace_id FROM pg_catalog.pg_namespace WHERE nspname = pg_schema COLLATE sys.database_default; + + object_type := ( + SELECT CASE + WHEN cs_as_sub_securable_class = 'column' + THEN CASE + WHEN (SELECT count(a.attname) + FROM pg_attribute a + INNER JOIN pg_class c ON c.oid = a.attrelid + INNER JOIN pg_namespace s ON s.oid = c.relnamespace + WHERE + a.attname = cs_as_sub_securable COLLATE sys.database_default + AND c.relname = object_name COLLATE sys.database_default + AND s.nspname = pg_schema COLLATE sys.database_default + AND NOT a.attisdropped + AND (s.nspname IN (SELECT nspname FROM sys.babelfish_namespace_ext) OR s.nspname = 'sys') + -- r = ordinary table, i = index, S = sequence, t = TOAST table, v = view, m = materialized view, c = composite type, f = foreign table, p = partitioned table + AND c.relkind IN ('r', 'v', 'm', 'f', 'p') + AND a.attnum > 0) = 1 + THEN 'column' + ELSE NULL + END + + WHEN (SELECT count(relname) + FROM pg_catalog.pg_class + WHERE relname = object_name COLLATE sys.database_default + AND relnamespace = namespace_id) = 1 + THEN 'table' + + WHEN (SELECT count(proname) + FROM pg_catalog.pg_proc + WHERE proname = object_name COLLATE sys.database_default + AND pronamespace = namespace_id + AND prokind = 'f') = 1 + THEN 'function' + + WHEN (SELECT count(proname) + FROM pg_catalog.pg_proc + WHERE proname = object_name COLLATE sys.database_default + AND pronamespace = namespace_id + AND prokind = 'p') = 1 + THEN 'procedure' + ELSE NULL + END + ); + + -- Object was not found + IF object_type IS NULL THEN + RETURN 0; + END IF; + + -- Get signature for function-like objects + IF object_type IN('function', 'procedure') THEN + SELECT CAST(oid AS regprocedure) + INTO function_signature + FROM pg_catalog.pg_proc + WHERE proname = object_name COLLATE sys.database_default + AND pronamespace = namespace_id; + END IF; + + return_value := ( + SELECT CASE + WHEN cs_as_permission = 'any' THEN babelfish_has_any_privilege(userid, object_type, pg_schema, object_name) + + WHEN object_type = 'column' + THEN CASE + WHEN cs_as_permission IN('insert', 'delete', 'execute') THEN NULL + ELSE CAST(has_column_privilege(userid, qualified_name, cs_as_sub_securable, cs_as_permission) AS integer) + END + + WHEN object_type = 'table' + THEN CASE + WHEN cs_as_permission = 'execute' THEN 0 + ELSE CAST(has_table_privilege(userid, qualified_name, cs_as_permission) AS integer) + END + + WHEN object_type = 'function' + THEN CASE + WHEN cs_as_permission IN('select', 'execute') + THEN CAST(has_function_privilege(userid, function_signature, 'execute') AS integer) + WHEN cs_as_permission IN('update', 'insert', 'delete', 'references') + THEN 0 + ELSE NULL + END + + WHEN object_type = 'procedure' + THEN CASE + WHEN cs_as_permission = 'execute' + THEN CAST(has_function_privilege(userid, function_signature, 'execute') AS integer) + WHEN cs_as_permission IN('select', 'update', 'insert', 'delete', 'references') + THEN 0 + ELSE NULL + END + + ELSE NULL + END + ); + + RETURN return_value; + EXCEPTION WHEN OTHERS THEN RETURN NULL; +END; +$$; \ No newline at end of file diff --git a/test/JDBC/expected/sys-has_perms_by_name-trailing-space-vu-cleanup.out b/test/JDBC/expected/sys-has_perms_by_name-trailing-space-vu-cleanup.out new file mode 100644 index 00000000000..a3332d045f2 --- /dev/null +++ b/test/JDBC/expected/sys-has_perms_by_name-trailing-space-vu-cleanup.out @@ -0,0 +1,3 @@ +--cleanup databases +DROP DATABASE IF EXISTS [ blank space ]; +GO diff --git a/test/JDBC/expected/sys-has_perms_by_name-trailing-space-vu-prepare.out b/test/JDBC/expected/sys-has_perms_by_name-trailing-space-vu-prepare.out new file mode 100644 index 00000000000..c4ac294b656 --- /dev/null +++ b/test/JDBC/expected/sys-has_perms_by_name-trailing-space-vu-prepare.out @@ -0,0 +1,7 @@ +--drop databases if exists +DROP DATABASE IF EXISTS [ blank space ]; +GO + +--create databases +CREATE DATABASE [ blank space ]; +GO diff --git a/test/JDBC/expected/sys-has_perms_by_name-trailing-space-vu-verify.out b/test/JDBC/expected/sys-has_perms_by_name-trailing-space-vu-verify.out new file mode 100644 index 00000000000..234a478647d --- /dev/null +++ b/test/JDBC/expected/sys-has_perms_by_name-trailing-space-vu-verify.out @@ -0,0 +1,40 @@ +-- test blank space end of the database name +SELECT HAS_PERMS_BY_NAME(' blank space ','DATABASE','CREATE FUNCTION'); +GO +~~START~~ +int +1 +~~END~~ + + +SELECT HAS_PERMS_BY_NAME('[ blank space ]','DATABASE','CREATE FUNCTION'); +GO +~~START~~ +int +1 +~~END~~ + + +SELECT HAS_PERMS_BY_NAME('[ blank space]','DATABASE','CREATE FUNCTION'); +GO +~~START~~ +int +0 +~~END~~ + + +SELECT HAS_PERMS_BY_NAME('[ blank space ]','DATABASE','CREATE FUNCTION'); +GO +~~START~~ +int +0 +~~END~~ + + +SELECT HAS_PERMS_BY_NAME('[ blank space ]','DATABASE','CREATE FUNCTION'); +GO +~~START~~ +int +0 +~~END~~ + diff --git a/test/JDBC/expected/sys-has_perms_by_name-vu-verify.out b/test/JDBC/expected/sys-has_perms_by_name-vu-verify.out index 471b920c9ab..3e6b279b7ed 100644 --- a/test/JDBC/expected/sys-has_perms_by_name-vu-verify.out +++ b/test/JDBC/expected/sys-has_perms_by_name-vu-verify.out @@ -2725,7 +2725,7 @@ SELECT HAS_PERMS_BY_NAME('dbo.t_perms_by_name ','OBJECT ', 'SELECT ', ' GO ~~START~~ int -1 +0 ~~END~~ diff --git a/test/JDBC/input/functions/sys-has_perms_by_name-trailing-space-vu-cleanup.sql b/test/JDBC/input/functions/sys-has_perms_by_name-trailing-space-vu-cleanup.sql new file mode 100644 index 00000000000..ac4604b653d --- /dev/null +++ b/test/JDBC/input/functions/sys-has_perms_by_name-trailing-space-vu-cleanup.sql @@ -0,0 +1,3 @@ +--cleanup databases +DROP DATABASE IF EXISTS [ blank space ]; +GO \ No newline at end of file diff --git a/test/JDBC/input/functions/sys-has_perms_by_name-trailing-space-vu-prepare.sql b/test/JDBC/input/functions/sys-has_perms_by_name-trailing-space-vu-prepare.sql new file mode 100644 index 00000000000..612bc7152ad --- /dev/null +++ b/test/JDBC/input/functions/sys-has_perms_by_name-trailing-space-vu-prepare.sql @@ -0,0 +1,7 @@ +--drop databases if exists +DROP DATABASE IF EXISTS [ blank space ]; +GO + +--create databases +CREATE DATABASE [ blank space ]; +GO \ No newline at end of file diff --git a/test/JDBC/input/functions/sys-has_perms_by_name-trailing-space-vu-verify.sql b/test/JDBC/input/functions/sys-has_perms_by_name-trailing-space-vu-verify.sql new file mode 100644 index 00000000000..7b58783b511 --- /dev/null +++ b/test/JDBC/input/functions/sys-has_perms_by_name-trailing-space-vu-verify.sql @@ -0,0 +1,15 @@ +-- test blank space end of the database name +SELECT HAS_PERMS_BY_NAME(' blank space ','DATABASE','CREATE FUNCTION'); +GO + +SELECT HAS_PERMS_BY_NAME('[ blank space ]','DATABASE','CREATE FUNCTION'); +GO + +SELECT HAS_PERMS_BY_NAME('[ blank space]','DATABASE','CREATE FUNCTION'); +GO + +SELECT HAS_PERMS_BY_NAME('[ blank space ]','DATABASE','CREATE FUNCTION'); +GO + +SELECT HAS_PERMS_BY_NAME('[ blank space ]','DATABASE','CREATE FUNCTION'); +GO \ No newline at end of file