LINUX.ORG.RU

PostgreSQL: UPDATE из DELETE-триггера в обход UPDATE-триггера

 , ,


0

1

Имеется такая схема:

CREATE FUNCTION array_unique(a BIGINT[]) RETURNS BIGINT[] AS $$
DECLARE
	element BIGINT;
	ret BIGINT[] := ARRAY[]::BIGINT[];
BEGIN
	FOREACH element IN ARRAY a LOOP
		IF NOT ret @> ARRAY[element] THEN
			SELECT array_append(ret, element) INTO ret;
		END IF;
	END LOOP;
	
	RETURN ret;
END;
$$ LANGUAGE plpgsql;

-- fields with trigger functions

CREATE TABLE fields (
	id BIGSERIAL PRIMARY KEY,
	name CITEXT NOT NULL,
	position BIGINT NOT NULL,
	visible1 BOOLEAN NOT NULL DEFAULT TRUE,
	visible2 BOOLEAN NOT NULL DEFAULT FALSE,
	deletable BOOLEAN NOT NULL DEFAULT TRUE,
	
	CONSTRAINT fields_name_unique UNIQUE (name),
	CONSTRAINT fields_name_trimmed CHECK (trim(name) = name),
	CONSTRAINT fields_name_long_enough CHECK (name <> ''),
	CONSTRAINT fields_name_short_enough CHECK (length(name) <= 256),
	
	CONSTRAINT fields_position_unique UNIQUE (position) DEFERRABLE,
	CONSTRAINT fields_position_not_negative CHECK (position >= 0)
);

CREATE FUNCTION fields_before_insert() RETURNS trigger AS $$
DECLARE
	nextval BIGINT;
BEGIN
	LOCK TABLE fields IN ACCESS EXCLUSIVE MODE;
	SELECT COALESCE(MAX(position) + 1, 0) INTO nextval FROM fields;
	NEW.position = nextval;
	RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE FUNCTION fields_before_delete() RETURNS trigger AS $$
BEGIN
	-- prevent deletion of field required for system operation
	IF OLD.deletable = FALSE THEN
		RETURN NULL;
	END IF;
	
	LOCK TABLE fields IN ACCESS EXCLUSIVE MODE;
	SET CONSTRAINTS fields_position_unique DEFERRED;
	RAISE NOTICE '{%}', OLD.position;
	UPDATE fields SET position = position - 1 WHERE position > OLD.position;
	RETURN OLD;
END;
$$ LANGUAGE plpgsql;

CREATE FUNCTION fields_before_update() RETURNS trigger AS $$
BEGIN
	-- position must be updated through fields_reorder(ids BIGINT[]) function, see below
	NEW.position = OLD.position;
	RAISE NOTICE '{%}', TG_OP;
	-- prevent removal of deletable flag
	IF OLD.deletable = FALSE THEN
		NEW.deletable = FALSE;
	END IF;
	
	RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER fields_before_insert BEFORE INSERT ON fields FOR EACH ROW EXECUTE PROCEDURE fields_before_insert();
CREATE TRIGGER fields_before_delete BEFORE DELETE ON fields FOR EACH ROW EXECUTE PROCEDURE fields_before_delete();
CREATE TRIGGER fields_before_update BEFORE UPDATE OF position, deletable ON fields FOR EACH ROW EXECUTE PROCEDURE fields_before_update();

CREATE FUNCTION fields_reorder(ids BIGINT[]) RETURNS VOID AS $$
DECLARE
	rec RECORD;
	pos BIGINT;
	id BIGINT;
BEGIN
	SELECT array_unique(ids) INTO ids;
	
	LOCK TABLE fields IN ACCESS EXCLUSIVE MODE;
	SET CONSTRAINTS fields_position_unique DEFERRED;
	
	pos := 0;
	
	FOREACH id IN ARRAY ids LOOP
		EXECUTE format('UPDATE fields SET position = %L WHERE id = %L', pos, id);
		pos := pos + 1;
	END LOOP;
	
	FOR rec IN SELECT fields.id FROM fields WHERE NOT (fields.id = ANY(ids)) LOOP
		EXECUTE FORMAT('UPDATE fields SET position = %L WHERE id = %L', pos, rec.id);
		pos := pos + 1;
	END LOOP;
END;
$$ LANGUAGE plpgsql;

INSERT INTO fields (name) VALUES ('Vendor Name');
INSERT INTO fields (name) VALUES ('VPN');
INSERT INTO fields (name) VALUES ('SKU');
INSERT INTO fields (name) VALUES ('EANUPC');
INSERT INTO fields (name) VALUES ('Dealer Price');
INSERT INTO fields (name) VALUES ('Stock');

-- end fields with trigger functions

fields_before_delete() DELETE-триггер сохраняет непрерывную последовательность в fields.position. fields_before_update() предотвращает установку fields.position в произвольное значение, последовательность должна меняться с помощью ф-ции fields_reorder(). Однако проблема в том что UPDATE в fields_before_delete() из-за триггера приводит к нежелательному в данном случае UPDATE-триггера fields_before_update(). Можно ли как то из DELETE-триггера поапдейтить это поле исключив при этом вызов UPDATE-триггера?


В триггере на delete делать update не только position, но и ещё какого-нибудь поля-флага. В триггере на update смотреть значение поля-флага и не делать NEW.position = OLD.position при наличии там определённого значения. Не забыть сбросить флаг.

SOmni ★★
()
Последнее исправление: SOmni (всего исправлений: 1)

Из DELETE вызываешь ALTER TABLE DISABLE TRIGGER, потом обратно ENABLE TRIGGER

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.