--
-- /packages/acs-kernel/sql/rel-constraints-create.sql
--
-- Add support for relational constraints based on relational segmentation.
--
-- @author Oumi Mehrotra (oumi@arsdigita.com)
-- @creation-date 2000-11-22
-- @cvs-id $Id: rel-constraints-create.sql,v 1.22 2024/09/11 06:15:48 gustafn Exp $
-- Copyright (C) 1999-2000 ArsDigita Corporation
-- This is free software distributed under the terms of the GNU Public
-- License. Full text of the license is available from the GNU Project:
-- http://www.fsf.org/copyleft/gpl.html
-- WARNING!
-- Relational constraints is a new and experimental concept. The API may
-- change in the future, particularly the functions marked "EXPERIMENTAL".
--
CREATE OR REPLACE FUNCTION inline_0 () RETURNS integer AS $$
BEGIN
PERFORM acs_object_type__create_type (
'rel_constraint',
'Relational Constraint',
'Relational Constraints',
'acs_object',
'rel_constraints',
'constraint_id',
'rel_constraint',
'f',
null,
null
);
return 0;
END;
$$ LANGUAGE plpgsql;
select inline_0 ();
drop function inline_0 ();
-- show errors
create table rel_constraints (
constraint_id integer
constraint rel_constraints_pk
primary key
constraint rc_constraint_id_fk
references acs_objects(object_id),
constraint_name varchar(100) not null,
rel_segment integer not null
constraint rel_constraints_rel_segment_fk
references rel_segments (segment_id),
rel_side char(3) default 'two' not null
constraint rel_constraints_rel_side_ck
check (rel_side in
('one', 'two')),
required_rel_segment integer not null
constraint rc_required_rel_segment_fk
references rel_segments (segment_id),
constraint rel_constraints_un
unique (rel_segment, rel_side, required_rel_segment)
);
-- required_rel_segment has a foreign key reference - create an index
create index rel_constraint_req_rel_seg_idx on rel_constraints(required_rel_segment);
comment on table rel_constraints is '
Defines relational constraints. The relational constraints system is
intended to support applications in modelling and applying
constraint rules on inter-party relatinships based on relational
party segmentation.
';
comment on column rel_constraints.constraint_name is '
The user-defined name of this constraint.
';
comment on column rel_constraints.rel_segment is '
The segment for which the constraint is defined.
';
comment on column rel_constraints.rel_side is '
The side of the relation the constraint applies to.
';
comment on column rel_constraints.required_rel_segment is '
The segment in which elements must be in to satisfy the constraint.
';
-----------
-- VIEWS --
-----------
-- View rel_constraints_violated_one
--
-- pseudo sql:
--
-- select all the side 'one' constraints
-- from the constraints and the associated relations of rel_segment
-- where the relation's container_id (i.e., object_id_one) is not in the
-- relational segment required_rel_segment.
-- create view rel_constraints_violated_one as
-- select constrained_rels.*
-- from (select rel_constraints.constraint_id, rel_constraints.constraint_name,
-- r.rel_id, r.container_id, r.party_id, r.rel_type,
-- rel_constraints.rel_segment,
-- rel_constraints.rel_side,
-- rel_constraints.required_rel_segment
-- from rel_constraints, rel_segment_party_map r
-- where rel_constraints.rel_side = 'one'
-- and rel_constraints.rel_segment = r.segment_id
-- ) constrained_rels,
-- rel_segment_party_map rspm
-- where rspm.segment_id(+) = constrained_rels.required_rel_segment
-- and rspm.party_id(+) = constrained_rels.container_id
-- and rspm.party_id is null;
create view constrained_rels1 as
select rel.constraint_id, rel.constraint_name,
r.rel_id, r.container_id, r.party_id, r.rel_type,
rel.rel_segment,
rel.rel_side,
rel.required_rel_segment
from rel_constraints rel, rel_segment_party_map r
where rel.rel_side = 'one'
and rel.rel_segment = r.segment_id;
create view rel_constraints_violated_one as
select c.*
from constrained_rels1 c left outer join rel_segment_party_map rspm
on (rspm.segment_id = c.required_rel_segment and
rspm.party_id = c.container_id)
where rspm.party_id is null;
-- View rel_constraints_violated_two
--
-- pseudo sql:
--
-- select all the side 'two' constraints
-- from the constraints and the associated relations of rel_segment
-- where the relation's party_id (i.e., object_id_two) is not in the
-- relational segment required_rel_segment.
-- create view rel_constraints_violated_two as
-- select constrained_rels.*
-- from (select rel_constraints.constraint_id, rel_constraints.constraint_name,
-- r.rel_id, r.container_id, r.party_id, r.rel_type,
-- rel_constraints.rel_segment,
-- rel_constraints.rel_side,
-- rel_constraints.required_rel_segment
-- from rel_constraints, rel_segment_party_map r
-- where rel_constraints.rel_side = 'two'
-- and rel_constraints.rel_segment = r.segment_id
-- ) constrained_rels,
-- rel_segment_party_map rspm
-- where rspm.segment_id(+) = constrained_rels.required_rel_segment
-- and rspm.party_id(+) = constrained_rels.party_id
-- and rspm.party_id is null;
create view constrained_rels2 as
select rel.constraint_id, rel.constraint_name,
r.rel_id, r.container_id, r.party_id, r.rel_type,
rel.rel_segment,
rel.rel_side,
rel.required_rel_segment
from rel_constraints rel, rel_segment_party_map r
where rel.rel_side = 'two'
and rel.rel_segment = r.segment_id;
create view rel_constraints_violated_two as
select c.*
from constrained_rels2 c left outer join rel_segment_party_map rspm
on (rspm.segment_id = c.required_rel_segment and
rspm.party_id = c.party_id)
where rspm.party_id is null;
-- View: rc_all_constraints
--
-- Question: Given group :group_id and rel_type :rel_type . . .
--
-- What segments must a party be in
-- if the party were to be on side :rel_side of a relation of
-- type :rel_type to group :group_id ?
--
-- Answer: select required_rel_segment
-- from rc_all_constraints
-- where group_id = :group_id
-- and rel_type = :rel_type
-- and rel_side = :rel_side
--
-- Notes: we take special care not to get identity rows, where group_id and
-- rel_type are equivalent to segment_id. This can happen if there are some
-- funky constraints in the system, such as membership to Arsdigita requires
-- user_profile to Arsdigita. Then you could get rows from the
-- rc_all_constraints view saying that:
-- user_profile to Arsdigita
-- requires being in the segment of Arsdigita Users.
--
-- This happens because user_profile is a type of memebrship, and there's a
-- constraint saying that membership to Arsdigita requires being in the
-- Arsdigita Users segment. We eliminate such rows from the rc_all_constraints
-- view with the "not (...)" clause below.
--
create view rc_all_constraints as
select group_rel_types.group_id,
group_rel_types.rel_type,
rel_constraints.rel_segment,
rel_constraints.rel_side,
required_rel_segment
from rel_constraints,
rel_segment_group_rel_type_map group_rel_types,
rel_segments req_seg
where rel_constraints.rel_segment = group_rel_types.segment_id
and rel_constraints.required_rel_segment = req_seg.segment_id
and not (req_seg.group_id = group_rel_types.group_id and
req_seg.rel_type = group_rel_types.rel_type);
create view rc_all_distinct_constraints as
select distinct
group_id, rel_type, rel_segment, rel_side, required_rel_segment
from rc_all_constraints;
-- THIS VIEW IS FOR COMPATIBILITY WITH EXISTING CODE
-- New code should use rc_all_constraints instead!
--
-- View: rc_required_rel_segments
--
-- Question: Given group :group_id and rel_type :rel_type . . .
--
-- What segments must a party be in
-- if the party were to be belong to group :group_id
-- through a relation of type :rel_type ?
--
-- Answer: select required_rel_segment
-- from rc_required_rel_segments
-- where group_id = :group_id
-- and rel_type = :rel_type
--
create view rc_required_rel_segments as
select distinct group_id, rel_type, required_rel_segment
from rc_all_constraints
where rel_side = 'two';
-- View: rc_parties_in_required_segs
--
-- Question: Given group :group_id and rel_type :rel_type . . .
--
-- What parties are "allowed" to be in group :group_id
-- through a relation of type :rel_type ? By "allowed",
-- we mean that no relational constraints would be violated.
--
-- Answer: select party_id, acs_object.name(party_id)
-- from parties_in_rc_required_rel_segments
-- where group_id = :group_id
-- and rel_type = :rel_type
--
create view comp_or_member_rel_types as
select o.object_type as rel_type
from acs_object_types o, acs_object_types o1
where o1.object_type in ('composition_rel', 'membership_rel')
and o.tree_sortkey between o1.tree_sortkey and tree_right(o1.tree_sortkey);
create view group_rel_type_combos as
select groups.group_id, comp_or_member_rel_types.rel_type
from groups, comp_or_member_rel_types;
create view parties_in_required_segs as
select required_segs.group_id,
required_segs.rel_type,
seg_parties.party_id,
seg_parties.segment_id,
count(*) as num_matching_segs
from rc_required_rel_segments required_segs,
rel_segment_party_map seg_parties
where required_segs.required_rel_segment = seg_parties.segment_id
group by required_segs.group_id,
required_segs.rel_type,
seg_parties.party_id,
seg_parties.segment_id;
create view total_num_required_segs as
select group_id, rel_type, count(*) as total
from rc_required_rel_segments
group by group_id, rel_type;
create view rc_parties_in_required_segs as
select parties_in_required_segs.group_id,
parties_in_required_segs.rel_type,
parties_in_required_segs.party_id
from
parties_in_required_segs,
total_num_required_segs
where
parties_in_required_segs.group_id = total_num_required_segs.group_id
and parties_in_required_segs.rel_type = total_num_required_segs.rel_type
and parties_in_required_segs.num_matching_segs = total_num_required_segs.total
UNION ALL
select group_rel_type_combos.group_id,
group_rel_type_combos.rel_type,
parties.party_id
from (rc_required_rel_segments right outer join group_rel_type_combos
on
(rc_required_rel_segments.group_id = group_rel_type_combos.group_id
and
rc_required_rel_segments.rel_type = group_rel_type_combos.rel_type)),
parties
where rc_required_rel_segments.group_id is null;
-- View: rc_valid_rel_types
--
-- Question: What types of membership or composition are "valid"
-- for group :group_id ? A membership or composition
-- type R is "valid" when no relational constraints would
-- be violated if a party were to belong to group :group_id
-- through a rel of type R.
--
-- Answer: select rel_type
-- from rc_valid_rel_types
-- where group_id = :group_id
--
--
-- create view rc_valid_rel_types as
-- select side_one_constraints.group_id,
-- side_one_constraints.rel_type
-- from (select required_segs.group_id,
-- required_segs.rel_type,
-- count(*) as num_satisfied
-- from rc_all_constraints required_segs,
-- rel_segment_party_map map
-- where required_segs.rel_side = 'one'
-- and required_segs.required_rel_segment = map.segment_id
-- and required_segs.group_id = map.party_id
-- group by required_segs.group_id,
-- required_segs.rel_type) side_one_constraints,
-- (select group_id, rel_type, count(*) as total
-- from rc_all_constraints
-- where rel_side = 'one'
-- group by group_id, rel_type) total_side_one_constraints
-- where side_one_constraints.group_id = total_side_one_constraints.group_id
-- and side_one_constraints.rel_type = total_side_one_constraints.rel_type
-- and side_one_constraints.num_satisfied = total_side_one_constraints.total
-- UNION ALL
-- select group_rel_type_combos.group_id,
-- group_rel_type_combos.rel_type
-- from (select * from rc_all_constraints where rel_side='one') rc_all_constraints,
-- (select groups.group_id, comp_or_member_rel_types.rel_type
-- from groups,
-- (select object_type as rel_type from acs_object_types
-- start with object_type = 'membership_rel'
-- or object_type = 'composition_rel'
-- connect by supertype = prior object_type) comp_or_member_rel_types
-- ) group_rel_type_combos
-- where rc_all_constraints.group_id(+) = group_rel_type_combos.group_id
-- and rc_all_constraints.rel_type(+) = group_rel_type_combos.rel_type
-- and rc_all_constraints.group_id is null;
create view side_one_constraints as
select required_segs.group_id,
required_segs.rel_type,
count(*) as num_satisfied
from rc_all_constraints required_segs,
rel_segment_party_map map
where required_segs.rel_side = 'one'
and required_segs.required_rel_segment = map.segment_id
and required_segs.group_id = map.party_id
group by required_segs.group_id,
required_segs.rel_type;
create view total_side_one_constraints as
select group_id, rel_type, count(*) as total
from rc_all_constraints
where rel_side = 'one'
group by group_id, rel_type;
create view rc_all_constraints_view as
select * from rc_all_constraints where rel_side='one';
create view rc_valid_rel_types as
select side_one_constraints.group_id,
side_one_constraints.rel_type
from side_one_constraints,
total_side_one_constraints
where side_one_constraints.group_id = total_side_one_constraints.group_id
and side_one_constraints.rel_type = total_side_one_constraints.rel_type
and side_one_constraints.num_satisfied = total_side_one_constraints.total
UNION ALL
select group_rel_type_combos.group_id,
group_rel_type_combos.rel_type
from rc_all_constraints_view right outer join group_rel_type_combos
on
(rc_all_constraints_view.group_id = group_rel_type_combos.group_id and
rc_all_constraints_view.rel_type = group_rel_type_combos.rel_type)
where rc_all_constraints_view.group_id is null;
-- View: rc_violations_by_removing_rel
--
-- Question: Given relation :rel_id
--
-- If we were to remove the relation specified by rel_id,
-- what constraints would be violated and by what parties?
--
-- Answer: select r.rel_id, r.constraint_id, r.constraint_name
-- acs_object_type.pretty_name(r.rel_type) as rel_type_pretty_name,
-- acs_object.name(r.object_id_one) as object_id_one_name,
-- acs_object.name(r.object_id_two) as object_id_two_name
-- from rc_violations_by_removing_rel r
-- where r.rel_id = :rel_id
--
create view rc_violations_by_removing_rel as
select r.rel_type as viol_rel_type, r.rel_id as viol_rel_id,
r.object_id_one as viol_object_id_one, r.object_id_two as viol_object_id_two,
s.rel_id,
cons.constraint_id, cons.constraint_name,
map.segment_id, map.party_id, map.group_id, map.container_id, map.ancestor_rel_type
from acs_rels r, rel_segment_party_map map, rel_constraints cons,
(select s.segment_id, r.rel_id, r.object_id_two
from rel_segments s, acs_rels r
where r.object_id_one = s.group_id
and r.rel_type = s.rel_type) s
where map.party_id = r.object_id_two
and map.rel_id = r.rel_id
and r.object_id_two = s.object_id_two
and cons.rel_segment = map.segment_id
and cons.required_rel_segment = s.segment_id;
-- View: rc_segment_required_seg_map
--
-- Question: Given a relational segment :rel_segment . . .
--
-- What are all the segments in the system that a party has to
-- be in if the party were to be on side :rel_side of a relation
-- in segment :rel_segment?
--
-- We want not only the direct required_segments (which we could
-- get from the rel_constraints table directly), but also the
-- indirect ones (i.e., the segments that are required by the
-- required segments, and so on).
--
-- Answer: select required_rel_segment
-- from rc_segment_required_seg_map
-- where rel_segment = :rel_segment
-- and rel_side = :rel_side
--
--
-- create view rc_segment_required_seg_map as
-- select rc.rel_segment, rc.rel_side, rc_required.required_rel_segment
-- from rel_constraints rc, rel_constraints rc_required
-- where rc.rel_segment in (
-- select rel_segment
-- from rel_constraints
-- start with rel_segment = rc_required.rel_segment
-- connect by required_rel_segment = prior rel_segment
-- and prior rel_side = 'two'
-- );
-- DCW 2001-04-19, replaced view with a table and a trigger since a tree query
-- won't work on the rel_constraints table, because instead of a tree, we have
-- a directed graph structure.
create table rc_segment_required_seg_map (
rel_segment integer not null
constraint rc_rel_segment_fk
references rel_segments (segment_id),
rel_side char(3) not null
constraint rc_rel_side_ck
check (rel_side in
('one', 'two')),
required_rel_segment integer not null
constraint rc_required_rel_segment_fk
references rel_segments (segment_id),
constraint rc_segment_required_seg_map_un
unique (rel_segment, rel_side, required_rel_segment)
);
create index rc_segment_required_seg_idx on
rc_segment_required_seg_map(required_rel_segment);
--
-- procedure rel_constraints_ins_tr/0
--
CREATE OR REPLACE FUNCTION rel_constraints_ins_tr(
) RETURNS trigger AS $$
DECLARE
v_rec record;
BEGIN
-- insert the constraint
insert into rc_segment_required_seg_map
(rel_segment, rel_side, required_rel_segment)
values
(new.rel_segment, new.rel_side, new.required_rel_segment);
-- add dependencies
insert into rc_segment_required_seg_map
select new.rel_segment, new.rel_side, required_rel_segment
from rc_segment_required_seg_map
where rel_segment = new.required_rel_segment
and rel_side = new.rel_side;
-- now update the rel_segments that depend on this segment
for v_rec in select rel_segment
from rc_segment_required_seg_map
where required_rel_segment = new.rel_segment
and rel_side = new.rel_side
LOOP
insert into rc_segment_required_seg_map
select v_rec.rel_segment, new.rel_side,
required_rel_segment
from rc_segment_required_seg_map
where rel_segment = new.rel_segment
and rel_side = new.rel_side;
end LOOP;
return new;
END;
$$ LANGUAGE plpgsql;
create trigger rel_constraints_ins_tr after insert
on rel_constraints for each row
execute procedure rel_constraints_ins_tr ();
--
-- procedure rel_constraints_del_tr/0
--
CREATE OR REPLACE FUNCTION rel_constraints_del_tr(
) RETURNS trigger AS $$
DECLARE
v_rec record;
BEGIN
-- now update the rel_segments that depend on this segment
for v_rec in select rel_segment
from rc_segment_required_seg_map
where required_rel_segment = old.rel_segment
and rel_side = old.rel_side
LOOP
delete from rc_segment_required_seg_map
where rel_segment = v_rec.rel_segment
and rel_side = old.rel_side
and required_rel_segment
in (select required_rel_segment
from rc_segment_required_seg_map
where rel_segment = old.rel_segment
and rel_side = old.rel_side);
end LOOP;
-- delete dependencies
delete from rc_segment_required_seg_map
where rel_segment = old.rel_segment
and rel_side = old.rel_side
and required_rel_segment
in (select required_rel_segment
from rc_segment_required_seg_map
where rel_segment = old.required_rel_segment
and rel_side = old.rel_side);
-- delete the constraint
delete from rc_segment_required_seg_map
where rel_segment = old.rel_segment
and rel_side = old.rel_side
and required_rel_segment = old.required_rel_segment;
return old;
END;
$$ LANGUAGE plpgsql;
create trigger rel_constraints_del_tr after delete
on rel_constraints for each row
execute procedure rel_constraints_del_tr ();
-- View: rc_segment_dependency_levels
--
-- This view is designed to determine what order of segments is safe
-- to use when adding a party to multiple segments.
--
-- Question: Given a table or view called segments_I_want_to_be_in,
-- which segments can I add a party to first, without violating
-- any relational constraints?
--
-- Answer: select segment_id
-- from segments_I_want_to_be_in s,
-- rc_segment_dependency_levels dl
-- where s.segment_id = dl.segment_id(+)
-- order by nvl(dl.dependency_level, 0)
--
-- Note: dependency_level = 1 is the minimum dependency level.
-- dependency_level = N means that you cannot add a party to the
-- segment until you first add the party to some
-- segment of dependency_level N-1 (this view doesn't
-- tell you which segment -- you can get that info
-- from rel_constraints table or other views.
--
-- Another Note: not all segments in rel_segments are returned by this view.
-- This view only returns segments S that have at least one rel_constraints row
-- where rel_segment = S. Segments that have no constraints defined on them
-- can be said to have dependency_level=0, hence the outer join and nvl in the
-- example query above (see "Answer:"). I could have embedded that logic into
-- this view, but that would unnecessarily degrade performance.
--
-- create view rc_segment_dependency_levels as
-- select rel_segment as segment_id,
-- max(tree_level) as dependency_level
-- from (select rel_segment, level as tree_level
-- from rel_constraints
-- connect by required_rel_segment = prior rel_segment
-- and prior rel_side = 'two')
-- group by rel_segment;
-- DCW 2001-04-19, this view is not a direct port rather it gives equivalent
-- information without the use of a tree query, which in this case would be
-- problematic, since we are actually dealing with a directed graph instead
-- of a tree structure. This view would also work for oracle.
create view rc_segment_dependency_levels as
select rel_segment as segment_id, count(*) as dependency_level
from rc_segment_required_seg_map
where rel_side = 'two'
group by segment_id;
--------------
-- PACKAGES --
--------------
-- create or replace package rel_constraint
-- as
--
-- function new (
-- --/** Creates a new relational constraint
-- --
-- -- @author Oumi Mehrotra (oumi@arsdigita.com)
-- -- @creation-date 12/2000
-- --
-- --*/
-- constraint_id in rel_constraints.constraint_id%TYPE default null,
-- constraint_type in acs_objects.object_type%TYPE default 'rel_constraint',
-- constraint_name in rel_constraints.constraint_name%TYPE,
-- rel_segment in rel_constraints.rel_segment%TYPE,
-- rel_side in rel_constraints.rel_side%TYPE default 'two',
-- required_rel_segment in rel_constraints.required_rel_segment%TYPE,
-- context_id in acs_objects.context_id%TYPE default null,
-- creation_user in acs_objects.creation_user%TYPE default null,
-- creation_ip in acs_objects.creation_ip%TYPE default null
-- ) return rel_constraints.constraint_id%TYPE;
--
-- procedure delete (
-- constraint_id in rel_constraints.constraint_id%TYPE
-- );
--
-- function get_constraint_id (
-- --/** Returns the constraint_id associated with the specified
-- -- rel_segment and required_rel_segment for the specified site.
-- --
-- -- @author Oumi Mehrotra (oumi@arsdigita.com)
-- -- @creation-date 12/2000
-- --
-- --*/
-- rel_segment in rel_constraints.rel_segment%TYPE,
-- rel_side in rel_constraints.rel_side%TYPE default 'two',
-- required_rel_segment in rel_constraints.required_rel_segment%TYPE
-- ) return rel_constraints.constraint_id%TYPE;
--
-- function violation (
-- --/** Checks to see if there a relational constraint is violated
-- -- by the presence of the specified relation. If not, returns
-- -- null. If so, returns an appropriate error string.
-- --
-- -- @author Oumi Mehrotra (oumi@arsdigita.com)
-- -- @creation-date 12/2000
-- --
-- -- @param rel_id The relation for which we want to find
-- -- any violations
-- --*/
-- rel_id in acs_rels.rel_id%TYPE
-- ) return varchar;
--
--
-- function violation_if_removed (
-- --/** Checks to see if removing the specified relation would violate
-- -- a relational constraint. If not, returns null. If so, returns
-- -- an appropriate error string.
-- --
-- -- @author Michael Bryzek (mbryzek@arsdigita.com)
-- -- @creation-date 1/2001
-- --
-- -- @param rel_id The relation that we are planning to remove
-- --*/
-- rel_id in acs_rels.rel_id%TYPE
-- ) return varchar;
--
-- end;
-- show errors