--
-- 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;