PostgreSQL触发器调用自定义函数报42601语法错误及解决
问题场景
我定义了以下自定义函数:
CREATE or replace FUNCTION get_column_names(param text) RETURNS text AS $get_column_names$ DECLARE return_value text; x record; y int; begin return_value := ''; y := 0; for x in SELECT * FROM information_schema.columns WHERE table_schema = 'public' AND table_name = param ORDER BY ordinal_position loop if (y = 0) then return_value = x.column_name; else return_value := return_value ||','|| x.column_name; end if; Y = Y+1; end loop; return return_value; END; $get_column_names$ LANGUAGE plpgsql;
单独调用该函数可以正常工作:
select get_column_names('users');
返回结果:
first_name,middle_name,last_name,gender,locale,auth_user_id,identity_id,active,date_created,provisioned,user_id,timezone,last_seen
但在触发器函数中调用它时触发错误,触发器函数代码如下:
CREATE OR REPLACE FUNCTION process_users_audit() RETURNS TRIGGER AS $users_audit$ BEGIN -- -- 在users_audit表中插入一行,记录users表上执行的操作 -- IF (TG_OP = 'DELETE') THEN INSERT INTO users_audit (user_audit_id, stamp, operation, db_user, get_column_names(TG_TABLE_NAME)) values (uuid_generate_v4(), now(), 'D', user, OLD.*); RETURN OLD; ELSIF (TG_OP = 'UPDATE') THEN INSERT INTO users_audit (user_audit_id, stamp, operation, db_user, get_column_names(TG_TABLE_NAME)) values (uuid_generate_v4(), now(), 'U', user, NEW.*); RETURN NEW; ELSIF (TG_OP = 'INSERT') THEN INSERT INTO users_audit (user_audit_id, stamp, operation, db_user, get_column_names(TG_TABLE_NAME)) values (uuid_generate_v4(), now(), 'I', user, NEW.*); RETURN NEW; END IF; RETURN NULL; -- 因为是AFTER触发器,返回结果会被忽略 END; $users_audit$ LANGUAGE plpgsql;
收到的错误信息:
SQL Error [42601]: ERROR: syntax error at or near "(" Position: 406
解决方案(更新)
调整@Ed Brook的方案后生效,主要修改了NEW的使用方式。可行代码如下:
CREATE OR REPLACE FUNCTION process_audit_table() RETURNS TRIGGER AS $audit_table$ BEGIN -- -- 在对应表的审计表中插入一行,记录原表上执行的操作, -- 利用特殊变量TG_OP判断操作类型 -- IF (TG_OP = 'DELETE') then EXECUTE 'INSERT INTO ' || concat(TG_TABLE_NAME, '_audit') || ' (' || get_primary_key_name(concat(TG_TABLE_NAME, '_audit')) || ', stamp, operation, db_user, ' || get_column_names(TG_TABLE_NAME) || ') values (uuid_generate_v4(), now(), ''D'', user, $1.*);' USING OLD; ELSIF (TG_OP = 'UPDATE') then EXECUTE 'INSERT INTO ' || concat(TG_TABLE_NAME, '_audit') || ' (' || get_primary_key_name(concat(TG_TABLE_NAME, '_audit')) || ', stamp, operation, db_user, ' || get_column_names(TG_TABLE_NAME) || ') values (uuid_generate_v4(), now(), ''U'', user, $1.*);' USING NEW; ELSIF (TG_OP = 'INSERT') then EXECUTE 'INSERT INTO ' || concat(TG_TABLE_NAME, '_audit') || ' (' || get_primary_key_name(concat(TG_TABLE_NAME, '_audit')) || ', stamp, operation, db_user, ' || get_column_names(TG_TABLE_NAME) || ') values (uuid_generate_v4(), now(), ''I'', user, $1.*);' USING NEW; END IF; RETURN NULL; -- 因为是AFTER触发器,返回结果会被忽略 END; $audit_table$ LANGUAGE plpgsql;
同时新增了动态获取审计表主键的逻辑,对应的get_primary_key_name函数代码:
CREATE OR REPLACE FUNCTION get_primary_key_name(table_name text) RETURNS text AS $primary_key_name$ DECLARE return_value text; BEGIN SELECT pg_attribute.attname INTO return_value FROM pg_index, pg_class, pg_attribute, pg_namespace WHERE pg_class.oid = table_name::regclass AND indrelid = pg_class.oid AND nspname = 'public' AND pg_class.relnamespace = pg_namespace.oid AND pg_attribute.attrelid = pg_class.oid AND pg_attribute.attnum = any(pg_index.indkey) AND indisprimary; RETURN return_value; END $primary_key_name$ LANGUAGE plpgsql;
内容的提问来源于stack exchange,提问作者gmaniac
相关产品推荐
相关产品推荐

