Skip to content

Proposal: Add path column to categories table as ltree type to make tree traversing easier #1122

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Aug 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/target
.cargo
.vscode/

# compiled output
/dist
Expand All @@ -21,3 +22,5 @@ yarn-error.log
testem.log
.env
docker-compose.override.yml
*~

52 changes: 52 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ dotenv = "0.11.0"
toml = "0.4"
diesel = { version = "1.3.0", features = ["postgres", "serde_json", "chrono", "r2d2"] }
diesel_full_text_search = "1.0.0"
diesel_ltree = "0.1.3"
serde_json = "1.0.0"
serde_derive = "1.0.0"
serde = "1.0.0"
Expand Down
1 change: 1 addition & 0 deletions app/models/category.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default DS.Model.extend({
crates_cnt: DS.attr('number'),

subcategories: DS.attr(),
parent_categories: DS.attr(),

crates: DS.hasMany('crate', { async: true }),
});
7 changes: 5 additions & 2 deletions app/templates/category/index.hbs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
{{ title category.category ' - Categories' }}

<div id='crates-heading'>
{{svg-jar "crate"}}
<h1>{{ category.category }}</h1>
{{#link-to "categories" (html-attributes aria-label="Categories")}}{{svg-jar "crate"}}{{/link-to}}
<h1>
{{#each category.parent_categories as |parent|}}{{link-to parent.category "category" parent.slug}}::{{/each}}
{{~ category.category }}
</h1>
</div>

<div>
Expand Down
2 changes: 1 addition & 1 deletion diesel.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
[print_schema]
file = "src/schema.rs"
with_docs = true
import_types = ["diesel::sql_types::*", "diesel_full_text_search::{TsVector as Tsvector}"]
import_types = ["diesel::sql_types::*", "diesel_full_text_search::{TsVector as Tsvector}", "diesel_ltree::Ltree"]
patch_file = "src/schema.patch"
14 changes: 14 additions & 0 deletions migrations/2017-10-08-193512_category_trees/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- This file should undo anything in `up.sql`
DROP TRIGGER set_category_path_update ON categories;

DROP TRIGGER set_category_path_insert ON categories;

DROP FUNCTION IF EXISTS set_category_path_to_slug();

DROP INDEX path_categories_idx;
DROP INDEX path_gist_categories_idx;

ALTER TABLE categories
DROP COLUMN path;

DROP EXTENSION ltree;
40 changes: 40 additions & 0 deletions migrations/2017-10-08-193512_category_trees/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
-- Your SQL goes here
CREATE EXTENSION ltree;

-- Create the new column which will represent our category tree.
-- Fill it with values from `slug` column and then set to non-null
ALTER TABLE categories
ADD COLUMN path ltree;

-- Unfortunately, hyphens (dashes) are not allowed...
UPDATE categories
SET path = text2ltree('root.' || trim(replace(replace(slug, '-', '_'), '::', '.')))
WHERE path is NULL;

ALTER TABLE CATEGORIES
ALTER COLUMN path SET NOT NULL;

-- Create some indices that allow us to use GIST operators: '@>', etc
CREATE INDEX path_gist_categories_idx ON categories USING GIST(path);
CREATE INDEX path_categories_idx ON categories USING btree(path);
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason to have both a gist and a btree index? The postgres docs say:

F.21.3. Indexes
ltree supports several types of indexes that can speed up the indicated operators:

  • B-tree index over ltree: <, <=, =, >=, >
  • GiST index over ltree: <, <=, =, >=, >, @>, <@, @, ~, ?

which looks like gist indexes take care of the same operators that btree indexes take care of?

All of this is probably moot anyway since the categories table is so smalll :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Honestly, I've just seen everyone do it that way, so I figured it was a best practice, if not necessary.


-- Create procedure and trigger to auto-update path
CREATE OR REPLACE FUNCTION set_category_path_to_slug()
RETURNS trigger AS
$BODY$
BEGIN
NEW.path = text2ltree('root.' || trim(replace(replace(NEW.slug, '-', '_'), '::', '.')));
RETURN NEW;
END;
$BODY$ LANGUAGE plpgsql;

CREATE TRIGGER set_category_path_insert
BEFORE INSERT
ON categories
FOR EACH ROW
EXECUTE PROCEDURE set_category_path_to_slug();

CREATE TRIGGER set_category_path_update
BEFORE UPDATE OF slug ON categories
FOR EACH ROW
EXECUTE PROCEDURE set_category_path_to_slug();
Loading