Snowflake - accessing case-senstive variable with $ - snowflake-cloud-data-platform

Snowlake allows to define variables:
SET var = 1;
SHOW VARIABLES;
/*
name value type comment
VAR 1 fixed
*/
We could define second "case-sensitive" version:
SET "var" = '9';
SHOW VARIABLES;
/*
name value type comment
VAR 1 fixed
var 9 text
*/
Now trying to access both variables:
SELECT $var;
--1
SELECT $VAR;
--1
SELECT $"var";
-- SQL compilation error: syntax error line 1 at position 7 unexpected '$'.
The only way I found to access "var" is by using:
SELECT GETVARIABLE('VAR'), GETVARIABLE('var');
/*
GETVARIABLE('VAR') GETVARIABLE('VAR')
1 9
*/
Is it possible to get 9 using $var_name syntax?

Related

How do I dynamically pass a role name to IS_ROLE_IN_SESSION?

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)
$$;

Snowflake with regular expression

Trying to run the below SQL in Snowflake:
SELECT fm_id ,
CASE
WHEN regexp_instr(ASSD,'...',1) > 0
THEN regexp_SUBSTR(ASSD,1,regexp_instr(ASSD,'...',1)-1)
ELSE ASSD
END ASSD
from
(SELECT a.fm_id,
listagg(a.STUID, '; ') within GROUP (
ORDER BY a.Fm_id, a.STUID ) ASSD
from stu_d a
where fm_id = 1222
group by a.fm_id
)
Getting error:
"Invalid parameter value: 0. Reason: Position must be positive"
seems it is failing at -1 or 0 value in above case statement.
What am I doing wrong?
Without seeing your table it's hard to say, but regexp_instr() will return 1 when the pattern is at the beginning of the string. Then, you subtract 1, and 0 is an invalid position argument to regexp_substr(). doc
Perhaps you intended to use SUBSTR not REGEX_SUBSTR()

Listagg for large data and included values in quotes

I would like to get all the type names of a user seperated in commas and included in single quotes. The problem I have is that &apos ; character is displayed as output instead of '.
Trial 1
SELECT LISTAGG(TYPE_NAME, ''',''') WITHIN GROUP (ORDER BY TYPE_NAME)
FROM ALL_TYPES
WHERE OWNER = 'USER1';
ORA-01489: result of string concatenation is too long
01489. 00000 - "result of string concatenation is too long"
*Cause: String concatenation result IS more THAN THE maximum SIZE.
*Action: Make sure that the result is less than the maximum size.
Trial 2
SELECT '''' || RTRIM(XMLAGG(XMLELEMENT(E,TYPE_NAME,q'$','$' ).EXTRACT('//text()')
ORDER BY TYPE_NAME).GetClobVal(),q'$','$') AS LIST
FROM ALL_TYPES
WHERE OWNER = 'USER1';
&apos ;TYPE1&apos ;,&apos ;TYPE2&apos ;, ............... ,&apos;TYPE3&apos ;,&apos ;
Trial 3
SELECT
dbms_xmlgen.CONVERT(XMLAGG(XMLELEMENT(E,TYPE_NAME,''',''').EXTRACT('//text()')
ORDER BY TYPE_NAME).GetClobVal())
AS LIST
FROM ALL_TYPES
WHERE OWNER = 'USER1';
TYPE1&amp ;apos ;,&amp ;apos ;TYPE2&amp ;apos ;, ......... ,&amp ;apos ;TYPE3&amp ;apos ;,&amp ;apos ;
I don;t want to call replace function and then make substring as follow
With tbla as (
SELECT REPLACE('''' || RTRIM(XMLAGG(XMLELEMENT(E,TYPE_NAME,q'$','$' ).EXTRACT('//text()')
ORDER BY TYPE_NAME).GetClobVal(),q'$','$'),'&apos;',''') AS LIST
FROM ALL_TYPES
WHERE OWNER = 'USER1')
select SUBSTR(list, 1, LENGTH(list) - 2)
from tbla;
Is there any other way ?
use dbms_xmlgen.convert(col, 1) to prevent escaping.
According to Official docs, the second param flag is:
flag
The flag setting; ENTITY_ENCODE (default) for encode, and
ENTITY_DECODE for decode.
ENTITY_DECODE - 1
ENTITY_ENCODE - 0 default
Try this:
select
''''||substr(s, 1, length(s) - 2) list
from (
select
dbms_xmlgen.convert(xmlagg(xmlelement(e,type_name,''',''')
order by type_name).extract('//text()').getclobval(), 1) s
from all_types
where owner = 'USER1'
);
Tested the similar code below with 100000 rows:
with t (s) as (
select level
from dual
connect by level < 100000
)
select
''''||substr(s, 1, length(s) - 2)
from (
select
dbms_xmlgen.convert(xmlagg(XMLELEMENT(E,s,''',''') order by s desc).extract('//text()').getClobVal(), 1) s
from t);

Undefined index: 0 when using hook_node_insert() Drupal 7

I'm writing a custom module to insert data into database using hook_node_insert() when creating a new node. But if I left any field in the node empty without adding anything (non required field) and save the field I get the following error even though i have used isset function to check for empty fields.
Notice: Undefined index: 0 in add_customer_node_insert() in line no:5
Line no -5
$node_id = isset($node->field_id['und'])? $node->field_id['und']['0']['value']:NULL;
You only check if $node->field_id['und'] is set but not if the next part of the multi dimensinal array $node->field_id['und'][0] is set which you use for your assignment to $node_id. Change your statement to
isset($node->field_id['und']['0']['value'])
In your following code
$node_id = isset($node->field_id['und'])? $node->field_id['und']['0']['value'] : NULL;
$node->field_id['und'] is an array. You should check this by using empty() function for example like this
$node_id = !empty($node->field_id['und']) ? $node->field_id['und']['0']['value'] : NULL;
OR if you want to use isset() function
$node_id = isset($node->field_id['und']['0']['value']) ? $node->field_id['und']['0']['value'] : NULL;

Cakephp looping through database saves, for some reason it only saves on the last instance of loop?

I'm trying to get this loop to save a new record to the database in cakephp on each iteration but for some reason its only saving it on the last one (so in this case it saves a record called "test9" but no others.. this type of save has worked for me so far in cakephp and I am completely stumped by this, I would appreciate any advice
The debug output just gives this for each record (including the save that works), so I can't determine anything from it:
26 SELECT COUNT() AS count FROM proxylinks AS Proxylink WHERE Proxylink.id = 13 1 1 0
27 SELECT COUNT() AS count FROM proxylinks AS Proxylink WHERE Proxylink.id = 13 1 1 0
28 UPDATE proxylinks SET link = 'test9' WHERE proxylinks.id = 13 1 0
$count = 10;
$v = 1;
do {
######### save link to database
$this->Prox->Proxylink->set(array('link' => 'test' . $v));
$this->Prox->Proxylink->save();
$v++;
} while ($v < $count);
You have to call ->create(), otherwise it's updating the previously saved record.
Quoting the manual:
When calling save in a loop, don't forget to call create().

Resources