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:
CREATE OR REPLACE FUNCTION element_pathname_update() RETURNS TRIGGER AS $$ BEGIN if (NEW.name != old.name OR new.parent_id != old.parent_id) 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.
With those changes, done, let’s modify the www layer. This change is simple:
define('FRESHPORTS_PORTS_TREE_PREFIX', '/ports/head/');
It was at this point, that I realized my previous move was in error. Take, for example, this port:
freshports.org=# select E.*, element_pathname(id) from element E where name = 'beadm' ORDER BY element_pathname(id); id | name | parent_id | directory_file_flag | status | element_pathname --------+-------+-----------+---------------------+--------+---------------------------------- 427802 | beadm | 218 | D | A | /ports/head/ports/sysutils/beadm (1 row)
That path is wrong. It should be: /ports/head/sysutils/beadm to reflect the svn path: . This SQL moves everything up one path, and deletes the now unused directory:
freshports.org=# begin; BEGIN freshports.org=# update element set parent_id = (select pathname_id('/ports/head')) where parent_id = (select pathname_id('/ports/head/ports')); UPDATE 80 freshports.org=# delete from element where id = (select pathname_id('/ports/head/ports')) ; DELETE 1 freshports.org=# commit; COMMIT freshports.org=#
However, this led me to an interesting situation… the element_pathname table…. is it still accurate with this type of move? Let’s check:
freshports.org=# select E.*, element_pathname(id) from element E where id = 427802 or parent_id = 427802; id | name | parent_id | directory_file_flag | status | element_pathname --------+-----------+-----------+---------------------+--------+-------------------------------------- 427802 | beadm | 218 | D | A | /ports/head/sysutils/beadm 427803 | Makefile | 427802 | F | A | /ports/head/sysutils/beadm/Makefile 427804 | distinfo | 427802 | F | A | /ports/head/sysutils/beadm/distinfo 427805 | pkg-descr | 427802 | F | A | /ports/head/sysutils/beadm/pkg-descr (4 rows) freshports.org=# select * from element_pathname where element_id in (select E.id from element E where id = 427802 or parent_id = 427802); element_id | pathname ------------+--------------------------------- 427802 | /ports/sysutils/beadm 427803 | /ports/sysutils/beadm/Makefile 427804 | /ports/sysutils/beadm/distinfo 427805 | /ports/sysutils/beadm/pkg-descr (4 rows) freshports.org=# select * from element_pathname where pathname like '/ports/head/%'; element_id | pathname ------------+--------------------------- 453747 | /ports/head/A 172891 | /ports/head/CHANGES 228686 | /ports/head/COPYRIGHT 441602 | /ports/head/CVSROOT 249957 | /ports/head/GIDs 77267 | /ports/head/INDEX 120467 | /ports/head/INDEX-5 229283 | /ports/head/KNOBS 83075 | /ports/head/LEGAL 117606 | /ports/head/MOVED 110905 | /ports/head/Makefile 77070 | /ports/head/Mk 105961 | /ports/head/README 453746 | /ports/head/TEST 453748 | /ports/head/TEST/B 453749 | /ports/head/TEST/C 105962 | /ports/head/Templates 77184 | /ports/head/Tools 249958 | /ports/head/UIDs 176987 | /ports/head/UPDATING 171607 | /ports/head/accessibility 159346 | /ports/head/arabic 350 | /ports/head/archivers 410 | /ports/head/astro 386 | /ports/head/audio 2710 | /ports/head/benchmarks 869 | /ports/head/biology 830 | /ports/head/cad 1660 | /ports/head/chinese 2191 | /ports/head/comms 423 | /ports/head/converters 582 | /ports/head/databases 802 | /ports/head/deskutils 84 | /ports/head/devel 148762 | /ports/head/dns 2 | /ports/head/editors 245 | /ports/head/emulators 118514 | /ports/head/finance 16545 | /ports/head/french 140 | /ports/head/ftp 18 | /ports/head/games 3747 | /ports/head/german 29 | /ports/head/graphics 11329 | /ports/head/hebrew 118517 | /ports/head/hungarian 39 | /ports/head/irc 129 | /ports/head/japanese 815 | /ports/head/java 1109 | /ports/head/korean 171 | /ports/head/lang 201 | /ports/head/mail 176 | /ports/head/math 6412 | /ports/head/mbone 42 | /ports/head/misc 118520 | /ports/head/multimedia 50 | /ports/head/net 229588 | /ports/head/net-im 173566 | /ports/head/net-mgmt 236506 | /ports/head/net-p2p 179 | /ports/head/news 2143 | /ports/head/palm 58316 | /ports/head/picobsd 148764 | /ports/head/polish 265343 | /ports/head/ports-mgmt 118523 | /ports/head/portuguese 360 | /ports/head/print 577 | /ports/head/russian 56065 | /ports/head/science 34 | /ports/head/security 465 | /ports/head/shells 218 | /ports/head/sysutils 188 | /ports/head/textproc 57265 | /ports/head/ukrainian 94 | /ports/head/vietnamese 9 | /ports/head/www 231 | /ports/head/x11 428 | /ports/head/x11-clocks 278044 | /ports/head/x11-drivers 516 | /ports/head/x11-fm 1229 | /ports/head/x11-fonts 3321 | /ports/head/x11-servers 171611 | /ports/head/x11-themes 55 | /ports/head/x11-toolkits 147 | /ports/head/x11-wm (84 rows)
No. They are wrong. The function, element_pathname, is correct, but the stored values are correct only for the elements which have been moved. For any of their descendents, it is wrong. The element_pathname_update() trigger needs to act recursively for any children.
My fix for that is:
CREATE OR REPLACE FUNCTION ElementPathnameUpdateChildren (int4) returns int4 AS $$ DECLARE p_ParentID ALIAS FOR $1; l_element_pathname RECORD; l_record_count int4; l_temp_count int4; BEGIN l_record_count := 0; FOR l_element_pathname IN SELECT * FROM element WHERE parent_id = p_ParentID LOOP l_record_count := l_record_count + 1; UPDATE element_pathname SET pathname = element_pathname(l_element_pathname.id) WHERE element_id = l_element_pathname.id; IF l_element_pathname.directory_file_flag = 'D' THEN SELECT ElementPathnameUpdateChildren(l_element_pathname.id) INTO l_temp_count; l_record_count := l_record_count + l_temp_count; END IF; END LOOP; RETURN l_record_count; END; $$ LANGUAGE 'plpgsql';
Which is invoked from within the trigger:
CREATE OR REPLACE FUNCTION element_pathname_update() RETURNS TRIGGER AS $$ BEGIN if (NEW.name != old.name OR new.parent_id != old.parent_id) THEN UPDATE element_pathname SET pathname = element_pathname(new.id) WHERE element_id = NEW.id; PERFORM ElementPathnameUpdateChildren(new.id); END IF; RETURN NEW; END $$ LANGUAGE 'plpgsql';