/*-------------------------------------------------------------------------
 *
 * tqual.c--
 *    POSTGRES time qualification code.
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    /usr/local/devel/pglite/cvs/src/backend/utils/time/tqual.c,v 1.3 1995/01/16 22:29:42 jolly Exp
 *
 *-------------------------------------------------------------------------
 */

/* #define TQUALDEBUG	1 */

#include "postgres.h"

#include "access/htup.h"
#include "access/xact.h"
#include "storage/bufmgr.h"
#include "access/transam.h"
#include "utils/elog.h"
#include "utils/palloc.h"
#include "utils/nabstime.h"

#include "utils/tqual.h"

/*
 * TimeQualMode --
 *	Mode indicator for treatment of time qualifications.
 */
typedef uint16	TimeQualMode;

#define TimeQualAt	0x1
#define TimeQualNewer	0x2
#define TimeQualOlder	0x4
#define TimeQualAll	0x8

#define TimeQualMask	0xf

#define TimeQualEvery	0x0
#define TimeQualRange	(TimeQualNewer | TimeQualOlder)
#define TimeQualAllAt	(TimeQualAt | TimeQualAll)

typedef struct TimeQualData {
    AbsoluteTime	start;
    AbsoluteTime	end;
    TimeQualMode	mode;
} TimeQualData;

typedef TimeQualData	*InternalTimeQual;

static TimeQualData	SelfTimeQualData;
TimeQual		SelfTimeQual = (Pointer)&SelfTimeQualData;

extern bool		PostgresIsInitialized;

/*
 * XXX Transaction system override hacks start here
 */
#ifndef	GOODAMI

static TransactionId	HeapSpecialTransactionId = InvalidTransactionId;
static CommandId	HeapSpecialCommandId = FirstCommandId;

void
setheapoverride(bool on)
{
    if (on) {
	TransactionIdStore(GetCurrentTransactionId(),
			   &HeapSpecialTransactionId);
	HeapSpecialCommandId = GetCurrentCommandId();
    } else {
	HeapSpecialTransactionId = InvalidTransactionId;
    }
}

/* static */
bool
heapisoverride()
{
    if (!TransactionIdIsValid(HeapSpecialTransactionId)) {
	return (false);
    }
    
    if (!TransactionIdEquals(GetCurrentTransactionId(),
			     HeapSpecialTransactionId) ||
	GetCurrentCommandId() != HeapSpecialCommandId) {
	HeapSpecialTransactionId = InvalidTransactionId;
	
	return (false);
    }
    return (true);
}

#endif	/* !defined(GOODAMI) */
/*
 * XXX Transaction system override hacks end here
 */

static bool HeapTupleSatisfiesItself(HeapTuple tuple);
static bool HeapTupleSatisfiesNow(HeapTuple tuple);
static bool HeapTupleSatisfiesSnapshotInternalTimeQual(HeapTuple tuple,
					   InternalTimeQual qual);
static bool HeapTupleSatisfiesUpperBoundedInternalTimeQual(HeapTuple tuple,
					       InternalTimeQual qual);
static bool HeapTupleSatisfiesUpperUnboundedInternalTimeQual(HeapTuple tuple,
						 InternalTimeQual qual);


     
/*
 * TimeQualIsValid --
 *	True iff time qualification is valid.
 */
bool
TimeQualIsValid(TimeQual qual)
{
    bool	hasStartTime;
    
    if (!PointerIsValid(qual) || qual == SelfTimeQual) {
	return (true);
    }
    
    if (((InternalTimeQual)qual)->mode & ~TimeQualMask) {
	return (false);
    }
    
    if (((InternalTimeQual)qual)->mode & TimeQualAt) {
	return (AbsoluteTimeIsBackwardCompatiblyValid(((InternalTimeQual)qual)->start));
    }
    
    hasStartTime = false;
    
    if (((InternalTimeQual)qual)->mode & TimeQualNewer) {
	if (!AbsoluteTimeIsBackwardCompatiblyValid(((InternalTimeQual)qual)->start)) {
	    return (false);
	}
	hasStartTime = true;
    }
    
    if (((InternalTimeQual)qual)->mode & TimeQualOlder) {
	if (!AbsoluteTimeIsBackwardCompatiblyValid(((InternalTimeQual)qual)->end)) {
	    return (false);
	}
	if (hasStartTime) {
	    return ((bool)!AbsoluteTimeIsBefore(
						((InternalTimeQual)qual)->end,
						((InternalTimeQual)qual)->start));
	}
    }
    return (true);
}

/*
 * TimeQualIsLegal --
 *	True iff time qualification is legal.
 *	I.e., true iff time qualification does not intersects the future,
 *	relative to the transaction start time.
 *
 * Note:
 *	Assumes time qualification is valid.
 */
bool
TimeQualIsLegal(TimeQual qual)
{
    Assert(TimeQualIsValid(qual));
    
    if (qual == NowTimeQual || qual == SelfTimeQual) {
	return (true);
    }
    
    /* TimeQualAt */
    if (((InternalTimeQual)qual)->mode & TimeQualAt) {
	AbsoluteTime a, b;
	
	a = ((InternalTimeQual)qual)->start;
	b = GetCurrentTransactionStartTime();
	
	if (AbsoluteTimeIsAfter(a, b))
	    return (false);
	else
	    return (true);
    }
    
    /* TimeQualOlder or TimeQualRange */
    if (((InternalTimeQual)qual)->mode & TimeQualOlder) {
	AbsoluteTime a, b;
	
	a = ((InternalTimeQual)qual)->end;
	b = GetCurrentTransactionStartTime();
	
	if (AbsoluteTimeIsAfter(a, b))
	    return (false);
	else
	    return (true);
    }
    
    /* TimeQualNewer */
    if (((InternalTimeQual)qual)->mode & TimeQualNewer) {
	AbsoluteTime a, b;
	
	a = ((InternalTimeQual)qual)->start;
	b = GetCurrentTransactionStartTime();
	
	if (AbsoluteTimeIsAfter(a, b))
	    return (false);
	else
	    return (true);
    }
    
    /* TimeQualEvery */
    return (true);
}

/*
 * TimeQualIncludesNow --
 *	True iff time qualification includes "now."
 *
 * Note:
 *	Assumes time qualification is valid.
 */
bool
TimeQualIncludesNow(TimeQual qual)
{
    Assert(TimeQualIsValid(qual));
    
    if (qual == NowTimeQual || qual == SelfTimeQual) {
	return (true);
    }
    
    if (((InternalTimeQual)qual)->mode & TimeQualAt) {
	return (false);
    }
    if (((InternalTimeQual)qual)->mode & TimeQualOlder &&
	!AbsoluteTimeIsAfter(
			     ((InternalTimeQual)qual)->end,
			     GetCurrentTransactionStartTime())) {
	
	return (false);
    }
    return (true);
}

/*
 * TimeQualIncludesPast --
 *	True iff time qualification includes some time in the past.
 *
 * Note:
 *	Assumes time qualification is valid.
 *	XXX may not be needed?
 */
bool
TimeQualIncludesPast(TimeQual qual)
{
    Assert(TimeQualIsValid(qual));
    
    if (qual == NowTimeQual || qual == SelfTimeQual) {
	return (false);
    }
    
    /* otherwise, must check archive (setting locks as appropriate) */
    return (true);
}

/*
 * TimeQualIsSnapshot --
 *	True iff time qualification is a snapshot qualification.
 *
 * Note:
 *	Assumes time qualification is valid.
 */
bool
TimeQualIsSnapshot(TimeQual qual)
{
    Assert(TimeQualIsValid(qual));
    
    if (qual == NowTimeQual || qual == SelfTimeQual) {
	return (false);
    }
    
    return ((bool)!!(((InternalTimeQual)qual)->mode & TimeQualAt));
}

/*
 * TimeQualIsRanged --
 *	True iff time qualification is a ranged qualification.
 *
 * Note:
 *	Assumes time qualification is valid.
 */
bool
TimeQualIsRanged(TimeQual qual)
{
    Assert(TimeQualIsValid(qual));
    
    if (qual == NowTimeQual || qual == SelfTimeQual) {
	return (false);
    }
    
    return ((bool)!(((InternalTimeQual)qual)->mode & TimeQualAt));
}

/*
 * TimeQualIndicatesDisableValidityChecking --
 *	True iff time qualification indicates validity checking should be
 *	disabled.
 *
 * Note:
 *	XXX This should not be implemented since this does not make sense.
 */
bool
TimeQualIndicatesDisableValidityChecking(TimeQual qual)
{
    Assert (TimeQualIsValid(qual));
    
    if (qual == NowTimeQual || qual == SelfTimeQual) {
	return (false);
    }
    
    if (((InternalTimeQual)qual)->mode & TimeQualAll) {
	return (true);
    }
    return (false);
}

/*
 * TimeQualGetSnapshotTime --
 *	Returns time for a snapshot time qual.
 *
 * Note:
 *	Assumes time qual is valid snapshot time qual.
 */
AbsoluteTime
TimeQualGetSnapshotTime(TimeQual qual)
{
    Assert(TimeQualIsSnapshot(qual));
    
    return (((InternalTimeQual)qual)->start);
}

/*
 * TimeQualGetStartTime --
 *	Returns start time for a ranged time qual.
 *
 * Note:
 *	Assumes time qual is valid ranged time qual.
 */
AbsoluteTime
TimeQualGetStartTime(TimeQual qual)
{
    Assert(TimeQualIsRanged(qual));
    
    return (((InternalTimeQual)qual)->start);
}

/*
 * TimeQualGetEndTime --
 *	Returns end time for a ranged time qual.
 *
 * Note:
 *	Assumes time qual is valid ranged time qual.
 */
AbsoluteTime
TimeQualGetEndTime(TimeQual qual)
{
    Assert(TimeQualIsRanged(qual));
    
    return (((InternalTimeQual)qual)->end);
}

/*
 * TimeFormSnapshotTimeQual --
 *	Returns snapshot time qual for a time.
 *
 * Note:
 *	Assumes time is valid.
 */
TimeQual
TimeFormSnapshotTimeQual(AbsoluteTime time)
{
    InternalTimeQual	qual;
    
    Assert(AbsoluteTimeIsBackwardCompatiblyValid(time));
    
    qual = (InternalTimeQual)palloc(sizeof *qual);
    
    qual->start = time;
    qual->end = INVALID_ABSTIME;
    qual->mode = TimeQualAt;
    
    return ((TimeQual)qual);
}

/*
 * TimeFormRangedTimeQual --
 *	Returns ranged time qual for a pair of times.
 *
 * Note:
 *	If start time is invalid, it is regarded as the epoch.
 *	If end time is invalid, it is regarded as "now."
 *	Assumes start time is before (or the same as) end time.
 */
TimeQual
TimeFormRangedTimeQual(AbsoluteTime startTime,
		       AbsoluteTime endTime)
{
    InternalTimeQual	qual;
    
    qual = (InternalTimeQual)palloc(sizeof *qual);
    
    qual->start = startTime;
    qual->end = endTime;
    qual->mode = TimeQualEvery;
    
    if (AbsoluteTimeIsBackwardCompatiblyValid(startTime)) {
	qual->mode |= TimeQualNewer;
    }
    if (AbsoluteTimeIsBackwardCompatiblyValid(endTime)) {
	qual->mode |= TimeQualOlder;
    }
    
    return ((TimeQual)qual);
}

/*
 * HeapTupleSatisfiesTimeQual --
 *	True iff heap tuple satsifies a time qual.
 *
 * Note:
 *	Assumes heap tuple is valid.
 *	Assumes time qual is valid.
 *	XXX Many of the checks may be simplified and still remain correct.
 *	XXX Partial answers to the checks may be cached in an ItemId.
 */
bool
HeapTupleSatisfiesTimeQual(HeapTuple tuple, TimeQual qual)
{
/*    extern TransactionId AmiTransactionId; */
    
    Assert(HeapTupleIsValid(tuple));
    Assert(TimeQualIsValid(qual));
    
    if (TransactionIdEquals(tuple->t_xmax, AmiTransactionId))
	return(false);
    
    if (qual == SelfTimeQual || heapisoverride()) {
	return (HeapTupleSatisfiesItself(tuple));
    }
    
    if (qual == NowTimeQual) {
	return (HeapTupleSatisfiesNow(tuple));
    }
    
    if (!TimeQualIsLegal(qual)) {
	elog(WARN, "HeapTupleSatisfiesTimeQual: illegal time qual");
    }
    
    if (TimeQualIndicatesDisableValidityChecking(qual)) {
	elog(WARN, "HeapTupleSatisfiesTimeQual: no disabled validity checking (yet)");
    }
    
    if (TimeQualIsSnapshot(qual)) {
	return (HeapTupleSatisfiesSnapshotInternalTimeQual(tuple,
							   (InternalTimeQual)qual));
    }
    
    if (TimeQualIncludesNow(qual)) {
	return (HeapTupleSatisfiesUpperUnboundedInternalTimeQual(tuple,
								 (InternalTimeQual)qual));
    }
    
    return (HeapTupleSatisfiesUpperBoundedInternalTimeQual(tuple,
							   (InternalTimeQual)qual));
}

/*
 * HeapTupleSatisfiesItself --
 *	True iff heap tuple is valid for "itself."
 *
 * Note:
 *	Assumes heap tuple is valid.
 */
/*
 * The satisfaction of "itself" requires the following:
 *
 * ((Xmin == my-transaction && (Xmax is null [|| Xmax != my-transaction)])
 * ||
 *
 * (Xmin is committed &&
 *	(Xmax is null || (Xmax != my-transaction && Xmax is not committed)))
 */
static bool
HeapTupleSatisfiesItself(HeapTuple tuple)
{
    /*
     * XXX Several evil casts are made in this routine.  Casting XID to be 
     * TransactionId works only because TransactionId->data is the first
     * (and only) field of the structure.
     */
    if (!AbsoluteTimeIsBackwardCompatiblyValid(tuple->t_tmin)) {
	if (TransactionIdIsCurrentTransactionId((TransactionId)tuple->t_xmin) &&
	    !TransactionIdIsValid((TransactionId)tuple->t_xmax)) {
	    return (true);
	}
	
	if (!TransactionIdDidCommit((TransactionId)tuple->t_xmin)) {
	    return (false);
	}
    }
    /* the tuple was inserted validly */
    
    if (AbsoluteTimeIsBackwardCompatiblyReal(tuple->t_tmax)) {
	return (false);
    }
    
    if (!TransactionIdIsValid((TransactionId)tuple->t_xmax)) {
	return (true);
    }
    
    if (TransactionIdIsCurrentTransactionId((TransactionId)tuple->t_xmax)) {
	return (false);
    }
    
    return ((bool)!TransactionIdDidCommit((TransactionId)tuple->t_xmax));
}

/*
 * HeapTupleSatisfiesNow --
 *	True iff heap tuple is valid "now."
 *
 * Note:
 *	Assumes heap tuple is valid.
 */
/*
 * The satisfaction of "now" requires the following:
 *
 * ((Xmin == my-transaction && Cmin != my-command &&
 *	(Xmax is null || (Xmax == my-transaction && Cmax != my-command)))
 * ||
 *
 * (Xmin is committed &&
 *	(Xmax is null || (Xmax == my-transaction && Cmax == my-command) ||
 *		(Xmax is not committed && Xmax != my-transaction))))
 *
 *	mao says 17 march 1993:  the tests in this routine are correct;
 *	if you think they're not, you're wrong, and you should think
 *	about it again.  i know, it happened to me.  we don't need to
 *	check commit time against the start time of this transaction
 *	because 2ph locking protects us from doing the wrong thing.
 *	if you mess around here, you'll break serializability.  the only
 *	problem with this code is that it does the wrong thing for system
 *	catalog updates, because the catalogs aren't subject to 2ph, so
 *	the serializability guarantees we provide don't extend to xacts
 *	that do catalog accesses.  this is unfortunate, but not critical.
 */
static bool
HeapTupleSatisfiesNow(HeapTuple tuple)
{
    if (AMI_OVERRIDE)
	return true;
    /*
     *  If the transaction system isn't yet initialized, then we assume
     *  that transactions committed.  We only look at system catalogs
     *  during startup, so this is less awful than it seems, but it's
     *  still pretty awful.
     */
    
    if (!PostgresIsInitialized)
	return ((bool)(TransactionIdIsValid((TransactionId)tuple->t_xmin) &&
		       !TransactionIdIsValid((TransactionId)tuple->t_xmax)));
    
    /*
     * XXX Several evil casts are made in this routine.  Casting XID to be 
     * TransactionId works only because TransactionId->data is the first
     * (and only) field of the structure.
     */
    if (!AbsoluteTimeIsBackwardCompatiblyValid(tuple->t_tmin)) {
	
	if (TransactionIdIsCurrentTransactionId((TransactionId)tuple->t_xmin)
	    && CommandIdIsCurrentCommandId(tuple->t_cmin)) {
	    
	    return (false);
	}
	
	if (TransactionIdIsCurrentTransactionId((TransactionId)tuple->t_xmin)
	    && !CommandIdIsCurrentCommandId(tuple->t_cmin)) {
	    
	    if (!TransactionIdIsValid((TransactionId)tuple->t_xmax)) {
		return (true);
	    }
	    
	    Assert(TransactionIdIsCurrentTransactionId((TransactionId)tuple->t_xmax));
	    
	    if (CommandIdIsCurrentCommandId(tuple->t_cmax)) {
		return (true);
	    }
	}
	
	/*
	 * this call is VERY expensive - requires a log table lookup.
	 */
	
	if (!TransactionIdDidCommit((TransactionId)tuple->t_xmin)) {
	    return (false);
	}
    }
    
    /* by here, the inserting transaction has committed */
    if (!TransactionIdIsValid((TransactionId)tuple->t_xmax)) {
	return (true);
    }
    
    if (TransactionIdIsCurrentTransactionId((TransactionId)tuple->t_xmax)) {
	return (false);
    }
    
    if (!TransactionIdDidCommit((TransactionId)tuple->t_xmax)) {
	return (true);
    }
    
    /* by here, deleting transaction has committed */
    return (false);
}

/*
 * HeapTupleSatisfiesSnapshotInternalTimeQual --
 *	True iff heap tuple is valid at the snapshot time qualification.
 *
 * Note:
 *	Assumes heap tuple is valid.
 *	Assumes internal time qualification is valid snapshot qualification.
 */
/*
 * The satisfaction of Rel[T] requires the following:
 *
 * (Xmin is committed && Tmin <= T &&
 *	(Xmax is null || (Xmax is not committed && Xmax != my-transaction) ||
 *		Tmax >= T))
 */
static bool
HeapTupleSatisfiesSnapshotInternalTimeQual(HeapTuple tuple,
					   InternalTimeQual qual)
{
    /*
     * XXX Several evil casts are made in this routine.  Casting XID to be 
     * TransactionId works only because TransactionId->data is the first
     * (and only) field of the structure.
     */
    if (!AbsoluteTimeIsBackwardCompatiblyValid(tuple->t_tmin)) {
	
	if (!TransactionIdDidCommit((TransactionId)tuple->t_xmin)) {
	    return (false);
	}
	
	tuple->t_tmin = TransactionIdGetCommitTime(tuple->t_xmin);
    }
    
    if (AbsoluteTimeIsBefore(TimeQualGetSnapshotTime((TimeQual)qual), tuple->t_tmin)) {
	return (false);
    }
    /* the tuple was inserted validly before the snapshot time */
    
    if (!AbsoluteTimeIsBackwardCompatiblyReal(tuple->t_tmax)) {
	
	if (!TransactionIdIsValid((TransactionId)tuple->t_xmax) ||
	    !TransactionIdDidCommit((TransactionId)tuple->t_xmax)) {
	    
	    return (true);
	}
	
	tuple->t_tmax = TransactionIdGetCommitTime(tuple->t_xmax);
    }
    
    return ((bool)
	    AbsoluteTimeIsAfter(tuple->t_tmax,
				TimeQualGetSnapshotTime((TimeQual)qual)));
}

/*
 * HeapTupleSatisfiesUpperBoundedInternalTimeQual --
 *	True iff heap tuple is valid within a upper bounded time qualification.
 *
 * Note:
 *	Assumes heap tuple is valid.
 *	Assumes time qualification is valid ranged qualification with fixed
 *	upper bound.
 */
/*
 * The satisfaction of [T1,T2] requires the following:
 *
 * (Xmin is committed && Tmin <= T2 &&
 *	(Xmax is null || (Xmax is not committed && Xmax != my-transaction) ||
 *		T1 is null || Tmax >= T1))
 */
static bool
HeapTupleSatisfiesUpperBoundedInternalTimeQual(HeapTuple tuple,
					       InternalTimeQual qual)
{
    /*
     * XXX Several evil casts are made in this routine.  Casting XID to be 
     * TransactionId works only because TransactionId->data is the first
     * (and only) field of the structure.
     */
    if (!AbsoluteTimeIsBackwardCompatiblyValid(tuple->t_tmin)) {
	
	if (!TransactionIdDidCommit((TransactionId)tuple->t_xmin)) {
	    return (false);
	}
	
	tuple->t_tmin = TransactionIdGetCommitTime(tuple->t_xmin);
    }
    
    if (AbsoluteTimeIsBefore(TimeQualGetEndTime((TimeQual)qual), tuple->t_tmin)) {
	return (false);
    }
    /* the tuple was inserted validly before the range end */
    
    if (!AbsoluteTimeIsBackwardCompatiblyValid(TimeQualGetStartTime((TimeQual)qual))) {
	return (true);
    }
    
    if (!AbsoluteTimeIsBackwardCompatiblyReal(tuple->t_tmax)) {
	
	if (!TransactionIdIsValid((TransactionId)tuple->t_xmax) ||
	    !TransactionIdDidCommit((TransactionId)tuple->t_xmax)) {
	    
	    return (true);
	}
	
	tuple->t_tmax = TransactionIdGetCommitTime(tuple->t_xmax);
    }
    
    return ((bool)AbsoluteTimeIsAfter(tuple->t_tmax,
				      TimeQualGetStartTime((TimeQual)qual)));
}

/*
 * HeapTupleSatisfiesUpperUnboundedInternalTimeQual --
 *	True iff heap tuple is valid within a upper bounded time qualification.
 *
 * Note:
 *	Assumes heap tuple is valid.
 *	Assumes time qualification is valid ranged qualification with no
 *	upper bound.
 */
/*
 * The satisfaction of [T1,] requires the following:
 *
 * ((Xmin == my-transaction && Cmin != my-command &&
 *	(Xmax is null || (Xmax == my-transaction && Cmax != my-command)))
 * ||
 *
 * (Xmin is committed &&
 *	(Xmax is null || (Xmax == my-transaction && Cmax == my-command) ||
 *		(Xmax is not committed && Xmax != my-transaction) ||
 *		T1 is null || Tmax >= T1)))
 */
static bool
HeapTupleSatisfiesUpperUnboundedInternalTimeQual(HeapTuple tuple,
						 InternalTimeQual qual)
{
    if (!AbsoluteTimeIsBackwardCompatiblyValid(tuple->t_tmin)) {
	
	if (TransactionIdIsCurrentTransactionId((TransactionId)tuple->t_xmin) &&
	    CommandIdIsCurrentCommandId(tuple->t_cmin)) {
	    
	    return (false);
	}
	
	if (TransactionIdIsCurrentTransactionId((TransactionId)tuple->t_xmin) &&
	    !CommandIdIsCurrentCommandId(tuple->t_cmin)) {
	    
	    if (!TransactionIdIsValid((TransactionId)tuple->t_xmax)) {
		return (true);
	    }
	    
	    Assert(TransactionIdIsCurrentTransactionId((TransactionId)tuple->t_xmax));
	    
	    return ((bool) !CommandIdIsCurrentCommandId(tuple->t_cmax));
	}
	
	if (!TransactionIdDidCommit((TransactionId)tuple->t_xmin)) {
	    return (false);
	}
	
	tuple->t_tmin = TransactionIdGetCommitTime(tuple->t_xmin);
    }
    /* the tuple was inserted validly */
    
    if (!AbsoluteTimeIsBackwardCompatiblyValid(TimeQualGetStartTime((TimeQual)qual))) {
	return (true);
    }
    
    if (!AbsoluteTimeIsBackwardCompatiblyReal(tuple->t_tmax)) {
	
	if (!TransactionIdIsValid((TransactionId)tuple->t_xmax)) {
	    return (true);
	}
	
	if (TransactionIdIsCurrentTransactionId((TransactionId)tuple->t_xmax)) {
	    return (CommandIdIsCurrentCommandId(tuple->t_cmin));
	}
	
	if (!TransactionIdDidCommit((TransactionId)tuple->t_xmax)) {
	    return (true);
	}
	
	tuple->t_tmax = TransactionIdGetCommitTime(tuple->t_xmax);
    }
    
    return ((bool)AbsoluteTimeIsAfter(tuple->t_tmax,
				      TimeQualGetStartTime((TimeQual)qual)));
}
