我创建了一个数据库触发器,用于将行数据存储在审计表中。 在更新操作期间,此触发器从主表中获取数据并将其插入历史表。 (历史表有列:日期,操作类型说更新/删除,实际行数据) 但是由于输入数据中引用的文本,触发器在某些情况下失败。
如何在触发器中转义引用的文本?
--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数据并创建正确的插入记录。
请分享您对如何编辑触发器以插入带单引号的文本的想法。
谢谢,
答案 0 :(得分: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;
最后:审计触发器之前已经编写过,并且有很多现成的解决方案: