PostgreSQL: Syntax Error when trying to access field in array - postgresql

I am working on a migration from Oracle 9i to PostgreSQL 9.3 and I need to migrate some packages with their content.
I am using Ora2PG for most of the work, but packages need to be migrated manually considering how specific to Oracle the code is.
I have a user defined Type called RelevCaspTyp with the following definition :
CREATE TYPE pkgstesa5152com.relevcasptyp AS (
-- SOME OTHER DATA
d1 timestamp,
d2 timestamp,
d1min timestamp,
d2min timestamp,
d1max timestamp,
d2max timestamp,
-- SOME OTHER DATA
);
Then, I create an array of objects of this type, in another Type :
CREATE TYPE pkgstesa5152com.relevcasptabtyp AS (relevcasptabtyp pkgstesa5152com.relevcasptyp[]);
And finally here's the function that triggers a syntax error :
CREATE OR REPLACE FUNCTION PkgStesa5152Com.calculd1d2 (RelevCaspTab INOUT pkgstesa5152com.RelevCaspTabTyp, i integer) AS $body$
BEGIN
IF RelevCaspTab[i].d1max IS NULL OR RelevCaspTab[i].d1min >= RelevCaspTab[i].d1max THEN
RelevCaspTab[i].d1 := RelevCaspTab[i].d1min;
ELSE
RelevCaspTab[i].d1 := RelevCaspTab[i].d1max;
END IF;
IF RelevCaspTab[i].d2max IS NULL OR RelevCaspTab[i].d2min >= RelevCaspTab[i].d2max THEN
RelevCaspTab[i].d2 := RelevCaspTab[i].d2min;
ELSE
RelevCaspTab[i].d2 := RelevCaspTab[i].d2max;
END IF;
END;
$body$
LANGUAGE PLPGSQL
;
When I try to import this function in my PostgreSQL database, I get this message :
ERROR: syntax error at or near "."
LINE 5: RelevCaspTab[i].d1 := RelevCaspTab[i].d1min;
^
As odd as it looks, it seems that LINE 4 (with the IF/THEN) passes correctly, unless this is yet another false positive and the issue is actually elsewhere.
EDIT : it looks like the problem actually comes from the affectation of a value to RelevCaspTab[i].d1. If I comment all the affectations in the function, it is created successfully.

if r[i].d1max is null or r[i].d1min >= r[i].d1max then
r[i] := (r[i].d1min,r[i].d2,r[i].d1min,r[i].d2min,r[i].d1max,r[i].d2max);
Edit
As suggested in the comments:
create type relevCaspTyp as (
d1 timestamp,
d1min timestamp,
d1max timestamp
);
create or replace function calculd1d2 (
r inout relevCaspTyp[], i integer
) as $body$
declare a relevCaspTyp;
begin
a := r[i];
if a.d1max is null or a.d1min >= a.d1max then
a.d1 := a.d1min;
else
a.d1 := a.d1max;
end if;
r[i] := a;
end;
$body$
language plpgsql
;
with r (r) as (values (array[('2017-01-01','2017-01-04','2017-01-03')::relevCaspTyp]))
select calculd1d2(r,1)
from r;
calculd1d2
-------------------------------------------------------------------------------
{"(\"2017-01-04 00:00:00\",\"2017-01-04 00:00:00\",\"2017-01-03 00:00:00\")"}

Related

PostgreSQL function to insert record not working as intended

I am trying to create a function to create a user (insert record into table USERS) if the user does not exist. The version of PostgreSQL that I am running is:
PostgreSQL 9.2.24 on x86_64-redhat-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-28), 64-bit
The table USERS has the following columns:
id serial primary key
,username text
,password_hash text
I have a function created that will return a userid for a given username. If the username doesn't exist then it returns 0, else a valid userid will be returned. This function (in my USER_DATA schema) is as follows:
CREATE OR REPLACE FUNCTION user_data.get_user_id(
p_username text)
RETURNS integer
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
DECLARE user_id int;
BEGIN
IF exists(select distinct id from user_data.users where username = p_username) THEN
SELECT distinct id INTO user_id
FROM user_data.users
WHERE username = p_username;
ELSE
user_id = 0;
END IF;
RETURN user_id;
END;
$BODY$;
I am now working on a function to create a user if GET_USER_ID returns 0. The three possible return values are:
TRUE - if user is created
FALSE - if username already exists and user is not created
ERROR - if there is any type of error
When testing this function, these are my results:
Running when user already exists: FALSE <-- this is expected
Running when user does not exist: ERROR <-- this is not expected.
Can anyone provide insight as to why I might be getting an error with the following logic:
CREATE OR REPLACE FUNCTION user_data.create_user(in p_username text, p_password_hash text)
returns text AS $$
DECLARE
v_user_created text;
v_user_id int;
v_error text;
BEGIN
select user_data.get_user_id(p_username) into v_user_id;
IF v_user_id = 0 THEN
insert into user_data.users(username, password_hash) values (p_username, p_password_hash);
commit;
v_user_created := 'TRUE';
ELSE
v_user_created := 'FALSE';
END IF;
RETURN v_user_created;
EXCEPTION WHEN OTHERS THEN
BEGIN
v_error := 'ERROR';
RETURN v_error;
END;
END;
$$ LANGUAGE plpgsql;
select user_data.create_user('new_user', 'sample_password_not_hashed');
Any help, insight, or criticism is greatly appreciated.
The COMMIT inside create_user is not necessary.
IF v_user_id = 0 THEN
insert into users(username, password_hash) values (p_username, p_password_hash);
--commit;
v_user_created := 'TRUE';
ELSE
v_user_created := 'FALSE';
END IF;
db<>fiddle demo

Delphi/FireDac + PostgreSQL + stored procedure + Procedure (To call a procedure, use CALL)

We are putting postgreSQL database support on our system, we currently work with Oracle.
We are trying to call a procedure in the database, just as we call in Oracle.
var conBanco := TFDConnection.Create(Self);
try
conBanco.Params.Database := 'Database';
conBanco.Params.UserName := 'UserName';
conBanco.Params.Password := 'Password';
conBanco.Params.Values['Server'] := 'IP';
conBanco.Params.DriverID := 'PG';
conBanco.Open();
var stpBanco := TFDStoredProc.Create(Self);
try
stpBanco.Connection := conBanco;
stpBanco.StoredProcName := 'gerador_padrao';
stpBanco.Prepare;
stpBanco.ParamByName('gerador').Value := 'pessoas';
stpBanco.ExecProc();
ShowMessage(VarToStrDef(stpBanco.ParamByName('parchave').Value, ''));
finally
stpBanco.Free;
end;
finally
conBanco.Free;
end;
However we get the following error:
exception class : Exception
exception message : EPgNativeException: [FireDAC][Phys][PG][libpq]
ERROR: superus.gerador_padrao(gerador => character varying, parchave
=> numeric) is a procedure. To call a procedure, use CALL.
Stored procedure in database:
CREATE OR REPLACE PROCEDURE superus.gerador_padrao
(gerador character varying, INOUT parchave numeric)
LANGUAGE plpgsql
AS $procedure$
begin
--Code
end;
$procedure$
;
The error occurs on the following line:
stpBanco.Prepare;
The above code works perfectly in Oracle, how do I call the procedure in PostgreSQL?
Thank you.
Below we will talk about Delphi 10.2.3, maybe in later versions it's different.
Inside the file FireDAC.Phys.PGMeta.pas has a TFDPhysPgCommandGenerator.GetStoredProcCall method, inside which the procedure text is formed and there is no option to call the procedure via CALL. Moreover, there is no "CALL " text in this entire file. Maybe this is a mistake, or maybe some kind of cunning idea... But that doesn't make it any easier for us.
// function returns void
if not lHasOut then begin
if FCommandKind = skStoredProc then
FCommandKind := skStoredProcNoCrs;
// NULL to avoid problem with execution of a
// void function in binary result format
Result := 'SELECT NULL FROM ';
end
else begin
if lHasCrs and (FCommandKind = skStoredProc) then
FCommandKind := skStoredProcWithCrs
else if lFunc then
lFunc := FCommandKind in [skStoredProc, skStoredProcNoCrs];
if lFunc then
Result := 'SELECT '
else
Result := 'SELECT * FROM ';
end;
In total, here I see 2 options:
Form the procedure call string yourself. I'm sure it's not difficult, although, of course, it's more correct when the component does it.
Instead of a procedure, make a function with the same content, and make your output parameter the result. It is important to remember - the procedure call then must pass through ExecFunc in this case;

Composite Array Type as OUTPUT parameter in PostgreSQL

I'm trying to insert data into composite array type in my function. It should accept data from composite array type of INPUT parameter and store data into OUPUT parameter of same type.
CREATE TYPE public.type_x_type AS (x integer);
CREATE TYPE public.type_y_type AS(x integer,y integer);
My function is
CREATE OR REPLACE FUNCTION GET_PRICE_PC_X
(
IP_PRICE_INFO IN TYPE_X_TYPE[],
PC_COST OUT TYPE_Y_TYPE[],
OP_RESP_CODE OUT VARCHAR,
OP_RESP_MSG OUT VARCHAR
)
RETURNS RECORD AS $$
DECLARE
SELECTED_PRICE CURSOR(IP_PFCNTR INT)
FOR
SELECT ID, PHONE FROM CUSTOMER WHERE ID=IP_PFCNTR;
J NUMERIC(10);
BEGIN
J := 0;
FOR I IN ARRAY_LOWER(IP_PRICE_INFO,1) .. ARRAY_UPPER(IP_PRICE_INFO,1)
LOOP
FOR K IN SELECTED_PRICE(IP_PRICE_INFO[I].X)
LOOP
PC_COST := ROW(K.ID,K.PHONE);
END LOOP;
END LOOP;
OP_RESP_CODE :='000';
OP_RESP_MSG :='Success';
EXCEPTION
WHEN OTHERS THEN
OP_RESP_CODE :='200';
OP_RESP_MSG :=SQLERRM;
END;
$$ language 'plpgsql';
select * from GET_PRICE_PC_X(ARRAY[ROW(1)] :: TYPE_X_TYPE[]);
And I'm getting the below error.
PC_COST | OP_RESPONSE_CODE | OP_RESP_MSG
---------------------------------------------------------
| 200 | malformed array literal: "(1,30003)"
I'll be calling that OUT type somewhere, so I need the data to be inserted into array.
When you develop a function, then doesn't use WHEN OTHERS. The debugging is terrible then. The problem of your function is a assignment a composite type to a array
PC_COST := ROW(K.ID,K.PHONE);
This is wrong. Probably you would to do append.
The critical part should to look like
J := 0; PC_COST := '{}';
FOR I IN ARRAY_LOWER(IP_PRICE_INFO,1) .. ARRAY_UPPER(IP_PRICE_INFO,1)
LOOP
FOR K IN SELECTED_PRICE(IP_PRICE_INFO[I].X)
LOOP
PC_COST := PC_COST || ROW(K.ID,K.PHONE)::type_y_type;
END LOOP;
END LOOP;
Your function can be replaced by one query - maybe less readable, but significantly faster - loops with nested queries can be slow (is faster run one simple SELECT than more trivial SELECTs):
CREATE OR REPLACE FUNCTION public.get_price_pc_x(ip_price_info type_x_type[],
OUT pc_cost type_y_type[],
OUT op_resp_code character varying,
OUT op_resp_msg character varying)
RETURNS record
LANGUAGE plpgsql STABLE
AS $function$
BEGIN
pc_cost := ARRAY(SELECT ROW(id, phone)::type_y_type
FROM customer
WHERE id IN (SELECT (unnest(ip_price_info)).x));
OP_RESP_CODE :='000';
OP_RESP_MSG :='Success';
EXCEPTION
WHEN OTHERS THEN
OP_RESP_CODE :='200';
OP_RESP_MSG :=SQLERRM;
END;
$function$;
Note: using NUMERIC type for cycle variable is a wrong idea - this type is expensive and should be used only for money or precious calculation. The int type is absolutely correct in this place.
Usually you can reduce you function more - the error handling should not be there - there is not a reason why this function should fail - (not a reason that can be handled)
CREATE OR REPLACE FUNCTION public.get_price_pc_x(ip_price_info type_x_type[])
RETURNS type_y_type[]
LANGUAGE sql STABLE
AS $function$
SELECT ARRAY(SELECT ROW(id, phone)::type_y_type
FROM customer
WHERE id IN (SELECT (unnest(ip_price_info)).x));
$function$;

Using OUT parameters in Postgres Stored Procedure

Is there any way I can set some value to OUT parameter in Postgres stored procedure? Below is the example what I want to do. In oracle PL/SQL you can use out parameters and set it to any value and return it. After that u can use that value and manipulate it.
CREATE OR REPLACE FUNCTION demo_procedure(p_invoice text, OUT p_out1 integer, OUT p_out2 integer)
returns SETOF record
as
$func$
DECLARE
s_inv_rest integer;
s_state integer;
BEGIN
BEGIN
SELECT "REST_TO_PAY", "STATE"
INTO s_inv_rest, s_state
FROM "INVOICE"
WHERE "INVOICE_REFERENCE" = p_invoice;
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_out1 := 1;
p_out2 := 2;
RETURN;
END;
END;
$func$
language plpgsql;
So in this example for instance if no data is found I want to return some out code and out message.

Assign complex type with UUID field from a function call

I am using PostgreSQL 9.3, working with plpgsql functions:
CREATE TEMP TABLE uuid_inside (
id uuid PRIMARY KEY
);
CREATE FUNCTION uuid_test_func(id uuid)
RETURNS uuid_inside AS
$$
DECLARE
res_uuid_inside uuid_inside;
BEGIN
IF (id = '00000000-0000-0000-0000-000000000001'::uuid) THEN
SELECT uuid_test_func('00000000-0000-0000-0000-000000000000'::uuid)
INTO res_uuid_inside;
RETURN res_uuid_inside;
END IF;
res_uuid_inside.id := id;
RETURN res_uuid_inside;
END;
$$
LANGUAGE plpgsql;
Call:
SELECT uuid_test_func('00000000-0000-0000-0000-000000000001'::uuid);
Message:
ERROR: invalid input syntax for uuid: "(00000000-0000-0000-0000-000000000000)"
SQL-state: 22P02
But this works fine:
SELECT uuid_test_func('00000000-0000-0000-0000-000000000002'::uuid);
The problem is not with recursive function calling - the original code is refering to some other function inside.
Simple function
The recursion in your function seems pointless. This simple sql function does the job (without nesting and without the composite type wrapper):
CREATE FUNCTION uuid_test_func(id uuid)
RETURNS uuid AS
$func$
SELECT CASE WHEN $1 = '00000000-0000-0000-0000-000000000001'::uuid
THEN '00000000-0000-0000-0000-000000000000'::uuid
ELSE $1
END
$func$ LANGUAGE plpgsql;
But that's probably just due to simplification for the demo.
Address problem in original
As for the error message. You are running into a confusing "feature" of PL/pgSQL:
Composite type assignment expects one column per type column. Assigning composite types as a whole is not an option.
This has nothing to do with the UUID type per se.
This modified version would work:
CREATE OR REPLACE FUNCTION uuid_test_func(id uuid)
RETURNS uuid_inside AS
$func$
DECLARE
res_uuid_inside uuid_inside;
BEGIN
IF (id = '00000000-0000-0000-0000-000000000001'::uuid) THEN
SELECT * FROM uuid_test_func('00000000-0000-0000-0000-000000000000'::uuid)
INTO res_uuid_inside.id;
-- INTO res_uuid_inside; -- would work, too - 1st col is assigned
RETURN res_uuid_inside;
END IF;
res_uuid_inside.id := id;
RETURN res_uuid_inside;
END
$func$ LANGUAGE plpgsql;
Closely related question:
Passing array of a composite type to stored procedure
Simpler function
That said, I'd suggest this simplified form (keeping the recursion and the composite result):
CREATE OR REPLACE FUNCTION uuid_test_func(id uuid)
RETURNS uuid_inside AS
$func$
BEGIN
IF (id = '00000000-0000-0000-0000-000000000001'::uuid) THEN
RETURN (SELECT f FROM uuid_test_func('00000000-0000-0000-0000-000000000000'::uuid) f);
ELSE
RETURN row(id)::uuid_inside;
END IF;
END
$func$ LANGUAGE plpgsql;