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

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;

Related

Exception when trying to execute a query from pascal

The following function takes the cuits (the cuit is like social security number) from a grid and inserts them into a temporary table. I'm using Delphi XE7 and Firebird 2.5.
function TfImportFileARBARetenPercep.fxListClientFromGrid(
pboClient: Boolean): WideString;
var
wsAux : WideString;
stTable, stCuit: String;
qCliProv, qCuitsExcl, qCuit : TFXQuery;
niRow : Integer;
begin
wsAux := '';
qCuitsExcl.SQL.Text := 'Create global temporary table TempExcl (cuitExcl varchar(13)) on commit delete rows';
qCuitsExcl.ExecSQL;
if pboClient then
begin
stTable := 'Client'
end
else
begin
stTable := 'Prov';
end;
for niRow := 1 to gDetails.RowCount - 1 do
if Trim(gDetails.Cells[3,niRow]) <> '' then
Begin
stCuit := QuotedStr(Trim(gDetails.Cells[3,niRow]));
qCuit.SQL.Text := 'Insert into TempExcl(:cuitExcl)';
qCuit.ParamByName('cuitExcl').AsString := stCuit;
qCuit.ExecSQL;//←←←←←←←←←←←←←←←←this line throws the exception
End;
qCuitsExcl.SQL.Text :='';
qCuitsExcl.SQL.Text := 'commit;';//to commit previous inserts
qCuitsExcl.SQL.Add('drop table TempExcl;');//in case the table is not deleted when the connection is closed
qCuitsExcl.SQL.Add('commit;');//commit previous drop
qCuitsExcl.ExecSQL;
//the rest of the code only returns the list of cuits that were loaded
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
try
qCliProv.SQL.Text :=
' Select Code' +
' From ' + stTable +
' Where Active = 1 ';
if gDetails.RowCount > 0 then
begin
qCliProv.SQL.Add('And Cuit Not In (Select cuitExcl from TempExcl)');
end;
qCliProv.Open;
wsAux := '';
while not qCliProv.Eof do
begin
wsAux := wsAux + iif((wsAux = ''), '', ',') + qCliProv.FieldByName('Code').AsString;
qCliProv.Next;
end;
Result := wsAux;
finally
FreeAndNil(qCliProv);
FreeAndNil(qCuit);
FreeAndNil(qCuitsExcluidos);
end;
end;
When trying to execute the line qCuit.ExecSQL; the following exception is thrown:
Project ERP.exe raised exception class EIBNativeException with message
'[FireDAC][Phys][FB] Dynamic SQL Error SQL error code = -104 Token
unknown - line 1, column 27 ?'.
I don't know why it throws this exception
The problem is that this is wrong:
qCuit.SQL.Text := 'Insert into TempExcl(:cuitExcl)';
This will result in a generated query that is:
Insert into TempExcl(?)
Which is a syntax error (the "Token unknown"), because Firebird doesn't expect a question mark (parameter placeholder) in this position, it expects an identifier (column name).
A valid query would be either:
Insert into TempExcl (cuitExcl) values (?)
or:
Insert into TempExcl values (?)
The first form is preferred, as you explicitly specify the columns, though in this case not really necessary as the table has one column anyway.
In other words, you need to use:
qCuit.SQL.Text := 'Insert into TempExcl (cuitExcl) values (:cuitExcl)';
As an aside, I'm not familiar with Delphi or how and when queries are prepared, but it might be better to move this statement out of the loop, so it is prepared only once (in case Delphi prepares the statement again any time the qCuit.SQL.Text is assigned a value).
The comment "// in case the table is not deleted when the connection is closed" seems to indicate a lack of understanding of Global Temporary Tables. They are intended to be permanent objects, available for re-use by your application. That is on commit delete rows makes the content available only to your current transaction, while on commit preserve rows will make the content available for the remainder of your connection (and only your connection), deleting the data when the connection is closed. Creating and dropping a GTT in something that is executed more than once is an indication something is wrong in your design.

How to run a true procedure in pgAdmin and evaluate results

I haven't seen any question that covers all of what I'm asking, so for grins am posting my journey here.
The task:
Run, in pgAdmin, a procedure (not a function) in Postgres. Evaluate and report back the results to the user on-screen.
For my situation, I had a procedure with the following signature:
CREATE OR REPLACE PROCEDURE sqls.transform_main(
INOUT returnval integer)
LANGUAGE 'plpgsql'
...
How does one accomplish this?
Replying (with code) to Jeremy's reply...
OK, I am seeing how your solution works. One question, though...
I want to pass in an actual variable that is defined in such a way that I can evaluate the returned value. (Assume this this an extended script that I don't want to turn into another stored procedure or function...)
Can I do that by your approach? I am thinking no, because by the time I've made it a script (not a single SQL call) I lose the output that I do see in your version, as you noted, in the Data Output tab.
DO
$$
declare return_val integer;
declare return_msg text;
declare notify_msg text := 0;
BEGIN
call test(return_val); -- I never see what this value is unless...
if (return_val != 0) then
notify_msg = 'Failure (' || return_val || ')';
else
notify_msg = 'success!';
end if;
RAISE NOTICE 'Return value is %', notify_msg; -- ...unless I do something like this
END
$$
The answer that worked for me:
(Edit: listen virtual; added to code below)
DO
$$
declare x integer;
declare y text;
BEGIN
listen virtual;
call sqls.transform_main(x);
y = 'Return value: ' || x;
perform pg_notify('virtual', y);
END
$$
If anyone else can improve on this, I'm all ears. Just thankful for something, at this point, having burned more time than I'd like to admit on the task.
I'm putting this down here because it's not helpful to paste code in the comments. Here's an example in psql. It's similar in pgadmin, except the results appear in the Data Output window.
# CREATE OR REPLACE PROCEDURE test(
INOUT returnval integer)
LANGUAGE 'plpgsql' AS
$$
BEGIN
returnval := returnval;
END;
$$
;
CREATE PROCEDURE
# call test(1);
returnval
-----------
1
(1 row)

PostgreSQL - not a known variable in procedure

I'm having trouble with writing a procedure in PostgreSQL. I can create the procedure, yet when i try to execute i get an error.The error i get
ERROR: "v_all_atts" is not a known variable
the query is:
CREATE OR REPLACE FUNCTION rebuild_views_with_extra_atts()
RETURNS VOID AS $$
DECLARE
v_all_atts varchar(4000);
BEGIN
CREATE OR REPLACE FUNCTION add_column(p_table text, p_column text,p_category text) RETURNS VOID AS $nothing$
declare
v_column_exists bigint := false ;
BEGIN
SELECT
string_agg( CASE WHEN owner='alarm' THEN 'ai' WHEN owner='fault' THEN 'fi'
END ||'.'||lower(alias) , ', ' ORDER BY owner, alias) AS string
INTO STRICT
v_all_atts
FROM
extra_attribute_cfg
WHERE
owner NOT LIKE 'virtual' and enable = true and v_column_exists = true;
IF LENGTH(v_all_atts) is not null THEN
v_all_atts := ', '||v_all_atts;
END IF;
v_view:= q'#
CREATE OR REPLACE VIEW alarm_view AS
SELECT
fi.fault_id, ai.alarm_id,
#'||v_all_atts||q'#
FROM
alarm ai
INNER JOIN fault fi
ON fi.fault_id = ai.fault_id
#';
EXECUTE v_view;
END;
$nothing$ language plpgsql;
end;
$$ LANGUAGE plpgsql;
I have taken a long look at Postgres documentation and cannot find what is wrong and didn't find any answer to this specific situation
Your rebuild_views_with_extra_atts() function is creating the add_column() function.
add_column() uses the v_all_atts variable, but it doesn't exist in that function, it exists only in the rebuild_views_with_extra_atts() function.
To resolve this, it really depends on what you're trying to do. If that variable should exist in the add_column() function, then declare it in there. If you're trying to use the value of v_all_atts when creating add_column() (e.g. so that the content of the function's body is dependent on the value of that variable), then you really need to use dynamic sql to generate a TEXT version of the CREATE OR REPLACE ... code, then EXECUTE it.

PostgreSQL: Syntax Error when trying to access field in array

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\")"}

How to use declared parameters as component of a query in Pl/pgSQL?

I am tryingto do a generic function in pl/pgsql. I am facing a problem that I can't figure out. See in the declare block I assigned value to some parameters. I would like to know how to use them as component of a query. (example : the BEGIN/END block, groupby_join_field)
CREATE OR REPLACE FUNCTION drillThrough(whereclause anyarray, groupbyclause anyarray) RETURNS void AS $$
DECLARE
where_table varchar(19) := whereclause[1];
where_join_field varchar(19) := whereclause[2];
where_value_field varchar(19) := whereclause[3];
where_value varchar(19) := whereclause[4];
groupby_table varchar(18) := groupbyclause[1];
groupby_join_field varchar(18) := groupbyclause[2];
groupby_value_field varchar(18) := groupbyclause[3];
BEGIN
INSERT INTO test SELECT dim_date.groupby_join_field, 1 FROM dim_date;
END;
$$ LANGUAGE plpgsql;
Every idea is welcome,
Edit :
In this case goupby_join_field has the value of year_id, so I would like the engine to understand dim_date.year_id
I don't know if I understood your question correctly, but I'll give it a shot.
You can execute arbitrary strings with the pl/pgSQL EXECUTE command. So you just have to build your query as a string.