Skip to content
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

ltree demo adaptation- issues with path #2

Open
lboc80 opened this issue Apr 17, 2018 · 2 comments
Open

ltree demo adaptation- issues with path #2

lboc80 opened this issue Apr 17, 2018 · 2 comments

Comments

@lboc80
Copy link

lboc80 commented Apr 17, 2018

hello, thanks for the repo and the example. I tried to adapt it in this way:
this is the 'employee' table from a django model:

created       | timestamp with time zone |           | not null |
modified      | timestamp with time zone |           | not null |
guid          | uuid                     |           | not null |
comp_user_id  | character varying(150)   |           | not null |
comp_username | character varying(150)   |           | not null |
comp_email    | character varying(254)   |           |          |
id            | integer                  |           | not null |
path          | ltree                    |           |          |
first_name    | character varying(150)   |           |          |
last_name     | character varying(150)   |           |          |
manager_id    | integer                  |           |          |
user_id       | integer                  |           |          |
Indexes:
    "corp_companyeuserprofile_pkey" PRIMARY KEY, btree (id)
    "corp_companyeuserprofile_user_id_key" UNIQUE CONSTRAINT, btree (user_id)
    "corp_companyeuserprofile_comp_email_6f7503d7" btree (comp_email)
    "corp_companyeuserprofile_comp_email_6f7503d7_like" btree (comp_email varchar_pattern_ops)
    "corp_companyeuserprofile_comp_user_id_328cb82e" btree (comp_user_id)
    "corp_companyeuserprofile_comp_user_id_328cb82e_like" btree (comp_user_id varchar_pattern_ops)
    "corp_companyeuserprofile_comp_username_9eec69b2" btree (comp_username)
    "corp_companyeuserprofile_comp_username_9eec69b2_like" btree (comp_username varchar_pattern_ops)
    "corp_companyeuserprofile_guid_e3160b25" btree (guid)
    "corp_companyeuserprofile_manager_id_2491a6e2" btree (manager_id)
    "cup_path_btree_idx" btree (path)
    "cup_path_gist_idx" gist (path)
Check constraints:
    "check_no_recursion" CHECK (index(path, id::text::ltree) = (nlevel(path) - 1))
Foreign-key constraints:
    "corp_comp_manager_id_2491a6e2_fk_cornersto" FOREIGN KEY (manager_id) REFERENCES corp_companyeuserprofile(id) DEFERRABLE INITIALLY DEFERRED
    "corp_comp_user_id_39765502_fk_users_use" FOREIGN KEY (user_id) REFERENCES users_user(id) DEFERRABLE INITIALLY DEFERRED
Referenced by:
    TABLE "corp_companyeuserprofile" CONSTRAINT "corp_comp_manager_id_2491a6e2_fk_cornersto" FOREIGN KEY (manager_id) REFERENCES corp_companyeuserprofile(id) DEFERRABLE INITIALLY DEFERRED
Triggers:
    cup_path_after_trg AFTER UPDATE ON corp_companyeuserprofile FOR EACH ROW WHEN (new.path IS DISTINCT FROM old.path) EXECUTE PROCEDURE _update_descendants_manager_path()
    cup_path_insert_trg BEFORE INSERT ON corp_companyeuserprofile FOR EACH ROW EXECUTE PROCEDURE _update_manager_path()
    cup_path_update_trg_two BEFORE UPDATE ON corp_companyeuserprofile FOR EACH ROW EXECUTE PROCEDURE _update_manager_path()

I also adapted the triggers:

-- function to calculate the path of any given manager
CREATE OR REPLACE FUNCTION _update_manager_path() RETURNS TRIGGER AS
$$
BEGIN
    IF NEW.manager_id IS NULL THEN
        NEW.path = NEW.id::text::ltree;
    ELSE
--           SELECT concat_ws('.', path::text, NEW.id::text)::ltree
          SELECT concat_ws('.', path::text, NEW.id::text)::ltree
          FROM comp_companyuserprofile
         WHERE NEW.manager_id IS NULL or id = NEW.manager_id
          INTO NEW.path;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- function to update the path of the descendants of a c.u.p.
CREATE OR REPLACE FUNCTION _update_descendants_manager_path() RETURNS TRIGGER AS
$$
BEGIN
    UPDATE comp_companyuserprofile
       SET path = concat_ws('.', NEW.path::text, subpath(comp_companyuserprofile.path, nlevel(OLD.path))::text)::ltree
     WHERE comp_companyuserprofile.path <@ OLD.path AND id != NEW.id;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- calculate the path every time we insert a new c.u.p.
DROP TRIGGER IF EXISTS cup_path_insert_trg ON comp_companyuserprofile;
CREATE TRIGGER cup_path_insert_trg
               BEFORE INSERT ON comp_companyuserprofile
               FOR EACH ROW
               EXECUTE PROCEDURE _update_manager_path();

-- calculate the path when updating the manager or the csod_user_id
DROP TRIGGER IF EXISTS cup_path_update_trg ON comp_companyuserprofile;
CREATE TRIGGER cup_path_update_trg
               BEFORE UPDATE ON comp_companyuserprofile
               FOR EACH ROW
               WHEN (OLD.manager_id IS DISTINCT FROM NEW.manager_id
                     OR OLD.csod_user_id IS DISTINCT FROM NEW.csod_user_id)
               EXECUTE PROCEDURE _update_descendants_manager_path();

-- if the path was updated, update the path of the descendants
DROP TRIGGER IF EXISTS cup_path_after_trg ON comp_companyuserprofile;
CREATE TRIGGER cup_path_after_trg
               AFTER UPDATE ON comp_companyuserprofile
               FOR EACH ROW
               WHEN (NEW.path IS DISTINCT FROM OLD.path)
               EXECUTE PROCEDURE _update_descendants_manager_path();

However path remains always empty after inserting or updating the entire row or the 'manager_id' column.
Am I doing something wrong?
I there a way to launch the update functions ath the end of the import process instead of implementing them as triggers?

@k4nar
Copy link
Contributor

k4nar commented Apr 17, 2018

Even if you insert a new row with no manager_id you don't have a path? That's weird…

And yes, you could use regular SQL functions instead of triggers, and call them when you need to. But only the triggers can ensure data integrity.

@andreynovikov
Copy link

I do not know if this is the same issue but I also had empty paths when using get_or_create:

obj, created = Object.objects.get_or_create(slug=slug)  # obj is saved under the hood and path is set by triggers
obj.field = value
obj.save()  # obj is saved but we have an empty path in Django object, triggers are not run as slug is not modified,
#             path is overwritten by Django ORM

I fixed this by adding refresh_from_db:

obj, created = Object.objects.get_or_create(slug=slug)  # obj is saved under the hood and path is set by triggers
obj.refresh_from_db()  # read generated path from db into object
obj.field = value
obj.save()  # obj is saved but we have correct path now

Another (tested) option is to exclude path from saved fields:

fields = [f.name for f in Object._meta.get_fields() if f.name != 'path' and not f.auto_created and not f.primary_key]
obj.save(update_fields=fields)

This can be put in save() method for convenience.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants