Make WordPress Core

Ticket #8384: pages.sql

File pages.sql, 21.8 KB (added by Denis-de-Bernardy, 16 years ago)
Line 
1/*
2 * Table: pages
3 */
4
5CREATE TABLE pages (
6        id                              uuid                    PRIMARY KEY REFERENCES nodes ( id )
7                                                                        DEFERRABLE INITIALLY DEFERRED,
8        main_id                 uuid                    NOT NULL REFERENCES pages ( id ) ON DELETE CASCADE
9                                                                        DEFERRABLE INITIALLY DEFERRED,
10        r                               boolean,
11        w                               boolean,
12        status                  node_status             NOT NULL DEFAULT 'draft',
13        published               timestamp(0) with time zone,
14        modified                timestamp(0) with time zone
15                                                                        NOT NULL DEFAULT now(),
16        user_id                 uuid                    REFERENCES users ( id ) ON DELETE SET NULL,
17        parent_id               uuid                    REFERENCES pages ( id ),
18        order_id                int                             NOT NULL,
19        lft                             int,
20        rgt                             int,
21        depth                   int,
22        sort_num                int[],
23        key                             varchar                 NOT NULL DEFAULT '',
24        title                   varchar                 NOT NULL,
25        sort_name               varchar                 NOT NULL,
26        scan_name               varchar(1)              NOT NULL,
27        abstract                text                    NOT NULL DEFAULT '',
28        content                 text                    NOT NULL DEFAULT '',
29        tsv                             tsvector                NOT NULL,
30        memo                    text                    NOT NULL DEFAULT '',
31        CHECK ( key <> '' OR status <> 'active' ),
32        CHECK ( title <> '' ),
33        CHECK ( order_id > 0 ),
34        CHECK ( lft IS NULL AND rgt IS NULL AND sort_num IS NULL AND depth IS NULL
35                OR lft IS NOT NULL AND lft > 0 AND rgt IS NOT NULL AND rgt > 0 AND sort_num IS NOT NULL AND sort_num >= '{1}'
36                ),
37        CHECK ( r IS NULL AND w IS NULL OR r IS NOT NULL AND w IS NOT NULL ),
38        CHECK ( NOT ( parent_id IS NULL AND r IS NULL ) ),
39        CHECK ( NOT ( w AND NOT r ) ),
40        CONSTRAINT pages_main_id_key UNIQUE ( main_id, id ),
41        CONSTRAINT pages_valid_main_id_fkey     FOREIGN KEY ( main_id, main_id )
42                                                                        REFERENCES pages ( main_id, id ) ON DELETE CASCADE
43                                                                        DEFERRABLE INITIALLY DEFERRED,
44        CONSTRAINT pages_valid_parent_id_fkey FOREIGN KEY ( parent_id, parent_id )
45                                                                        REFERENCES pages ( main_id, id ) ON DELETE CASCADE
46                                                                        DEFERRABLE INITIALLY DEFERRED
47) WITH ( FILLFACTOR = 90 );
48
49CREATE UNIQUE INDEX pages_key_key ON pages ( key, parent_id )
50WHERE   id = main_id AND key <> '';
51
52CREATE INDEX pages_sort ON pages ( sort_name );
53CREATE INDEX pages_scan ON pages ( scan_name, sort_name );
54CREATE INDEX pages_tree ON pages ( lft, rgt DESC );
55CREATE INDEX pages_tsv ON pages USING GIN ( tsv );
56
57CREATE INDEX pages_parent ON pages ( parent_id ) WHERE id = main_id;
58CREATE INDEX pages_latest ON pages ( main_id ) WHERE status <> 'revision';
59CREATE INDEX pages_modified ON pages ( modified DESC, main_id );
60
61CREATE INDEX pages_current ON pages ( id ) WHERE id = main_id;
62CREATE INDEX pages_current_tree ON pages ( lft, rgt DESC ) WHERE id = main_id;
63
64CREATE TRIGGER __insert_page BEFORE INSERT ON pages
65        FOR EACH ROW EXECUTE PROCEDURE __insert_node('page');
66
67CREATE TRIGGER __delete_page AFTER DELETE ON pages
68        FOR EACH ROW EXECUTE PROCEDURE __delete_node();
69
70
71/*
72 * Function: page_children(parent_id)
73 */
74
75CREATE OR REPLACE FUNCTION page_children(uuid)
76        RETURNS SETOF tree_nodes
77AS $$
78DECLARE
79        p_id    alias for $1;
80        branch  tree_nodes;
81        leaf    tree_nodes;
82        i               int := 0;
83        max_i   int;
84        num             int := 0;
85BEGIN
86        FOR branch IN
87        SELECT  main_id as id,
88                        parent_id,
89                        0 as order_id,
90                        0 as lft,
91                        0 as rgt,
92                        '{}' as sort_num
93        FROM    pages
94        WHERE   id = main_id
95        AND             parent_id = p_id
96        ORDER BY order_id
97        LOOP
98                num := num + 1;
99       
100                branch.sort_num[1] = num;
101       
102                i := i + 1;
103                branch.lft := i;
104       
105                SELECT  rgt
106                INTO    max_i
107                FROM    page_children(branch.id)
108                ORDER BY rgt DESC
109                LIMIT 1;
110       
111                IF max_i IS NULL
112                THEN
113                        branch.rgt := i + 1;
114       
115                        RETURN NEXT branch;
116               
117                        i := i + 1;
118                ELSE
119                        branch.rgt := i + max_i + 1;
120       
121                        RETURN NEXT branch;
122               
123                        FOR leaf IN
124                        SELECT  id,
125                                        parent_id,
126                                        order_id,
127                                        lft + i,
128                                        rgt + i,
129                                        array_prepend(num, sort_num) as sort_num
130                        FROM    page_children(branch.id)
131                        LOOP
132                                RETURN NEXT leaf;
133                        END LOOP;
134               
135                        i := i + max_i + 1;
136                END IF;
137        END LOOP;
138
139        RETURN;
140END;
141$$ LANGUAGE plpgsql STABLE STRICT;
142
143
144/*
145 * Function: page_tree()
146 */
147
148CREATE OR REPLACE FUNCTION page_tree()
149        RETURNS SETOF tree_nodes
150AS $$
151DECLARE
152        branch  tree_nodes;
153        leaf    tree_nodes;
154        o_id    int := 0;
155        i               int := 0;
156        max_i   int;
157        num             int := 0;
158BEGIN
159        FOR branch IN
160        SELECT  main_id as id,
161                        parent_id,
162                        0 as order_id,
163                        0 as lft,
164                        0 as rgt,
165                        '{}' as sort_num
166        FROM    pages
167        WHERE   parent_id IS NULL
168        AND             id = main_id
169        ORDER BY order_id
170        LOOP
171                num := num + 1;
172                o_id := o_id + 1;
173                i := i + 1;
174       
175                branch.sort_num[1] := num;
176                branch.order_id := o_id;
177                branch.lft := i;
178       
179                SELECT  rgt
180                INTO    max_i
181                FROM    page_children(branch.id)
182                ORDER BY rgt DESC
183                LIMIT 1;
184       
185                IF max_i IS NULL
186                THEN
187                        branch.rgt := i + 1;
188       
189                        RETURN NEXT branch;
190               
191                        i := i + 1;
192                ELSE
193                        branch.rgt := i + max_i + 1;
194       
195                        RETURN NEXT branch;
196       
197                        FOR leaf IN
198                        SELECT  id,
199                                        parent_id,
200                                        order_id,
201                                        lft + i as lft,
202                                        rgt + i as rgt,
203                                        array_prepend(num, sort_num) as sort_num
204                        FROM    page_children(branch.id)
205                        LOOP
206                                o_id := o_id + 1;
207                                leaf.order_id := leaf.order_id + o_id;
208               
209                                RETURN NEXT leaf;
210                        END LOOP;
211       
212                        i := i + max_i + 1;
213                END IF;
214        END LOOP;
215
216        RETURN;
217END;
218$$ LANGUAGE plpgsql STABLE STRICT;
219
220
221/*
222 * Trigger: _save_page()
223 */
224
225CREATE OR REPLACE FUNCTION _save_page()
226        RETURNS trigger
227AS $$
228DECLARE
229        do_stuff        boolean := false;
230        str                     varchar;
231        arr                     int[];
232        i                       integer;
233        new_key         varchar;
234BEGIN
235        -- sanitize fields
236        NEW.title := trim(NEW.title);
237       
238        -- sort_name, scan_name
239        NEW.sort_name := lower(NEW.title);
240        NEW.scan_name := upper(substr(NEW.title, 1, 1));
241       
242        -- key
243        IF NEW.status = 'active' OR NEW.key <> ''
244        THEN
245                NEW.key := str2key(NEW.key);
246               
247                IF NEW.key = ''
248                THEN
249                        NEW.key := str2key(NEW.title);
250                END IF;
251       
252                -- catch key conflict
253                new_key := NEW.key;
254       
255                IF EXISTS (
256                        SELECT  NULL
257                        FROM    pages
258                        WHERE   key = new_key
259                        AND             parent_id IS NOT DISTINCT FROM NEW.parent_id
260                        AND             main_id <> NEW.main_id
261                        )
262                THEN
263                        i := 2;
264               
265                        IF new_key ~ ( '^' || str2key(NEW.title) || E'_\\d+$' )
266                        THEN
267                                NEW.key := str2key(NEW.title);
268                        ELSE
269                                NEW.key := new_key;
270                        END IF;
271               
272                        LOOP
273                                new_key := NEW.key || '_' || i;
274                       
275                                IF NOT EXISTS (
276                                        SELECT  NULL
277                                        FROM    pages
278                                        WHERE   key = new_key
279                                        AND             parent_id IS NOT DISTINCT FROM NEW.parent_id
280                                        AND             main_id <> NEW.main_id
281                                        )
282                                THEN
283                                        NEW.key := new_key;
284                                        EXIT;
285                                END IF;
286                       
287                                i := i + 1;
288                        END LOOP;
289                END IF;
290        END IF;
291       
292        -- tsv
293        IF NEW.status <> 'revision'
294        THEN
295                NEW.tsv := setweight(to_tsvector(str2keywords(NEW.title)), 'A')
296                        || setweight(to_tsvector(tags2keywords(NEW.id)), 'B')
297                        || setweight(to_tsvector(xml2text(NEW.abstract)), 'C')
298                        || setweight(to_tsvector(xml2text(NEW.content)), 'D');
299        ELSE
300                NEW.tsv := '';
301        END IF;
302       
303        RETURN NEW;
304END;
305$$ LANGUAGE plpgsql;
306
307CREATE TRIGGER _save_page BEFORE INSERT OR UPDATE ON pages
308        FOR EACH ROW EXECUTE PROCEDURE _save_page();
309
310
311/*
312 * Trigger: _insert_page()
313 */
314
315CREATE OR REPLACE FUNCTION _insert_page()
316        RETURNS trigger
317AS $$
318BEGIN
319        -- main_id
320        IF NEW.main_id IS NULL
321        THEN
322                NEW.main_id := NEW.id;
323        END IF;
324       
325        -- active
326        IF NEW.status = 'active' AND NEW.published IS NULL
327        THEN
328                NEW.published := now();
329        ELSEIF NEW.status = 'future' AND NEW.published IS NULL
330        THEN
331                NEW.published := date_trunc('day', now() + interval '1 day');
332        ELSEIF NEW.status = 'future' AND NEW.published <= now()
333        THEN
334                NEW.status = 'active';
335        END IF;
336       
337        -- lft, rgt, sort_num, depth
338        IF NEW.main_id <> NEW.id
339        THEN
340                SELECT  lft,
341                                rgt,
342                                sort_num,
343                                depth
344                INTO    NEW.lft,
345                                NEW.rgt,
346                                NEW.sort_num,
347                                NEW.depth
348                FROM    pages
349                WHERE   id = NEW.main_id;
350               
351                IF NEW.status = 'active'
352                THEN
353                        NEW.status := 'revision';
354                END IF;
355        END IF;
356       
357        -- order_id
358        IF NEW.order_id IS NULL
359        THEN
360                SELECT  order_id + 1
361                INTO    NEW.order_id
362                FROM    pages
363                WHERE   parent_id IS NOT DISTINCT FROM NEW.parent_id
364                AND             order_id IS NOT NULL
365                ORDER BY order_id DESC
366                LIMIT 1;
367               
368                IF NOT FOUND
369                THEN
370                        NEW.order_id := 1;
371                END IF;
372        END IF;
373       
374        RETURN NEW;
375END;
376$$ LANGUAGE plpgsql;
377
378CREATE TRIGGER _insert_page BEFORE INSERT ON pages
379        FOR EACH ROW EXECUTE PROCEDURE _insert_page();
380
381
382/*
383 * Trigger: insert_page()
384 */
385
386CREATE OR REPLACE FUNCTION insert_page()
387        RETURNS trigger
388AS $$
389DECLARE
390        do_perms        boolean := false;
391        profile_rec     record;
392        page_rec        record;
393BEGIN
394        IF NEW.id <> NEW.main_id
395        THEN
396                IF NEW.status IN ('obsolete', 'revision', 'future')
397                THEN
398                        -- publish
399                        IF NEW.status <> 'future'
400                        THEN
401                                do_perms := NEW.status = 'obsolete'
402                                OR NEW.status = 'revision'
403                                AND EXISTS (
404                                                SELECT  NULL
405                                                FROM    pages
406                                                WHERE   id = NEW.main_id
407                                                AND             ROW(r, w, parent_id) IS DISTINCT FROM ROW(NEW.r, NEW.w, NEW.parent_id)
408                                                );
409                               
410                                UPDATE  pages
411                                SET             status = CASE NEW.status
412                                                WHEN 'revision'
413                                                THEN
414                                                        'active'::node_status
415                                                ELSE
416                                                        'obsolete'::node_status
417                                                END,
418                                                published = NEW.published,
419                                                user_id = NEW.user_id,
420                                                modified = now(),
421                                                key = NEW.key,
422                                                title = NEW.title,
423                                                abstract = NEW.abstract,
424                                                content = NEW.content,
425                                                memo = NEW.memo,
426                                                r = NEW.r,
427                                                w = NEW.w
428                                WHERE   id = NEW.main_id;
429                        END IF;
430               
431                        -- auto-clean
432                        DELETE FROM pages
433                        WHERE   main_id = NEW.main_id
434                        AND             id <> main_id
435                        AND             status IN ( 'obsolete', 'draft', 'pending' );
436                ELSE
437                        do_perms := EXISTS (
438                                SELECT  NULL
439                                FROM    pages
440                                WHERE   id = NEW.main_id
441                                AND             ROW(r, w) IS DISTINCT FROM ROW(NEW.r, NEW.w)
442                                );
443                       
444                        IF do_perms
445                        THEN
446                                -- update perms
447                                UPDATE  pages
448                                SET             r = NEW.r,
449                                                w = NEW.w
450                                WHERE   main_id = NEW.main_id
451                                AND             id <> NEW.id;
452                        END IF;
453                       
454                        -- auto-clean
455                        UPDATE  pages
456                        SET             status = 'draft'
457                        WHERE   status IN ('pending', 'future')
458                        AND             main_id = NEW.main_id
459                        AND             id <> NEW.id;
460                END IF;
461        END IF;
462       
463        -- enforce unique order_id
464        IF NEW.parent_id IS NULL
465        THEN
466                IF EXISTS (
467                        SELECT  NULL
468                        FROM    pages
469                        WHERE   parent_id IS NULL
470                        AND             main_id <> NEW.main_id
471                        AND             order_id = NEW.order_id
472                        )
473                THEN
474                        UPDATE  pages
475                        SET             order_id = order_id + 1
476                        WHERE   parent_id IS NULL
477                        AND             main_id <> NEW.main_id
478                        AND             order_id >= NEW.order_id;
479                END IF;
480        ELSE
481                IF EXISTS (
482                        SELECT  NULL
483                        FROM    pages
484                        WHERE   parent_id = NEW.parent_id
485                        AND             main_id <> NEW.main_id
486                        AND             order_id = NEW.order_id
487                        )
488                THEN
489                        UPDATE  pages
490                        SET             order_id = order_id + 1
491                        WHERE   parent_id = NEW.parent_id
492                        AND             main_id <> NEW.main_id
493                        AND             order_id >= NEW.order_id;
494                END IF;
495        END IF;
496       
497        -- propagate parent_id and order_id
498        UPDATE  pages
499        SET             parent_id = NEW.parent_id,
500                        order_id = NEW.order_id
501        WHERE   main_id = NEW.main_id
502        AND             ROW(parent_id, order_id) IS DISTINCT FROM ROW(NEW.parent_id, NEW.order_id);
503       
504        IF FOUND OR NEW.id = NEW.main_id
505        THEN
506                -- reindex doc tree
507                UPDATE  pages
508                SET             lft = tree.lft,
509                                rgt = tree.rgt,
510                                sort_num = tree.sort_num,
511                                order_id = tree.sort_num[array_upper(tree.sort_num, 1)],
512                                depth = array_upper(tree.sort_num, 1) - 1
513                FROM    page_tree() as tree
514                WHERE   main_id = tree.id;
515        END IF;
516               
517        IF NEW.id = NEW.main_id
518        THEN
519                -- insert into guest_pages
520                INSERT INTO guest_nodes
521                SELECT  pages.id as id,
522                                bool_and(parents.r IS NOT FALSE AND parents.status = 'active')
523                                OR bool_or(parents.w IS TRUE) as read,
524                                bool_or(parents.w IS TRUE) as write
525                FROM    pages
526                JOIN    pages as parents
527                ON              parents.id = parents.main_id
528                AND             parents.lft <= pages.lft
529                AND             parents.rgt >= pages.rgt
530                WHERE   pages.id = NEW.id
531                GROUP BY pages.id;
532        ELSEIF do_perms
533        THEN
534                -- update guest_pages
535                UPDATE  guest_nodes as guest_pages
536                SET             read = new_perms.read,
537                                write = new_perms.write
538                FROM    (
539                                SELECT  children.id,
540                                                bool_and(parents.r IS NOT FALSE AND parents.status = 'active')
541                                                OR bool_or(parents.w IS TRUE) as read,
542                                                bool_or(parents.w IS TRUE) as write
543                                FROM    pages
544                                JOIN    pages as children
545                                ON              children.id = children.main_id
546                                AND             children.lft >= pages.lft
547                                AND             children.rgt <= pages.rgt
548                                JOIN    pages as parents
549                                ON              parents.id = parents.main_id
550                                AND             parents.lft <= children.lft
551                                AND             parents.rgt >= children.rgt
552                                WHERE   pages.id = NEW.main_id
553                                GROUP BY children.id
554                                ) as new_perms
555                WHERE   guest_pages.id = new_perms.id
556                AND             ROW(guest_pages.read, guest_pages.write) <> ROW(new_perms.read, new_perms.write);
557               
558                FOR profile_rec IN
559                SELECT  perms.profile_id
560                FROM    pages
561                JOIN    pages as children
562                ON              children.id = children.main_id
563                AND             children.lft >= pages.lft
564                AND             children.rgt <= pages.rgt
565                JOIN    profile2node as perms
566                ON              perms.id = children.id
567                WHERE   pages.id = NEW.main_id
568                GROUP BY perms.profile_id
569                LOOP
570                        SELECT  parents.lft,
571                                        parents.rgt
572                        INTO    page_rec
573                        FROM    pages
574                        JOIN    pages as children
575                        ON              children.id = children.main_id
576                        AND             children.lft >= pages.lft
577                        AND             children.rgt <= pages.rgt
578                        JOIN    pages as parents
579                        ON              parents.id = parents.main_id
580                        AND             parents.lft <= children.lft
581                        AND             parents.rgt >= children.rgt
582                        JOIN    page_perms as perms
583                        ON              perms.profile_id = profile_rec.profile_id
584                        AND             perms.id = parents.id
585                        WHERE   pages.id = NEW.main_id
586                        ORDER BY parents.lft
587                        LIMIT   1;
588
589                        IF NOT FOUND
590                        THEN
591                                -- delete from profile_pages
592                                DELETE FROM profile2node as perms
593                                USING   pages
594                                JOIN    pages as children
595                                ON              children.id = children.main_id
596                                AND             children.lft >= pages.lft
597                                AND             children.rgt <= pages.rgt
598                                WHERE   perms.profile_id = profile_rec.profile_id
599                                AND             perms.id = children.id
600                                AND             pages.id = NEW.main_id;
601                        ELSE
602                                -- delete from profile_pages
603                                DELETE FROM profile2node as perms
604                                USING   pages as parents
605                                WHERE   perms.profile_id = profile_rec.profile_id
606                                AND             perms.id = parents.id
607                                AND             parents.id = parents.main_id
608                                AND             parents.lft < page_rec.lft
609                                AND             parents.rgt > page_rec.rgt;
610
611                                -- update profile_pages
612                                UPDATE  profile2node as perms
613                                SET             read = new_perms.read,
614                                                write = new_perms.write,
615                                                sticky = new_perms.sticky
616                                FROM    (
617                                                SELECT  children.id,
618                                                                bool_and(perms.read AND parents.status = 'active')
619                                                                OR bool_or(perms.write) as read,
620                                                                bool_or(perms.write) as write,
621                                                                bool_or(perms.sticky) as sticky
622                                                FROM    pages
623                                                JOIN    pages as children
624                                                ON              children.id = children.main_id
625                                                AND             children.lft >= pages.lft
626                                                AND             children.rgt <= pages.rgt
627                                                JOIN    pages as parents
628                                                ON              parents.id = parents.main_id
629                                                AND             parents.lft <= children.lft
630                                                AND             parents.rgt >= children.rgt
631                                                JOIN    page_perms as perms
632                                                ON              perms.profile_id = profile_rec.profile_id
633                                                AND             perms.id = parents.id
634                                                WHERE   pages.id = NEW.main_id
635                                                GROUP BY children.id
636                                                ) as new_perms
637                                WHERE   perms.profile_id = profile_rec.profile_id
638                                AND             perms.id = new_perms.id
639                                AND             ROW(perms.read, perms.write, perms.sticky) <> ROW(new_perms.read, new_perms.write, new_perms.sticky);
640                        END IF;
641                END LOOP;
642        END IF;
643       
644        RETURN NULL;
645END;
646$$ LANGUAGE plpgsql;
647
648CREATE TRIGGER insert_page AFTER INSERT ON pages
649        FOR EACH ROW EXECUTE PROCEDURE insert_page();
650
651
652/*
653 * Trigger: _cascade_page()
654 */
655
656CREATE OR REPLACE FUNCTION _cascade_page()
657        RETURNS trigger
658AS $$
659BEGIN
660        IF OLD.id = OLD.main_id
661        THEN
662                -- delete related pages
663                DELETE FROM pages
664                WHERE   main_id = OLD.main_id
665                AND             id <> OLD.id;
666        END IF;
667       
668        RETURN OLD;
669END;
670$$ LANGUAGE plpgsql;
671
672CREATE TRIGGER _cascade_page BEFORE DELETE ON pages
673        FOR EACH ROW EXECUTE PROCEDURE _cascade_page();
674
675
676/*
677 * Trigger: delete_page()
678 */
679
680CREATE OR REPLACE FUNCTION delete_page()
681        RETURNS trigger
682AS $$
683BEGIN
684        IF OLD.id = OLD.main_id
685        THEN
686                -- reindex tree
687                UPDATE  pages
688                SET             lft = tree.lft,
689                                rgt = tree.rgt,
690                                sort_num = tree.sort_num,
691                                order_id = tree.sort_num[array_upper(tree.sort_num, 1)],
692                                depth = array_upper(tree.sort_num, 1) - 1
693                FROM    page_tree() as tree
694                WHERE   main_id = tree.id;
695        END IF;
696       
697        RETURN NULL;
698END;
699$$ LANGUAGE plpgsql;
700
701CREATE TRIGGER delete_page AFTER DELETE ON pages
702        FOR EACH ROW EXECUTE PROCEDURE delete_page();
703
704
705/*
706 * Table: page_perms
707 */
708
709CREATE TABLE page_perms (
710        profile_id              uuid                    NOT NULL REFERENCES profiles ( id ) ON DELETE CASCADE
711                                                                        DEFERRABLE INITIALLY DEFERRED,
712        id                              uuid                    NOT NULL REFERENCES pages ( id ) ON DELETE CASCADE
713                                                                        DEFERRABLE INITIALLY DEFERRED,
714        read                    boolean                 NOT NULL DEFAULT true,
715        write                   boolean                 NOT NULL DEFAULT false,
716        sticky                  boolean                 NOT NULL DEFAULT false,
717        PRIMARY KEY ( profile_id, id ),
718        CHECK ( NOT ( NOT read AND write ) ),
719        CHECK ( NOT ( NOT sticky AND NOT read ) ),
720        CHECK ( NOT ( NOT sticky AND write ) ),
721        CONSTRAINT page_perms_valid_id_fkey FOREIGN KEY ( id, id )
722                                                                        REFERENCES pages ( main_id, id ) ON DELETE CASCADE
723                                                                        DEFERRABLE INITIALLY DEFERRED
724) WITH ( FILLFACTOR = 80 );
725
726CREATE UNIQUE INDEX page_perms_reverse ON page_perms ( id, profile_id );
727CREATE INDEX page_perms_profile_id ON page_perms ( profile_id );
728CREATE INDEX page_perms_id ON page_perms ( id );
729
730
731/*
732 * Trigger: insert_page_perms()
733 */
734
735CREATE OR REPLACE FUNCTION insert_page_perms()
736        RETURNS trigger
737AS $$
738BEGIN
739        -- insert into profile_pages
740        INSERT INTO profile2node
741        SELECT  NEW.profile_id,
742                        children.id,
743                        bool_and(perms.read AND parents.status = 'active')
744                        OR bool_or(perms.write) as read,
745                        bool_or(perms.write) as write,
746                        bool_or(perms.sticky) as sticky
747        FROM    pages
748        JOIN    pages as children
749        ON              children.id = children.main_id
750        AND             children.lft >= pages.lft
751        AND             children.rgt <= pages.rgt
752        JOIN    pages as parents
753        ON              parents.id = parents.main_id
754        AND             parents.lft <= children.lft
755        AND             parents.rgt >= children.rgt
756        JOIN    page_perms as perms
757        ON              perms.profile_id = NEW.profile_id
758        AND             perms.id = parents.id
759        LEFT JOIN profile2node
760        ON              profile2node.profile_id = perms.profile_id
761        AND             profile2node.id = children.id
762        WHERE   pages.id = NEW.id
763        AND             profile2node.id IS NULL
764        GROUP BY children.id;
765       
766        -- update profile_pages
767        UPDATE  profile2node as perms
768        SET             read = new_perms.read,
769                        write = new_perms.write,
770                        sticky = new_perms.sticky
771        FROM    (
772                        SELECT  children.id,
773                                        bool_and(perms.read AND parents.status = 'active')
774                                        OR bool_or(perms.write) as read,
775                                        bool_or(perms.write) as write,
776                                        bool_or(perms.sticky) as sticky
777                        FROM    pages
778                        JOIN    pages as children
779                        ON              children.id = children.main_id
780                        AND             children.lft >= pages.lft
781                        AND             children.rgt <= pages.rgt
782                        JOIN    pages as parents
783                        ON              parents.id = parents.main_id
784                        AND             parents.lft <= children.lft
785                        AND             parents.rgt >= children.rgt
786                        JOIN    page_perms as perms
787                        ON              perms.profile_id = NEW.profile_id
788                        AND             perms.id = parents.id
789                        WHERE   pages.id = NEW.id
790                        GROUP BY children.id
791                        ) as new_perms
792        WHERE   perms.profile_id = NEW.profile_id
793        AND             perms.id = new_perms.id
794        AND             ROW(perms.read, perms.write, perms.sticky) <> ROW(new_perms.read, new_perms.write, new_perms.sticky);
795       
796        RETURN NULL;
797END;
798$$ LANGUAGE plpgsql;
799
800CREATE TRIGGER insert_page_perms AFTER INSERT ON page_perms
801        FOR EACH ROW EXECUTE PROCEDURE insert_page_perms();
802
803
804/*
805 * Trigger: update_page_perms()
806 */
807
808CREATE OR REPLACE FUNCTION update_page_perms()
809        RETURNS trigger
810AS $$
811BEGIN
812        IF ROW(NEW.read, NEW.write, NEW.sticky) <> ROW(OLD.read, OLD.write, OLD.sticky)
813        THEN
814                -- update profile_pages
815                UPDATE  profile2node as perms
816                SET             read = new_perms.read,
817                                write = new_perms.write,
818                                sticky = new_perms.sticky
819                FROM    (
820                                SELECT  children.id,
821                                                bool_and(perms.read AND parents.status = 'active')
822                                                OR bool_or(perms.write) as read,
823                                                bool_or(perms.write) as write,
824                                                bool_or(perms.sticky) as sticky
825                                FROM    pages
826                                JOIN    pages as children
827                                ON              children.id = children.main_id
828                                AND             children.lft >= pages.lft
829                                AND             children.rgt <= pages.rgt
830                                JOIN    pages as parents
831                                ON              parents.id = parents.main_id
832                                AND             parents.lft <= children.lft
833                                AND             parents.rgt >= children.rgt
834                                JOIN    page_perms as perms
835                                ON              perms.profile_id = NEW.profile_id
836                                AND             perms.id = parents.id
837                                WHERE   pages.id = NEW.id
838                                GROUP BY children.id
839                                ) as new_perms
840                WHERE   perms.profile_id = NEW.profile_id
841                AND             perms.id = new_perms.id
842                AND             ROW(perms.read, perms.write, perms.sticky) <> ROW(new_perms.read, new_perms.write, new_perms.sticky);
843        END IF;
844       
845        RETURN NULL;
846END;
847$$ LANGUAGE plpgsql;
848
849CREATE TRIGGER update_page_perms AFTER UPDATE ON page_perms
850        FOR EACH ROW EXECUTE PROCEDURE update_page_perms();
851
852
853/*
854 * Trigger: delete_page_perms()
855 */
856
857CREATE OR REPLACE FUNCTION delete_page_perms()
858        RETURNS trigger
859AS $$
860DECLARE
861        rec     record;
862BEGIN
863        SELECT  parents.lft,
864                        parents.rgt
865        INTO    rec
866        FROM    pages
867        JOIN    pages as children
868        ON              children.id = children.main_id
869        AND             children.lft >= pages.lft
870        AND             children.rgt <= pages.rgt
871        JOIN    pages as parents
872        ON              parents.id = parents.main_id
873        AND             parents.lft <= children.lft
874        AND             parents.rgt >= children.rgt
875        JOIN    page_perms as perms
876        ON              perms.profile_id = OLD.profile_id
877        AND             perms.id = parents.id
878        WHERE   pages.id = OLD.id
879        ORDER BY parents.lft
880        LIMIT   1;
881       
882        IF NOT FOUND
883        THEN
884                -- delete from profile_pages
885                DELETE FROM profile2node as perms
886                USING   pages
887                JOIN    pages as children
888                ON              children.id = children.main_id
889                AND             children.lft >= pages.lft
890                AND             children.rgt <= pages.rgt
891                WHERE   perms.profile_id = OLD.profile_id
892                AND             perms.id = children.id
893                AND             pages.id = OLD.id;
894        ELSE
895                -- delete from profile_pages
896                DELETE FROM profile2node as perms
897                USING   pages as parents
898                WHERE   perms.profile_id = OLD.profile_id
899                AND             perms.id = parents.id
900                AND             parents.id = parents.main_id
901                AND             parents.lft < rec.lft
902                AND             parents.rgt > rec.rgt;
903               
904                -- update profile_pages
905                UPDATE  profile2node as perms
906                SET             read = new_perms.read,
907                                write = new_perms.write,
908                                sticky = new_perms.sticky
909                FROM    (
910                                SELECT  children.id,
911                                                bool_and(perms.read AND parents.status = 'active')
912                                                OR bool_or(perms.write) as read,
913                                                bool_or(perms.write) as write,
914                                                bool_or(perms.sticky) as sticky
915                                FROM    pages
916                                JOIN    pages as children
917                                ON              children.id = children.main_id
918                                AND             children.lft >= pages.lft
919                                AND             children.rgt <= pages.rgt
920                                JOIN    pages as parents
921                                ON              parents.id = parents.main_id
922                                AND             parents.lft <= children.lft
923                                AND             parents.rgt >= children.rgt
924                                JOIN    page_perms as perms
925                                ON              perms.profile_id = OLD.profile_id
926                                AND             perms.id = parents.id
927                                WHERE   pages.id = OLD.id
928                                GROUP BY children.id
929                                ) as new_perms
930                WHERE   perms.profile_id = OLD.profile_id
931                AND             perms.id = new_perms.id
932                AND             ROW(perms.read, perms.write, perms.sticky) <> ROW(new_perms.read, new_perms.write, new_perms.sticky);
933        END IF;
934       
935        RETURN NULL;
936END;
937$$ LANGUAGE plpgsql;
938
939CREATE TRIGGER delete_page_perms AFTER DELETE ON page_perms
940        FOR EACH ROW EXECUTE PROCEDURE delete_page_perms();