How do I dynamically pass a role name to IS_ROLE_IN_SESSION? - snowflake-cloud-data-platform

I'm setting up a masking policy that can be bypassed if the user's current role inherits from a specified role. This can be easily done with the function IS_ROLE_IN_SESSION. The challenge is I want to be able to change the specified role without having to modify the masking policy.
These examples assume the user is using a role other than ACCOUNTADMIN.
I got it to work with a session variable, but this is not secure since I can't control access to session variables:
create or replace table tab as select * from values('personal value') d (data);
set unmask_role = 'PUBLIC';
alter table tab modify column data unset masking policy;
create or replace masking policy hide as (d varchar) returns varchar ->
iff(is_role_in_session($unmask_role),d,replace(d,'personal value','hidden'));
alter table tab modify column data set masking policy hide;
set unmask_role = 'PUBLIC';
select * from tab;
-- Works as expected: shows personal value
set unmask_role = 'ACCOUNTADMIN';
select * from tab;
-- Works as expected: shows hidden
Ideally I would provide the role in a table since I can control access to the contents of a table but I can't get past these errors:
create or replace table unmask_role_tab as select 'PUBLIC' role;
alter table tab modify column data unset masking policy;
create or replace masking policy hide as (d varchar) returns varchar ->
iff(is_role_in_session((select role from unmask_role_tab)),d,replace(d,'personal value','hidden'));
alter table tab modify column data set masking policy hide;
select * from tab;
-- Fails with error:
-- SQL compilation error: error line Check Arg at position 0 invalid argument for function [IS_ROLE_IN_SESSION] unexpected argument [(SELECT UNMASK_ROLE_TAB.ROLE AS "ROLE" FROM UNMASK_ROLE_TAB AS UNMASK_ROLE_TAB)] at position 0,
alter table tab modify column data unset masking policy;
create or replace masking policy hide as (d varchar) returns varchar ->
(select iff(is_role_in_session(role),d,replace(d,'personal value','hidden')) from unmask_role_tab);
alter table tab modify column data set masking policy hide;
select * from tab;
-- Fails with error:
-- SQL compilation error: error line Check Arg at position 0 invalid argument for function [IS_ROLE_IN_SESSION] unexpected argument [UNMASK_ROLE_TAB.ROLE] at position 0,

It is an interesting question as it boils down to how to pass a "non-static" value to function that requires string_literal
IS_ROLE_IN_SESSION
is_role_in_session( '<string_literal>' )
Using view instead of table(if new entries has to be added then view defintion has to be updated, without changing masking policy definition):
create or replace table tab as select * from values('personal value') d (data);
CREATE OR REPLACE VIEW unmask_role_view
AS
SELECT 1 AS col WHERE IS_ROLE_IN_SESSION('PUBLIC')
-- UNION SELECT 1 AS col WHERE IS_ROLE_IN_SESSION('...') -- more entries
;
create or replace masking policy hide as (d varchar) returns varchar ->
case when exists(SELECT 1 FROM unmask_role_view) then d
else replace(d,'personal value','hidden')
end;
alter table tab modify column data set masking policy hide;
select * from tab;
A solution that requires defining all roles that should have access to data. It has one advantage though the roles are listed explicitly. One of the drawbacks is maintenance of this table.
create or replace table tab as select * from values('personal value') d (data);
create or replace table unmask_role_tab as select 'PUBLIC' role;
-- here we compare against CURRENT_ROLE
-- so we need all roles that have access to masked data
create or replace masking policy hide as (d varchar) returns varchar ->
case when exists(SELECT 1 FROM unmask_role_tab u WHERE u.role = CURRENT_ROLE()) then d
else replace(d,'personal value','hidden')
end;
alter table tab modify column data set masking policy hide;
select * from tab;
CREATE MASKING POLICY
CREATE [ OR REPLACE ] MASKING POLICY [ IF NOT EXISTS ] <name> AS
(VAL <data_type>) RETURNS <data_type> -> <expression_ON_VAL>
You can use:
Conditional Expression Functions
Context Functions,
and UDFs to write the SQL expression.
Attempt 1: Standard call
SELECT IS_ROLE_IN_SESSION(u.role) FROM unmask_role_tab u;
-- SQL compilation error: error line Check Arg at position 0 invalid argument
-- for function [IS_ROLE_IN_SESSION] unexpected argument [U.ROLE] at position 0
SELECT IS_ROLE_IN_SESSION(u.role::STRING) FROM unmask_role_tab u;
-- SQL compilation error: error line Check Arg at position 0 invalid argument
-- for function [IS_ROLE_IN_SESSION] unexpected argument [U.ROLE] at position 0
Attempt 2: Create UDF(executiing build SQL is not available)
CREATE OR REPLACE FUNCTION role_check(role_name STRING)
RETURNS boolean
LANGUAGE JAVASCRIPT
AS
$$
var res = snowflake.createStatement({sqlText: 'SELECT IS_ROLE_IN_SESSION(:1)'
, binds:[ROLE_NAME]}).execute()
res.next();
return res.getColumnValue(1);
$$;
SELECT role_check(u.role) FROM unmask_role_tab u;
-- JavaScript execution error: Uncaught ReferenceError:
-- snowflake is not defined in ROLE_CHECK
Attempt 3 SQL UDF(same error like with direct call
CREATE OR REPLACE FUNCTION role_check(role_name STRING)
RETURNS BOOLEAN
LANGUAGE SQL
AS $$
IS_ROLE_IN_SESSION(ROLE_NAME)
$$;
SELECT *, role_check(role) FROM unmask_role_tab;
-- SQL compilation error: error line Check Arg at position 0 invalid argument
-- for function [IS_ROLE_IN_SESSION] unexpected argument [UNMASK_ROLE_TAB.ROLE]
Attempt 4 User-Defined stored procedure:
CREATE OR REPLACE PROCEDURE role_check_proc(role_name STRING)
RETURNS boolean
LANGUAGE JAVASCRIPT
AS
$$
var res = snowflake.createStatement({sqlText: 'SELECT IS_ROLE_IN_SESSION(:1)'
,binds:[ROLE_NAME]}).execute()
res.next();
return res.getColumnValue(1);
$$;
CALL role_check_proc((SELECT role FROM unmask_role_tab));
-- TRUE
-- Works only if table contains single entry
It returns result but stored procedure call cannot be used in masking policy/SQL query call.
Wrapping them with function will not work as it is not possible to call SP from function.
CREATE OR REPLACE FUNCTION role_check(role_name STRING)
RETURNS BOOLEAN
LANGUAGE SQL
AS $$
CALL role_check_proc(ROLE_NAME::STRING)
$$;

Related

How can I add defined parameter to (NEXT VALUE FOR) command?

CREATE OR ALTER FUNCTION sso.FINDSEQVALUE
(#sequence_text text)
RETURNS int
AS
BEGIN
DECLARE #value int;
DECLARE #sequence_value nvarchar(150);
SELECT #sequence_value = CAST(#sequence_text AS nvarchar(150));
SELECT #value = NEXT VALUE FOR #sequence_value;
RETURN #value;
END;
I have a problem. I have created a function on SQL Server and I defined the parameter as you can see. But I cannot add the this command #sequence_value after NEXT VALUE FOR command and I am getting an error.
Incorrect syntax near '#sequence_value'
Somebody can say that "You can use (SELECT NEXT VALUE FOR [SEQUENCE])". But I need this function because of there are two different database on my project. I need same function for databases. In addition function parameter need to be text.
What should I do?

Snowflake - CREATE table with a date variable in its name

I would like to create a table in snowflake, appending the date to the end of its name. What is the best way to do that?
original table = "DB"."SCHEMA"."CLONEME"
desired new table = "DB"."SCHEMA"."CLONEME_20200812BKP"
Tried setting the date variables, but it didn't work.
First attempt:
set var1= (SELECT TO_CHAR(DATE_TRUNC('DAY',CONVERT_TIMEZONE('UTC', CURRENT_DATE())),'YYYYMMDD'));
set var2 = concat('DB.SCHEMA.CLONEME_',$var1);
create table $var2 clone DB.SCHEMA.CLONEME;
-- and got the following error:
-- SQL compilation error: syntax error line 1 at position 13 unexpected '$var2'.
I'd recommend using the IDENTIFIER function:
https://docs.snowflake.com/en/sql-reference/identifier-literal.html
Example:
CREATE OR REPLACE TABLE CLONEME(
src_string VARCHAR(20));
INSERT INTO CLONEME
VALUES('JKNHJYGHTFGRTYGHJ'), ('ABC123'), (null), ('0123456789');
set var1= (SELECT TO_CHAR(DATE_TRUNC('DAY',CONVERT_TIMEZONE('UTC', CURRENT_DATE())),'YYYYMMDD'));
set var2 = concat('CLONEME_',$var1);
SELECT getvariable('VAR1'), getvariable('VAR2');
--20200812 CLONEME_20200812
create table identifier($var2) clone CLONEME;
--Table CLONEME_20200812 successfully created

Find different in two jsonb , PostgreSQL trigger function

user
CREATE TABLE IF NOT EXISTS "user"(
"id" SERIAL NOT NULL,
"create_date" timestamp without time zone NOT NULL,
"last_modified_date" timestamp without time zone,
"last_modified_by_user_id" integer,
"status" integer NOT NULL,
PRIMARY KEY ("id")
);
user_track
CREATE TABLE IF NOT EXISTS "user_track"(
"date" timestamp without time zone,
"by_user_id" integer,
"origin_value" jsonb,
"new_value" jsonb
);
function
CREATE OR REPLACE FUNCTION user_track_insert()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO user_track(date, new_value)
VALUES(NEW.create_date, row_to_json(NEW)::jsonb);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION user_track_delete()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO user_track(date, by_user_id, origin_value)
VALUES(create_date, user_id, row_to_json(OLD)::jsonb);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION user_track_update()
RETURNS TRIGGER AS $$
DECLARE
js_new jsonb := row_to_json(NEW)::jsonb;
js_old jsonb := row_to_json(OLD)::jsonb;
BEGIN
INSERT INTO user_track(date, by_user_id, origin_value, new_value)
VALUES(NEW.create_date, OLD.id, js_old - js_new, js_new - js_old);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
trigger
CREATE TRIGGER user_track_insert_trigger AFTER INSERT ON "user"
FOR EACH ROW EXECUTE PROCEDURE user_track_insert();
CREATE TRIGGER user_track_delete_trigger AFTER DELETE ON "user"
FOR EACH ROW EXECUTE PROCEDURE user_track_delete();
CREATE TRIGGER user_track_update_trigger AFTER UPDATE ON "user"
FOR EACH ROW EXECUTE PROCEDURE user_track_update();
When I do update in pgadmin got error:
ERROR: operator does not exist: jsonb - jsonb
LINE 2: VALUES(NEW.create_date, OLD.id, js_old - js_new, js_n...
^
ERROR: operator does not exist: jsonb - jsonb
SQL state: 42883
Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts.
Context: PL/pgSQL function user_track_update() line 6 at SQL statement
I only want to save json data present changed column/value not whole row all column.
I can't find jsonb - jsonb in document, so seems there is no such operator ...
How to pass parameter into user_track_delete function, for example if I want to some other use.id who execute this action ?
PostgreSQL 9.5.2

how to call a pg function with libpq and get the param value

ALL
I have a postgresql function,so this:
CREATE OR REPLACE FUNCTION query_callouts(
INOUT io_cursor_ref refcursor,
INOUT opstatus integer,
INOUT errtext character varying)
RETURNS record AS
$BODY$
DECLARE
BEGIN
OPEN $1 FOR
SELECT tablename FROM pg_tables limit 10;
--SELECT * from call_out_numbers;
RETURN;
Exception
When Others Then
GET STACKED DIAGNOSTICS opstatus = RETURNED_SQLSTATE,
errText = MESSAGE_TEXT;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION query_callouts(refcursor, integer, character varying)
OWNER TO postgres;
I want to known ,how to use libpq in my c codes to visit the function - query_callouts and get the param io_cursor_ref and opstatus and errtext?
You can call the function just like you are executing any query:
select * from query_callouts('mycur', 0, '');
io_cursor_ref | opstatus | errtext
---------------+----------+---------
mycur | 0 |
(1 row)
opstatus and errtext would be set to the appropriate values if an exception occurs.
io_cursor_ref contains the name you have passed to the function.
Internally, a refcursor value is simply the string name of a so-called
portal containing the active query for the cursor. This name can be
passed around, assigned to other refcursor variables, and so on,
without disturbing the portal.
Note, you can use a refcursor only within a transaction.
All portals are implicitly closed at transaction end. Therefore a
refcursor value is usable to reference an open cursor only until the
end of the transaction.
You can use explicit transaction:
begin;
select * from query_callouts('mycur', 0, '');
fetch all in mycur;
-- save or show the query result
-- and finally
commit;
or use mycur inside a function.
The quotes are from the documentation.
It's just like any other query.
SELECT * FROM query_callouts('cursorname', 4, 'msg')
I suspect some if not all of your parameters should be OUT parameters not INOUT parameters, though. You don't ever use the input value of errtext at least.

Need to eliminate the last 4 characters in a string(varchar)

i am using a stored procedure, where it is taking policy number as parameter which is varchar. I need to eliminate the last 4 characters of the policy number when we retrive from the tables. But the data for policy numbers is not consistent, so I am confused how to use the logic for this. The sample policy numbers are:
KSDRE0021-000
APDRE-10-21-000
KSDRE0021
APDRE-10-21
These are four formats where policies are there in our tables.For some policies there is no tailing end '-000', so that is the challenging part. Now, I need to eliminate the tailing part '-000' from the policies when I retrieve the data from tables.
This is the sample code, which is pulling the policy data from tables.
Create Proc usp.dbo.policydataSP #policy_num varchar(18)
AS
Begin
Select * from policy_table pt
where pt.policy_num = #policy_num
End
STEP 1: Create a User Defined Function to normalize a policy number.
create function dbo.normalize_policy_num
(#policy_num varchar(100))
returns varchar(100)
as
begin
-- replace trailing digits
if (#policy_num like '%-[0-9][0-9][0-9]')
set #policy_num = left(#policy_num, len(#policy_num) - 4)
-- replace remaining hyphens
set #policy_num = replace(#policy_num, '-', '')
return #policy_num
end
What this essentially doing is stripping off the trailing '-000' from policy numbers that contain the pattern, then removing remaining hyphens. This function seems to work on your supplied policy numbers:
-- returns: KSDRE0021
select dbo.normalize_policy_num('KSDRE0021-000')
-- returns: APDRE1021
select dbo.normalize_policy_num('APDRE-10-21-000')
-- returns: KSDRE0021
select dbo.normalize_policy_num('KSDRE0021')
-- returns: APDRE1021
select dbo.normalize_policy_num('APDRE-10-21')
STEP 2: Modify your SP as follows:
create proc usp.dbo.policydataSP
#policy_num varchar(18)
as
begin
select
dbo.normalize_policy_num(pt.policy_num) as normalized_policy_num,
pt.*
from policy_table pt
where dbo.normalize_policy_num(#policy_num) = dbo.normalize_policy_num(pt.policy_num)
Note: If you are able to modify the table schema, you could add a persisted computed column using the UDF specified above. If you add an index to it, queries will run much faster. However, there will be some penalty for inserts, so there is a trade-off.
this is probably your best bet. Match the policy number up to the length of the requested parameter:
Create Proc usp.dbo.policydataSP
#policy_num varchar(18)
AS
Begin
Select * from policy_table pt where LEFT(len(#policy_num),pt.policy_num) = #policy_num
End
If you only want to strip -000 when returning results:
select case right(policy_num, 4)
when '-000' then left(policy_num, len(policy_num) - 4)
else policy_num end as policy_num
from policy_table pt
where pt.policy_num = #policy_num
If you want to strip any 3-digit value following a dash:
select case when policy_num like '%-[0-9][0-9][0-9]' then left(policy_num, len(policy_num) - 4)
else policy_num end as policy_num
from policy_table pt
where pt.policy_num = #policy_num

Resources