Context
Postgresql has an INHERITS keyword which lets you inherit one table from another, but it is known to have several caveats, mainly because it is not a true column-based inheritance.
The main problem I encountered with the provided INHERITS feature is that other tables cannot reference the parent_id field in the parent table (the parent table PK index will not store child PK values). My goal here is to overcome this problem by providing an inheritance mechanism that really gathers all PK index values in the parent table.
I implemented a working solution using views and rules, detailed below, and I'd be glad to get some feedback on it.
Implementation
The implementation is as follow:
- add a
classfield in the parent table to keep track of which is which - have a base child table containing child extra fields and a foreign key towards the parent
- create a view merging the parent and child fields using the foreign key
- create insert, update and delete rules on the view - the insertion rule takes care of serial fields, if any
CREATE TABLE parent (
parent_id serial,
name varchar(100) NOT NULL,
description text,
class varchar(30),
PRIMARY KEY (parent_id)
);
CREATE TABLE base_child (
parent_id serial, -- could be int
extra_infos TEXT,
PRIMARY KEY (parent_id),
FOREIGN KEY (parent_id) REFERENCES parent (parent_id) ON DELETE CASCADE
);
CREATE VIEW child AS
SELECT
parent.parent_id, name, description, class, extra_infos
FROM base_child JOIN parent ON parent.parent_id = base_child.parent_id;
CREATE RULE insert_child AS ON INSERT TO child DO INSTEAD (
INSERT INTO parent (parent_id, name ,description, class)
VALUES ( COALESCE(NEW.parent_id,NEXTVAL('parent_parent_id_seq')), NEW.name, NEW.description, 'child')
RETURNING parent.*, null::text;
SELECT SETVAL('parent_parent_id_seq', (SELECT MAX(parent_id) FROM parent)) parent_id;
INSERT INTO base_child (parent_id, extra_infos)
VALUES (CURRVAL('parent_parent_id_seq'), NEW.extra_infos);
);
CREATE RULE update_child AS ON UPDATE TO child DO INSTEAD (
UPDATE parent
SET name = NEW.name, description = NEW.description
WHERE parent_id = NEW.parent_id
RETURNING NEW.*;
UPDATE child
SET extra_infos = NEW.extra_infos
WHERE parent_id = NEW.parent_id;
);
CREATE RULE delete_child AS ON DELETE TO child DO INSTEAD (
DELETE FROM parent WHERE parent_id = OLD.parent_id;
);
This implementation still has a few shortcomings:
The SQL DDL creation script needs to be automatically generated, but let's say it's ok with a dedicated tool that can replace
INHERITSuses with the proper rules and views declarations.When the DDL evolves, one has to first drop the rules and the view, make the changes, then recreate the rules and the view.
In the insertion rule, the returned values cannot contain the actual inserted values for the child table fields (*). The rule returns typed null values instead. It means that while
INSERT RETURNINGwill effectively return the potentially generated key, it won't return any other child field.
Questions
I chose to use rules rather than triggers, rules being more efficient and more tied to the model than triggers, at least that was my impression. But would an implementation using
INSTEAD OFtriggers be better in some way?Is there a way to have the insertion rule actually return all child field values?
Do you see any potential risk or side effect other than the ones mentioned above?
Is there any other way to implement this feature?
* Inside the rule, I have to insert in the parent first, the rule expects the first statement to specify a returning clause, and it seems I cannot use NEW inside the RETURNING clause, otherwise I get:
ERROR: invalid reference to FROM-clause entry for table "new"
HINT: There is an entry for table "new", but it cannot be referenced from this part of the query.
What may happen is that the NEW from the insert statement shadows the NEW from the insertion being processed.
parent,childetc) suggest that this is example code rather than real code from a project. Code Review is about improving existing, working code. Example code is not reviewable because it leaves us guessing at your intentions. Unlike Stack Overflow, Code Review needs to look at concrete code in a real context. Please see Why is hypothetical example code off-topic for CR? \$\endgroup\$parentandchildbecause in the context of the problem I try to address, tables are anonymous objects. Replacing them with real tables would just make the question harder to read. The code is a real and tested code. \$\endgroup\$