Postgresql: How to escape single quotes in Database trigger?
I created a database trigger to store the row data in an auditing table. During the update operation, this trigger takes data from the main table and inserts it to a history table. (history table has columns: date, operation type say Update/Delete, actual row data) But the trigger fails in some cases because of the quoted text in input data.
How can I escape the quoted text in my trigger?
--My trigger
CREATE OR REPLACE FUNCTION audit.if_modified() RETURNS TRIGGER AS $function$
DECLARE
temp_row RECORD; -- a temporary variable used on updates/deletes
v_sql text;
BEGIN
IF TG_WHEN <> 'AFTER' THEN
RAISE EXCEPTION 'audit.if_modified() may only run as an AFTER trigger';
END IF;
v_sql = 'select * from ' || TG_TABLE_NAME::regclass || '_history';
execute v_sql into temp_row;
select now() into temp_row.action_tstamp_tx;
temp_row.action = SUBSTRING(TG_OP,1,1);
IF (TG_OP = 'UPDATE' AND TG_LEVEL = 'ROW') THEN
temp_row.row_data = OLD;
ELSIF (TG_OP = 'DELETE' AND TG_LEVEL = 'ROW') THEN
temp_row.row_data = OLD;
ELSIF (TG_OP = 'INSERT' AND TG_LEVEL = 'ROW') THEN
temp_row.row_data = NEW;
ELSE
RAISE EXCEPTION '[audit.if_modified] - Trigger func added as trigger for unhandled case: %, %',TG_OP, TG_LEVEL;
RETURN NULL;
END IF;
EXECUTE 'INSERT INTO audit.' || TG_TABLE_NAME::regclass || '_history VALUES (''' ||
temp_row.action_tstamp_tx || ''',''' ||
temp_row.action || ''',''' ||
temp_row.row_data || ''')';
RETURN NULL;
END;
$function$
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = audit,public,pg_catalog;
This works fine for normal use cases but if the varchar data has single quoted text, then it fails to load data into history table.
ERROR: syntax error at or near "s"
LINE 1: ...VALUES ('2016-02-22 11:44:43.994295-06','U','(6,Tom's,"2016-02...
^
QUERY: INSERT INTO audit.test_history VALUES ('2016-02-22 11:44:43.994295-06','U','(6,Tom's,"2016-02-22 09:49:32.315543")')
CONTEXT: PL/pgSQL function if_modified() line 30 at EXECUTE
I am new to Postgresql. I tried with options like
regexp_replace() API
and
SELECT into temp_row.row_data unnest(('{' || trim((temp_row.row_data)::text, '()') || '}')::text[]);
etc but I couldn't understand how to loop through the ROWTYPE data and create the correct insert record.
Please share your thoughts on how can I edit my trigger to insert text with single quotes.
Thanks,
In general single, quotes are escaped by doubling them.
To put concatenate your variables into a SQL string, you should use quote_literal()
- that function takes care of properly escaping single quote, eg:
quote_literal(temp_row.row_data)
Having said that: the better (and safer) solution is to use parameters combined with format()
:
EXECUTE
format('INSERT INTO audit.%I_history values ($1, $2, $3)', tg_table_name)
using temp_row.action_tstamp_tx, temp_row.action, temp_row.row_data;
The %I
placeholder usually takes care of properly escaping an identifier, although in this case it would not work. If you want to be 100% sure that even non-standard table names work properly, you need to first put the target table name into a variable and use that for the format()
function:
l_tablename := TG_TABLE_NAME || '_history';
EXECUTE
format('INSERT INTO audit.%I_history values ($1, $2, $3)', l_tablename)
using ....
This part:
v_sql = 'select * from ' || TG_TABLE_NAME::regclass || '_history';
execute v_sql into temp_row;
is going to fail after the first row as well. execute .. into ...
expects the query to return a single . The statement you are using will return all rows from the history table.
I also don't understand why you do that in the first place.
You don't need to select from the history table at all.
Something like this should be enough ( untested! ):
IF (TG_OP = 'UPDATE' AND TG_LEVEL = 'ROW') THEN
temp_row := OLD;
ELSIF (TG_OP = 'DELETE' AND TG_LEVEL = 'ROW') THEN
temp_row := OLD;
ELSIF (TG_OP = 'INSERT' AND TG_LEVEL = 'ROW') THEN
temp_row := NEW;
ELSE
RAISE EXCEPTION '[audit.if_modified] - Trigger func added as trigger for unhandled case: %, %',TG_OP, TG_LEVEL;
RETURN NULL;
END IF;
execute format ('insert ... values ($1, $2, $3')
using now(), SUBSTRING(TG_OP,1,1), temp_row;
Finally: audit triggers have been written before, and there are a lot of ready-made solutions for this:
上一篇: 设计层次表