状态机代表DB,执行状态转换

时间:2016-09-08 13:29:08

标签: postgresql state-machine

我有一台有限状态机,它代表了工作的各个阶段。 我需要在Postgres数据库中表示状态。我想通过禁止从一个状态到另一个状态的更新来强制执行代码正确性,除非状态机允许这样做。

实现目标的一种天真的方法可能是在表上获取独占锁,在事务中检查当前状态和下一个状态,在无效更新的情况下中止错误。

这显然是一个表演杀手,因为我将在每次州过渡时锁定 Job 表。

有没有办法通过约束来实现相同的目标?

1 个答案:

答案 0 :(得分:3)

触发器是您解决问题的答案。

让我们考虑简单的表格:

CREATE TABLE world (id serial PRIMARY KEY, state VARCHAR);
insert into world (state) values ('big bang');
insert into world (state) values ('stars formation');
insert into world (state) values ('human era');

触发器将调用的函数。在此定义状态机逻辑。 RAISE EXCEPTION非常有用,因为您可以在此处提供自定义消息。

CREATE FUNCTION check_world_change() RETURNS trigger as $check_world_change$
BEGIN
  IF OLD.state = 'big bang' AND NEW.state = 'human era' THEN
    RAISE EXCEPTION 'Dont skip stars';
  END IF;
  IF OLD.state = 'stars formation' AND NEW.state = 'big bang' THEN
    RAISE EXCEPTION 'Impossible to reverse order of things';
  END IF;
  RETURN NEW;
END;
$check_world_change$ LANGUAGE plpgsql;

定义表格的触发器:

CREATE TRIGGER check_world_change BEFORE UPDATE ON world 
  FOR EACH ROW EXECUTE PROCEDURE check_world_change();

现在,当您尝试更新其中一行的状态时,您将收到错误:

world=# select * from world;
 id |      state
----+-----------------
  2 | stars formation
  1 | human era
  3 | big bang
(3 rows)

world=# update world set state='human era' where state='big bang';
ERROR:  Wrong transition
world=# select * from world;
 id |      state
----+-----------------
  2 | stars formation
  1 | human era
  3 | big bang
(3 rows)

参考文献:

https://www.postgresql.org/docs/9.5/static/plpgsql-trigger.html https://www.postgresql.org/docs/9.5/static/sql-createtrigger.html