Unexpected end of command - line 1, column 532676549 (with execute statement in firebird 2.5) - firebird

I have a problem with execeute statement in a stored procedure and i cant figure it out, i try the text of the query in a variable, show it in a exception to see if was well, copy and execute it and worket, but inside the execute statement i get the same error "Unexpected end of command - line 1, column 532676549"
execute statement 'insert into rep_balancediario(id_rep_balancediario,id_plancuenta,id_saldocontable,nivel,codigoreducido,
SALDOANTERIOR,CREDITO,DEBITO,SALDODIA, cuentacontable,imputable,id_moneda,contracuenta,saldocontabledes,ID_SUCURSALAGENCIA)
values(gen_id(id_rep_balancediario,1),'||:v_id_PlanCuenta||','||:V_NROSALDOCONTABLE||','||:v_Nivel||',"'||:v_CodigoReducido||'",'||
:V_SALDOANTERIORMN||','||:V_CREDITOMN||','||:V_DEBITOMN||','||:v_resultadosaldoactual||',"'||cast(:v_CuentaContable as VARCHAR(11))||'","S",'
||:v_nromoneda||',
"'||cast(:v_contracuenta as VARCHAR (11))||'","'||cast(:V_DESSALDOCONTABLE as VARCHAR(255))||' >'||cast(:v_des_suc as varchar(100))||'",'
||:VE_IDSUCURSALAGENCIA||')'
--:V_str_sql
with AUTONOMOUS TRANSACTION;
does some one know what could by the problem?

Unexpected end of command usually means that the parser reached the end of the statement while it was still expecting tokens, or reached a parser state where it should be complete but there are still tokens left. The fact you are concatenating values into the query is a red flag. Also the "column 532676549" could be an indication of problem, as that exceeds query length limits (64k in Firebird 2.5 and earlier), and also suggests you have literals that are larger than supported (32k).
Execute statement supports parametrized queries: use it. I'd suggest that you rewrite your query to a parametrized query and see if that works, as an example:
execute statement ('insert into aTable (column1, column2) values (?, ?)') (aVariable, 'literal');
or
execute statement ('insert into aTable (column1, column2) values (:var1, :var2)') (var1 := aVariable, var2 := 'literal');
I also see that your query contains double quotes where I'd expect single quotes, which suggests that you use the deprecated dialect 1, which brings yet another set of potential problems.

Related

How to do variable substitution in plpgsql?

I've got a bit of complex sql code that I'm converting from MSSql to Postgres (using Entity Framework Core 2.1), to deal with potential race conditions when inserting to a table with a unique index. Here's the dumbed-down version:
const string QUERY = #"
DO
$$
BEGIN
insert into Foo (Field1,Field2,Field3)
values (#value1,#value2,#value3);
EXCEPTION WHEN others THEN
-- do nothing; it's a race condition
END;
$$ LANGUAGE plpgsql;
select *
from Foo
where Field1 = #value1
and Field2 = #value2;
";
return DbContext.Foos
.FromSql(QUERY,
new NpgsqlParameter("value1", value1),
new NpgsqlParameter("value2", value2),
new NpgsqlParameter("value3", value3))
.First();
In other words, try to insert the record, but don't throw an exception if the attempt to insert it results in a unique index violation (index is on Field1+Field2), and return the record, whether it was created by me or another thread.
This concept worked fine in MSSql, using a TRY..CATCH block. As far as I can tell, the way to handle Postgres exceptions is as I've done, in a plpgsql block.
BUT...
It appears that variable substitution in plpgsql blocks doesn't work. The code above fails on the .First() (no elements in sequence), and when I comment out the EXCEPTION line, I see the real problem, which is:
Npgsql.PostgresException : 42703: column "value1" does not exist
When I test using regular Sql, i.e. doing the insert without using a plpgsql block, this works fine.
So, what is the correct way to do variable substitution in a plpgsql block?
The reason this doesn't work is that the body of the DO statement is actually a string, a text. See reference
$$ is just another way to delimit text in postgresql. It can be just as well be replaced with ' or $somestuff$.
As it is a string, Npgsql and Postgresql have no reason to mess with #value1 in it.
Solutions? Only a very ugly one, so not using this construction, as you're not able to pass it any values. And messing with string concatenation is no different than doing concatenation in C# in the first place.
Alternatives? Yes!
You don't need to handle exceptions in plpgsql blocks. Simply insert, use the ON CONFLICT DO NOTHING, and be on your way.
INSERT INTO Foo (Field1,Field2,Field3)
VALUES (#value1,#value2,#value3)
ON CONFLICT DO NOTHING;
select *
from Foo
where Field1 = #value1
and Field2 = #value2;
Or if you really want to keep using plpgsql, you can simply create a temporary table, using the ON COMMIT DROP option, fill it up with these parameters as one row, then use it in the DO statement. For that to work all your code must execute as part of one transaction. You can use one explicitly just in case.
The only ways to pass parameters to plpgsql code is via these 2 methods:
Declaring a function, then calling it with arguments
When already inside a plpgsql block you can call:
EXECUTE $$ INSERT ... VALUES ($1, $2, $3); $$ USING 3, 'text value', 5.234;
End notes:
As a fellow T-SQL developer who loved its freedom, but transitioned to Postgresql, I have to say that the BIG difference is that on one side there's T-SQL which gives the power, and on the other side it's a very powerful Postgresql-flavored SQL. plpgsql is very rarely warranted. In fact, in a code base of megabytes of complex SQL stuff, I can rewrite pretty much every plpgsql code in SQL. That's how powerful it really is compared to MSSQL-flavored SQL. It just takes some getting used to, and befriending the very ample documentation. Good luck!

How to return values from dynamically generated "insert" command?

I have a stored procedure that performs inserts and updates in the tables. The need to create it was to try to centralize all the scan functions before inserting or updating records. Today the need arose to return the value of the field ID of the table so that my application can locate the registry and perform other stored procedures.
Stored procedure
SET TERM ^ ;
CREATE OR ALTER procedure sp_insupd (
iaction varchar(3),
iusuario varchar(20),
iip varchar(15),
imodulo varchar(30),
ifieldsvalues varchar(2000),
iwhere varchar(1000),
idesclogs varchar(200))
returns (
oid integer)
as
declare variable vdesc varchar(10000);
begin
if (iaction = 'ins') then
begin
vdesc = idesclogs;
/*** the error is on the line below ***/
execute statement 'insert into '||:imodulo||' '||:ifieldsvalues||' returning ID into '||:oid||';';
end else
if (iaction = 'upd') then
begin
execute statement 'select '||:idesclogs||' from '||:imodulo||' where '||:iwhere into :vdesc;
execute statement 'execute procedure SP_CREATE_AUDIT('''||:imodulo||''');';
execute statement 'update '||:imodulo||' set '||:ifieldsvalues||' where '||:iwhere||';';
end
insert into LOGS(USUARIO, IP, MODULO, TIPO, DESCRICAO) values (
:iusuario, :iip, :imodulo, (case :iaction when 'ins' then 1 when 'upd' then 2 end), :vdesc);
end^
SET TERM ; ^
The error in the above line is occurring due to syntax error. The procedure is compiled normally, that is, the error does not happen in the compilation, since the line in question is executed through the "execute statement". When there was no need to return the value of the ID field, the procedure worked normally with the line like this:
...
execute statement 'insert into '||:imodulo||' '||:ifieldsvalues||';';
...
What would be the correct way for the value of the ID field to be stored in the OID variable?
What is REAL VALUE in ifieldsvalues ?
you can not have BOTH
'insert into '||:imodulo||' '||:ifieldsvalues
'update '||:imodulo||' set '||:ifieldsvalues
because methods to specify column names and column values in INSERT and UPDATE statements is fundamentally different!!! You either would have broken update-stmt or broken insert-stmt!
The error in the above line is occurring due to syntax error
This is not enough. Show the real error text, all of it.
It includes the actual command you generate and it seems you had generated it really wrong way.
all the scan functions before inserting or updating records
Move those functions out of the SQL server and into your application server.
Then you would not have to make insert/update in that "strings splicing" way, which is VERY fragile and "SQL injection" friendly. You stepped into the road to hell here.
the error does not happen in the compilation
Exactly. And that is only for starters. You are removing all the safety checks that should had helped you in applications development.
http://searchsoftwarequality.techtarget.com/definition/3-tier-application
https://en.wikipedia.org/wiki/Multitier_architecture#Three-tier_architecture
http://bobby-tables.com
On modern Firebird versions EXECUTE STATEMENT command can have the same INTO clause as PSQL SELECT command.
https://www.firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-psql-coding.html#fblangref25-psql-execstmt
Use http://translate.ru to read http://www.firebirdsql.su/doku.php?id=execute_statement
Or just see SQL examples there. Notice, however, those examples all use SELECT dynamic command, not INSERT. So I am not sure it would work that way.
This works in Firebird 2.5 (but not in Firebird 2.1) PSQL blocks.
execute statement 'insert into Z(payload) values(2) returning id' into :i;
To run it from IBExpert/FlameRobin/iSQL interactive shell add that obvious boilerplate:
execute block returns (i integer) as
begin
execute statement 'insert into Z(payload) values(2) returning id' into :i;
suspend;
end

Define variable and run a query using EXECUTE BLOCK

I have a large query in Firebird (which I run using FlameRobin), using a parameter all over the place, but getting the query below to run will do:
SELECT * FROM customers WHERE customerid = 1234;
I want to define 1234 as a variable, say customerID, so that I can easily replace it with something else.
I've learned that I need to put this inside a EXECUTE BLOCK.
EXECUTE BLOCK
AS
DECLARE customerID INT = 1234;
BEGIN
SELECT * FROM customers WHERE customerid = :customerID
END
If of any importance, the error I am getting is Engine Message :
Dynamic SQL Error
SQL error code = -104
Unexpected end of command - line 3, column 26
The problem is that FlameRobin needs to know when a statement ends and the next statement starts. By default it uses the semicolon (;) for this. However an EXECUTE BLOCK is essentially a stored procedure that isn't stored in the database, so it contains PSQL code that also uses the semicolon as a statement separator.
The consequence of this is that you get syntax errors because FlameRobin is sending incomplete statements to the server (that is: it is sending a statement after each ; it encounters).
You need to instruct FlameRobin to use a different statement terminator using SET TERM. Other Firebird query tools (eg isql) require this as well, but it is not actually part of the syntax of Firebird itself!
So you need to execute your code as:
-- Instruct flamerobin to use # as the terminator
SET TERM #;
EXECUTE BLOCK
AS
DECLARE customerID INT = 1234;
BEGIN
SELECT * FROM customers WHERE customerid = :customerID;
END#
-- Restore terminator to ;
SET TERM ;#
However doing this will still result in an error, because this query is invalid for PSQL: A SELECT in a PSQL block requires an INTO clause to map the columns to variables. And to get the values out of the EXECUTE BLOCK returned to FlameRobin you also need to specify a RETURNS clause as described in the documentation of EXECUTE BLOCK:
-- Instruct flamerobin to use # as the terminator
SET TERM #;
EXECUTE BLOCK
RETURNS (col1 INTEGER, col2 VARCHAR(100))
AS
DECLARE customerID INT = 1234;
BEGIN
SELECT col1, col2 FROM customers WHERE customerid = :customerID INTO :col1, :col2;
SUSPEND;
END#
-- Restore terminator to ;
SET TERM ;#
As far as I know the SUSPEND is technically not required here, but Flamerobin will not fetch the returned row if it isn't included.
However the above will not work if the select produces multiple rows. For that you need to use FOR SELECT ... DO combined with a SUSPEND:
-- Instruct flamerobin to use # as the terminator
SET TERM #;
EXECUTE BLOCK
RETURNS (col1 INTEGER, col2 VARCHAR(100))
AS
DECLARE customerID INT = 1234;
BEGIN
FOR SELECT col1, col2 FROM customers WHERE customerid = :customerID INTO :col1, :col2
DO
SUSPEND;
END#
-- Restore terminator to ;
SET TERM ;#
The SUSPEND here returns the row and waits until the caller has fetched that row and then continues with the FOR loop. This way it will iterate over the results.
IMHO this too much effort for parameterization. You might want to consider simply not parametrizing when you use FlameRobin, or use a tool that supports asking for parameter values for the normal parameter placeholders of Firebird (but to be honest I am not sure if there are any).
Well you need a input and return value, ex:
EXECUTE BLOCK (p_customerID integer=?)
returns(col_1 type of column table.id_cv)
as
begin
for
select col_1 from table
where customerID=:p_customerID
into col_1 do
suspend;
end
Delphi as client side:
myquery1.ParamByName('p_customerID ').Value:=1234;
myquery1.Open()
if not myquery1.IsEmpty then
begin
// using returning values
if myquery1.FieldbyName('col_1').AsString='this' then DoThat();
end;
Use this way ever yout need input parameters and/or returning values;

Error 28 "Out of Stack Space" executing a huge query with VB6 & ADO 2.8

Scenario:
Executing an SQL command from a Visual Basic 6 application using ADO Connection.Execute method through PostgreSQL OLEDB Provider to a PostgreSQL 9.2 database.
Query:
It's a simple EXECUTE prepared_statement_name (x, y, z), though it involves a PostGIS geometry type, thus it becomes something like:
EXECUTE prepared_statement_name (1, ST_GeomFromText('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))', 900001));
Problem:
When the geometry is a huge and complex MULTIPOLYGON which contains many vertexes, the query becomes very lengthy (a few thousands characters) and the Connection.Execute method causes a Error 28: "Out Of Stack Space".
There is no recursion or nested loops involved in the process, it's quite clear the error is due to the excessive length of the query.
I think I could avoid the error if I passed the huge query in "chunks" to the provider before executing it, but this is just an idea and I don't know wether it is possible or not and how.
I have no clue, any help is appreciated.
Since it sounds like this is a VB6 level problem and you're already on current Pg versions I fear you may have to use some specacularly ugly workarounds.
If at all possible, try to find a way to increase the VB6 query buffer size, send the query in chunks via the VB6 ODBC interface, etc. Consider the following an absolute last resort.
Maybe this will give you some saner clues. I don't speak VB6 (thankfully) so I can't evaulate it: http://www.mrexcel.com/forum/excel-questions/61340-error-28-out-stack-space.html
Use the following only as a last resort if all else fails:
Create a TEMPORARY table like CREATE TEMPORARY TABLE my_query(id integer, text querychunk).
INSERT INTO the temporary table your statement, chunk by chunk, using parameterised queries to avoid quoting issues.
Create a wrapper PL/PgSQL function that does a RETURN QUERY EXECUTE format('EXECUTE stm_name(...)', passing the string_agg of the temp table as a parameter. Yes, that's amazingly ugly.
Here's a demo, some of the most horrible code I've ever written:
CREATE TABLE real_table (blah text);
PREPARE test_stm2(text) AS INSERT INTO real_table VALUES ($1);
CREATE TEMPORARY TABLE data_chunks(datord integer, datchunk text);
PREPARE chunk_insert(integer, text) AS INSERT INTO data_chunks(datord,datchunk) VALUES ($1,$2);
-- You'll really want to do this via proper parameterised statements
-- to avoid quoting nightmares; I'm using dollar-quoting as a workaround
EXECUTE chunk_insert(0, $val$POLYGON((0 0, 0 1, 1 1,$val$);
EXECUTE chunk_insert(1, $val$ 1, 1 0, 0 0))$val$);
DO
$$
BEGIN
EXECUTE 'EXECUTE test_stm2($1);'
USING
(SELECT string_agg(datchunk,'' ORDER BY datord) FROM data_chunks);
END;
$$ LANGUAGE plpgsql;
Result:
regress=> SELECT * FROM real_table ;
blah
---------------------------------------
POLYGON((0 0, 0 1, 1 1, 1, 1 0, 0 0))
(1 row)
A similar approach is possible for SELECT. You'd use RETURN QUERY EXECUTE within a function defined by CREATE OR REPLACE FUNCTION since DO blocks can't return a result. For example, for a query that returns a SETOF INTEGER you might write:
CREATE OR REPLACE FUNCTION test_wrapper_func() RETURNS SETOF integer AS $$
BEGIN
RETURN QUERY EXECUTE format('EXECUTE test_stm(%L);', (SELECT string_agg(datchunk,'' ORDER BY datord) FROM data_chunks));
END;
$$ LANGUAGE plpgsql;
You'll note the two-level EXECUTE. That's because the PL/PgSQL EXECUTE is quite a different statement to SQL-level EXECUTE. PL/PgSQL EXECUTE runs a string as dynamic SQL, wheras the SQL EXECUTE runs a prepared statement. Here we're running a prepared statement via dynamic SQL. Ick.
Wondering why I'm using PL/PgSQL? Because you can't use a subquery as an EXECUTE parameter. You can avoid the PL/PgSQL wrapper for the query if you don't run it as a prepared statement.
regress=> EXECUTE test_stm2( (SELECT string_agg(datchunk,'' ORDER BY datord) FROM data_chunks) );
ERROR: cannot use subquery in EXECUTE parameter

Loop through array parameter WITHOUT foreach

CREATE OR REPLACE FUNCTION fnMyFunction(recipients recipient[]) ...
FOREACH v_recipient IN ARRAY recipients
LOOP
v_total := v_total + v_recipient.amount;
INSERT INTO tmp_recipients(id, amount)
VALUES(v_recipient.id, v_recipient.amount::numeric(10,2));
END LOOP;
...
This works great in dev environment, but just found out the release environment is 8.4 which doesn't appear to support the FOREACH construct. I was hoping someone might shed some light on an alternative implementation for loop though the array parameter set and using values from array in a similar fashion to avoid a complete refactor.
The error message I am receiving is:
ERROR: syntax error at or near "FOREACH" SQL state: 42601 Context: SQL
statement in PL/PgSQL function "fnMyFunction" near line ##
The db environment is on a shared host so I have no options for platform upgrade.
I tagged postgres 9.1 and 8.4 because the function works properly in 9.x but fails on 8.4.
Use unnest; I think it was in 8.4. Untested but I think is right:
FOR v_recipient IN SELECT vr FROM unnest(recipients) x(vr)
LOOP
....
END LOOP;
If you can't do that you'll have to loop over array_length using indexing into the array.
The way you have it, you execute one INSERT at a time. With relational databases, set-based operations are regularly much faster than iterating through records one at a time.
This should be simpler, faster and work with PostgreSQL 8.4 or later:
INSERT INTO tmp_recipients(id, amount)
SELECT (r.col).*
FROM (SELECT unnest(recipients) AS col) r
This assumes that the composite base type of the array consists of (id, amount) - in that order - and the type of amount can be coerced to numeric(10,2). Else, or just to be sure, be more explicit:
INSERT INTO tmp_recipients(id, amount)
SELECT (r.col).id, (r.col).amount::numeric(10,2)
FROM (SELECT unnest(recipients) AS col) r
The parentheses around (r.col) are not optional. They are required to disambiguate the syntax for the composite type.