-- -- packages/acs-kernel/sql/acs-relationships-create.sql -- -- XXX Fill this in later. -- -- @creation-date 2000-08-13 -- -- @author rhs@mit.edu -- -- @cvs-id $Id: acs-relationships-create.sql,v 1.34 2018/11/01 08:38:00 gustafn Exp $ -- ---------------------------------------------------------------- -- KNOWLEDGE LEVEL: RELATIONSHIP TYPES AND RELATIONSHIP RULES -- ---------------------------------------------------------------- create table acs_rel_roles ( role varchar(100) not null constraint acs_rel_roles_role_pk primary key, pretty_name varchar(100) not null, pretty_plural varchar(100) not null ); create table acs_rel_types ( rel_type varchar(1000) not null constraint acs_rel_types_rel_type_pk primary key constraint acs_rel_types_rel_type_fk references acs_object_types(object_type), object_type_one varchar(1000) not null constraint acs_rel_types_obj_type_1_fk references acs_object_types (object_type), role_one varchar(100) constraint acs_rel_types_role_one_fk references acs_rel_roles (role), min_n_rels_one integer default 0 not null constraint acs_rel_types_min_n_1_ck check (min_n_rels_one >= 0), max_n_rels_one integer constraint acs_rel_types_max_n_1_ck check (max_n_rels_one >= 0), object_type_two varchar(1000) not null constraint acs_rel_types_obj_type_2_fk references acs_object_types (object_type), role_two varchar(100) constraint acs_rel_types_role_two_fk references acs_rel_roles (role), min_n_rels_two integer default 0 not null constraint acs_rel_types_min_n_2_ck check (min_n_rels_two >= 0), max_n_rels_two integer constraint acs_rel_types_max_n_2_ck check (max_n_rels_two >= 0), composable_p boolean default 't' not null, constraint acs_rel_types_n_rels_one_ck check (min_n_rels_one <= max_n_rels_one), constraint acs_rel_types_n_rels_two_ck check (min_n_rels_two <= max_n_rels_two) ); create index acs_rel_types_objtypeone_idx on acs_rel_types (object_type_one); create index acs_rel_types_role_one_idx on acs_rel_types (role_one); create index acs_rel_types_objtypetwo_idx on acs_rel_types (object_type_two); create index acs_rel_types_role_two_idx on acs_rel_types (role_two); comment on table acs_rel_types is $$ Each row in <code>acs_rel_types</code> represents a type of relationship between objects. For example, the following DML statement: <blockquote><pre> insert into acs_rel_types (rel_type, object_type_one, role_one, min_n_rels_one, max_n_rels_one, object_type_two, role_two, min_n_rels_two, max_n_rels_two) values ('employment', 'person', 'employee', 0, null, 'company', 'employer', 0, null) </pre></blockquote> defines an "employment" relationship type that can be expressed in natural language as: <blockquote> A person may be the employee of zero or more companies, and a company may be the employer of zero or more people. </blockquote> $$; -- added select define_function_args('acs_rel_type__create_role','role,pretty_name;null,pretty_plural;null'); -- -- procedure acs_rel_type__create_role/3 -- CREATE OR REPLACE FUNCTION acs_rel_type__create_role( create_role__role varchar, create_role__pretty_name varchar, -- default null create_role__pretty_plural varchar -- default null ) RETURNS integer AS $$ DECLARE BEGIN insert into acs_rel_roles (role, pretty_name, pretty_plural) values (create_role__role, coalesce(create_role__pretty_name,create_role__role), coalesce(create_role__pretty_plural,create_role__role)); return 0; END; $$ LANGUAGE plpgsql; -- -- procedure acs_rel_type__create_role/1 -- CREATE OR REPLACE FUNCTION acs_rel_type__create_role( create_role__role varchar ) RETURNS integer AS $$ DECLARE BEGIN perform acs_rel_type__create_role(create_role__role, NULL, NULL); return 0; END; $$ LANGUAGE plpgsql; -- procedure drop_role -- added select define_function_args('acs_rel_type__drop_role','role'); -- -- procedure acs_rel_type__drop_role/1 -- CREATE OR REPLACE FUNCTION acs_rel_type__drop_role( drop_role__role varchar ) RETURNS integer AS $$ DECLARE BEGIN delete from acs_rel_roles where role = drop_role__role; return 0; END; $$ LANGUAGE plpgsql; -- function role_pretty_name -- added select define_function_args('acs_rel_type__role_pretty_name','role'); -- -- procedure acs_rel_type__role_pretty_name/1 -- CREATE OR REPLACE FUNCTION acs_rel_type__role_pretty_name( role_pretty_name__role varchar ) RETURNS varchar AS $$ DECLARE v_pretty_name acs_rel_roles.pretty_name%TYPE; BEGIN select r.pretty_name into v_pretty_name from acs_rel_roles r where r.role = role_pretty_name__role; return v_pretty_name; END; $$ LANGUAGE plpgsql stable strict; -- function role_pretty_plural -- added select define_function_args('acs_rel_type__role_pretty_plural','role'); -- -- procedure acs_rel_type__role_pretty_plural/1 -- CREATE OR REPLACE FUNCTION acs_rel_type__role_pretty_plural( role_pretty_plural__role varchar ) RETURNS varchar AS $$ DECLARE v_pretty_plural acs_rel_roles.pretty_plural%TYPE; BEGIN select r.pretty_plural into v_pretty_plural from acs_rel_roles r where r.role = role_pretty_plural__role; return v_pretty_plural; END; $$ LANGUAGE plpgsql stable strict; -- procedure create_type -- procedure create_type -- acs_rel_type__create_type /15 vs. /16 has -- /15 has no "roles" (one and two), but "type_extension_table" -- /16 -- pos 1-7: same as /15 -- pos 08: object_type_one (varchar) -- pos 09: role_one (varchar) -- pos 10: min_n_rels_one (integer) -- pos 11: max_n_rels (integer) -- pos 12: object_type_one (varchar) -- pos 13: role_two (varchar) -- pos 14: min_n_rels_two (integer) -- pos 15: max_n_rels_two (integer) -- pos 16: composable_p (boolean) -- /15 -- pos 1-7: same as /15 -- pos 08: type_extension_table (varchar) -- pos 09: object_type_one (varchar) -- pos 10: min_n_rels_one (integer) -- pos 11: max_n_rels (integer) -- pos 12: object_type_two (varchar) -- pos 13: min_n_rels_two (integer) -- pos 14: max_n_rels_two (integer) -- pos 15: composable_p (boolean) select define_function_args('acs_rel_type__create_type','rel_type,pretty_name,pretty_plural,supertype;relationship,table_name,id_column,package_name,object_type_one,role_one;null,min_n_rels_one,max_n_rels_one,object_type_two,role_two;null,min_n_rels_two,max_n_rels_two,composable_p;t'); -- -- procedure acs_rel_type__create_type/16 -- CREATE OR REPLACE FUNCTION acs_rel_type__create_type( create_type__rel_type varchar, create_type__pretty_name varchar, create_type__pretty_plural varchar, create_type__supertype varchar, -- default 'relationship' create_type__table_name varchar, create_type__id_column varchar, create_type__package_name varchar, create_type__object_type_one varchar, create_type__role_one varchar, -- default null create_type__min_n_rels_one integer, create_type__max_n_rels_one integer, create_type__object_type_two varchar, create_type__role_two varchar, -- default null create_type__min_n_rels_two integer, create_type__max_n_rels_two integer, create_type__composable_p boolean default true ) RETURNS integer AS $$ DECLARE type_extension_table acs_object_types.type_extension_table%TYPE default null; abstract_p acs_object_types.abstract_p%TYPE default 'f'; name_method acs_object_types.name_method%TYPE default null; BEGIN PERFORM acs_object_type__create_type( create_type__rel_type, create_type__pretty_name, create_type__pretty_plural, create_type__supertype, create_type__table_name, create_type__id_column, create_type__package_name, abstract_p, type_extension_table, name_method ); insert into acs_rel_types (rel_type, object_type_one, role_one, min_n_rels_one, max_n_rels_one, object_type_two, role_two, min_n_rels_two, max_n_rels_two, composable_p) values (create_type__rel_type, create_type__object_type_one, create_type__role_one, create_type__min_n_rels_one, create_type__max_n_rels_one, create_type__object_type_two, create_type__role_two, create_type__min_n_rels_two, create_type__max_n_rels_two, create_type__composable_p); return 0; END; $$ LANGUAGE plpgsql; -- -- procedure acs_rel_type__create_type/15 -- CREATE OR REPLACE FUNCTION acs_rel_type__create_type( create_type__rel_type varchar, create_type__pretty_name varchar, create_type__pretty_plural varchar, create_type__supertype varchar, -- default 'relationship' create_type__table_name varchar, create_type__id_column varchar, create_type__package_name varchar, create_type__type_extension_table varchar, -- default null create_type__object_type_one varchar, create_type__min_n_rels_one integer, create_type__max_n_rels_one integer, create_type__object_type_two varchar, create_type__min_n_rels_two integer, create_type__max_n_rels_two integer, create_type__composable_p boolean default true ) RETURNS integer AS $$ DECLARE abstract_p acs_object_types.abstract_p%TYPE default 'f'; name_method acs_object_types.name_method%TYPE default null; create_type__role_one acs_rel_types.role_one%TYPE default null; create_type__role_two acs_rel_types.role_two%TYPE default null; BEGIN PERFORM acs_object_type__create_type( create_type__rel_type, create_type__pretty_name, create_type__pretty_plural, create_type__supertype, create_type__table_name, create_type__id_column, create_type__package_name, abstract_p, create_type__type_extension_table, name_method ); insert into acs_rel_types (rel_type, object_type_one, role_one, min_n_rels_one, max_n_rels_one, object_type_two, role_two, min_n_rels_two, max_n_rels_two, composable_p) values (create_type__rel_type, create_type__object_type_one, create_type__role_one, create_type__min_n_rels_one, create_type__max_n_rels_one, create_type__object_type_two, create_type__role_two, create_type__min_n_rels_two, create_type__max_n_rels_two, create_type__composable_p); return 0; END; $$ LANGUAGE plpgsql; -- procedure drop_type -- added select define_function_args('acs_rel_type__drop_type','rel_type,cascade_p;f'); -- -- procedure acs_rel_type__drop_type/2 -- CREATE OR REPLACE FUNCTION acs_rel_type__drop_type( drop_type__rel_type varchar, drop_type__cascade_p boolean -- default 'f' ) RETURNS integer AS $$ DECLARE v_cascade_p boolean; BEGIN -- XXX do cascade_p. -- JCD: cascade_p seems to be ignored in acs_o_type__drop_type anyway... if drop_type__cascade_p is null then v_cascade_p := 'f'; else v_cascade_p := drop_type__cascade_p; end if; delete from acs_rel_types where rel_type = drop_type__rel_type; PERFORM acs_object_type__drop_type(drop_type__rel_type, v_cascade_p); return 0; END; $$ LANGUAGE plpgsql; -- show errors select acs_rel_type__create_type ( 'relationship', 'Relationship', 'Relationships', 'acs_object', 'acs_rels', 'rel_id', 'acs_rel', 'acs_rel_types', 'acs_object', 0, null::integer, 'acs_object', 0, null::integer ); -- show errors -------------------------------------- -- OPERATIONAL LEVEL: RELATIONSHIPS -- -------------------------------------- create sequence t_acs_rel_id_seq; create view acs_rel_id_seq as select nextval('t_acs_rel_id_seq') as nextval; create table acs_rels ( rel_id integer not null constraint acs_rels_rel_id_fk references acs_objects (object_id) on delete cascade constraint acs_rels_rel_id_pk primary key, rel_type varchar(100) not null constraint acs_rels_rel_type_fk references acs_rel_types (rel_type), object_id_one integer not null constraint acs_rels_object_id_one_fk references acs_objects (object_id) on delete cascade, object_id_two integer not null constraint acs_rels_object_id_two_fk references acs_objects (object_id) on delete cascade, constraint acs_object_rels_un unique (rel_type, object_id_one, object_id_two) ); create index acs_rels_object_id_one_idx on acs_rels (object_id_one); create index acs_rels_object_id_two_idx on acs_rels (object_id_two); comment on table acs_rels is ' The acs_rels table is essentially a generic mapping table for acs_objects. Once we come up with a way to associate attributes with relationship types, we could replace many of the ACS 3.x mapping tables like user_content_map, user_group_map, and user_group_type_modules_map with this one table. Much application logic consists of asking questions like "Does object X have a relationship of type Y to object Z?" where all that differs is X, Y, and Z. Thus, the value of consolidating many mapping tables into one is that we can provide a generic API for defining and querying relationships. In addition, we may need to design a way to enable "type_specific" storage for relationships (i.e., foreign key columns for one-to-many relationships and custom mapping tables for many-to-many relationships), instead of only supporting "generic" storage in the acs_rels table. This would parallel what we do with acs_attributes. '; ---------------------------- -- Application Data Links -- ---------------------------- create sequence acs_data_links_seq start with 1; create table acs_data_links ( rel_id integer not null constraint acs_data_links_rel_id_pk primary key, object_id_one integer not null constraint acs_data_links_obj_one_fk references acs_objects (object_id) on delete cascade, object_id_two integer not null constraint acs_data_links_obj_two_fk references acs_objects (object_id) on delete cascade, relation_tag varchar(100), constraint acs_data_links_un unique (object_id_one, object_id_two, relation_tag) ); create index acs_data_links_id_one_idx on acs_data_links (object_id_one); create index acs_data_links_id_two_idx on acs_data_links (object_id_two); create index acs_data_links_rel_tag_idx on acs_data_links (relation_tag); -------------- -- TRIGGERS -- -------------- -- added by oumi@arsdigita.com - Jan 11, 2001 -- -- procedure acs_rels_in_tr/0 -- CREATE OR REPLACE FUNCTION acs_rels_in_tr( ) RETURNS trigger AS $$ DECLARE dummy integer; target_object_type_one acs_object_types.object_type%TYPE; target_object_type_two acs_object_types.object_type%TYPE; actual_object_type_one acs_object_types.object_type%TYPE; actual_object_type_two acs_object_types.object_type%TYPE; BEGIN -- DRB: The obvious rewrite to use Dan's port of this to use tree_ancestor_keys kills -- Postgres!!! Argh!!! This is fast, to, so there ... -- Get all the object type info from the relationship. select rt.object_type_one, rt.object_type_two, o1.object_type, o2.object_type into target_object_type_one, target_object_type_two, actual_object_type_one, actual_object_type_two from acs_rel_types rt, acs_objects o1, acs_objects o2 where rt.rel_type = new.rel_type and o1.object_id = new.object_id_one and o2.object_id = new.object_id_two; if not exists (select 1 from (select tree_ancestor_keys(acs_object_type_get_tree_sortkey(actual_object_type_one)) as tree_sortkey) parents1, (select tree_ancestor_keys(acs_object_type_get_tree_sortkey(actual_object_type_two)) as tree_sortkey) parents2, (select tree_sortkey from acs_object_types where object_type = target_object_type_one) root1, (select tree_sortkey from acs_object_types where object_type = target_object_type_two) root2 where root1.tree_sortkey = parents1.tree_sortkey and root2.tree_sortkey = parents2.tree_sortkey) then raise EXCEPTION '-20001: % violation: Invalid object types. Object % (%) must be of type % Object % (%) must be of type %', new.rel_type, new.object_id_one, actual_object_type_one, target_object_type_one, new.object_id_two, actual_object_type_two, target_object_type_two; end if; return new; END; $$ LANGUAGE plpgsql; create trigger acs_rels_in_tr before insert or update on acs_rels for each row execute procedure acs_rels_in_tr (); -- show errors -- create or replace package acs_rel -- as -- -- function new ( -- rel_id in acs_rels.rel_id%TYPE default null, -- rel_type in acs_rels.rel_type%TYPE default 'relationship', -- object_id_one in acs_rels.object_id_one%TYPE, -- object_id_two in acs_rels.object_id_two%TYPE, -- context_id in acs_objects.context_id%TYPE default null, -- creation_user in acs_objects.creation_user%TYPE default null, -- x creation_ip in acs_objects.creation_ip%TYPE default null -- ) return acs_rels.rel_id%TYPE; -- -- procedure delete ( -- rel_id in acs_rels.rel_id%TYPE -- ); -- -- end; -- show errors -- create or replace package body acs_rel -- function new select define_function_args('acs_rel__new','rel_id;null,rel_type;relationship,object_id_one,object_id_two,context_id;null,creation_user;null,creation_ip;null'); -- -- procedure acs_rel__new/7 -- CREATE OR REPLACE FUNCTION acs_rel__new( new__rel_id integer, -- default null new__rel_type varchar, -- default 'relationship' new__object_id_one integer, new__object_id_two integer, context_id integer, -- default null creation_user integer, -- default null creation_ip varchar -- default null ) RETURNS integer AS $$ DECLARE v_rel_id acs_rels.rel_id%TYPE; BEGIN v_rel_id := acs_object__new ( new__rel_id, new__rel_type, now(), creation_user, creation_ip, context_id, 't', new__rel_type || ': ' || new__object_id_one || ' - ' || new__object_id_two, null ); insert into acs_rels (rel_id, rel_type, object_id_one, object_id_two) values (v_rel_id, new__rel_type, new__object_id_one, new__object_id_two); return v_rel_id; END; $$ LANGUAGE plpgsql; -- procedure delete select define_function_args('acs_rel__delete','rel_id'); -- -- procedure acs_rel__delete/1 -- CREATE OR REPLACE FUNCTION acs_rel__delete( rel_id integer ) RETURNS integer AS $$ DECLARE BEGIN PERFORM acs_object__delete(rel_id); return 0; END; $$ LANGUAGE plpgsql; ----------- -- VIEWS -- ----------- -- These views are handy for metadata driven UI -- View: rel_types_valid_obj_one_types -- -- Question: Given rel_type :rel_type, -- -- What are all the valid object_types for object_id_one of -- a relation of type :rel_type -- -- Answer: select object_type -- from rel_types_valid_obj__one_types -- where rel_type = :rel_type -- create view rel_types_valid_obj_one_types as select rt.rel_type, th.object_type from acs_rel_types rt, (select object_type, ancestor_type from acs_object_type_supertype_map UNION ALL select object_type, object_type as ancestor_type from acs_object_types) th where rt.object_type_one = th.ancestor_type; -- View: rel_types_valid_obj_two_types -- -- Question: Given rel_type :rel_type, -- -- What are all the valid object_types for object_id_two of -- a relation of type :rel_type -- -- Answer: select object_type -- from rel_types_valid_obj_two_types -- where rel_type = :rel_type -- create view rel_types_valid_obj_two_types as select rt.rel_type, th.object_type from acs_rel_types rt, (select object_type, ancestor_type from acs_object_type_supertype_map UNION ALL select object_type, object_type as ancestor_type from acs_object_types) th where rt.object_type_two = th.ancestor_type;