2016-02-22 1 views
1

Я создал триггер базы данных для хранения данных строки в таблице аудита. Во время операции обновления этот триггер принимает данные из основной таблицы и вставляет их в таблицу предыстории. (таблица истории имеет столбцы: дата, тип операции говорят об обновлении/удалении, фактические данные строки) Но триггер в некоторых случаях не работает из-за цитируемого текста во входных данных.Postgresql: Как избежать одиночных кавычек в триггере базы данных?

Как я могу избежать цитируемого текста в моем триггере?

--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; 

Это отлично работает для обычных случаев использования, но если данные VARCHAR имеет единственный цитируемый текст, то он не может загрузить данные в таблицу истории.

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 

Я новичок в Postgresql. Я попытался с опциями, как

regexp_replace() API 

и

SELECT into temp_row.row_data unnest(('{' || trim((temp_row.row_data)::text, '()') || '}')::text[]); 

и т.д., но я не мог понять, как проходные данные ROWTYPE и создать правильную запись вставки.

Пожалуйста, поделитесь своими мыслями о том, как я могу изменить свой триггер, чтобы вставить текст с одинарными кавычками.

Спасибо,

ответ

3

В общем сингл кавычки экранируются от их удвоения.

Чтобы поместить конкатенации переменных в строку SQL, вы должны использовать quote_literal() - эта функция заботится о правильном отводящих одинарных кавычках, например:

quote_literal(temp_row.row_data) 

Сказав, что: лучше (и безопаснее), решения использовать параметры в сочетании с 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; 

%I заполнитель, как правило, берет на себя должным образом избежать идентификатор, хотя в этом случае он не будет работать. Если вы хотите быть на 100% уверен, что даже нестандартные имена таблиц работают должным образом, вам нужно сначала поместить имя целевой таблицы в переменную и использовать его для format() функции:

l_tablename := TG_TABLE_NAME || '_history'; 
EXECUTE 
    format('INSERT INTO audit.%I_history values ($1, $2, $3)', l_tablename) 
    using .... 

Эта часть :

v_sql = 'select * from ' || TG_TABLE_NAME::regclass || '_history'; 
execute v_sql into temp_row; 

будет неудачно после первого ряда. execute .. into ... ожидает, что запрос вернет . Выбранное вами заявление вернет все строк из таблицы истории.

Я также не понимаю, почему вы это делаете в первую очередь.

Вам необязательно выбирать из таблицы истории.

Нечто должно быть достаточно (непроверено!):

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; 

Наконец: аудит Триггеры были написаны раньше, и есть много готовых решений для этого: