Skip to content
This repository has been archived by the owner on Apr 24, 2024. It is now read-only.

Hierarchy Implementation Proposal #793

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions doc/decisions/database_plant_hierarchy.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ See the [PSQL documentation](https://www.postgresql.org/docs/current/ddl-inherit

> Table inheritance is typically established when the child table is created, using the INHERITS clause of the CREATE TABLE statement.

> Rust Diesel isn't intended for that. To only select data from a specific table, and not include all child tables, we would need to use the `FROM ONLY` keyword, which is not implemented in Rust Diesel.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> Rust Diesel isn't intended for that. To only select data from a specific table, and not include all child tables, we would need to use the `FROM ONLY` keyword, which is not implemented in Rust Diesel.
> Rust Diesel isn't intended for that.
> To only select data from a specific table, and not include all child tables, we would need to use the `FROM ONLY` keyword, which is not implemented in Rust Diesel.

Sentences need to be written on separate lines according to our documentation guidelines.


So the inheritance is useful to deal with complex DDL structure on the startup, but will not help us to avoid bulk operations e.g. updating a column for every `variety` in the entire `genus`

### One table per taxonomy rank and one for concrete plants.
Expand Down Expand Up @@ -85,6 +87,28 @@ Cons:
- Almost everything in the plants table needs to be nullable.
- More complex insert and update logic.

### One table per taxonomy rank and one for concrete plants. + View and custom insert/update/delete functionality
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This title is a bit confusing in my opinion.


[Example](example_migrations/one-table-per-taxonomy-view-functions)

It's similar to `One table for taxonomy ranks and one for concrete plants` We are extending it with a view and custom functions to reduce insert and update complexity in the backend.

Pros:

- Inserting new plants is easy. We only need to implement minor backend changes.

Cons:

- Attribute overrides can only be done on the variety or cultivar level.
temmey marked this conversation as resolved.
Show resolved Hide resolved
- More complex insert and update logic.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it would be hidden behind triggers? In general this sounds like a good idea, as we then don't need to keep Rust, maybe Python (E2E) and JavaScript code (scraper) in sync.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and the view would act just like our plant table right now, we only extend it with some more properties.

When a species/variety is added or updated, the columns can't just be set.
First, we need to make sure all higher levels are in the table.
Then we need to check for each column value if there is a higher rank that already defines the same value.
Only if we can't find a match, the value should be written.
- We can offset this issue by implementing insert/update functions.
Since they are going to be complicated, long-term maintainability may be an issue.
- Almost everything in the plants and parent tables needs to be nullable. (Is this a downside?)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think so. If everything is nullable (even if this goes against our data model) we need to perform more manual integrity checks.


## Decision

- We go with the last option "All ranks in one table" as described [in our documentation](../database/hierarchy.md).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
-- since this is WIP, i will keep this empty for now.
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
-- Create the "family" table
CREATE TABLE family (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL,
property1 TEXT,
CONSTRAINT family_name_key UNIQUE (name)
);

-- Create the "genus" table
CREATE TABLE genus (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL,
property1 TEXT,
family_id INTEGER NOT NULL REFERENCES family (id),
CONSTRAINT genus_name_key UNIQUE (name)
);

-- Create the "species" table
CREATE TABLE species (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL,
property1 TEXT,
genus_id INTEGER NOT NULL REFERENCES genus (id),
CONSTRAINT species_name_key UNIQUE (name)
);

-- Create the "variety" table
CREATE TABLE variety (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL,
property1 TEXT,
species_id INTEGER NOT NULL REFERENCES species (id),
CONSTRAINT variety_name_key UNIQUE (name)
);

-- Create the "cultivar" table
CREATE TABLE cultivar (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL,
property1 TEXT,
species_id INTEGER NOT NULL REFERENCES species (id),
variety_id INTEGER NOT NULL REFERENCES variety (id),
CONSTRAINT cultivar_name_key UNIQUE (name)
);

-- Create the "plantsDetails" table
CREATE TABLE plantsdetails (
id INTEGER NOT NULL,
unique_name TEXT NOT NULL,
common_name_en TEXT [],
common_name_de TEXT [],
property1 TEXT,
family_id INTEGER NOT NULL REFERENCES family (id),
genus_id INTEGER NOT NULL REFERENCES genus (id),
species_id INTEGER NOT NULL,
variety_id INTEGER,
cultivar_id INTEGER,
CONSTRAINT unique_name_key UNIQUE (unique_name),
CONSTRAINT check_variety_or_cultivar CHECK (
variety_id IS NULL OR cultivar_id IS NULL
)
);

-- Create a view joining the tables together
-- COALESCE function accepts an unlimited number of arguments.
-- It returns the first argument that is not null.
CREATE OR REPLACE VIEW plants AS
SELECT
p.id AS plant_id,
p.unique_name,
p.common_name_en,
p.common_name_de,
f.name AS family_name,
g.name AS genus_name,
s.name AS species_name,
v.name AS variety_name,
c.name AS cultivar_name,
coalesce(
p.property1,
c.property1,
v.property1,
s.property1,
g.property1,
f.property1
) AS property1
FROM plants AS p
INNER JOIN family AS f ON p.family_id = f.id
INNER JOIN genus AS g ON p.genus_id = g.id
INNER JOIN species AS s ON p.species_id = s.id
LEFT JOIN variety AS v ON p.variety_id = v.id
LEFT JOIN cultivar AS c ON p.cultivar_id = c.id;


CREATE OR REPLACE FUNCTION insert_plant_view_placeholder()
RETURNS TRIGGER AS $$
BEGIN
-- Placeholder function, no implementation provided.
-- will insert a new item and only fill columns, if they differ from parent tables.
RETURN NEW;
END;
$$
LANGUAGE plpgsql;

CREATE TRIGGER insert_plant_view_trigger
INSTEAD OF INSERT ON plants_view
FOR EACH ROW
EXECUTE FUNCTION insert_plant_view_placeholder();



CREATE OR REPLACE FUNCTION update_plant_view_placeholder()
RETURNS TRIGGER AS $$
BEGIN
-- Placeholder function, no implementation provided.
-- will only update values in plantDetails if they differ from a parent table.
RETURN NEW;
END;
$$
LANGUAGE plpgsql;

CREATE TRIGGER update_plant_view_trigger
INSTEAD OF UPDATE ON plants_view
FOR EACH ROW
EXECUTE FUNCTION update_plant_view_placeholder();


CREATE OR REPLACE FUNCTION delete_plant_view_placeholder()
RETURNS TRIGGER AS $$
BEGIN
-- Placeholder function, no implementation provided.
-- Will simply delete from the plantsDetails table
RETURN OLD;
END;
$$
LANGUAGE plpgsql;

CREATE TRIGGER delete_plant_view_trigger
INSTEAD OF DELETE ON plants_view
FOR EACH ROW
EXECUTE FUNCTION delete_plant_view_placeholder();
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# One Table Per Taxonomy + View + Custom Insert, Update, Delete functions