|
"Enforcing that a quote has at least one quote_line is pretty tough, at least in Postgres, and involves triggers and locks." The reason it seems simple when using a few classes is because there is an implicit assumption that concurrency is a non-issue and there's only one application involved. But those aren't good assumptions, so the approach using classes will start to look more complex (and involve locks, etc.). In Postgres, the trick is to use SERIALIZABLE transactions everywhere (should be the default, eventually), which avoids the need for explicit locking. Then, add a trigger that is fired before changes to either table, and it would simply check that the condition holds for the quote that was modified. Here's some code, since you asked: create table quote(
quote_id int8 primary key,
customer_id int8,
valid daterange
);
create table quote_line(
quote_id int8 references quote(quote_id),
amount numeric(10,2)
);
create or replace function check_quote() returns trigger language plpgsql as $$
declare
line_count int;
line_sum numeric;
begin
select into line_count, line_sum
count(*), sum(amount) from quote_line where quote_id=NEW.quote_id;
if (line_count < 1 OR line_sum < 0 OR line_sum > 1000000)
then raise exception 'invalid quote';
else return NEW;
end if;
end;
$$;
create constraint trigger quote_check_trig after insert or update or delete on quote deferrable initially deferred for each row execute procedure check_quote();
create constraint trigger quote_line_check_trig after insert or update or delete on quote_line deferrable initially deferred for each row execute procedure check_quote();
Note that I did not need to add the CHECK constraint, because it's much better to use the appropriate data type -- DATERANGE -- instead of hacking it together from parts. |
If you forget to use serializable transactions every time you update these tables, is it possible to store incorrect data?