diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2000-12-07 18:38:59 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2000-12-07 18:38:59 +0000 |
commit | 821f4673ffa53ddfb792cbf646783037dd6796ca (patch) | |
tree | b7c090084a6fe4ed59f779878c90e9b84d26c97b /src/backend/utils/adt/timestamp.c | |
parent | 8bb4dab94d26e330cc1f0327b99753d89f5c90ee (diff) | |
download | postgresql-821f4673ffa53ddfb792cbf646783037dd6796ca.tar.gz postgresql-821f4673ffa53ddfb792cbf646783037dd6796ca.zip |
Make OVERLAPS operators conform to SQL92 spec regarding NULL handling.
As I read it, the spec requires a non-null result in some cases where
one of the inputs is NULL: specifically, if the other endpoint of that
interval is between the endpoints of the other interval, then the result
is known TRUE despite the missing endpoint. The spec could've been a
lot simpler if they did not intend this behavior.
I did not force an initdb for this change, but if you don't do one you'll
still see the old strict-function behavior.
Diffstat (limited to 'src/backend/utils/adt/timestamp.c')
-rw-r--r-- | src/backend/utils/adt/timestamp.c | 112 |
1 files changed, 91 insertions, 21 deletions
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 6930205b156..7ec213baab7 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/adt/timestamp.c,v 1.39 2000/12/03 20:45:36 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/adt/timestamp.c,v 1.40 2000/12/07 18:38:59 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -825,53 +825,123 @@ interval_hash(PG_FUNCTION_ARGS) return hash_any((char *) key, sizeof(double) + sizeof(int4)); } -/* overlaps_timestamp() - * Implements the SQL92 OVERLAPS operator. - * Algorithm from Date and Darwen, 1997 +/* overlaps_timestamp() --- implements the SQL92 OVERLAPS operator. + * + * Algorithm is per SQL92 spec. This is much harder than you'd think + * because the spec requires us to deliver a non-null answer in some cases + * where some of the inputs are null. */ Datum overlaps_timestamp(PG_FUNCTION_ARGS) { /* The arguments are Timestamps, but we leave them as generic Datums - * to avoid unnecessary conversions between value and reference forms... + * to avoid unnecessary conversions between value and reference forms + * --- not to mention possible dereferences of null pointers. */ Datum ts1 = PG_GETARG_DATUM(0); Datum te1 = PG_GETARG_DATUM(1); Datum ts2 = PG_GETARG_DATUM(2); Datum te2 = PG_GETARG_DATUM(3); + bool ts1IsNull = PG_ARGISNULL(0); + bool te1IsNull = PG_ARGISNULL(1); + bool ts2IsNull = PG_ARGISNULL(2); + bool te2IsNull = PG_ARGISNULL(3); #define TIMESTAMP_GT(t1,t2) \ DatumGetBool(DirectFunctionCall2(timestamp_gt,t1,t2)) #define TIMESTAMP_LT(t1,t2) \ DatumGetBool(DirectFunctionCall2(timestamp_lt,t1,t2)) -#define TIMESTAMP_EQ(t1,t2) \ - DatumGetBool(DirectFunctionCall2(timestamp_eq,t1,t2)) - /* Make sure we have ordered pairs... */ - if (TIMESTAMP_GT(ts1, te1)) + /* + * If both endpoints of interval 1 are null, the result is null (unknown). + * If just one endpoint is null, take ts1 as the non-null one. + * Otherwise, take ts1 as the lesser endpoint. + */ + if (ts1IsNull) { - Datum tt = ts1; - + if (te1IsNull) + PG_RETURN_NULL(); + /* swap null for non-null */ ts1 = te1; - te1 = tt; + te1IsNull = true; } - if (TIMESTAMP_GT(ts2, te2)) + else if (!te1IsNull) { - Datum tt = ts2; + if (TIMESTAMP_GT(ts1, te1)) + { + Datum tt = ts1; + + ts1 = te1; + te1 = tt; + } + } + /* Likewise for interval 2. */ + if (ts2IsNull) + { + if (te2IsNull) + PG_RETURN_NULL(); + /* swap null for non-null */ ts2 = te2; - te2 = tt; + te2IsNull = true; + } + else if (!te2IsNull) + { + if (TIMESTAMP_GT(ts2, te2)) + { + Datum tt = ts2; + + ts2 = te2; + te2 = tt; + } } - PG_RETURN_BOOL((TIMESTAMP_GT(ts1, ts2) && - (TIMESTAMP_LT(ts1, te2) || TIMESTAMP_LT(te1, te2))) || - (TIMESTAMP_GT(ts2, ts1) && - (TIMESTAMP_LT(ts2, te1) || TIMESTAMP_LT(te2, te1))) || - TIMESTAMP_EQ(ts1, ts2)); + /* + * At this point neither ts1 nor ts2 is null, so we can consider three + * cases: ts1 > ts2, ts1 < ts2, ts1 = ts2 + */ + if (TIMESTAMP_GT(ts1, ts2)) + { + /* This case is ts1 < te2 OR te1 < te2, which may look redundant + * but in the presence of nulls it's not quite completely so. + */ + if (te2IsNull) + PG_RETURN_NULL(); + if (TIMESTAMP_LT(ts1, te2)) + PG_RETURN_BOOL(true); + if (te1IsNull) + PG_RETURN_NULL(); + /* If te1 is not null then we had ts1 <= te1 above, and we just + * found ts1 >= te2, hence te1 >= te2. + */ + PG_RETURN_BOOL(false); + } + else if (TIMESTAMP_LT(ts1, ts2)) + { + /* This case is ts2 < te1 OR te2 < te1 */ + if (te1IsNull) + PG_RETURN_NULL(); + if (TIMESTAMP_LT(ts2, te1)) + PG_RETURN_BOOL(true); + if (te2IsNull) + PG_RETURN_NULL(); + /* If te2 is not null then we had ts2 <= te2 above, and we just + * found ts2 >= te1, hence te2 >= te1. + */ + PG_RETURN_BOOL(false); + } + else + { + /* For ts1 = ts2 the spec says te1 <> te2 OR te1 = te2, which is a + * rather silly way of saying "true if both are nonnull, else null". + */ + if (te1IsNull || te2IsNull) + PG_RETURN_NULL(); + PG_RETURN_BOOL(true); + } #undef TIMESTAMP_GT #undef TIMESTAMP_LT -#undef TIMESTAMP_EQ } |