Home > Software design >  Passing variant data to Snowflake
Passing variant data to Snowflake

Time:03-20

I have written a stored procedure in Snowflake. I am calling it from a python lambda function and I have already verified that I am passing valid json. I can't figure out how to use the data I passed to the stored procedure in my insert.

The part with "values(${variant_data}" in the insert is what is giving me trouble.

I read this how to pass variant data into snowflake table using snowflake stored procedure, which said that what I needed to do isn't completely supported in Snowflake. Apparently, I would need to convert the "variant_data" to string and then back to variant to use the data in my insert but the example wasn't clear to me.

This is my stored procedure.

create or replace procedure  MY_STORED_PROCEDURE("variant_data" variant)
    returns string
    language javascript
    strict
    execute as owner
    as
    $$
    var variant_data=variant_data;
    var sql_command = `
    insert into MY_TABLE(MY_VARIANT_DATA) values(${variant_data})
    `
    $$;

If someone could explain how to use variant data in my insert, I'd appreciate it.

CodePudding user response:

Well first steps lets check our base SQL is in a acceptable form:

create table MY_TABLE(MY_VARIANT_DATA variant);
insert into my_table(my_variant_data) values 
    (parse_json('{"key1":"value1","key2":1234}'));

Invalid expression [PARSE_JSON('{"key1":"value1","key2":1234}')] in VALUES clause

ok, so lets make that a SELECT and PARSE_JSON:

insert into my_table(my_variant_data) select 
    (parse_json('{"key1":"value1","key2":1234}'));
number of rows inserted
1

ok so that works for a single value (of which is really not how you should load data into snowflake, I mean if you have 1 piece of data per hour sure, its' fine but if you doing this often, your tables will be very fragmented)..

so lets try casting to ::VARIANT

insert into my_table(my_variant_data) values 
   ('{"key1":"value1","key2":1234}'::variant);

Invalid expression [CAST('{"key1":"value1","key2":1234}' AS VARIANT)] in VALUES clause

ok, so a multi-insert via a SELEC FROM VALUES:

insert into my_table(my_variant_data) 
    select parse_json(column1) 
    from values
        ('{"key1":"value1","key2":1234}'),
        ('{"key1":"value2","key2":1235}');
number of rows inserted
2

Ok, so we now have a couple of patterns of valid INSERT sql..

lets recreate the procedure using that, this will look like "cheating" as it's a full working code, but it took me way to long to get things working. Basically I didn't read Felipe's answer too deeply, anyways, in the end I settle for string concatenation, which is a security risk, so I really wouldn't do it this way:

create or replace procedure MY_STORED_PROCEDURE("variant_as_string" text)
    returns string
    language javascript
    strict
    execute as owner
    as
    $$
    var sql_command = "insert into my_table(my_variant_data) select (parse_json('"  variant_as_string  "'));";
    var sql = snowflake.createStatement( {sqlText: sql_command});
    var resultSet = sql.execute();
    return sql_command;
    $$;

which works! although should maybe not be used..

-- lets the prior inserts
truncate table MY_TABLE;

-- do new  insert
call MY_STORED_PROCEDURE('{"key1":"value4","key2":1238}');

-- look at the results
select * from my_table;
MY_VARIANT_DATA
{ "key1": "value4", "key2": 1238 }

but that also changed the input type from variant to test...

by the way how I debugged this stuff was comment bit out like so:

create or replace procedure MY_STORED_PROCEDURE("variant_data" variant)
    returns string
    language javascript
    strict
    execute as owner
    as
    $$
    var sql_command = "insert into my_table(my_variant_data) select (parse_json('"  JSON.stringify(variant_data)  "'));";
    //var sql = snowflake.createStatement( {sqlText: sql_command});
    //var resultSet = sql.execute();
    return sql_command;
    $$;

and read the SQL to see it made me happy, anyway the with those two line uncommented out, works for variant data:

call MY_STORED_PROCEDURE(parse_json('{"key1":"value12","key2":12345}'));
select * from my_table;
MY_VARIANT_DATA
{ "key1": "value4", "key2": 1238 }
{ "key1": "value12", "key2": 12345 }

So there you have it, two ways to inject a single row in a table. Both which possible should be avoid for SQL injection reasons, a to do that correctly, you should use Felipe's answer on that other question.

create or replace procedure MY_STORED_PROCEDURE("variant_data" variant)
    returns string
    language javascript
    strict
    execute as owner
    as
    $$
    var sql_command = "insert into my_table(my_variant_data) select (parse_json(:1));";
    var sql = snowflake.createStatement( {sqlText: sql_command, binds:[JSON.stringify(variant_data)]});
    var resultSet = sql.execute();
    return sql_command;
    $$;
call MY_STORED_PROCEDURE(parse_json('{"key1":"value123","key2":123456}'));

which gives:

MY_VARIANT_DATA
{ "key1": "value4", "key2": 1238 }
{ "key1": "value12", "key2": 12345 }
{ "key1": "value123", "key2": 123456 }
  • Related