2
\$\begingroup\$

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:

  1. add a class field in the parent table to keep track of which is which
  2. have a base child table containing child extra fields and a foreign key towards the parent
  3. create a view merging the parent and child fields using the foreign key
  4. 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:

  1. The SQL DDL creation script needs to be automatically generated, but let's say it's ok with a dedicated tool that can replace INHERITS uses with the proper rules and views declarations.

  2. When the DDL evolves, one has to first drop the rules and the view, make the changes, then recreate the rules and the view.

  3. 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 RETURNING will effectively return the potentially generated key, it won't return any other child field.

Questions

  1. 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 OF triggers be better in some way?

  2. Is there a way to have the insertion rule actually return all child field values?

  3. Do you see any potential risk or side effect other than the ones mentioned above?

  4. 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.

\$\endgroup\$
5
  • \$\begingroup\$ Please edit your question so that the title describes the purpose of the code, rather than its mechanism. We really need to understand the motivational context to give good reviews. Thanks! \$\endgroup\$ Commented Jan 19, 2022 at 9:05
  • \$\begingroup\$ Also, the table names (parent, child etc) 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\$ Commented Jan 19, 2022 at 9:07
  • \$\begingroup\$ I'd be glad to edit the title if needed, but the purpose of the code is precisely to implement a proper table inheritance mechanism, so I don't know how to be more precise. I use parent and child because 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\$ Commented Jan 19, 2022 at 9:18
  • \$\begingroup\$ What can you do with it? What functionality does it provide to your users? It might help to show your testing, as that can often explain better than words how you expect it to be used. \$\endgroup\$ Commented Jan 19, 2022 at 9:21
  • \$\begingroup\$ I tried to clarify the post. \$\endgroup\$ Commented Jan 19, 2022 at 14:33

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.