-- packages/acs-events/sql/postgresql/test/timespan-test.sql -- -- Regression tests for timespan API -- Separated from time_interval-test.sql -- -- @author jowell@jsabino.com -- @creation-date 2001-06-26 -- -- $Id: timespan-test.sql,v 1.2 2003/05/17 09:47:26 jeffd Exp $ -- Note: These tests use the semi-ported utPLSQL regression package \i utest-create.sql -- Set-up the regression test create function ut__setup() returns integer as ' begin raise notice ''Setting up timespans test...''; -- create copies of the tables (shadow tables) to verify API operations -- No need for execute here? create table ut_timespans as select * from timespans; -- For testing purposes, both tables should still be empty PERFORM ut_assert__eqtable (''Comparing copied data for time interval'', ''timespans'', ''ut_timespans'' ); -- Store keys that are in the table prior to the regresion test create table ut_interval_ids as select interval_id from time_intervals; create table ut_timespan_ids as select timespan_id from timespans; return 0; end;' language 'plpgsql'; -- Clean up the mess that regression testing did create function ut__teardown() returns integer as ' begin raise notice ''Tearing down timespans test...''; -- Delete intervals added by tests -- cascade delete in timespans should delete corresponding entries in that table -- Note that we exclude deleting rows that existed prior to regression test delete from timespans where timespan_id not in (select timespan_id from ut_timespan_ids); -- This is sufficient, actually. delete from time_intervals where interval_id not in (select interval_id from ut_interval_ids); -- Drop test tables -- cascade option does not work? drop table ut_timespans; drop table ut_interval_ids; drop table ut_timespan_ids; return 0; end;' language 'plpgsql'; -- Postgres has this weird behavior that you cannot change a row twice -- within a transaction. -- We test the creation of a time interval entry create function ut__new( integer -- time_intervals.interval_id%TYPE; ) returns integer as ' declare new__interval_id alias for $1; v_interval_id time_intervals.interval_id%TYPE; v_timespan_id timespans.timespan_id%TYPE; begin -- The new function will create a copy on the time_intervals table v_timespan_id := timespan__new(new__interval_id); -- Since the timespan__new function creates a copy of the interval -- we need the copied interval_id select interval_id into v_interval_id from timespans where timespan_id = v_timespan_id; -- Create shadow entries, too. insert into ut_timespans (timespan_id,interval_id) values (v_timespan_id,v_interval_id); -- The new function will create a copy on the time_intervals table -- We do two test. First, we check whether the copying mechanism is ok PERFORM ut_assert__eq (''Test of timespan__new copying mechanism: '', time_interval__eq(v_interval_id, new__interval_id), true ); -- Second, we check whether the timespans table is properly populated PERFORM ut_assert__eqtable (''Test of timespan__new entry in timespans table: '', ''ut_timespans'', ''timespans'' ); -- If successful, interval id is correct return v_timespan_id; end;' language 'plpgsql'; -- We test the creation of a time interval entry create function ut__new( timestamptz, timestamptz ) returns integer as ' declare new__date1 alias for $1; new__date2 alias for $2; v_interval_id time_intervals.interval_id%TYPE; begin -- We first want to create an entry in the time interval table -- because the timespan_new function copies this interval v_interval_id := time_interval__new(new__date1, new__date2); -- Create a new timespan using the function above return ut__new(v_interval_id); end;' language 'plpgsql'; -- Check the deletion of a time interval create function ut__delete( integer -- timespans.timespan_id%TYPE ) returns integer as ' declare delete__timespan_id alias for $1; begin -- Delete the row from actual table PERFORM timespan__delete(delete__timespan_id); PERFORM ut_assert__eqtable (''Testing timespan__delete: '', ''ut_timespans'', ''timespans'' ); -- Delete entry from shadow table -- JS: Aha, a demonstration of the effect of transactions to foreign keys -- JS: It seems that while timespan__delete would remove the row from -- JS: time_intervals, the cascade delete removal of the corresponding row -- JS: in timespans is not yet done until the transation is complete. Thus, -- JS: deleting the row in the shadow table within this function/transaction -- JS: will cause the comparison of the timespans table and the shadow table -- JS: to fail (since delete will immediately remove the row from the shadow -- JS: table). We do the delete outside this function/transaction instead. -- Delete from shadow table -- delete from ut_timespans -- where timespan_id = delete__timespan_id; -- If successful, interval id is correct return 0; end;' language 'plpgsql'; create function ut__join_interval ( integer, -- timespans.timespan_id%TYPE; integer, -- time_intervals.interval_id%TYPE; boolean -- copy_p (if false, create new) ) returns integer as ' declare join_interval__timespan_id alias for $1; join_interval__interval_id alias for $2; join_interval__copy_p alias for $3; v_interval_id time_intervals.interval_id%TYPE; v_interval_id_ck time_intervals.interval_id%TYPE; v_interval_id_cp time_intervals.interval_id%TYPE; begin -- Get interval id of orginal interval (before join) select interval_id into v_interval_id from timespans where timespan_id = join_interval__timespan_id; -- Join the supplied interval with existing interval -- Return the interval_id being joined (will be different if copy_p = true) v_interval_id_cp := timespan__join_interval(join_interval__timespan_id, join_interval__interval_id, join_interval__copy_p); -- Dont forget to put the newly created timepsan into the shadow table insert into ut_timespans (timespan_id,interval_id) values (join_interval__timespan_id,v_interval_id_cp); -- Check if there are now two intervals with the same timespan_id in timespans table PERFORM ut_assert__eqquery (''Testing timespan__join with two intervals (2 entries): '', ''select count(*) from timespans where timespan_id = '' || join_interval__timespan_id, ''select 2 from dual'' ); -- This is probably a more robust check, since we want to compare the resulting timespan table PERFORM ut_assert__eqtable (''Testing timespan__join: table comparison test: '', ''ut_timespans'', ''timespans'' ); -- Did not do the interval check since it is dependent upon join_interval__copy_p -- Besides, it seems silly to me: since there are only two intervals, checking table equality -- AND checking that only two intervals are in the time span should be enough! return 0; end;' language 'plpgsql'; create function ut__join( integer, -- timespans.timespan_id%TYPE; integer -- timespans.timespan_id%TYPE; ) returns integer as ' declare join__timespan_id_1 alias for $1; join__timespan_id_2 alias for $2; rec_timespan record; begin PERFORM timespan__join(join__timespan_id_1,join__timespan_id_2); -- Joining means that the intervals in join__timespan_id_2 are -- included in the intervals in join__timespan_id_1 FOR rec_timespan IN select * from timespans where timespan_id = join__timespan_id_2 LOOP insert into ut_timespans (timespan_id,interval_id) values (join__timespan_id_1,rec_timespan.interval_id); END LOOP; -- Check equality of tables PERFORM ut_assert__eqtable (''Testing timespan__join by specifying timespan_id: '', ''ut_timespans'', ''timespans'' ); return 0; end;' language 'plpgsql'; create function ut__join( integer, -- timespans.timespan_id%TYPE; timestamptz, -- time_intervals.start_date%TYPE; timestamptz -- time_intervals.end_date%TYPE; ) returns integer as ' declare join__timespan_id alias for $1; join__start_date alias for $2; join__end_date alias for $3; v_interval_id time_intervals.interval_id%TYPE; begin v_interval_id := timespan__join(join__timespan_id,join__start_date,join__end_date); -- Joining means that the interval becomes part -- of the timespan specified by join__timespan_id insert into ut_timespans (timespan_id,interval_id) values (join__timespan_id,v_interval_id); -- Check equality of tables PERFORM ut_assert__eqtable (''Testing timespan__join by specifying start and end dates: '', ''ut_timespans'', ''timespans'' ); return 0; end;' language 'plpgsql'; create function ut__copy( integer, -- timespans.timespan_id%TYPE interval -- offset ) returns integer as ' declare copy__timespan_id alias for $1; copy__offset alias for $2; v_timespan_id timespans.timespan_id%TYPE; v_interval_id time_intervals.interval_id%TYPE; v_interval_id_ck time_intervals.interval_id%TYPE; rec_timespan record; begin v_timespan_id := timespan__copy(copy__timespan_id,copy__offset); -- Put copy in shadow table. There may be more than one interval in a -- time interval so we need to loop through all for rec_timespan in select * from timespans where timespan_id = v_timespan_id loop -- Populate the shadow table insert into ut_timespans (timespan_id,interval_id) values (rec_timespan.timespan_id,rec_timespan.interval_id); end loop; -- Check proper population of shadow table PERFORM ut_assert__eqtable (''Testing timespan__copy: '', ''ut_timespans'', ''timespans'' ); return v_timespan_id; end;' language 'plpgsql'; create function ut__exists_p ( integer, -- timespans.timespan_id%TYPE; boolean ) returns integer as ' declare exists_p__timespan_id alias for $1; exists_p__result alias for $2; begin PERFORM ut_assert__eq (''Testing timespan__exists_p: '', timespan__exists_p(exists_p__timespan_id), exists_p__result ); return 0; end;' language 'plpgsql'; create function ut__multi_interval_p ( integer, -- timespans.timespan_id%TYPE boolean -- result ) returns integer as ' declare multi_interval_p__timespan_id alias for $1; multi_interval_p__result alias for $2; begin return ut_assert__eq (''Testing timespan__multi_interval_p: '', timespan__multi_interval_p(multi_interval_p__timespan_id), multi_interval_p__result ); end;' language 'plpgsql'; create function ut__overlaps_interval_p( integer, -- timespans.timespan_id%TYPE; integer, -- time_intervals.interval_id%TYPE; boolean ) returns integer as ' declare overlaps_interval_p__timespan_id alias for $1; overlaps_interval_p__interval_id alias for $2; overlaps_interval_p__result alias for $3; begin return ut_assert__eq (''Testing timespan__overlaps_interval_p: '', timespan__overlaps_interval_p(overlaps_interval_p__timespan_id, overlaps_interval_p__interval_id), overlaps_interval_p__result ); end;' language 'plpgsql'; create function ut__overlaps_p( integer, -- timespans.timespan_id%TYPE; integer, -- timespans.timespan_id%TYPE; boolean ) returns integer as ' declare overlaps_p__timespan_1_id alias for $1; overlaps_p__timespan_2_id alias for $2; overlaps_p__result alias for $3; begin return ut_assert__eq (''Testing timespan__overlaps_p, timespan vs. timespan: '', timespan__overlaps_p(overlaps_p__timespan_1_id, overlaps_p__timespan_2_id), overlaps_p__result ); end;' language 'plpgsql'; create function ut__overlaps_p( integer, -- timespans.timespan_id%TYPE; timestamptz, -- time_intervals.start_date%TYPE; timestamptz, -- time_intervals.end_date%TYPE; boolean ) returns integer as ' declare overlaps_p__timespan_id alias for $1; overlaps_p__start_date alias for $2; overlaps_p__end_date alias for $3; overlaps_p__result alias for $4; begin return ut_assert__eq (''Test of timespan__overlaps_p, timespan vs. start and end dates: '', timespan__overlaps_p(overlaps_p__timespan_id, overlaps_p__start_date, overlaps_p__end_date), overlaps_p__result ); end;' language 'plpgsql'; create function ut__interval_delete ( integer, -- timespans.timespan_id%TYPE; integer -- time_intervals.interval_id%TYPE; ) returns integer as ' declare interval_delete__timespan_id alias for $1; interval_delete__interval_id alias for $2; begin PERFORM timespan__interval_delete(interval_delete__timespan_id,interval_delete__interval_id); -- Remove from shadow table delete from ut_timespans where timespan_id = interval_delete__timespan_id and interval_id = interval_delete__interval_id; return ut_assert__eqtable(''Testing timespan__interval_delete: '', ''ut_timespans'', ''timespans'' ); end;' language 'plpgsql'; create function ut__regression1() returns integer as ' declare v_result integer := 0; v_interval_id time_intervals.interval_id%TYPE; v_interval_id_ck time_intervals.interval_id%TYPE; v_timespan_id timespans.timespan_id%TYPE; v_timespan_id_ck timespans.timespan_id%TYPE; begin raise notice ''Regression test, part 1 (creates and edits).''; -- First create an interval v_interval_id := time_interval__new(timestamptz ''2001-01-01'',timestamptz ''2001-01-20''); --Check if creation of timespans work by supplying an interval id to be copied PERFORM ut__new(v_interval_id); -- We first check if the creation of timespans work -- This should be equivalent to what we have above v_timespan_id := ut__new(timestamptz ''2001-01-25'',timestamptz ''2001-02-02''); -- Test if timespan exists PERFORM ut__exists_p(v_timespan_id,true); -- Unfortunately, we cannot delete the timespan and then check its non-existence -- (transactions). So we check for a known non-existent timespan PERFORM ut__exists_p(v_timespan_id+100,false); -- Check if multi-interval (obviously not) PERFORM ut__multi_interval_p(v_timespan_id,false); -- The interval does not overlap the timespan PERFORM ut__overlaps_interval_p(v_timespan_id,v_interval_id,false); -- Join the first interval with the second, without making a copy PERFORM ut__join_interval(v_timespan_id,v_interval_id,false); -- Should now be a multi-interval timespan PERFORM ut__multi_interval_p(v_timespan_id,true); -- Now that the interval is part of the timespan, they should overlap PERFORM ut__overlaps_interval_p(v_timespan_id,v_interval_id,true); -- A new timespans v_timespan_id := ut__new(timestamptz ''2001-03-05'',timestamptz ''2001-03-31''); v_timespan_id_ck := ut__new(timestamptz ''2001-06-05'',timestamptz ''2001-06-30''); -- These timespans should not overlap PERFORM ut__overlaps_p(v_timespan_id,v_timespan_id_ck,false); -- Check overlaps against these known dates PERFORM ut__overlaps_p(v_timespan_id,timestamptz ''2001-02-06'',timestamptz ''2001-03-25'',true); PERFORM ut__overlaps_p(v_timespan_id,timestamptz ''2001-03-07'',timestamptz ''2001-04-01'',true); PERFORM ut__overlaps_p(v_timespan_id,timestamptz ''2001-01-01'',timestamptz ''2001-03-20'',true); PERFORM ut__overlaps_p(v_timespan_id,timestamptz ''2001-01-01'',null,true); PERFORM ut__overlaps_p(v_timespan_id,null,timestamptz ''2001-04-01'',true); PERFORM ut__overlaps_p(v_timespan_id,timestamptz ''2001-04-01'',timestamptz ''2001-04-30'',false); PERFORM ut__overlaps_p(v_timespan_id,timestamptz ''2001-02-01'',timestamptz ''2001-02-27'',false); -- Join the first interval with the second, making a copy PERFORM ut__join_interval(v_timespan_id,v_interval_id,true); -- Join the two (the joined interval is longer) PERFORM ut__join(v_timespan_id_ck,v_timespan_id); -- These timespans should now overlap PERFORM ut__overlaps_p(v_timespan_id,v_timespan_id_ck,true); -- Join an interval instead PERFORM ut__join(v_timespan_id_ck,timestamptz ''2001-12-01'',timestamptz ''2001-12-31''); -- Copy a timespan (will only contain two) PERFORM ut__copy(v_timespan_id,interval ''0 days''); -- Now try to delete the interval just joined PERFORM ut__interval_delete(v_timespan_id,v_interval_id); -- We will improve the regression test so there is reporting -- of individual test results. For now, reaching this far is -- enough to declare success. return v_result; end;' language 'plpgsql'; create function ut__regression2() returns integer as ' declare v_result integer := 0; rec_timespan record; begin raise notice ''Regression test, part 2 (deletes).''; -- Remove all entries made by regression test -- This also tests the deletion mechanism FOR rec_timespan IN select * from timespans where timespan_id not in (select timespan_id from ut_timespan_ids) LOOP PERFORM ut__delete(rec_timespan.timespan_id); END LOOP; -- We will improve the regression test so there is reporting -- of individual test results. For now, reaching this far is -- enough to declare success. return v_result; end;' language 'plpgsql'; -------------------------------------------------------------------------------- -- Main regression test. PostgreSQL does not allow multiple changes made to a -- primary key inside a transaction if the primary key is referenced by another -- table (e.g., insert and delete). As a fix, we break down the regression test -- so that row creations and edits are separate from row deletions -------------------------------------------------------------------------------- select (case when ut__setup() = 0 then 'Regression test properly set up.' end) as setup_result; select (case when ut__regression1() = 0 then 'Regression test, part 1 successful.' end) as test_result; select * from time_intervals; select * from timespans; select * from ut_timespans; select (case when ut__regression2() = 0 then 'Regression test, part 2 successful.' end) as test_result; -- Unfortunately, we need to recheck the deletion since we cannot put -- actual deletion of entries in the shadow table inside the ut__delete -- function due to the transactional nature of the functions delete from ut_timespans where timespan_id not in (select timespan_id from ut_timespan_ids); select (case when ut_assert__eqtable('Recheck of deletion','timespans','ut_timespans') = 0 then 'Recheck of deletion successful.' end) as recheck_result; select (case when ut__teardown() = 0 then 'Regression test properly torn down.' end) as teardown_result; -- Clean up created functions. -- This depends on openacs4 installed. select drop_package('ut'); -------------------------------------------------------------------------------- -- End of regression test -------------------------------------------------------------------------------- \i utest-drop.sql