Pass array from node-postgres to plpgsql function - arrays

The plpgsql function:
CREATE OR REPLACE FUNCTION testarray (int[]) returns int as $$
DECLARE
len int;
BEGIN
len := array_upper($1);
return len;
END
$$ language plpgsql;
The node-postgres query + test array:
var ta = [1,2,3,4,5];
client.query('SELECT testarray($1)', [ta], function(err, result) {
console.log('err: ' + err);
console.log('result: ' + result);
});
Output from node server:
err: error: array value must start with "{" or dimension information
result: undefined
I also tried cast the parameter in the client query like testarray($1::int[]) which returned the same error.
I changed the function argument to (anyarray int) and the output error changed:
err: error: invalid input syntax for integer: "1,2,3,4,5"
result: undefined
As well as a couple other variations.
I seek the variation that produces no error and returns 5.
I read about the Postgres parse-array issue and this stackoverflow question on parameterised arrays in node-postgres:
node-postgres: how to execute "WHERE col IN (<dynamic value list>)" query?
But the answer didn't seem to be there.

The parameter has to be in one of these forms:
'{1,2,3,4,5}' -- array literal
'{1,2,3,4,5}'::int[] -- array literal with explicit cast
ARRAY[1,2,3,4,5] -- array constructor
Also, you can simplify your function:
CREATE OR REPLACE FUNCTION testarray (int[])
RETURNS int AS
$func$
BEGIN
RETURN array_length($1, 1);
END
$func$ LANGUAGE plpgsql IMMUTABLE;
Or a simple SQL function:
CREATE OR REPLACE FUNCTION testarray2 (int[])
RETURNS int AS 'SELECT array_length($1, 1)' LANGUAGE sql IMMUTABLE;
Or just use array_length($1, 1) directly.
array_upper() is the wrong function. Arrays can have arbitrary subscripts. array_length() does what you are looking for. Related question:
Normalize array subscripts for 1-dimensional array so they start with 1
Both array_length() and array_upper() require two parameters. The second is the array dimension - 1 in your case.

thanks to the responses from PinnyM and Erwin. I reviewed the options and reread related answers.
the array formats described by Erwin work in node-postgres literally as follows:
'select testarray(' + "'{1,2,3,4,5}'" + ')'
'select testarray(' + "'{1,2,3,4,5}'" + '::INT[])'
'select testarray(ARRAY[1,2,3,4,5])'
the tl:dr of javascript quoting
to parameterize them in node-postgres:
(based on this answer)
var ta = [1,2,3,4,5];
var tas = '{' + ta.join() + '}';
...skipped over the pg connect code
client.query("select testarray($1)", [tas] ...
client.query("select testarray($1::int[])", [tas] ...
not sure about the ARRAY constructor.

Based on the answer you posted, this might work for you:
var ta = [1,2,3,4,5];
var params = [];
for(var i = 1, i <= ta.length; i++) {
params.push('$'+i);
}
var ta = [1,2,3,4,5];
client.query('SELECT testarray(\'{' + params.join(', ') + '}\')', ta, function(err, result) {
console.log('err: ' + err);
console.log('result: ' + result);
});

Related

Assign result from stored procedure to a variable

I have created a stored procedure that returns a create table sql statement; I want to be able to now call that procedure and assign the result to a variable like:
set create_table_statement = call sp_create_stage_table(target_db, table_name);
snowflake will not let me do this, so is there a way I can.
Context
We have just been handed over our new MDP which is built on AWS-S3, DBT & Snowflake, next week we go into production but we have 200+ tables and snowlpipes to code out. I wanted to semi automate this by generating the create table statements based off the tables metadata and then calling the results from that to create the tables. At the moment we're having to run the SQL, copy+paste the results in and then run that, which is fine in dev/pre-production mode when it's a handful of tables. but with just 2 of us it will be a lot of work to get all those tables and pipes created.
so I've found a work around, by creating a second procedure and calling the first one as a se=ql string to get the results as a string - then calling that string as a sql statement. like:
create or replace procedure sp_create_stage_table("db_name" string, "table_name" string)
returns string
language javascript
as
$$
var sql_string = "call sp_get_create_table_statement('" + db_name + "','" + table_name + "');";
var get_sql_query = snowflake.createStatement({sqlText: sql_string});
var get_result_set = get_sql_query.execute();
get_result_set.next();
var get_query_value = get_result_set.getColumnValue(1);
sql_string = get_query_value.toString();
try {
var main_sql_query = snowflake.createStatement({sqlText: sql_string});
main_sql_query.execute();
return "Stage Table " + table_name + " Successfully created in " + db_name + " database."
}
catch (err){
return "an error occured! \n error_code: " + err.code + "\n error_state: " + err.state + "\n error_message: " + err.message;
}
$$;
It is possible to assign scalar result of stored procedure to session variable. Instead:
SET var = CALL sp();
The pattern is:
SET var = (SELECT * FROM TABLE(RESULT_SCAN(LAST_QUERY_ID())));
Sample:
CREATE OR REPLACE PROCEDURE TEST()
RETURNS VARCHAR
LANGUAGE SQL
AS
BEGIN
RETURN 'Result from stored procedrue';
END;
CALL TEST();
SET variable = (SELECT * FROM TABLE(RESULT_SCAN(LAST_QUERY_ID())));
SELECT $variable;
-- Result from stored procedrue

Snowflake regexp_replace not working as expected

I tried to get paths enclosed by double quotes (ex: "path"."to"."element"). It also strips any bracket-enclosed array element references (like "[0]")
var path_name = "regexp_replace(regexp_replace("customers[0].name",'\\[(.+)\\]'),'(\\w+)','"\\1"')" ;
I tried this method but it is displaying error
So this is a really poorly written question. But lets play the guessing game anyways.
So you have a Javascript stored procedure, and you have that line it side it, and it doesn't work as you expect: lets guess it looks like:
create or replace procedure sp()
returns VARCHAR
language javascript
as
$$
var txt = '"customers[0].name"';
var sql_regexp1 = '\\\\[(.+)\\\\]';
var sql_regexp2 = '(\\\\w+)';
var sql_rep_2 = '\"\\\\1\"';
var full_rep1 = "regexp_replace('" + txt + "','"+ sql_regexp1 +"')";
var full_rep2 = "select regexp_replace(" + full_rep1 + ",'"+ sql_regexp2 +"','"+ sql_rep_2 + "');";
//return full_rep2;
var statement = snowflake.createStatement( {sqlText: full_rep2} );
var result_set1 = statement.execute();
result_set1.next()
return result_set1.getColumnValue(1);
$$;
;
and if you uncomment out the early return to can see the full_rep2
thus you can test that the inner SQL
select regexp_replace('"customers[0].name"','\\[(.+)\\]');
gives:
REGEXP_REPLACE('"CUSTOMERS[0].NAME"','\[(.+)\]')
"customers.name"
lets assume that's correct!
then you can check the outer replace:
select regexp_replace(regexp_replace('"customers[0].name"','\\[(.+)\\]'),'(\\w+)','"\\1"');
which gives:
REGEXP_REPLACE(REGEXP_REPLACE('"CUSTOMERS[0].NAME"','\[(.+)\]'),'(\W+)','"\1"')
""customers"."name""
and if we call the stored procedure:
call sp();
we get:
SP
""customers"."name""
So this was "how I debugged the SQL/Javascript" to have "valid working SQL. The question then becomes, what output did you want. And can you get there from here.

Snowflake Behavior differently for literal values and table values

I have a function AVGF, which is suppose to output average of comma separated sequence
CREATE OR REPLACE FUNCTION AVGF (STR VARCHAR)
RETURNS VARCHAR
LANGUAGE JAVASCRIPT
AS $$
var str_array = STR.split(",");
var total = 0.0
for (i = 0; i < str_array.length; i += 1) {
total += parseFloat(str_array[i]);
}
return total/str_array.length;
$$
;
It works fine for example mentioned below
SELECT AVGF('1,2,3')
Output: 2
However, when I use it on top of table it doesn't work
SELECT AVGF(concat(TO_CHAR(ic.col1) ,',',TO_CHAR(ic.col2) )::STRING)
from tab
Output: JavaScript execution error: Uncaught TypeError: Cannot read property 'split' of undefined in AVGF at ' var str_array = STR.split(",");' position 21
where tab is table with col1(number) & col2(float)
Any reason for this and any solution?
This was a casting issue, modified the function as mentioned below to make it work
CREATE OR REPLACE FUNCTION AVGF (STR VARCHAR)
RETURNS VARCHAR
LANGUAGE JAVASCRIPT
AS $$
var str_array = String(STR).split(",");
var total = 0.0
for (i = 0; i < str_array.length; i += 1) {
total += parseFloat(str_array[i]);
}
return total/str_array.length;
$$
;

Why am I getting an incorrect array size from lazarus?

I have an array that is being created by parsing a semi-colon delimited list.
The splitting of the string into an array works and if I use a for-in and display each element I get the correct results.
procedure badSizeOfDemo(arrayString: String);
var
semiColonSplitLine: TStringList;
begin
semiColonSplitLine:= TStringList.Create;
semiColonSplitLine.StrictDelimiter := true;
semiColonSplitLine.Delimiter:= ';';
semiColonSplitLine.DelimitedText:= arrayString;
showMessage('arrayString: ' + arrayString);
showMessage('size of split line: ' + IntToStr(SizeOf(semiColonSplitLine)));
end;
With the above code I always get the size of the array as '4' even when the arrayString contains ''.
Am I missing something fundamental here?
FURTHER PROCESSING following switch to using .Count
After splitting the array like this I then do a for-in and check that every element can be converted to a number before building an array of integers.
This essentially works, but then when I try to get the size of the integer array I get an illegal qualifier error.
procedure badSizeOfDemo(arrayString: String);
var
semiColonSplitLine: TStringList;
myElement: String = '';
myIntegerArray: array of integer;
count: Integer = 0;
begin
semiColonSplitLine:= TStringList.Create;
semiColonSplitLine.StrictDelimiter := true;
semiColonSplitLine.Delimiter:= ';';
semiColonSplitLine.DelimitedText:= arrayString;
showMessage('arrayString: ' + arrayString);
showMessage('size of split line: ' + IntToStr(semiColonSplitLine.Count));
for myElement in semiColonSplitLine do
begin
Try
showMessage('field from split line: ' + myElement);
myIntegerArray[count]:=StrToInt(myElement);
except
On E : EConvertError do
ShowMessage('Invalid number encountered');
end;
count:=count+1;
end;
showMessage('myIntegerArray now has ' + myIntegerArray.Count + ' elements');
end;

Problems with string parameter insertion into prepared statement

I have a database running on an MS SQL Server. My application communicates via JDBC and ODBC with it. Now I try to use prepared statements.
When I insert a numeric (Long) parameter everything works fine. When I insert a string
parameter it does not work. There is no error message, but an empty result set.
WHERE column LIKE ('%' + ? + '%') --inserted "test" -> empty result set
WHERE column LIKE ? --inserted "%test%" -> empty result set
WHERE column = ? --inserted "test" -> works
But I need the LIKE functionality. When I insert the same string directly into the query string (not as a prepared statement parameter) it runs fine.
WHERE column LIKE '%test%'
It looks a little bit like double quoting for me, but I never used quotes inside a string. I use preparedStatement.setString(int index, String x) for insertion.
What is causing this problem?
How can I fix it?
Thanks in advance.
What are you inserting at '?'
If you are inserting
test
Then this will result in
WHERE column LIKE ('%' + test + '%')
which will fail. If you are inserting
"test"
Then this will result in
WHERE column LIKE ('%' + "test" + '%')
Which will fail.
You need to insert
'test'
Then this will result in
WHERE column LIKE ('%' + 'test' + '%')
And this should work.
I don't know why = "test" works, it should not unless you have a column called test.
I am using SUN's JdbcOdbcBridge. As far as I read yet, you should avoid to use it. Maybe there is a better implementation out there.
For now, I wrote the folling method. It inserts string-type parameters into the statement with string operations before the statement is compiled.
You should build a map of the parameters with the parameter index as the key and the value as the parameter itself.
private static String insertStringParameters(String statement, Map<Integer, Object> parameters) {
for (Integer parameterIndex : parameters.keySet()) {
Object parameter = parameters.get(parameterIndex);
if (parameter instanceof String) {
String parameterString = "'" + (String) parameter + "'";
int occurence = 0;
int stringIndex = 0;
while(occurence < parameterIndex){
stringIndex = statement.indexOf("?", stringIndex) + 1;
occurence++;
}
statement = statement.substring(0, stringIndex - 1) + parameterString + statement.substring(stringIndex);
}
}
return statement;
}

Resources