-- acs-kernel/sql/postgresql/upgrade/upgrade-4.6-4.6.1.sql -- -- @author Jeff Davis (davis@xarg.net) -- @creation-date 2002-12-17 -- @cvs-id $Id: upgrade-4.6-4.6.1d1.sql,v 1.1 2005/02/26 19:48:54 jeffd Exp $ -- Add two new datatypes (supported by templating already). -- insert into acs_datatypes (datatype, max_n_values) values ('url', null); insert into acs_datatypes (datatype, max_n_values) values ('email', null); -- declaring this function isstrict,iscachable can make a significant -- performance difference since this is used in some potentially -- expensive queries create or replace function acs__magic_object_id (varchar) returns integer as ' declare magic_object_id__name alias for $1; magic_object_id__object_id acs_objects.object_id%TYPE; begin select object_id into magic_object_id__object_id from acs_magic_objects where name = magic_object_id__name; return magic_object_id__object_id; end;' language 'plpgsql' with(isstrict,iscachable); -------------------------------------------------------------------------------- -- -- Tilmann Singer - delete direct permissions when deleting an object. -- create or replace function acs_object__delete (integer) returns integer as ' declare delete__object_id alias for $1; obj_type record; begin -- Delete dynamic/generic attributes delete from acs_attribute_values where object_id = delete__object_id; -- Delete direct permissions records. delete from acs_permissions where object_id = delete__object_id; -- select table_name, id_column -- from acs_object_types -- start with object_type = (select object_type -- from acs_objects o -- where o.object_id = delete__object_id) -- connect by object_type = prior supertype -- There was a gratuitous join against the objects table here, -- probably a leftover from when this was a join, and not a subquery. -- Functionally, this was working, but time taken was O(n) where n is the -- number of objects. OUCH. Fixed. (ben) for obj_type in select o2.table_name, o2.id_column from acs_object_types o1, acs_object_types o2 where o1.object_type = (select object_type from acs_objects o where o.object_id = delete__object_id) and o1.tree_sortkey between o2.tree_sortkey and tree_right(o2.tree_sortkey) order by o2.tree_sortkey desc loop -- Delete from the table. -- DRB: I removed the quote_ident calls that DanW originally included -- because the table names appear to be stored in upper case. Quoting -- causes them to not match the actual lower or potentially mixed-case -- table names. We will just forbid squirrely names that include quotes. -- daveB -- ETP is creating a new object, but not a table, although it does specify a -- table name, so we need to check if the table exists. Wp-slim does this too if table_exists(obj_type.table_name) then execute ''delete from '' || obj_type.table_name || '' where '' || obj_type.id_column || '' = '' || delete__object_id; end if; end loop; return 0; end;' language 'plpgsql'; -------------------------------------------------------------------------------- -- DRB: Change security context to object -4 create or replace function acs_objects_context_id_in_tr () returns opaque as ' declare security_context_root integer; begin insert into acs_object_context_index (object_id, ancestor_id, n_generations) values (new.object_id, new.object_id, 0); if new.context_id is not null and new.security_inherit_p = ''t'' then insert into acs_object_context_index (object_id, ancestor_id, n_generations) select new.object_id as object_id, ancestor_id, n_generations + 1 as n_generations from acs_object_context_index where object_id = new.context_id; else security_context_root = acs__magic_object_id(''security_context_root''); if new.object_id != security_context_root then insert into acs_object_context_index (object_id, ancestor_id, n_generations) values (new.object_id, security_context_root, 1); end if; end if; return new; end;' language 'plpgsql'; create or replace function acs_objects_context_id_up_tr () returns opaque as ' declare pair record; security_context_root integer; begin if new.object_id = old.object_id and new.context_id = old.context_id and new.security_inherit_p = old.security_inherit_p then return new; end if; -- Remove my old ancestors from my descendants. delete from acs_object_context_index where object_id in (select object_id from acs_object_contexts where ancestor_id = old.object_id) and ancestor_id in (select ancestor_id from acs_object_contexts where object_id = old.object_id); -- Kill all my old ancestors. delete from acs_object_context_index where object_id = old.object_id; insert into acs_object_context_index (object_id, ancestor_id, n_generations) values (new.object_id, new.object_id, 0); if new.context_id is not null and new.security_inherit_p = ''t'' then -- Now insert my new ancestors for my descendants. for pair in select * from acs_object_context_index where ancestor_id = new.object_id LOOP insert into acs_object_context_index (object_id, ancestor_id, n_generations) select pair.object_id, ancestor_id, n_generations + pair.n_generations + 1 as n_generations from acs_object_context_index where object_id = new.context_id; end loop; else security_context_root = acs__magic_object_id(''security_context_root''); if new.object_id != security_context_root then -- We need to make sure that new.OBJECT_ID and all of its -- children have security_context_root as an ancestor. for pair in select * from acs_object_context_index where ancestor_id = new.object_id LOOP insert into acs_object_context_index (object_id, ancestor_id, n_generations) values (pair.object_id, security_context_root, pair.n_generations + 1); end loop; end if; end if; return new; end;' language 'plpgsql'; -- DRB: This is the function that actually changes security_context_root -- to -4 rather than 0 drop trigger acs_objects_context_id_in_tr on acs_objects; drop trigger acs_objects_context_id_up_tr on acs_objects; delete from acs_magic_objects where name = 'security_context_root'; select acs_object__new ( -4, 'acs_object', now(), null, null, null ); insert into acs_magic_objects (name, object_id) values ('security_context_root', -4); update acs_object_context_index set ancestor_id = -4 where ancestor_id = 0; update acs_object_context_index set object_id = -4 where object_id = 0; update acs_permissions set object_id = -4 where object_id = 0; update acs_objects set context_id = -4 where context_id = 0; -- Content Repository sets parent_id to security_context_root -- for content modules update cr_items set parent_id = -4 where parent_id = 0; select acs_object__delete(0); create trigger acs_objects_context_id_in_tr after insert on acs_objects for each row execute procedure acs_objects_context_id_in_tr (); create trigger acs_objects_context_id_up_tr after update on acs_objects for each row execute procedure acs_objects_context_id_up_tr (); ------------------------------------------------------------------------- -- DRB: We now will turn the magic -1 party into a group that contains -- all registered users and a new unregistered visitor. This will allow -- us to do all permission checking on a materialized version of the -- party_member_map. -- Make our new "Unregistered Visitor" be object 0, which corresponds -- with the user_id assigned throughout the toolkit Tcl code insert into acs_objects (object_id, object_type) values (0, 'person'); insert into parties (party_id) values (0); insert into persons (person_id, first_names, last_name) values (0, 'Unregistered', 'Visitor'); insert into acs_magic_objects (name, object_id) values ('unregistered_visitor', 0); -- Now transform the old special -1 party into a legitimate group with -- one user, our Unregistered Visitor update acs_objects set object_type = 'group' where object_id = -1; insert into groups (group_id, group_name, join_policy) values (-1, 'The Public', 'closed'); -- Add our only user, the Unregistered Visitor select membership_rel__new ( null, 'membership_rel', acs__magic_object_id('the_public'), 0, 'approved', null, null); -- Now declare "The Public" to be composed of itself and the "Registered -- Users" group select composition_rel__new ( null, 'composition_rel', acs__magic_object_id('the_public'), acs__magic_object_id('registered_users'), null, null); ------------------------------------------------------------------------------- -- DRB: Replace the old party_emmber_map and party_approved_member_map views -- (they were both the same and very slow) with a table containing the same -- information. This can be used to greatly speed permissions checking. drop view party_member_map; drop view party_approved_member_map; -- Though for permission checking we only really need to map parties to -- member users, the old view included identity entries for all parties -- in the system. It doesn't cost all that much to maintain the extra -- rows so we will, just in case some overly clever programmer out there -- depends on it. -- This represents a large amount of redundant data which is separately -- stored in the group_element_index table. We might want to clean this -- up in the future but time constraints on 4.6.1 require I keep this -- relatively simple. Implementing a real "subgroup_rel" would help a -- lot by in itself reducing the number of redundant rows in the two -- tables. create table party_approved_member_map ( party_id integer constraint party_member_party_nn not null constraint party_member_party_fk references parties, member_id integer constraint party_member_member_nn not null constraint party_member_member_fk references parties, tag integer constraint party_member_tag_nn not null, constraint party_approved_member_map_pk primary key (party_id, member_id, tag) ); -- Need this to speed referential integrity create index party_member_member_idx on party_approved_member_map(member_id); -- Every person is a member of itself insert into party_approved_member_map (party_id, member_id, tag) select party_id, party_id, 0 from parties; -- Every party is a member if it is an approved member of -- some sort of membership_rel insert into party_approved_member_map (party_id, member_id, tag) select group_id, member_id, rel_id from group_approved_member_map; -- Every party is a member if it is an approved member of -- some sort of relation segment insert into party_approved_member_map (party_id, member_id, tag) select segment_id, member_id, rel_id from rel_seg_approved_member_map; analyze party_approved_member_map; -- Helper functions to maintain the materialized party_approved_member_map. create or replace function party_approved_member__add_one(integer, integer, integer) returns integer as ' declare p_party_id alias for $1; p_member_id alias for $2; p_rel_id alias for $3; begin insert into party_approved_member_map (party_id, member_id, tag) values (p_party_id, p_member_id, p_rel_id); return 1; end;' language 'plpgsql'; create or replace function party_approved_member__add(integer, integer, integer, varchar) returns integer as ' declare p_party_id alias for $1; p_member_id alias for $2; p_rel_id alias for $3; p_rel_type alias for $4; v_segments record; begin perform party_approved_member__add_one(p_party_id, p_member_id, p_rel_id); -- if the relation type is mapped to relational segments unmap them too for v_segments in select segment_id from rel_segments s, acs_object_types o1, acs_object_types o2 where o1.object_type = p_rel_type and o1.tree_sortkey between o2.tree_sortkey and tree_right(o2.tree_sortkey) and s.rel_type = o2.object_type and s.group_id = p_party_id loop perform party_approved_member__add_one(v_segments.segment_id, p_member_id, p_rel_id); end loop; return 1; end;' language 'plpgsql'; create or replace function party_approved_member__remove_one(integer, integer, integer) returns integer as ' declare p_party_id alias for $1; p_member_id alias for $2; p_rel_id alias for $3; begin delete from party_approved_member_map where party_id = p_party_id and member_id = p_member_id and tag = p_rel_id; return 1; end;' language 'plpgsql'; create or replace function party_approved_member__remove(integer, integer, integer, varchar) returns integer as ' declare p_party_id alias for $1; p_member_id alias for $2; p_rel_id alias for $3; p_rel_type alias for $4; v_segments record; begin perform party_approved_member__remove_one(p_party_id, p_member_id, p_rel_id); -- if the relation type is mapped to relational segments unmap them too for v_segments in select segment_id from rel_segments s, acs_object_types o1, acs_object_types o2 where o1.object_type = p_rel_type and o1.tree_sortkey between o2.tree_sortkey and tree_right(o2.tree_sortkey) and s.rel_type = o2.object_type and s.group_id = p_party_id loop perform party_approved_member__remove_one(v_segments.segment_id, p_member_id, p_rel_id); end loop; return 1; end;' language 'plpgsql'; -- Triggers to maintain party_approved_member_map when parties are created or -- destroyed. create or replace function parties_in_tr () returns opaque as ' begin insert into party_approved_member_map (party_id, member_id, tag) values (new.party_id, new.party_id, 0); return new; end;' language 'plpgsql'; create trigger parties_in_tr after insert on parties for each row execute procedure parties_in_tr (); create or replace function parties_del_tr () returns opaque as ' begin delete from party_approved_member_map where party_id = old.party_id and member_id = old.party_id; return old; end;' language 'plpgsql'; create trigger parties_del_tr before delete on parties for each row execute procedure parties_del_tr (); -- Triggers to maintain party_approved_member_map when relational segments are -- created or destroyed. We only remove the (segment_id, member_id) rows as -- removing the relational segment itself does not remove members from the -- group with that rel_type. This was intentional on the part of the aD folks -- who added relational segments to ACS 4.2. create or replace function rel_segments_in_tr () returns opaque as ' begin insert into party_approved_member_map (party_id, member_id, tag) select new.segment_id, element_id, rel_id from group_element_index where group_id = new.group_id and rel_type = new.rel_type; return new; end;' language 'plpgsql'; create trigger rel_segments_in_tr before insert on rel_segments for each row execute procedure rel_segments_in_tr (); create or replace function rel_segments_del_tr () returns opaque as ' begin delete from party_approved_member_map where party_id = old.segment_id and member_id in (select element_id from group_element_index where group_id = old.group_id and rel_type = old.rel_type); return old; end;' language 'plpgsql'; create trigger rel_segments_del_tr before delete on rel_segments for each row execute procedure rel_segments_del_tr (); -- The insert trigger was dummied up in groups-create.sql, so we just need -- to replace the trigger function, not create the trigger create or replace function membership_rels_in_tr () returns opaque as ' declare v_object_id_one acs_rels.object_id_one%TYPE; v_object_id_two acs_rels.object_id_two%TYPE; v_rel_type acs_rels.rel_type%TYPE; v_error text; map record; begin -- First check if added this relation violated any relational constraints v_error := rel_constraint__violation(new.rel_id); if v_error is not null then raise EXCEPTION ''-20000: %'', v_error; end if; select object_id_one, object_id_two, rel_type into v_object_id_one, v_object_id_two, v_rel_type from acs_rels where rel_id = new.rel_id; -- Insert a row for me in the group_element_index. insert into group_element_index (group_id, element_id, rel_id, container_id, rel_type, ancestor_rel_type) values (v_object_id_one, v_object_id_two, new.rel_id, v_object_id_one, v_rel_type, ''membership_rel''); if new.member_state = ''approved'' then perform party_approved_member__add(v_object_id_one, v_object_id_two, new.rel_id, v_rel_type); end if; -- For all groups of which I am a component, insert a -- row in the group_element_index. for map in select distinct group_id from group_component_map where component_id = v_object_id_one loop insert into group_element_index (group_id, element_id, rel_id, container_id, rel_type, ancestor_rel_type) values (map.group_id, v_object_id_two, new.rel_id, v_object_id_one, v_rel_type, ''membership_rel''); if new.member_state = ''approved'' then perform party_approved_member__add(map.group_id, v_object_id_two, new.rel_id, v_rel_type); end if; end loop; return new; end;' language 'plpgsql'; create or replace function membership_rels_up_tr () returns opaque as ' declare map record; begin if new.member_state = old.member_state then return new; end if; for map in select group_id, element_id, rel_type from group_element_index where rel_id = new.rel_id loop if new.member_state = ''approved'' then perform party_approved_member__add(map.group_id, map.element_id, new.rel_id, map.rel_type); else perform party_approved_member__remove(map.group_id, map.element_id, new.rel_id, map.rel_type); end if; end loop; return new; end;' language 'plpgsql'; create trigger membership_rels_up_tr before update on membership_rels for each row execute procedure membership_rels_up_tr (); create or replace function membership_rels_del_tr () returns opaque as ' declare v_error text; map record; begin -- First check if removing this relation would violate any relational constraints v_error := rel_constraint__violation_if_removed(old.rel_id); if v_error is not null then raise EXCEPTION ''-20000: %'', v_error; end if; for map in select group_id, element_id, rel_type from group_element_index where rel_id = old.rel_id loop perform party_approved_member__remove(map.group_id, map.element_id, old.rel_id, map.rel_type); end loop; delete from group_element_index where rel_id = old.rel_id; return old; end;' language 'plpgsql'; ------------------------------------------------------------------------------------ -- DRB: upgrade to Dan Wickstrom's version of acs-permissions which materializes the -- acs_privilege_descendant_map view. drop view acs_privilege_descendant_map; create table acs_privilege_descendant_map ( privilege varchar(100) not null constraint acs_priv_hier_priv_fk references acs_privileges (privilege), descendant varchar(100) not null constraint acs_priv_hier_child_priv_fk references acs_privileges (privilege) ); -- DRB: Empirical testing showed that even with just 61 entries in the new table -- this index sped things up by roughly 15% create index acs_priv_desc_map_idx on acs_privilege_descendant_map(descendant); create view acs_privilege_descendant_map_view as select p1.privilege, p2.privilege as descendant from acs_privileges p1, acs_privileges p2 where exists (select h2.child_privilege from acs_privilege_hierarchy_index h1, acs_privilege_hierarchy_index h2 where h1.privilege = p1.privilege and h2.privilege = p2.privilege and h2.tree_sortkey between h1.tree_sortkey and tree_right(h1.tree_sortkey)) or p1.privilege = p2.privilege; insert into acs_privilege_descendant_map (privilege, descendant) select privilege, descendant from acs_privilege_descendant_map_view; drop view acs_object_grantee_priv_map; create view acs_object_grantee_priv_map as select a.object_id, a.grantee_id, m.descendant as privilege from acs_permissions_all a, acs_privilege_descendant_map m where a.privilege = m.privilege; create or replace function acs_priv_hier_ins_del_tr () returns opaque as ' declare new_value integer; new_key varbit default null; v_rec record; deleted_p boolean; begin -- if more than one node was deleted the second trigger call -- will error out. This check avoids that problem. if TG_OP = ''DELETE'' then select count(*) = 0 into deleted_p from acs_privilege_hierarchy_index where old.privilege = privilege and old.child_privilege = child_privilege; if deleted_p then return new; end if; end if; -- recalculate the table from scratch. delete from acs_privilege_hierarchy_index; -- first find the top nodes of the tree for v_rec in select privilege, child_privilege from acs_privilege_hierarchy where privilege NOT in (select distinct child_privilege from acs_privilege_hierarchy) LOOP -- top level node, so find the next key at this level. select max(tree_leaf_key_to_int(tree_sortkey)) into new_value from acs_privilege_hierarchy_index where tree_level(tree_sortkey) = 1; -- insert the new node insert into acs_privilege_hierarchy_index (privilege, child_privilege, tree_sortkey) values (v_rec.privilege, v_rec.child_privilege, tree_next_key(null, new_value)); -- now recurse down from this node PERFORM priv_recurse_subtree(tree_next_key(null, new_value), v_rec.child_privilege); end LOOP; -- materialize the map view to speed up queries -- DanW (dcwickstrom@earthlink.net) 30 Jan, 2003 delete from acs_privilege_descendant_map; insert into acs_privilege_descendant_map (privilege, descendant) select privilege, descendant from acs_privilege_descendant_map_view; return new; end;' language 'plpgsql'; -- New fast version of acs_object_party_privilege_map drop view acs_object_party_privilege_map; create view acs_object_party_privilege_map as select c.object_id, pdm.descendant as privilege, pamm.member_id as party_id from acs_object_context_index c, acs_permissions p, acs_privilege_descendant_map pdm, party_approved_member_map pamm where c.ancestor_id = p.object_id and pdm.privilege = p.privilege and pamm.party_id = p.grantee_id; drop view all_object_party_privilege_map; create view all_object_party_privilege_map as select * from acs_object_party_privilege_map; -- Really speedy version of permission_p written by Don Baccus create or replace function acs_permission__permission_p (integer,integer,varchar) returns boolean as ' declare permission_p__object_id alias for $1; permission_p__party_id alias for $2; permission_p__privilege alias for $3; exists_p boolean; begin return exists (select 1 from acs_permissions p, party_approved_member_map m, acs_object_context_index c, acs_privilege_descendant_map h where p.object_id = c.ancestor_id and h.descendant = permission_p__privilege and c.object_id = permission_p__object_id and m.member_id = permission_p__party_id and p.privilege = h.privilege and p.grantee_id = m.party_id); end;' language 'plpgsql'; -- No longer needed with fast acs_object_party_privilege_map drop function acs_permission__user_with_perm_exists_p (integer,varchar);