具有当前表名称的Postgresql动态函数

编程入门 行业动态 更新时间:2024-10-24 04:48:30
本文介绍了具有当前表名称的Postgresql动态函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

我有一个接受表名数组的函数( audit.create_audit_table())。它创建单个函数 audit.if_modified_func(),然后遍历每个表名,创建一个审计表,并将触发器应用于主表。该函数可以编译并且不会出错。当我运行函数

I have a function (audit.create_audit_table()) that accepts an array of table names. It creates a single function audit.if_modified_func() and then loops through each table name and creates an audit table and applies a trigger to the main table. The function compiles and is created with no errors. When I run the function

select audit.create_audit_table(ARRAY['organization'])

我一直收到以下错误,我不确定为什么,因为我认为 TG_TABLE_NAME 是一个自动变量,它将使我能够访问正在执行 audit.if_modified_func()

I keep getting the following error and I am not sure why because I thought that TG_TABLE_NAME is a automatic variable which will give me access to the current table that is executing audit.if_modified_func()

ERROR :

ERROR: column "tg_table_name" does not exist LINE 3: audit_row audit.' || quote_ident(TG_TABLE_NAME::TEXT)||'; ^

这里是函数:

CREATE OR REPLACE FUNCTION audit.create_audit_table(table_names character varying[]) RETURNS character varying AS $BODY$ DECLARE table_name varchar; i int; BEGIN EXECUTE 'CREATE OR REPLACE FUNCTION audit.if_modified_func() RETURNS TRIGGER AS $$ DECLARE audit_row audit.' || quote_ident(TG_TABLE_NAME::TEXT)||'; include_values boolean; log_diffs boolean; h_old hstore; h_new hstore; excluded_cols text[] = ARRAY[]::text[]; BEGIN IF TG_WHEN <> ''AFTER'' THEN RAISE EXCEPTION ''audit.if_modified_func() may only run as an AFTER trigger''; END IF; audit_row = ROW( nextval(''audit.'|| quote_ident(TG_TABLE_NAME::text) ||'_event_id_seq''), -- event_id TG_TABLE_SCHEMA::text, -- schema_name TG_TABLE_NAME::text, -- table_name TG_RELID, -- relation OID for much quicker searches session_user::text, -- session_user_name current_timestamp, -- action_tstamp_tx statement_timestamp(), -- action_tstamp_stm clock_timestamp(), -- action_tstamp_clk txid_current(), -- transaction ID current_setting(''application_name''), -- client application inet_client_addr(), -- client_addr inet_client_port(), -- client_port current_query(), -- top-level query or queries (if multistatement) from client substring(TG_OP,1,1), -- action NULL, NULL, -- row_data, changed_fields ''f'' -- statement_only ); IF NOT TG_ARGV[0]::boolean IS DISTINCT FROM ''f''::boolean THEN audit_row.client_query = NULL; END IF; IF TG_ARGV[1] IS NOT NULL THEN excluded_cols = TG_ARGV[1]::text[]; END IF; IF (TG_OP = ''UPDATE'' AND TG_LEVEL = ''ROW'') THEN audit_row.row_data = hstore(OLD.*) - excluded_cols; audit_row.changed_fields = (hstore(NEW.*) - audit_row.row_data) - excluded_cols; IF audit_row.changed_fields = hstore('''') THEN -- All changed fields are ignored. Skip this update. RETURN NULL; END IF; ELSIF (TG_OP = ''DELETE'' AND TG_LEVEL = ''ROW'') THEN audit_row.row_data = hstore(OLD.*) - excluded_cols; ELSIF (TG_OP = ''INSERT'' AND TG_LEVEL = ''ROW'') THEN audit_row.row_data = hstore(NEW.*) - excluded_cols; ELSIF (TG_LEVEL = ''STATEMENT'' AND TG_OP IN (''INSERT'',''UPDATE'',''DELETE'',''TRUNCATE'')) THEN audit_row.statement_only = ''t''; ELSE RAISE EXCEPTION ''[audit.if_modified_func] - Trigger func added as trigger for unhandled case: %%, %%'',TG_OP, TG_LEVEL; RETURN NULL; END IF; INSERT INTO audit.'|| quote_ident(TG_TABLE_NAME::TEXT) ||' VALUES (audit_row.*); RETURN null; END; $$ LANGUAGE plpgsql; ALTER FUNCTION audit.if_modified_func() OWNER TO postgres;'; FOR i in 1..array_upper(table_names, 1) LOOP EXECUTE format(' DROP TABLE IF EXISTS audit.%1$s; CREATE TABLE audit.%1$s ( event_id bigserial primary key, schema_name text not null, table_name text not null, relid oid not null, session_user_name text, action_tstamp_tx TIMESTAMP WITH TIME ZONE NOT NULL, action_tstamp_stm TIMESTAMP WITH TIME ZONE NOT NULL, action_tstamp_clk TIMESTAMP WITH TIME ZONE NOT NULL, transaction_id bigint, application_name text, client_addr inet, client_port integer, client_query text, action TEXT NOT NULL CHECK (action IN (''I'',''D'',''U'', ''T'')), row_data hstore, changed_fields hstore, statement_only boolean not null ); REVOKE ALL ON audit.%1$s FROM public; COMMENT ON TABLE audit.%1$s IS ''History of auditable actions on audited tables, from audit.if_modified_func()''; COMMENT ON COLUMN audit.%1$s.event_id IS ''Unique identifier for each auditable event''; COMMENT ON COLUMN audit.%1$s.schema_name IS ''Database schema audited table for this event is in''; COMMENT ON COLUMN audit.%1$s.table_name IS ''Non-schema-qualified table name of table event occured in''; COMMENT ON COLUMN audit.%1$s.relid IS ''Table OID. Changes with drop/create. Get with ''''tablename''''::regclass''; COMMENT ON COLUMN audit.%1$s.session_user_name IS ''Login / session user whose statement caused the audited event''; COMMENT ON COLUMN audit.%1$s.action_tstamp_tx IS ''Transaction start timestamp for tx in which audited event occurred''; COMMENT ON COLUMN audit.%1$s.action_tstamp_stm IS ''Statement start timestamp for tx in which audited event occurred''; COMMENT ON COLUMN audit.%1$s.action_tstamp_clk IS ''Wall clock time at which audited event''''s trigger call occurred''; COMMENT ON COLUMN audit.%1$s.transaction_id IS ''Identifier of transaction that made the change. May wrap, but unique paired with action_tstamp_tx.''; COMMENT ON COLUMN audit.%1$s.client_addr IS ''IP address of client that issued query. Null for unix domain socket.''; COMMENT ON COLUMN audit.%1$s.client_port IS ''Remote peer IP port address of client that issued query. Undefined for unix socket.''; COMMENT ON COLUMN audit.%1$s.client_query IS ''Top-level query that caused this auditable event. May be more than one statement.''; COMMENT ON COLUMN audit.%1$s.application_name IS ''Application name set when this audit event occurred. Can be changed in-session by client.''; COMMENT ON COLUMN audit.%1$s.action IS ''Action type; I = insert, D = delete, U = update, T = truncate''; COMMENT ON COLUMN audit.%1$s.row_data IS ''Record value. Null for statement-level trigger. For INSERT this is the new tuple. For DELETE and UPDATE it is the old tuple.''; COMMENT ON COLUMN audit.%1$s.changed_fields IS ''New values of fields changed by UPDATE. Null except for row-level UPDATE events.''; COMMENT ON COLUMN audit.%1$s.statement_only IS ''''''t'''' if audit event is from an FOR EACH STATEMENT trigger, ''''f'''' for FOR EACH ROW''; CREATE INDEX %1$s_relid_idx ON audit.%1$s(relid); CREATE INDEX %1$s_action_tstamp_tx_stm_idx ON audit.%1$s(action_tstamp_stm); CREATE INDEX %1$s_action_idx ON audit.%1$s(action); ', table_names[i]); EXECUTE format(' DROP TRIGGER IF EXISTS audit_trigger_row ON %1$s; CREATE TRIGGER audit_trigger_row AFTER INSERT OR UPDATE OR DELETE ON public.%1$s FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func();', table_names[i]); EXECUTE format(' DROP TRIGGER IF EXISTS audit_trigger_stm ON %1$s; CREATE TRIGGER audit_trigger_stm AFTER TRUNCATE ON public.%1$s FOR EACH STATEMENT EXECUTE PROCEDURE audit.if_modified_func();', table_names[i]); END LOOP; RETURN 'SUCCESS'; END; $BODY$ LANGUAGE plpgsql; ALTER FUNCTION audit.create_audit_table(character varying[]) OWNER TO postgres;

UPDATE 03/31:

好,所以我创建了 if_modified_func()函数而不使用动态SQL,并且我将audit_row声明为 audit_row RECORD; 我不确定在插入值时需要强制转换部分。我也不确定这是否是插入的正确方法

Ok, so I created the if_modified_func() function without the dynamic sql and I declared the audit_row as audit_row RECORD; I am not sure about the part of "needing a cast upon inserting the values". I am also not sure if this is the correct way to do the insert

EXECUTE格式($ string $ INSERT INTO audit。%1 $ s VALUES(audit_row。*); $ string $,TG_TABLE_NAME :: text);

我现在在运行选择audit.create_audit_table(ARRAY ['organization'])

错误:

ERROR: record "audit_row" has no field "row_data" CONTEXT: PL/pgSQL function audit.if_modified_func() line 42 at assignment

以下是更新的函数:

CREATE OR REPLACE FUNCTION audit.if_modified_func() RETURNS TRIGGER AS $$ DECLARE audit_row RECORD; include_values boolean; log_diffs boolean; h_old hstore; h_new hstore; excluded_cols text[] = ARRAY[]::text[]; BEGIN IF TG_WHEN <> 'AFTER' THEN RAISE EXCEPTION 'audit.if_modified_func() may only run as an AFTER trigger'; END IF; audit_row = ROW( nextval(format('audit.%1$s_event_id_seq',TG_TABLE_NAME::text)), -- event_id TG_TABLE_SCHEMA::text, -- schema_name TG_TABLE_NAME::text, -- table_name TG_RELID, -- relation OID for much quicker searches session_user::text, -- session_user_name current_timestamp, -- action_tstamp_tx statement_timestamp(), -- action_tstamp_stm clock_timestamp(), -- action_tstamp_clk txid_current(), -- transaction ID current_setting('application_name'), -- client application inet_client_addr(), -- client_addr inet_client_port(), -- client_port current_query(), -- top-level query or queries (if multistatement) from client substring(TG_OP,1,1), -- action NULL, NULL, -- row_data, changed_fields 'f' -- statement_only ); IF NOT TG_ARGV[0]::boolean IS DISTINCT FROM 'f'::boolean THEN audit_row.client_query = NULL; END IF; IF TG_ARGV[1] IS NOT NULL THEN excluded_cols = TG_ARGV[1]::text[]; END IF; IF (TG_OP = 'UPDATE' AND TG_LEVEL = 'ROW') THEN audit_row.row_data = hstore(OLD.*) - excluded_cols; audit_row.changed_fields = (hstore(NEW.*) - audit_row.row_data) - excluded_cols; IF audit_row.changed_fields = hstore('') THEN -- All changed fields are ignored. Skip this update. RETURN NULL; END IF; ELSIF (TG_OP = 'DELETE' AND TG_LEVEL = 'ROW') THEN audit_row.row_data = hstore(OLD.*) - excluded_cols; ELSIF (TG_OP = 'INSERT' AND TG_LEVEL = 'ROW') THEN audit_row.row_data = hstore(NEW.*) - excluded_cols; ELSIF (TG_LEVEL = 'STATEMENT' AND TG_OP IN ('INSERT','UPDATE','DELETE','TRUNCATE')) THEN audit_row.statement_only = 't'; ELSE RAISE EXCEPTION '[audit.if_modified_func] - Trigger func added as trigger for unhandled case: %, %',TG_OP, TG_LEVEL; RETURN NULL; END IF; EXECUTE format('INSERT INTO audit.%1$s VALUES (audit_row.*)', TG_TABLE_NAME::text); RETURN null; END; $$ LANGUAGE plpgsql; ALTER FUNCTION audit.if_modified_func() OWNER TO postgres;

推荐答案

TG_TABLE_NAME 是一个特殊的触发器变量,仅在触发器函数内部可用。您的 create_audit_table()不是触发函数。

TG_TABLE_NAME is a special, trigger variable, which is only available inside trigger functions. Your create_audit_table() is not a trigger function.

此外,您还在不断重新定义实际的触发函数( if_modified_func()),可无效任何先前创建的触发器。

Also, you constantly redefining your real trigger function (if_modified_func()), which "invalidates" any earlier created triggers.

创建您的触发器函数无动态SQL的魔力(动态SQL只需要在这些审计表中插入值)。然后,您可以将审计逻辑添加到具有以下内容的表中:

Create your trigger function without the dynamic SQL magic (dynamic SQL will only need to insert values to these audit tables). Then, you can add your audit logic to a table with:

CREATE TRIGGER audit_trigger_row AFTER INSERT OR UPDATE OR DELETE ON public.<your_table_name> FOR EACH ROW EXECUTE PROCEDURE <your_audit_trigger_function_name>();

您可以放置​​此对象(但只能放置此对象-如果存在的话,可以放置 ),以便更轻松地附加此审核逻辑。

You can put this (but only this -- maybe with drop if exists) inside a function, to allow attaching this audit logic more easily.

注释:

  • 在触发函数中,您不能使用%ROWTYPE 变量(因为您不知道确切的表格(只有名称)。解决方案很简单:只需使用 RECORD 类型(尽管在插入值时需要强制转换)。
  • Don'对于这么长的字符串,请不要使用单引号。请使用 $ your_keyword $< string_value> $ your_keyword $ 格式。可能使用 format()函数,而不仅仅是连接值。您的代码将更具可读性。
  • Inside the trigger function, you cannot use a %ROWTYPE variable (because you don't know the exact table. you only have its name). The solution is simple: just use the RECORD type instead (you will need a cast upon inserting the values though).
  • Don't use single quotes for such long strings. Use the $your_keyword$<string_value>$your_keyword$ format instead. With possibly the format() function instead of just concatenating values. Your code will be much more readable.

编辑:利用您的 RECORD 变量,您应该:

Edit: to utilize your RECORD variable, you should either:

  • 使用结构初始化它。

  • Initialize it with a structure. You can do this in your case with f.ex.

SELECT nextval('audit.'|| quote_ident(TG_TABLE_NAME) || '_event_id_seq') AS event_id, TG_TABLE_SCHEMA AS schema_name, TG_TABLE_NAME AS table_name, TG_RELID AS relid, session_user AS session_user_name, current_timestamp AS action_tstamp_tx, statement_timestamp() AS action_tstamp_stm, clock_timestamp() AS action_tstamp_clk, txid_current() AS transaction_id, current_setting('application_name') AS application_name, inet_client_addr() AS client_addr, inet_client_port() AS client_port, current_query() AS client_query, substring(TG_OP, 1, 1) AS action, NULL::hstore AS row_data, NULL::hstore AS changed_fields, FALSE AS statement_only INTO audit_row;

  • 使用 ROW()构造函数。第一列的名称为 f1 ,第二列的名称为 f2 ,依此类推。

  • Use the predefined names of the ROW() constructor. The first column's name if f1, the second's is f2, etc.

    audit_row.f15 = hstore(OLD.*) - excluded_cols;

  • 选择上述方法之一后,您可以应该插入以下行:

    After choosing one of the above methods, you should insert the row like:

    EXECUTE format('INSERT INTO audit.%1$s VALUES (($1::text::audit.%1$s).*)', quote_ident(TG_TABLE_NAME)) USING audit_row;

    注意:甚至强制转换为 text 是必需的,因为 EXECUTE 无法知道 audit_row 的实际结构。

    Note: even the cast to text is required due to the fact that EXECUTE cannot know the actual structure of audit_row.

    rextester/GUAJ1339

    更多推荐

    具有当前表名称的Postgresql动态函数

    本文发布于:2023-10-25 14:13:03,感谢您对本站的认可!
    本文链接:https://www.elefans.com/category/jswz/34/1527191.html
    版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
    本文标签:函数   名称   动态   Postgresql

    发布评论

    评论列表 (有 0 条评论)
    草根站长

    >www.elefans.com

    编程频道|电子爱好者 - 技术资讯及电子产品介绍!