This article was originally written in September 2012, but never published. I’m publishing it today, much later than expected.
I started looking at how to create these new directories as mentioned yesterday.
This starts the process:
$ psql freshports.org psql (9.0.8, server 9.0.9) Type "help" for help. freshports.org=# select Element_Add('/base/head', 'D'); element_add ------------- 453742 (1 row) freshports.org=# select Element_Add('/doc/head', 'D'); element_add ------------- 453743 (1 row) freshports.org=# select Element_Add('/ports/head', 'D'); element_add ------------- 0 (1 row) freshports.org=#
Looking at the last add, it returned zero, which means it already exists. Looking into that, I see a rogue commit which got through while I was still amending my dev box. Deleting the commit and the directories under /ports/head fixes that:
freshports.org=# begin; BEGIN freshports.org=# delete from commit_log where message_id = '201209061429.q86ETxwZ034822@svn.freebsd.org'; DELETE 1 freshports.org=# select pathname_id('/ports/head/ftp'); pathname_id ------------- 453738 (1 row) freshports.org=# delete from element where id = 453738; DELETE 1 freshports.org=# commit; COMMIT freshports.org=#
Oh wait, there is a problem. The /ports directory already exists. Rather than move everything under /ports/* to /ports/head, it’s easier to create a temporary location, move one element, then rename. You’ll see….
freshports.org=# select Element_Add('/portsTMP/head', 'D'); element_add ------------- 453745 (1 row) freshports.org=#
Let’s delete what we created in error:
freshports.org=# select pathname_id('/ports/head'); pathname_id ------------- 453737 (1 row) freshports.org=# begin; delete from element where id = (select pathname_id('/ports/head')); BEGIN DELETE 1 freshports.org=# select pathname_id('/ports/head'); pathname_id ------------- (1 row) freshports.org=# commit; COMMIT freshports.org=#
Now, let’s move those elements around:
freshports.org=# begin; BEGIN freshports.org=# update element set name = 'ports' where id = (select pathname_id('/portsTMP')); ERROR: duplicate key value violates unique constraint "element_pathname_pathname" DETAIL: Key (pathname)=(/ports) already exists. CONTEXT: SQL statement "UPDATE element_pathname SET pathname = element_pathname(new.id) WHERE element_id = NEW.id" PL/pgSQL function "element_pathname_update" line 3 at SQL statement freshports.org=# rollback; ROLLBACK
What? Pathnames (e.g. /ports) are both calculated and stored. They are stored for doing certain queries, and they are calculated for historical reasons.
Thus, the following two queries should be identical:
freshports.org=# select element_pathname(453742); element_pathname ------------------ /base/head (1 row) freshports.org=# select * from element_pathname where element_id = 453742; element_id | pathname ------------+------------ 453742 | /base/head (1 row)
The pathname is kept in sync by these triggers:
CREATE OR REPLACE FUNCTION element_pathname_insert() RETURNS TRIGGER AS $$ BEGIN INSERT INTO element_pathname (element_id, pathname) VALUES (NEW.id, element_pathname(NEW.id)); RETURN NEW; END $$ LANGUAGE 'plpgsql'; DROP TRIGGER element_pathname_insert ON element; CREATE TRIGGER element_pathname_insert AFTER INSERT on element FOR EACH ROW EXECUTE PROCEDURE element_pathname_insert(); CREATE OR REPLACE FUNCTION element_pathname_update() RETURNS TRIGGER AS $$ BEGIN if (NEW.name != old.name) THEN UPDATE element_pathname SET pathname = element_pathname(new.id) WHERE element_id = NEW.id; END IF; RETURN NEW; END $$ LANGUAGE 'plpgsql'; DROP TRIGGER element_pathname_update ON element; CREATE TRIGGER element_pathname_update AFTER UPDATE on element FOR EACH ROW EXECUTE PROCEDURE element_pathname_update();
There are two situations under which an element’s pathname can change. The above code considers only one.
- The name changes
- The parent_id changes
The fix is in the IF statement within the update trigger:
REATE OR REPLACE FUNCTION element_pathname_update() RETURNS TRIGGER AS $$ BEGIN if (NEW.name != old.name) THEN UPDATE element_pathname SET pathname = element_pathname(new.id) WHERE element_id = NEW.id; END IF; RETURN NEW; END $$ LANGUAGE 'plpgsql';
Now, let’s try that update again:
freshports.org=# begin; BEGIN freshports.org=# update element set name = 'ports' where id = (select pathname_id('/portsTMP')); UPDATE 1 freshports.org=# commit; COMMIT
Much better.