Postgresql before update/insert trigger doesn't appear to work - postgresql

I have the following trigger function:
CREATE OR REPLACE FUNCTION update_modelname_function()
RETURNS trigger AS
$BODY$
BEGIN
IF tg_op = 'INSERT' THEN
new.model_name := upper(new.model_name);
RETURN new;
END IF;
IF tg_op = 'UPDATE' THEN
old.model_name := upper(old.model_name);
RETURN new;
END IF;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
What I'm trying to achieve is for the value of the column model_name to always be uppercased when it's persisted in the table. However nothing seems to happen. Any ideas?

You accidentally updated OLD instead of NEW. Try:
CREATE OR REPLACE FUNCTION update_modelname_function()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
IF TG_OP = 'INSERT' THEN
NEW.model_name := upper(NEW.model_name);
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
NEW.model_name := upper(NEW.model_name); -- !
RETURN NEW;
END IF;
END
$func$;
If the example shows the whole code, and the actual trigger(s) only fires on INSERT and/or UPDATE, further simplify:
CREATE OR REPLACE FUNCTION update_modelname_function()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
NEW.model_name := upper(NEW.model_name);
RETURN NEW;
END
$func$;

Related

Trigger doesn't work and doesn't show any errors

I'm trying to create a trigger that will shorten the name and the middle name to initials.
That's what i have:
CREATE OR REPLACE FUNCTION myfunc() RETURNS TRIGGER AS $$
DECLARE nm VARCHAR(50);
DECLARE mdnm VARCHAR(50);
BEGIN
nm = LEFT(NEW.name, 1);
mdnm = LEFT(NEW.middle_name, 1);
SET NEW.name = nm;
SET NEW.middle_name = mdnm;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER table_before_insert BEFORE INSERT OR UPDATE ON table1
FOR EACH ROW EXECUTE PROCEDURE myfunc();
But for some reasons it doesn't work, what can it be?
As documented in the manual variables are assigned with := or =. But not with the SET command - which changes configuration properties. You also don't need a separate DECLARE block for each variable:
So your trigger function should look like this:
CREATE OR REPLACE FUNCTION myfunc() RETURNS TRIGGER AS $$
DECLARE
nm VARCHAR(50);
mdnm VARCHAR(50);
BEGIN
nm := LEFT(NEW.name, 1);
mdnm := LEFT(NEW.middle_name, 1);
NEW.name := nm;
NEW.middle_name := mdnm;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
You don't even need the variables:
CREATE OR REPLACE FUNCTION myfunc() RETURNS TRIGGER AS $$
BEGIN
NEW.name := LEFT(NEW.name, 1);
NEW.middle_name := LEFT(NEW.middle_name, 1);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Build a trigger that avoids to update the values of one column

I have a table in PostgretSQL. I want to build a trigger that avoids to update colb1 column. This column has five alternatives: Rea, Can, Loa, Mul, Alm. So the trigger doesn't let upload values from Rea to Can.
I've built this function but this is fail.
CREATE OR REPLACE FUNCTION example_trigger()
RETURNS trigger AS
$BODY$
BEGIN
new.colb1.tabl1 = 'Rea' := old.colb1.tabl1 = 'Can';
new.colb1.tabl1 = 'Can' := old.colb1.tabl1 = 'Rea';
RETURN new;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
DROP TRIGGER IF EXISTS trigger_name ON table1;
CREATE TRIGGER trigger_name BEFORE UPDATE ON table1
FOR EACH ROW EXECUTE PROCEDURE example_trigger();
I edited my post with changes:
CREATE OR REPLACE FUNCTION example_trigger()
RETURNS TRIGGER AS
$$
BEGIN
NEW.colb1 := OLD.colb1;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER not_changes
BEFORE UPDATE
ON tabl1
FOR EACH ROW
EXECUTE PROCEDURE example_trigger();
It's not exactly that I want because I want that the values Loa, Mul and Alm can be update.
A slight modification of your function to stop the modification of colb1 from Rea or Can to Can or Rea.
CREATE OR REPLACE FUNCTION example_trigger()
RETURNS TRIGGER AS
$$
BEGIN
IF OLD.colb1 in ('Rea', Can') AND NEW.colb1 IN ('Rea', 'Can') THEN
NEW.colb1 := OLD.colb1;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
This will not stop a user from using an intermediate UPDATE to change to one of Loa/Mul/Alm and then to either Rea or Can

Trigger close the pg Admin?

I created a trigger fk_pay_trigger on INSERT
CREATE TRIGGER fk_pay_trigger
BEFORE INSERT ON fk_payment_temp
FOR EACH ROW
EXECUTE PROCEDURE add_insert_trigger_fk()
and function attach is:
-- Function: add_insert_trigger_fk()
-- DROP FUNCTION add_insert_trigger_fk();
CREATE OR REPLACE FUNCTION add_insert_trigger_fk()
RETURNS trigger AS
$BODY$
Declare
begin
if (TG_TABLE_NAME = 'fk_payment_temp') then
if (TG_OP = 'INSERT') then
Insert into fk_payment_temp_backup values(now(),NEW.settlement_ref_no, NEW.order_type, NEW.fulfilment_type, NEW.seller_sku, NEW.wsn,
NEW.order_id, NEW.order_item_id, NEW.order_date, NEW.dispatch_date, NEW.delivery_date,
NEW.cancellation_date, NEW.settlement_date, NEW.order_status, NEW.quantity, NEW.order_item_value,
NEW.sale_transaction_amount, NEW.discount_transaction_amount, NEW.refund,
NEW.protection_fund, NEW.total_marketplace_fee, NEW.service_tax, NEW.settlement_value,
NEW.commission_rate, NEW.commission, NEW.payment_rate, NEW.payment_fee, NEW.fee_discount,
NEW.cancellation_fee, NEW.fixed_fee, NEW.emi_fee, NEW.total_weight, NEW.shipping_fee,
NEW.reverse_shipping_fee, NEW.shipping_zone, NEW.token_of_apology, NEW.pick_and_pack_fee,
NEW.storage_fee, NEW.removal_fee, NEW.invoice_id, NEW.invoice_date, NEW.invoice_amount,
NEW.sub_category, NEW.total_offer_amount, NEW.my_offer_share, NEW.flipkart_offer_share);
return NEW;
END if;
End if ;
return null;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Its working fine. but the problem is :
when trigger perform . PG admin Shut down automatically .
I dont know what happen . Is there is any problem in my code or any problem ?

DRY postgresql 9.4 trigger functions

I've written 3 functions to log transactions to designated tables:
CREATE OR REPLACE FUNCTION log_sites() RETURNS TRIGGER AS $body$
DECLARE
target_row sites%ROWTYPE;
BEGIN
IF (TG_OP = 'DELETE') THEN
-- No NEW row
target_row = OLD;
ELSE
target_row = NEW;
END IF;
INSERT INTO sites_history (transaction_type,
transaction_time,
site_id,
address,
name,
shared_key)
VALUES (TG_OP,
NOW(),
target_row.site_id,
target_row.address,
target_row.name,
target_row.shared_key);
RETURN target_row;
END;
$body$
LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION log_licenses() RETURNS TRIGGER AS $body$
DECLARE
target_row licenses%ROWTYPE;
BEGIN
IF (TG_OP = 'DELETE') THEN
target_row = OLD;
ELSE
target_row = NEW;
END IF;
INSERT INTO licenses_history (transaction_type,
transaction_time,
license_id,
start_date,
expiration_date,
site_id)
VALUES (TG_OP,
NOW(),
target_row.license_id,
target_row.start_date,
target_row.expiration_date,
target_row.site_id);
RETURN target_row;
END;
$body$
LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION log_clients() RETURNS TRIGGER AS $body$
DECLARE
target_row clients%ROWTYPE;
BEGIN
IF (TG_OP = 'DELETE') THEN
target_row = OLD;
ELSE
target_row = NEW;
END IF;
INSERT INTO clients_history (transaction_type,
transaction_time,
mac_address,
hardware,
license_id,
site_id)
VALUES (TG_OP,
NOW(),
target_row.mac_address,
target_row.hardware,
target_row.license_id,
target_row.site_id);
RETURN target_row;
END;
$body$
LANGUAGE plpgsql;
This results in a big ugly block of PL/pgSQL, which nobody at my job is especially familiar with, myself included. A coworker suggested it'd be nice to consolidate/DRYify all this stuff, but for the life of me, I'm not sure how, especially considering each table needs a separate trigger, and the way triggers pass data to their functions. Any suggestions?
ETA:
1) Here are the triggers:
CREATE TRIGGER sites_log
AFTER INSERT OR UPDATE OR DELETE
ON sites
FOR EACH ROW EXECUTE PROCEDURE log_transactions();
CREATE TRIGGER licenses_log
AFTER INSERT OR UPDATE OR DELETE
ON licenses
FOR EACH ROW EXECUTE PROCEDURE log_transactions();
CREATE TRIGGER clients_log
AFTER INSERT OR UPDATE OR DELETE
ON clients
FOR EACH ROW EXECUTE PROCEDURE log_transactions();
Here's what I've got now, after quite a bit of messing around:
CREATE OR REPLACE FUNCTION log_transactions() RETURNS TRIGGER LANGUAGE plpgsql AS $body$
DECLARE
target_row RECORD;
target_cols text[];
col_name RECORD;
col_name_str text;
right_now timestamp without time zone;
q_str text;
BEGIN
right_now := now();
target_cols := '{}';
FOR col_name IN SELECT column_name::text FROM information_schema.columns WHERE table_name = TG_TABLE_NAME AND table_schema = TG_TABLE_SCHEMA LOOP
col_name_str := col_name.column_name::text;
target_cols = ARRAY_APPEND(target_cols, col_name_str);
END LOOP;
RAISE NOTICE 'target_cols: %', target_cols;
IF (TG_OP = 'DELETE') THEN
target_row := OLD;
ELSE
target_row := NEW;
END IF;
RAISE NOTICE 'target_row: %', target_row;
EXECUTE format('INSERT INTO %I_history (transaction_time, transaction_type) VALUES (%L, %L)', TG_TABLE_NAME, right_now, TG_OP);
q_str := format('UPDATE %I_history SET (%s) = ', TG_TABLE_NAME, array_to_string(target_cols, ', ')) || '$1' || format(' WHERE transaction_type = %L AND transaction_time = %L', TG_OP, right_now);
EXECUTE q_str USING target_row;
RETURN target_row;
END;
$body$;
This doesn't work either, and it's spiraling out of control, complexity-wise.
Personally, I use a set of home-grown functions Below for auditing any table I want. All I do is run audit.enable() on any table I want to audit and it's stored in the general tables I have here. Yes, it's not exactly what you're doing, but it's the most "DRY" think there is really, I do it once and never again-- ever.
CREATE TABLE audit.audit_log
(
audit_log_seq serial primary key,
schema_name text NOT NULL,
table_name text NOT NULL,
db_username text,
user_seq bigint,
logged_ip inet,
log_timestamp TIMESTAMP NOT NULL DEFAULT (now()),
action TEXT NOT NULL CHECK (action IN ('I','D','U')),
comment varchar(500),
old_data hstore,
new_data hstore,
query text
) WITH
(
fillfactor=100
);
CREATE INDEX audit_log_schema_table_idx ON audit.audit_log(schema_name,table_name);
CREATE INDEX audit_log_timestamp_utc_idx ON audit.audit_log(log_timestamp);
CREATE INDEX audit_log_uname on audit.audit_log(user_seq);
-- generic function for all tables
CREATE OR REPLACE FUNCTION audit.log_func() RETURNS TRIGGER AS $body$
DECLARE
v_old_data hstore;
v_new_data hstore;
v_query text;
v_comment varchar;
-- v_old_data TEXT;
-- v_new_data TEXT;
BEGIN
v_query=current_query();
IF (TG_OP = 'UPDATE') THEN
v_old_data := hstore(OLD.*);
v_new_data := hstore(NEW.*);
v_comment=v_new_data -> 'audit_comment';
ELSIF (TG_OP = 'DELETE') THEN
v_old_data := hstore(OLD.*);
ELSIF (TG_OP = 'INSERT') THEN
v_new_data := hstore(NEW.*);
v_comment=v_new_data -> 'audit_comment';
ELSE
RAISE WARNING '[audit.log_func] - Other action occurred: %, at %',TG_OP,now();
RETURN NULL;
END IF;
INSERT INTO audit.audit_log (schema_name,table_name,db_username,user_seq,logged_ip,action,old_data,new_data,query, comment)
VALUES (TG_TABLE_SCHEMA::TEXT,
TG_TABLE_NAME::TEXT,
session_user::TEXT,
coalesce(current_setting('mvc.user_seq'),'0')::bigint, --current user
inet_client_addr(),
substring(TG_OP,1,1),
v_old_data,
v_new_data,
v_query,
v_comment);
IF (TG_OP = 'DELETE') THEN
RETURN OLD;
END IF;
RETURN NEW;
EXCEPTION
WHEN data_exception THEN
RAISE WARNING '[audit.log_func] - UDF ERROR [DATA EXCEPTION] - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM;
RETURN NULL;
WHEN unique_violation THEN
RAISE WARNING '[audit.log_func] - UDF ERROR [UNIQUE] - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM;
RETURN NULL;
--WHEN OTHERS THEN
-- RAISE WARNING '[audit.log_func] - UDF ERROR [OTHER] - SQLSTATE: %, SQLERRM: %',SQLSTATE,SQLERRM;
-- RETURN NULL;
END;
$body$ language plpgsql security definer;
CREATE OR REPLACE FUNCTION audit.enable(p_table_name text,p_schema_name text DEFAULT 'dallas') RETURNS VOID as $body$
DECLARE
BEGIN
EXECUTE 'create trigger trg_audit_'||p_table_name||' BEFORE INSERT OR UPDATE OR DELETE ON '||p_schema_name||'.'||p_table_name|| ' FOR EACH ROW EXECUTE PROCEDURE audit.log_func()';
exception when duplicate_object then null;
END;
$body$ language plpgsql security definer;

How to store values in trigger PostgreSQL

I have a trigger that looks something like this:
CREATE OR REPLACE FUNCTION CHECK_SCHEDULE()
RETURNS TRIGGER AS
$BODY$
BEGIN
IF EXISTS(
SELECT DAY, TIME FROM MEETING
WHERE NEW.DAY = MEETING.DAY AND NEW.TIME > MEETING.TIME
) THEN
RAISE EXCEPTION 'THERE IS A MEETING HAPPENING ON % % ', NEW.DAY, NEW.TIME;
ELSE
RETURN NEW;
END IF;
END;
$BODY$ LANGUAGE PLPGSQL;
This works fine except I want the message to be the time it's conflicting with: There is a meeting happening on MEETING.DAY and MEETING.TIME.
However I cannot do this because it doesn't know what these variables are. Is it possible to store the values in my select clause so I can use them later?
You can move the day and time into a declared variable (e.g. a RECORD) for reference later.
CREATE OR REPLACE FUNCTION CHECK_SCHEDULE()
RETURNS TRIGGER AS
$BODY$
DECLARE
meetinginfo RECORD;
BEGIN
SELECT meeting.day, meeting.time
INTO meetinginfo
FROM meeting
WHERE new.day = meeting.day
AND new.time > meeting.time
ORDER BY new.time
LIMIT 1;
IF FOUND THEN
RAISE EXCEPTION 'THERE IS A MEETING HAPPENING ON % %', meetinginfo.day, meetinginfo.time;
END IF;
RETURN NEW;
END;
$BODY$ LANGUAGE plpgsql;