
-- This data model is for the Software Development Module (SDM)
-- The tables were designed by Ben Adida (ben@mit.edu) and Philip Greenspun (philg@mit.edu)
-- The details of the data model were done by Ben Adida (ben@mit.edu)

-- packages
-- packages are totally independent products. One package might
-- be the ACS, while another is AOLServer.

-- ported to Postgres by Ben Adida (ben@mit.edu)

create sequence package_id_sequence;

create table packages (
	package_id		integer not null primary key,
	package_name		varchar(100) not null,
	private_p		char(1) check (private_p in ('t','f')),
	description		varchar(4000)
);

create view public_packages as select * from packages where private_p='f';

create sequence package_release_id_sequence;

create table package_releases (
	release_id			integer not null primary key,
	package_id			integer not null references packages,
	-- These version numbers are for 1.1.7, 2.0.1
	major_version			integer not null,
	minor_version			integer not null,
	patch_version			integer not null,
	beta_version			integer,
	-- Release Dates
	anticipated_release_date	datetime,
	release_date			datetime,
	manager				integer not null references users,
	general_description		varchar(4000),
	release_notes			lztext,
	supported_platforms		varchar(250),
	release_filename		varchar(250)
);
	
alter table packages add
	current_release		integer references package_releases;

create table package_admins (
	package_id		integer not null references packages,
	user_id			integer not null references users,
	primary key (package_id, user_id)
);

create sequence module_id_sequence;

create table modules (
	module_id		integer not null primary key,
	package_id		integer references packages,
	module_name		varchar(100) not null,
	owner			integer not null references users,
	private_p		char(1) check (private_p in ('t','f')),
	description		varchar(4000)
);

create view public_modules as select * from modules where private_p='f';

-- A table for assigning users to modules, so that they may see
-- the module if the module is private, and later upload code to the
-- module

create table module_users (
	module_id		integer not null references modules,
	user_id			integer not null references users,
	primary key(module_id, user_id)
);


-- procs to determine if a user can see/edit packages/modules.
-- because of Oracle deadlock issues, these should be selected
-- from dual and usually not at the same time as other columns
-- are selected from tables.

create function user_can_edit_package_p(integer, integer)
returns char as '
DECLARE
	v_user_id alias for $1;
	v_package_id alias for $2;
	v_count integer;
BEGIN
	select count(*) into v_count from package_admins where package_id= v_package_id and user_id= v_user_id;
	IF (v_count > 0)
	THEN return(''t'');
	ELSE return(''f'');
	END IF;
END;
' language 'plpgsql';

create function user_can_edit_module_p(integer, integer)
returns char as '
DECLARE
	v_user_id alias for $1;
	v_module_id alias for $2;
	v_count integer;
BEGIN
	select count(*) into v_count from modules where module_id= v_module_id and owner= v_user_id;
	if v_count > 0
	THEN return(''t'');
	END IF;	
	
	return(''f'');
END;
' language 'plpgsql';

create function user_can_see_package_p(integer, integer)
returns char as '
DECLARE
	v_user_id alias for $1;
	v_package_id alias for $2;
	v_count integer;
BEGIN
	select count(*) into v_count from public_packages where package_id= v_package_id;
	IF (v_count > 0)
	THEN return(''t'');
	END IF;

	select count(*) into v_count from module_users where module_id IN (select module_id from modules where package_id= v_package_id) and user_id= v_user_id;
	IF (v_count > 0)
	THEN return(''t'');
	END IF;

	IF (user_can_edit_package_p(v_user_id, v_package_id)=''t'')
	THEN return(''t'');
	END IF;

	return(''f'');
END;
' language 'plpgsql';

create function user_can_see_module_p(integer, integer)
returns char as '
DECLARE
	v_user_id alias for $1;
	v_module_id alias for $2;
	v_count integer;
	v_package_id integer;
BEGIN
	select count(*) into v_count from public_modules where module_id= v_module_id;
	IF (v_count > 0)
	THEN return(''t'');
	END IF;

	select count(*) into v_count from module_users where user_id= v_user_id and module_id= v_module_id;
	IF (v_count > 0)
	THEN return(''t'');
	END IF;

	select package_id into v_package_id from modules where module_id= v_module_id;
	IF (user_can_edit_package_p(v_user_id, v_package_id)=''t'')
	THEN return(''t'');
	END IF;
	
	IF (user_can_edit_module_p(v_user_id, v_module_id)=''t'')
	THEN return(''t'');
	END IF;

	return(''f'');
		
END;
' language 'plpgsql';



-- This table describes relationships between modules, and
-- who owns the "glue" between them
create table module_relationships (
	first_module		integer not null references modules,
	second_module		integer not null references modules,
	owner			integer not null references users,
	primary key (first_module, second_module)
);

create table package_release_downloads (
	package_id			integer not null references packages,
	release_id			integer not null references package_releases,
	download_date			datetime,
	ip_address			varchar(15)
);

create function release_lessthan_p(integer, integer, integer, integer, integer, integer, integer, integer)
returns char as '
DECLARE
	first_major alias for $1;
	first_minor alias for $2;
	first_patch alias for $3;
	first_beta alias for $4;
	second_major alias for $5;
	second_minor alias for $6;
	second_patch alias for $7;
	second_beta alias for $8;
BEGIN	
	IF first_major < second_major 
	THEN RETURN ''t''; 
	END IF;

	IF first_major > second_major
	THEN RETURN ''f'';
	END IF;

	IF first_minor < second_minor 
	THEN RETURN ''t''; 
	END IF;

	IF first_minor > second_minor
	THEN RETURN ''f'';
	END IF;

	IF first_patch < second_patch 
	THEN RETURN ''t''; 
	END IF;

	IF first_patch > second_patch
	THEN RETURN ''f'';
	END IF;

	IF first_beta is NULL
	THEN RETURN ''f'';
	END IF;

	IF second_beta is NULL
	THEN RETURN ''t'';
	END IF;

	IF first_beta > second_beta
	THEN RETURN ''t'';
	END IF;
		
	RETURN ''f'';
END;
' language 'plpgsql';

create function release_name(integer,integer,integer,integer)
returns varchar as '
DECLARE
	major_version alias for $1;
	minor_version alias for $2;
	patch_version alias for $3;
	beta_version alias for $4;
BEGIN
	IF major_version IS NULL
	THEN RETURN NULL;
	END IF;

	IF beta_version is NULL or beta_version=0
	THEN
	return (major_version || ''.'' || minor_version || ''.'' || patch_version);
	ELSE
	return (major_version || ''.'' || minor_version || ''.'' || patch_version || ''b'' || beta_version);
	END IF;
END;
' language 'plpgsql';

-- Note that a baf is a bug-and-feature.
-- Since we don't want to write bug-and-feature everywhere
-- we go, we'll call a bug-and-feature a "baf" from now on.
create table baf_status (
       baf_status_id	integer not null primary key,
       baf_status	varchar(100)
);

insert into baf_status values (1, 'open');
insert into baf_status values (2, 'fixed');
insert into baf_status values (3, 'closed');
insert into baf_status values (4, 'reopened');

create sequence baf_id_sequence start 5;

create table bugs_and_features (
	baf_id				integer not null primary key,
	old_baf_id			integer references bugs_and_features,
	baf_type			varchar(20) not null check (baf_type in ('bug', 'feature')),
	package_id			integer not null references packages,
	module_id			integer references modules,
	baf_status			integer not null references baf_status,
	last_updated_by			integer references users,
	-- who pointed this out
	entered_by			integer not null references users,
	-- who's working on this
	-- we're moving to a mapping table
	-- assigned_user			references users,
	-- a priority between 1 and 9, 1 being the highest priority
	severity		        varchar(20) not null,
	insertion_date			datetime,
	expected_completion		integer references package_releases,
	completion			integer references package_releases,
	description			lztext	
);

create index baf_type_index on bugs_and_features(baf_type);

create view open_bafs as select * from bugs_and_features where completion is NULL;

-- In case we want to easily view these separately
create view bugs as select * from bugs_and_features where baf_type='bug';
create view open_bugs as select * from bugs where completion is NULL;
create view closed_bugs as select * from bugs where completion is not NULL;

create view features as select * from bugs_and_features where baf_type='feature';
create view open_features as select * from features where completion is NULL;
create view closed_features as select * from features where completion is not NULL;


-- BAF Auditing
create table baf_audit (
       baf_id	       integer not null,
       who	       varchar(250),
       what	       varchar(250),
       old_value       varchar(250),
       new_value       varchar(250),
       audit_date       datetime
);

create index baf_audit_baf_id on baf_audit(baf_id);
create index baf_audit_audit_date on baf_audit(audit_date);

create function trig_baf_audit() returns opaque
as '
DECLARE
	the_name	varchar(200);
BEGIN
	select first_names || '' '' || last_name into the_name
	from users where user_id= NEW.last_updated_by;

	IF OLD.baf_type != NEW.baf_type
	THEN 
	insert into baf_audit (baf_id, who, what, old_value, new_value, audit_date)
	values (NEW.baf_id, the_name, ''baf_type'', OLD.baf_type, NEW.baf_type, sysdate());
	END IF;

	IF OLD.baf_status != NEW.baf_status
	THEN 
	insert into baf_audit (baf_id, who, what, old_value, new_value, audit_date)
	values (NEW.baf_id, the_name, ''baf_status'', sdm_get_baf_status(OLD.baf_status), sdm_get_baf_status(NEW.baf_status), sysdate());
	END IF;

	IF OLD.severity != NEW.severity
	THEN 
	insert into baf_audit (baf_id, who, what, old_value, new_value, audit_date)
	values (NEW.baf_id, the_name, ''severity'', OLD.severity, NEW.severity, sysdate());
	END IF;

	IF OLD.expected_completion != NEW.expected_completion
	THEN 
	insert into baf_audit (baf_id, who, what, old_value, new_value, audit_date)
	values (NEW.baf_id, the_name, ''expected_completion'', fetch_release_name(OLD.expected_completion), fetch_release_name(NEW.expected_completion), sysdate());
	END IF;

	return NEW;
END;
' language 'plpgsql';

create trigger baf_audit
after update on bugs_and_features
for each row
execute procedure trig_baf_audit();

create function trig_lowlevel_baf_audit() returns opaque
as '
DECLARE
	v_baf_type_text varchar(100);
	v_bogus integer;
BEGIN
	IF NEW.what=''baf_type''
	THEN
	v_bogus:= baf_edit_notification(NEW.baf_id::integer, ''This '' || NEW.old_value || '' is now classified as a '' || NEW.new_value);
	ELSE
	v_bogus:= baf_edit_notification(NEW.baf_id::integer, NEW.what || '' changed from '' || NEW.old_value || '' to '' || NEW.new_value);
	END IF;
	
	RETURN NEW;
END;
' language 'plpgsql';

create trigger lowlevel_baf_audit
before insert on baf_audit
for each row
execute procedure trig_lowlevel_baf_audit();

create function user_can_edit_baf_p(integer, integer)
returns char
as '
DECLARE
	v_user_id alias for $1;
	v_baf_id alias for $2;
	v_check integer;
	v_check_char char(1);
BEGIN
	select user_can_edit_module_p(v_user_id, module_id) into v_check_char
	from bugs_and_features where baf_id= v_baf_id;

	if v_check_char = ''t''
	then return ''t'';
	end if;

	select user_can_edit_package_p(v_user_id, package_id) into v_check_char
	from bugs_and_features where baf_id= v_baf_id;

	if v_check_char = ''t''
	then return ''t'';
	end if;

	select count(*) into v_check from baf_assignments where baf_id= v_baf_id and user_id= v_user_id;
	if v_check > 0
	then return ''t'';
	end if;

	select count(*) into v_check from bugs_and_features where baf_id= v_baf_id and entered_by= v_user_id;
	if v_check > 0
	then return ''t'';
	end if;

	return ''f'';
END;
' language 'plpgsql';

-- A way for users to rank the importance of a bug
create table baf_ratings (
       user_id		 integer not null references users,
       baf_id		 integer not null references bugs_and_features,
       primary key(user_id, baf_id),
       rating		 integer not null check (rating between 1 and 10),
       rating_date	 datetime
);


-- Who's assigned to what bugs
create table baf_assignments (
       baf_id		     integer not null references bugs_and_features,
       user_id		     integer not null references users,
       role		     varchar(100),
       assignment_date	     datetime
);       

-- Sometimes, bugs apply to releases before the date when they were found.
create table bug_release_map (
	bug_id		integer not null references bugs_and_features,
	release_id	integer not null references package_releases,
	discovery_date	datetime ,
	primary key (bug_id, release_id)
);

-- We want to map what a user might be interested in
-- this is mostly for bugs/features, but might be for
-- tasks
-- for now, let's see how it works with just bafs.

create table user_baf_interest_map (
	user_id			integer not null references users,
	baf_id			integer not null references bugs_and_features,
	primary key (user_id, baf_id)
);

-- User Interests in Packages

create table user_package_interest_map (
       user_id			       integer not null references users,
       package_id		       integer not null references packages,
       primary key (user_id, package_id)
);

-- comments on bugs and features

create view baf_comments as select * from general_comments where on_which_table='bugs_and_features';


--
-- Here we get into the source repository stuff
-- ben@mit.edu
--

create table package_repositories (
	package_id		integer not null primary key references packages,
	repository_name		varchar(100) not null,
	file_glob_patterns	varchar(200)
);

create function package_has_repository_p(integer)
returns char as '
DECLARE
	v_package_id alias for $1;
	v_count integer;
BEGIN
	select count(*) into v_count from package_repositories where package_id= v_package_id;

	if (v_count > 0)
	THEN return ''t'';
	ELSE return ''f'';
	END IF;
END;
' language 'plpgsql';


--
-- The CVS specific stuff

create table cvs_package_data (
	package_id		integer not null primary key references packages,
	cvs_server		varchar(200),
	cvs_username		varchar(100),
	cvs_password		varchar(100),
	cvs_path		varchar(200),
	cvs_package_name	varchar(200)
);

--
-- Spamming stuff
-- 
create table sdm_notification_prefs (
       user_id			    integer not null primary key references users,
       package_pref		    varchar(50) default 'none' check (package_pref in ('none', 'hourly', 'daily', 'weekly')),
       baf_pref			    varchar(50) default 'none' check (baf_pref in ('none', 'hourly', 'daily', 'weekly'))
);


create table sdm_notifications (
       user_id		       integer not null primary key references users,
       baf_notifications       lztext,
       package_notifications   lztext
);

-- PL/SQL to do notifications
create function baf_edit_notification(integer, varchar) returns integer
as '
DECLARE
	v_baf_id alias for $1;
	v_message alias for $2;
	v_one_baf	bugs_and_features%ROWTYPE;
BEGIN
	select * into v_one_baf from bugs_and_features where baf_id= v_baf_id;

	update sdm_notifications set
	baf_notifications= coalesce(baf_notifications,'''') || to_char(sysdate(),''YYYY-MM-DD'') || '' '' || v_one_baf.baf_type || '' #'' || v_baf_id ||
			   '': '' || v_message || ''\n'' || substr(v_one_baf.description,0,60) || ''... \n\n''
	where user_id in (select user_id from user_baf_interest_map where baf_id= v_baf_id);

	return 1;
END;
' language 'plpgsql';

create function package_edit_notification(integer, varchar) returns integer
as '
DECLARE
	v_package_id alias for $1;
	v_message alias for $2;
	v_one_package	packages%ROWTYPE;
BEGIN
	select * into v_one_package from packages where package_id= v_package_id;
	
	update sdm_notifications set
	package_notifications= coalesce(package_notifications,'''') || to_char(sysdate(),''YYYY-MM-DD'') || '' '' || v_one_package.package_name || '': '' ||
			       v_message || ''\n''
	where user_id in (select user_id from user_package_interest_map where package_id= v_package_id);

	return 1;
END;
' language 'plpgsql';


--
-- VIEWS!
--

create view packages_with_cvs_data as select packages.*,cvs_server, cvs_username, cvs_password, cvs_path, cvs_package_name from packages,cvs_package_data where packages.package_id=cvs_package_data.package_id;


--
-- Procs for Postgres no Outer Joins
--

create function baf_module(integer)
returns varchar as '
DECLARE
	v_baf_id alias for $1;
	the_name varchar(100);
BEGIN
	select module_name into the_name from modules where module_id=
	       (select module_id from bugs_and_features where baf_id= v_baf_id);

	return the_name;
END;
' language 'plpgsql';


create function fetch_release_name(integer)
returns varchar as '
DECLARE
	v_release_id alias for $1;
	v_release_name varchar(100);
BEGIN
	select release_name(major_version, minor_version, patch_version, beta_version)
	into v_release_name
	from package_releases where release_id = v_release_id;

	return v_release_name;
END;
' language 'plpgsql';


create function get_baf_status_id(varchar)
returns integer as '
DECLARE
	status_name alias for $1;
BEGIN
	return baf_status_id from baf_status where baf_status= status_name;
END;
' language 'plpgsql';


create function sdm_get_baf_status(integer)
returns varchar as '
DECLARE
	v_baf_status_id alias for $1;
BEGIN
	return baf_status from baf_status where baf_status_id= v_baf_status_id;
END;
' language 'plpgsql';


create function baf_rating(integer)
returns numeric
as '
DECLARE
	v_baf_id alias for $1;
BEGIN
	return avg(rating) from baf_ratings where baf_id= v_baf_id;
END;
' language 'plpgsql';

create function baf_n_interested(integer)
returns numeric
as '
DECLARE
	v_baf_id alias for $1;
BEGIN
	return count(*) from user_baf_interest_map where baf_id= v_baf_id;
END;
' language 'plpgsql';

-- Stuff about assignments
create function sdm_baf_assigned(integer)
returns varchar as '
DECLARE
	v_baf_id alias for $1;
	v_assigned     varchar(200);
	v_one_row      users%ROWTYPE;
BEGIN
	v_assigned:= '''';

	FOR v_one_row IN select * from users where user_id in 
	(select user_id from baf_assignments where baf_id= v_baf_id) LOOP
	    IF v_assigned!=''''
	    then v_assigned:= v_assigned || '', '';
	    end if;

	    v_assigned:= v_assigned || v_one_row.first_names || '' '' || v_one_row.last_name;
	END LOOP;

	IF v_assigned= ''''
	then v_assigned:= ''(no one)'';
	end if;

	return v_assigned;
END;
' language 'plpgsql';

-- another function for outer join problems
create function sdm_completion_date(integer)
returns datetime as '
DECLARE
	v_baf_id alias for $1;
	v_completion integer;
BEGIN
	select completion into v_completion from bugs_and_features where baf_id= v_baf_id;
	if v_completion is NULL
	then return null;
	else return sysdate();
	end if;
END;
' language 'plpgsql';

create function sdm_baf_due_date(integer)
returns datetime as '
DECLARE
	v_baf_id alias for $1;
	v_release_id integer;
BEGIN
	select expected_completion into v_release_id from bugs_and_features;

	if v_release_id is null
	then return NULL;
	end if;

	return anticipated_release_date from package_releases where release_id= v_release_id;
END;
' language 'plpgsql';


-- Accepting patches

create sequence sdm_patch_id_sequence;

create table sdm_package_patches (
       patch_id			 integer not null primary key,
       user_id			 integer not null references users,
       package_id		 integer not null references packages,
       package_release_id	 integer not null references package_releases,
       submission_date		 datetime,
       patch_file		 varchar(200),
       patch_description	 varchar(4000),
       accepted_p		 char(1) check (accepted_p in ('t','f')),
       action_user		 integer references users,
       action_date		 datetime,
       action_description	 varchar(4000)
);

create table sdm_package_patch_ratings (
       patch_id			       integer not null references sdm_package_patches,
       user_id			       integer not null references users,
       primary key (patch_id, user_id),
       numeric_rating		       integer check (numeric_rating between 1 and 10),
       description		       varchar(4000),
       rating_date		       datetime
);

create function sdm_package_patch_rating(integer)
returns float
as '
DECLARE
	v_patch_id alias for $1;
BEGIN
	return avg(numeric_rating) from sdm_package_patch_ratings where patch_id= v_patch_id;
END;
' language 'plpgsql';

