While executing below shown trigger code using ANT I am getting the error
org.postgresql.util.PSQLException: ERROR: unterminated quoted string at or near "' DECLARE timeout integer"
Position: 57
I am able to sucessfully execute the below code through PGADmin (Provided by postgres) and command line utility "psql" and the trigger function is added but while executing through ANT it fails everytime
BEGIN TRANSACTION;
CREATE OR REPLACE FUNCTION sweeper() RETURNS trigger as '
DECLARE
timeout integer;
BEGIN
timeout = 30 * 24 * 60 * 60 ;
DELETE FROM diagnosticdata WHERE current_timestamp - teststarttime > (timeout * ''1 sec''::interval);
return NEW;
END;
' LANGUAGE 'plpgsql';
-- Trigger: sweep on diagnosticdata
CREATE TRIGGER sweep
AFTER INSERT
ON diagnosticdata
FOR EACH ROW
EXECUTE PROCEDURE sweeper();
END;
I encountered this error in liquibase and this page was one of the first search results so I guess I share my solution at this page:
You can put your whole sql in a separate file and include this in the changeset.
Its important to set the splitStatements option to false.
The whole changeset would then look like
<changeSet author="fgrosse" id="530b61fec3ac9">
<sqlFile path="your_sql_file_here.sql" splitStatements="false"/>
</changeSet>
I always like to have those big SQL parts (like function updates and such) in separate files.
This way you get proper syntax highlighting when opening the sql file and dont have to intermix XML and SQL in one file.
Edit: as mentioned in the comments its worth noting that the sql change supports the splitStatements option as well (thx to AndreyT for pointing that out).
I had the same problem with the JDBC driver used by Liquibase.
It seems that the driver explodes each line ended by a semicolon and runs it as a separate SQL command. That is why the code below will be executed by the JDBC driver in the following sequence:
CREATE OR REPLACE FUNCTION test(text) RETURNS VOID AS ' DECLARE tmp text
BEGIN tmp := "test"
END;
' LANGUAGE plpgsql
Of course, this is invalid SQL and causes the following error:
unterminated dollar-quoted string at or near ' DECLARE tmp text
To correct this, you need to use backslashes after each line ended with semicolon:
CREATE OR REPLACE FUNCTION test(text)
RETURNS void AS ' DECLARE tmp text; \
BEGIN
tmp := "test"; \
END;' LANGUAGE plpgsql;
Alternatively, you can place the whole definition in one line.
I am using HeidiSQL client and this was solved by placing DELIMITER // before CREATE OR REPLACE statement. There is a also a 'Send batch in one go' option in HeidiSQL that essentially achieves the same thing.
This error arises as an interaction between the particular client used to connect to the server and the form of the function. To illustrate:
The following code will run without casualty in Netbeans 7, Squirrel, DbSchema, PgAdmin3
CREATE OR REPLACE FUNCTION author.revision_number()
RETURNS trigger AS
$BODY$
begin
new.rev := new.rev + 1;
new.revised := current_timestamp;
return new;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Please note that the 'begin' statement comes immediately after the '$' quoted string.
The next code will halt all the above clients except PgAdmin3.
CREATE OR REPLACE FUNCTION author.word_count()
RETURNS trigger AS
$BODY$
declare
wordcount integer := 0; -- counter for words
indexer integer := 1; -- position in the whole string
charac char(1); -- the first character of the word
prevcharac char(1);
begin
while indexer <= length(new.blab) loop
charac := substring(new.blab,indexer,1); -- first character of string
if indexer = 1 then
prevcharac := ' '; -- absolute start of counting
else
prevcharac := substring(new.blab, indexer - 1, 1); -- indexer has increased
end if;
if prevcharac = ' ' and charac != ' ' then
wordcount := wordcount + 1;
end if;
indexer := indexer + 1;
end loop;
new.words := wordcount;
return new;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
The crucial difference in the second example is the 'declare' section. The ploy of using back-slashes raises an error with PgAdmin3.
In summary I suggest trying different tools. Some tools even though they are supposed to be writing text files put invisible stuff into the text. Notoriously this occurs with the Unicode BOM which will halt any php file that tries to implement sessions or namespaces.
Whilst this is no solution I hope it helps.
I had the same problem with zeos and c++ builder.
The solution in my case:
Change the property delimiter (usually ";") to another in the component (class) I used.
dm->ZSQLProcessor1->DelimiterType=sdGo;
Perhaps Ant have something similar.
I know this question was asked a long time ago but I had kind of the same issue with a Postgresql script (run from Jenkins) using Ant's SQL Task.
I tried to run this SQL (saved in a file named audit.sql):
DROP SCHEMA IF EXISTS audit CASCADE
;
CREATE SCHEMA IF NOT EXISTS audit AUTHORIZATION faktum
;
CREATE FUNCTION audit.extract_interval_trigger ()
RETURNS trigger AS $extractintervaltrigger$
BEGIN
NEW."last_change_ts" := current_timestamp;
NEW."last_change_by" := current_user;
RETURN NEW;
END;
$extractintervaltrigger$ LANGUAGE plpgsql
;
but got the error "unterminated dollar-quoted string". No problem running it from pgAdmin.
I found out that it is not the driver that split the script at every ";" but rather Ant.
At http://grokbase.com/t/postgresql/pgsql-jdbc/06cjx3s3y0/ant-sql-tag-for-dollar-quoting I found the answer:
Ant eats double-$$ as part of its variable processing. You have to use
$BODY$ (or similar) in the stored procs, and put the delimiter on its
own line (with delimitertype="row"). Ant will cooperate then.
My Ant SQL script looks like this and it works:
<sql
driver="org.postgresql.Driver" url="jdbc:postgresql://localhost:5432/jenkins"
userid="user" password="*****"
keepformat="true"
autocommit="true"
delimitertype="row"
encoding="utf-8"
src="audit.sql"
/>
This example worked for me with PostgreSQL 14.1 and HeidiSQL 9.4.0.5125
DROP TABLE IF EXISTS emp;
CREATE TABLE emp (
empname text NOT NULL,
salary integer
);
DROP TABLE IF EXISTS EMP_AUDIT;
CREATE TABLE emp_audit(
operation char(1) NOT NULL,
stamp timestamp NOT NULL,
userid text NOT NULL,
empname text NOT NULL,
salary integer
);
DELIMITER //
CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS $$
BEGIN
--
-- Create a row in emp_audit to reflect the operation performed on emp,
-- make use of the special variable TG_OP to work out the operation.
--
IF (TG_OP = 'DELETE') THEN
INSERT INTO emp_audit SELECT 'D', now(), user, OLD.*;
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO emp_audit SELECT 'U', now(), user, NEW.*;
RETURN NEW;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO emp_audit SELECT 'I', now(), user, NEW.*;
RETURN NEW;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS emp_audit ON emp;
CREATE TRIGGER emp_audit
AFTER INSERT OR UPDATE OR DELETE ON emp
FOR EACH ROW EXECUTE PROCEDURE process_emp_audit();
I was receiving the same error because I had my semicolon in a new line like this:
WHERE colA is NULL
;
Make sure they are in a single line as
WHERE colA is NULL;
Related
I'm trying to debug this adn find out why I'm getting syntax error:
CREATE OR REPLACE FUNCTION public.myfunc(_report_id integer, _cutoff_date date)
RETURNS record
LANGUAGE plpgsql
AS $function$
declare
_deliverable_id RECORD ;
BEGIN
FOR _deliverable_id IN
SELECT deliverable_id FROM public.deliverables where report_id=_report_id
LOOP
execute format('DROP TABLE IF EXISTS report.products_%I',_deliverable_id);
END LOOP;
END
$function$
;
When I execute this, I get:
syntax error at or near ""(1111)""
1111 is one deliverable for sure, so this leads me to think it has something to do with the execute statement format, or the way I'm using %I?
%I is replaced as a whole identifier. If you want to concatenate things, you need to do it before replacement.
You can test/debug this for yourself by inspecting the result of the format() function:
select format('DROP TABLE IF EXISTS report.products_%I',42);
returns DROP TABLE IF EXISTS report.products_"42"
you need to use:
select format('DROP TABLE IF EXISTS report.%I',concat('products_', 42));
which correctly returns DROP TABLE IF EXISTS report.products_42
(obviously you need to replace 42 with your variable.
I am trying to create a function to create table backup dynamically.
But I am getting error like :
ERROR: syntax error at or near "'
Here's one of my approach, which I am trying:
CREATE OR REPLACE FUNCTION public.test () RETURNS varchar AS
$BODY$ DECLARE backup_string varchar(50);
BEGIN
backup_string = (SELECT '_'||LPAD(DATE_PART('DAY',CURRENT_DATE)::VARCHAR,2,'0')||DATE_PART('MONTH',CURRENT_DATE)::VARCHAR||DATE_PART('YEAR',CURRENT_DATE)::VARCHAR||'_1');
EXECUTE 'SELECT * INTO table_name'|| backup_string ||' FROM table_name';
RETURN 'Y';
EXCEPTION WHEN others THEN RETURN 'N';
END
; $BODY$
LANGUAGE 'plpgsql'
GO
SELECT * FROM test()
I am not getting, why that execute statement giving me error like that.
I suggest so simplify your code and make use of the format() function to generate the dynamic SQL. That way you can avoid the clutter that concatenation generates and you can concentrate on the actual SQL code. In addition to that it also properly deals with identifiers that might need quoting.
When dealing with dynamic SQL it's always a good idea to store the generated SQL statement in a variable, so that it can be printed for debugging purposes if you get an error. Looking at the generated SQL usually tells you where the generation code went wrong.
CREATE OR REPLACE FUNCTION test()
RETURNS varchar
AS
$BODY$
DECLARE
l_source_table text;
l_backup_table text;
l_sql text;
BEGIN
l_source_table := 'table_name';
l_backup_table := l_source_table||'_'||to_char(current_date, 'ddmmyyyy')||'_1';
l_sql := format('create table %I as select * from %I', l_backup_table, l_source_table);
-- for debugging purposes:
raise notice 'Running: %', l_sql
EXECUTE l_sql;
RETURN 'Y';
EXCEPTION
WHEN others THEN RETURN 'N';
END;
$BODY$
LANGUAGE plpgsql;
Note that I also used variables for the source and backup table to be able to use that as a place holder for the format() function.
Online example
I'm trying to dynamically partition log entries in Postgres. I have 53 child tables (1 for each week's worth of log entries), and would like to route INSERTs to a child table using a trigger.
I run the function with INSERT INTO log5 VALUES (NEW.*), and it works.
I run the function with the EXECUTE statement instead, and it fails. Within the EXECUTE statement, it's recognizing NEW as a table name and not a variable passed to the trigger function. Any ideas on how to fix? Thanks!
The error:
QUERY: INSERT INTO log5 VALUES (NEW.*)
CONTEXT: PL/pgSQL function log_roll_test() line 6 at EXECUTE statement
ERROR: missing FROM-clause entry for table "new" SQL state: 42P01
My function:
CREATE FUNCTION log_roll_test() RETURNS trigger AS $body$
DECLARE t text;
BEGIN
t := 'log' || extract(week FROM NEW.updt_ts); --child table name
--INSERT INTO log5 VALUES (NEW.*);
EXECUTE format('INSERT INTO %I VALUES (NEW.*);', t);
RETURN NULL;
END;
$body$ LANGUAGE plpgsql;
My trigger:
CREATE TRIGGER log_roll_test
BEFORE INSERT ON log FOR EACH ROW
EXECUTE PROCEDURE log_roll_test();
CREATE FUNCTION log_roll_test()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
EXECUTE format('INSERT INTO %I SELECT ($1).*' -- !
, to_char(NEW.updt_ts, '"log"WW')) -- child table name
USING NEW; -- !
RETURN NULL;
END
$func$;
You cannot reference NEW inside the query string. NEW is visible in the function body, but not inside EXECUTE environment. The best solution is to pass values in the USING clause.
I also substituted the equivalent to_char(NEW.updt_ts, '"log"WW') for the table name. to_char() is faster and simpler here.
Using postgresql 8.4, I'm trying to write a function, and it looks like this:
CREATE OR REPLACE FUNCTION addorupdate( smallint, varchar(7) ) RETURNS void AS
$$
BEGIN
IF EXISTS (SELECT * FROM consist WHERE slave = $1) THEN
UPDATE consist SET
master = $2
where slave = $1;
ELSE
INSERT INTO consist(slave, master) VALUES ( $2, $1 );
END IF;
END;
$$
LANGUAGE SQL;
However, it fails like this:
ERROR: syntax error at or near "IF"
LINE 4: IF EXISTS (SELECT * FROM consist WHERE slave = $1) THEN
...and I've been wasting too much time and caffeine on figuring out why and could use someone with fresh eyes to help me out.
If it's not clear what i'm trying to achieve:
slaves is a column of unique values. If it exists, UPDATE it with the current master. If not, INSERT.
UPDATE:
Changed language to plpgsql, and it now throws:
ERROR: language "plpgsql" does not exist
UPDATE:
CREATE LANGUAGE plpgsql;
(: RESOLVED :)
Your language needs to be plpgsql not sql.
In Postgresql, I have an UPDATE rule on a table which only needs to call a dctUpdate function without doing a whole SQL statement, since the SQL statement is actually done in the function. The only way I know of calling the function is through SELECT dctUpdate(windowId):
create or replace function infoUpdate(windowId in numeric) returns void as $$
begin
if windowId is null then
update info_timestamp set timestamp = now();
else
update info_timestamp set timestamp = now() where window_id = windowId;
end if;
end;
$$ LANGUAGE plpgsql;
create or replace rule info_update_rule as on update to some_table do also select infoUpdate(NEW.window_id);
However, on the command line, when that rule gets triggered because I updated a row in some_table, I get useless output from the SELECT clause that calls the function :
db=# update some_table set name = 'foobar' where window_id = 1;
infoupdate
-----------
(1 row)
UPDATE 1
Is there a way to have info_update_rule call the infoUpdate function without it displaying dummy output?
I've found no options to implement this using rules, but there is an alternative way of implementing this usign triggers.
So, you define trigger function as following:
CREATE OR REPLACE FUNCTION ur_wrapper_trg()
RETURNS trigger AS
$BODY$
begin
perform infoUpdate(NEW.window_id);
RETURN NEW;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION ur_wrapper_trg() OWNER TO postgres;
Note PERFORM syntax is used. This syntax is identical to SELECT syntax except it supresses all output.
Than you define a trigger
CREATE TRIGGER some_table_utrg
BEFORE UPDATE
ON some_table
FOR EACH ROW
EXECUTE PROCEDURE ur_wrapper_trg();
In the end, you remve your rule.
Haven't tested with null, but with actual windos_ids works as expected, without any unwanted output.
Consult with Triggers and Rules vs triggers for detailed description.
The closes solution to which I came is to call \t \a before select function() and right after it. The only remaining thing is a new line for each call.