Firebird's execute statement with bigint named input parameter - firebird

Let's say I have a code like this:
execute block
as
declare var_mask bigint;
declare var_dummy int;
begin
var_mask = bin_shl(1, (64 - 1));
execute statement ('
select first 1 null
from rdb$database
where bin_and(cast(0 as bigint), :var_mask) <> cast(0 as bigint)
')
(var_mask := var_mask)
into :var_dummy
;
end
This one gives nice arithmetic exception, numeric overflow, or string truncation.
numeric value is out of range..
To make it work I have to do explicit cast of the variable:
execute block
as
declare var_mask bigint;
declare var_dummy int;
begin
var_mask = bin_shl(1, (64 - 1));
execute statement ('
select first 1 null
from rdb$database
where bin_and(cast(0 as bigint), cast(:var_mask as bigint)) <> cast(0 as bigint)
')
(var_mask := var_mask)
into :var_dummy
;
end
Does anybody know why? The type information should carry, isn't it?

Because BIN_AND describes the second parameter as INTEGER, even when you pass a BIGINT to the first one. Whether this is good or bad is subject to discussion.

To add to Adriano's answer the type information actually does not carry - more here, from me actually :).

Related

Understanding PLPGSQL basics

I there, I am new to PL/pgSQL, the project I am working on started using it for some data-intensive handling implementations, I am failing to understand some basic foundations, even reviewing the docs again and again
So I have a function that looks like this:
CREATE OR REPLACE FUNCTION sum_stuff_over_value(
param1 uuid,
param2 uuid,
param3 enum
)
RETURNS float AS
$$
DECLARE
table_name varchar;
column_name varchar;
resolution integer;
another_table varchar := 'name_of_another_table';
another_column varchar := 'name_of_another_column';
sum float;
BEGIN
-- Get data from another table fiven a param2 ID
SELECT * INTO table_name, column_name, esolution
FROM get_table_and_column_by_Id(param2, param3);
-- Sum table column over region
EXECUTE format(
'SELECT sum(grid_mat.%I * grid_def.%I)
FROM
get_uncompact_region($1, $2) region
INNER JOIN %I grid_mat ON grid_mat.index = region.index
INNER JOIN %I grid_def ON grid_def.index = region.index;
', column_name, another_column, table_name, another_table)
USING param1, resolution
INTO sum;
RETURN sum;
END;
$$
LANGUAGE plpgsql;
I'd say I fairly understand the very very basic flow, instantiate vars, some of them assigned, etc...
What I am struggling the most is understanding which value holds %I, and how here
INNER JOIN %I grid_mat ON grid_mat.index = region.index
INNER JOIN %I grid_def ON grid_def.index = region.index;
%I is holding (I believe) different values to join different tables
I tried to figure it out by raising notices to print values, but I couldn't make it work. I am trying to add some breakpoints to debug this isolated in the DB but as it's new to me is not being straightforward
Can anyone help me understand what is going on here?
Thanks a lot in advance
You should read the documentation of format. The first %I will be replaced by the value of the second argument of format (column_name), escaped as an identifier, the second %I will be replaced by the value of another_column, and so on.
FROM https://www.postgresql.org/docs/14/plpgsql-statements.html#PLPGSQL-STATEMENTS-SQL-ONEROW.
around section: Example 43.1. Quoting Values in Dynamic Queries
quote:
Dynamic SQL statements can also be safely constructed using the format function (see Section 9.4.1). For example:
EXECUTE format('UPDATE tbl SET %I = %L '
'WHERE key = %L', colname, newvalue, keyvalue);
Section 9.4.1 LINK: :
https://www.postgresql.org/docs/14/functions-string.html#FUNCTIONS-STRING-FORMAT
Quote from Section 9.4.1
type (required) The type of format conversion to use to produce the
format specifier's output. The following types are supported:
s formats the argument value as a simple string. A null value is
treated as an empty string.
I treats the argument value as an SQL identifier, double-quoting it if
necessary. It is an error for the value to be null (equivalent to
quote_ident).
L quotes the argument value as an SQL literal. A null value is
displayed as the string NULL, without quotes (equivalent to
quote_nullable).
There have serval examples, quote:
SELECT format('INSERT INTO %I VALUES(%L)', 'Foo bar', E'O\'Reilly');
Result: INSERT INTO "Foo bar" VALUES('O''Reilly')
Finally explain:
your EXECUTE format string have 4 %I. Then
1st %I refer to column_name
2nd %I refer to another_column
3rd %I refer to table_name
4th %I refer to another_table

pgsql concat for number returning null

I have created a sequence like below:
CREATE SEQUENCE public.shiwangini_seq
INCREMENT BY 1
MINVALUE 1
MAXVALUE 9223372036854775807
START 150037
CACHE 1
NO CYCLE;
After this - I have created a function which will use this sequence to generate next value:
create or replace function shiwangini_unq(out v_final_value2 bigint) as $$
declare
v_seq_value bigint;
v_shard_id bigint;
v_final_value2 bigint ;
begin
select nextval('shiwangini_seq') into v_seq_value ;
v_shard_id := 2;
select concat (v_seq_value , v_shard_id ) into v_final_value2 ;
end ;
$$ language plpgsql ;
Now, this function returns null whenever I call it. Like below:
select shiwangini_unq(); ----null
I checked concat() manages type conversion itself. Even after that, in my case it's returning null. I help will be really appreciated to make it working. Thanks in advance!
You can write this as normal SQL function (without OUT parameter) and eliminate all data conversations.
create or replace
function shiwangini_unq()
returns bigint
language sql
as $$
select 10*nextval('shiwangini_seq')+2;
$$;
OR just get rid of the function altogether.
create sequence shiwangini_seqX start with 12 increment by 10;
See demo
Your main problem is, that the variable v_final_value2 overshadows your out parameter.
But all that isn't needed to begin with.
You should declare a proper return type. But you are overcomplicating things here. You can make this more efficient without PL/pgSQL and a simple SQL function:
create or replace function shiwangini_unq()
return bigint
as $$
select concat(nextval('shiwangini_seq'), 2)::bigint;
$$ language sql;
You give the return type of your function as an input, that is the problem. The below code is working well for me if there any error please text me back.
Try this
create or replace function shiwangini_unq() returns bigint as $$
declare
v_seq_value bigint;
v_shard_id bigint;
v_final_value2 bigint ;
begin
select nextval('shiwangini_seq') into v_seq_value ;
v_shard_id := 2;
select concat (v_seq_value, v_shard_id ) into v_final_value2 ;
return v_final_value2;
end ;
$$ language plpgsql ;

PL/pgSQL column name the same as variable

I'm new to plpgsql and I'm trying to create function that will check if a certain value exists in table and if not will add a row.
CREATE OR REPLACE FUNCTION hire(
id_pracownika integer,
imie character varying,
nazwisko character varying,
miasto character varying,
pensja real)
RETURNS TEXT AS
$BODY$
DECLARE
wynik TEXT;
sprawdzenie INT;
BEGIN
sprawdzenie = id_pracownika;
IF EXISTS (SELECT id_pracownika FROM pracownicy WHERE id_pracownika=sprawdzenie) THEN
wynik = "JUZ ISTNIEJE";
RETURN wynik;
ELSE
INSERT INTO pracownicy(id_pracownika,imie,nazwisko,miasto,pensja)
VALUES (id_pracownika,imie,nazwisko,miasto,pensja);
wynik = "OK";
RETURN wynik;
END IF;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
The issue is that I'm getting errors saying that id_pracownika is a column name and a variable.
How to specify that "id_pracownika" in such context refers to column name?
Assuming id_pracownika is The PRIMARY KEY of the table. Or at least defined UNIQUE. (If it's not NOT NULL, NULL is a corner case.)
SELECT or INSERT
Your function is another implementation of "SELECT or INSERT" - a variant of the UPSERT problem, which is more complex in the face of concurrent write load than it might seem. See:
Is SELECT or INSERT in a function prone to race conditions?
With UPSERT in Postgres 9.5 or later
In Postgres 9.5 or later use UPSERT (INSERT ... ON CONFLICT ...) Details in the Postgres Wiki. This new syntax does a clean job:
CREATE OR REPLACE FUNCTION hire(
_id_pracownika integer
, _imie varchar
, _nazwisko varchar
, _miasto varchar
, _pensja real)
RETURNS text
LANGUAGE plpgsql AS
$func$
BEGIN
INSERT INTO pracownicy
( id_pracownika, imie, nazwisko, miasto, pensja)
VALUES (_id_pracownika,_imie,_nazwisko,_miasto,_pensja)
ON CONFLICT DO NOTHING;
IF FOUND THEN
RETURN 'OK';
ELSE
RETURN 'JUZ ISTNIEJE'; -- already exists
END IF;
END
$func$;
About the special variable FOUND:
Why is IS NOT NULL false when checking a row type?
Table-qualify column names to disambiguate where necessary. (You can also prefix function parameters with the function name, but that gets awkward quickly.)
But column names in the target list of an INSERT may not be table-qualified. Those are never ambiguous anyway.
Best avoid ambiguities a priori. Some (including me) like to prefix all function parameters and variables with an underscore.
If you positively need a column name as function parameter name, one way to avoid naming collisions is to use an ALIAS inside the function. One of the rare cases where ALIAS is actually useful.
Or reference function parameters by ordinal position: $1 for id_pracownika in this case.
If all else fails, you can decide what takes precedence by setting #variable_conflict. See:
Naming conflict between function parameter and result of JOIN with USING clause
There is more:
There are intricacies to the RETURNING clause in an UPSERT. See:
How to use RETURNING with ON CONFLICT in PostgreSQL?
String literals (text constants) must be enclosed in single quotes: 'OK', not "OK". See:
Insert text with single quotes in PostgreSQL
Assigning variables is comparatively more expensive than in other programming languages. Keep assignments to a minimum for best performance in plpgsql. Do as much as possible in SQL statements directly.
VOLATILE COST 100 are default decorators for functions. No need to spell those out.
Without UPSERT in Postgres 9.4 or older
...
IF EXISTS (SELECT FROM pracownicy p
WHERE p.id_pracownika = hire.id_pracownika) THEN
RETURN 'JUZ ISTNIEJE';
ELSE
INSERT INTO pracownicy(id_pracownika,imie,nazwisko,miasto,pensja)
VALUES (hire.id_pracownika,hire.imie,hire.nazwisko,hire.miasto,hire.pensja);
RETURN 'OK';
END IF;
...
But there is a tiny race condition between the SELECT and the INSERT, so not bullet-proof under heavy concurrent write-load.
In an EXISTS expression, the SELECT list does not matter. SELECT id_pracownika, SELECT 1, or even SELECT 1/0 - all the same. Just use an empty SELECT list. Only the existence of any qualifying row matters. See:
What is easier to read in EXISTS subqueries?
It is a example tested by me where I use EXECUTE to run a select and put its result in a cursor, using dynamic column names.
1. Create the table:
create table people (
nickname varchar(9),
name varchar(12),
second_name varchar(12),
country varchar(30)
);
2. Create the function:
CREATE OR REPLACE FUNCTION fun_find_people (col_name text, col_value varchar)
RETURNS void AS
$BODY$
DECLARE
local_cursor_p refcursor;
row_from_people RECORD;
BEGIN
open local_cursor_p FOR
EXECUTE 'select * from people where '|| col_name || ' LIKE ''' || col_value || '%'' ';
raise notice 'col_name: %',col_name;
raise notice 'col_value: %',col_value;
LOOP
FETCH local_cursor_p INTO row_from_people; EXIT WHEN NOT FOUND;
raise notice 'row_from_people.nickname: %', row_from_people.nickname ;
raise notice 'row_from_people.name: %', row_from_people.name ;
raise notice 'row_from_people.country: %', row_from_people.country;
END LOOP;
END;
$BODY$ LANGUAGE 'plpgsql'
3. Run the function
select fun_find_people('name', 'Cristian');
select fun_find_people('country', 'Chile');
inspire with Erwin Brandstetter's answers.
CREATE OR REPLACE FUNCTION test_upsert(
_parent_id int,
_some_text text)
RETURNS text
LANGUAGE plpgsql AS
$func$
DECLARE a text;
BEGIN
INSERT INTO parent_tree (parent_id, some_text)
VALUES (_parent_id,_some_text)
ON CONFLICT DO NOTHING
RETURNING 'ok' into a;
return a;
IF NOT FOUND THEN
return 'JUZ ISTNIEJE';
END IF;
END
$func$;
Follow Erwin's answer. I make a variable hold the return type text.
If conflict do nothing then the function will return nothing. For example, already have parent_id = 10, Then the result would be as following:
test_upsert
------------
(1 row)
NOT Sure the usage of:
IF NOT FOUND THEN
return 'JUZ ISTNIEJE';
END IF;

Declare variable set = select

How do I declare a variable for used in a PostgreSQL 9.3 query?
CREATE or replace FUNCTION public.test()
returns int4
AS
$BODY$
DECLARE
cod_process bigint :=30001;
cod_instance bigint ;
utc_log timestamp without time zone := localtimestamp;
cod_log_type varchar(100) :='information ';
txt_log_text varchar(100):= 'start process';
txt_log varchar(100):= txt_log_text||'_'||cod_process;
set cod_instance= select max(cod_instance) as cod_instance from public.instance where public.instance.cod_process=cod_process;
BEGIN
INSERT INTO public.log (cod_process, cod_instance, utc_log,cod_log_type,txt_log)
VALUES (cod_process, cod_instance, utc_log,cod_log_type,txt_log );
RETURN 11;
END;
$BODY$ LANGUAGE 'plpgsql';
ERROR: type "cod_instance" does not exist
SQL state: 42704
Character: 383
Your demo function would work like this:
CREATE or replace FUNCTION public.test()
RETURNS int4 AS
$func$
DECLARE
_cod_process bigint := 30001;
_cod_instance bigint := (SELECT max(cod_instance)
FROM public.instance
WHERE cod_process = _cod_process);
_utc_log timestamp := localtimestamp;
_cod_log_type varchar(100) := 'information';
_txt_log_text varchar(100) := 'start process';
_txt_log varchar(100) := txt_log_text || '_' || cod_process;
BEGIN
INSERT INTO public.log
( cod_process, cod_instance, utc_log, cod_log_type, txt_log)
VALUES (_cod_process, _cod_instance, _utc_log, _cod_log_type, _txt_log);
RETURN 11;
END
$func$ LANGUAGE plpgsql;
Major points
You cannot use SET to assign a variable. That's taken to be the SQL command SET for setting run-time parameters.
But you can assign a variable at declaration time, even use a subquery for that.
UseLANGUAGE plpgsql, not LANGUAGE 'plpgsql'. It's an identifier.
#a_horse_with_no_name already wrote about naming conflicts.
Using a clean format goes a long way when debugging code ...
But you can probably simplify to:
CREATE OR REPLACE FUNCTION public.test(_cod_process bigint = 30001)
RETURNS integer AS
$func$
INSERT INTO public.log
(cod_process, cod_instance , utc_log, cod_log_type , txt_log)
SELECT $1, max(cod_instance), now() , 'information', 'start process_' || $1
FROM public.instance
WHERE cod_process = $1
GROUP BY cod_process
RETURNING 11
$func$ LANGUAGE sql;
Call:
SELECT public.test(); -- for default 30001
SELECT public.test(1234);
And depending on the actual data type of utc_log you probably want now() AT TIME ZONE 'UTC':
Ignoring timezones altogether in Rails and PostgreSQL
You need to run the select using the into clause inside the actual code block, not in the declare block:
begin
select max(cod_instance)
into cod_instance
from public.instance
where public.instance.cod_process=cod_process;
....
end;
It's usually not such a good idea to give variables (or parameters) the same name as columns in the table. There are certain cases where this can confuse the parser. To avoid any potential problems, try to use different names for your variables, e.g. by prefixing them (e.g. l_cod_process instead of cod_process or l_cod_instance instead of cod_instance)
More details on variable assignment can be found in the manual: http://www.postgresql.org/docs/current/static/plpgsql-statements.html

Unexpected behaviour for custom type returned from a function

I have created a custom type
CREATE TYPE rc_test_type AS (a1 bigint);
and a function
CREATE OR REPLACE FUNCTION public.rc_test_type_function(test_table character varying, dummy integer)
RETURNS rc_test_type AS
$BODY$
DECLARE
ret rc_test_type;
query text;
BEGIN
query := 'SELECT count(*) from ' || test_table ;
EXECUTE query into ret.a1;
RETURN ret;
END $BODY$
LANGUAGE plpgsql VOLATILE
If I run
SELECT * FROM rc_test_type_function('some_table', 1);
I get
"a1"
1389
So far so good.
If I run
SELECT p FROM (SELECT rc_test_type_function('some_table', s.step) AS p
FROM some_other_table s) foo;
I get
"p"
"(1389)"
"(1389)"
since 'some_other_table' has just two records. Fine.
But then if I try
SELECT p.a1 FROM (select rc_test_type_function('some_table', s.step) AS p
FROM some_other_table s) foo;
I get the error
missing FROM-clause entry in subquery for table »p«
which I find strange since the subquery has not changed.
Two questions:
Can anyone explain what's going on?
How do I extract the field value a1 from the returned array?
Use parentheses around the composite type:
SELECT (p).a1
FROM (SELECT rc_test_type_function('some_table', s.step) AS p
FROM some_other_table s
) foo;
Even though your type has just a single column is still a composite type - with its own column name. Doesn't make a lot of sense, but that's how you built it.
(You might want to just use a simple type or maybe a DOMAIN instead.)
Quoting the manual here:
(compositecol).somefield
(mytable.compositecol).somefield
The parentheses are required here to show that compositecol is a column name not a
a table name, or that mytable is a table name not a schema name in the second case.
Proper function
Omitting the part with the composite type, your function would be safer, simpler and faster this way:
CREATE OR REPLACE FUNCTION foo(test_table varchar, dummy int, OUT p bigint)
AS
$func$
BEGIN
EXECUTE format('SELECT count(*) from %I', test_table) -- !avoid SQLi!
INTO p;
END
$func$ LANGUAGE plpgsql;
Avoid SQL injection with dynamic SQL!
An OUT parameter simplifies the syntax in this case. You don't need a DECLARE clause at all, and no RETURN either
Even better
CREATE OR REPLACE FUNCTION foo(test_table regclass, dummy int, OUT p bigint)
AS
$func$
BEGIN
EXECUTE 'SELECT count(*) from ' || test_table
INTO p;
END
$func$ LANGUAGE plpgsql;
By using the object identifier regclass this would also work with schema-qualified table names. And SQLi is not possible to begin with. The function would fail immediately if the table name is illegal and it is quoted automatically when converted to text automatically.