Skip to content

Commit 821f467

Browse files
committed
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.
1 parent 8bb4dab commit 821f467

File tree

3 files changed

+294
-70
lines changed

3 files changed

+294
-70
lines changed

src/backend/utils/adt/date.c

Lines changed: 193 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $Header: /cvsroot/pgsql/src/backend/utils/adt/date.c,v 1.53 2000/12/03 14:51:01 thomas Exp $
11+
* $Header: /cvsroot/pgsql/src/backend/utils/adt/date.c,v 1.54 2000/12/07 18:38:59 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -529,37 +529,122 @@ time_smaller(PG_FUNCTION_ARGS)
529529
PG_RETURN_TIMEADT((time1 < time2) ? time1 : time2);
530530
}
531531

532-
/* overlaps_time()
533-
* Implements the SQL92 OVERLAPS operator.
534-
* Algorithm from Date and Darwen, 1997
532+
/* overlaps_time() --- implements the SQL92 OVERLAPS operator.
533+
*
534+
* Algorithm is per SQL92 spec. This is much harder than you'd think
535+
* because the spec requires us to deliver a non-null answer in some cases
536+
* where some of the inputs are null.
535537
*/
536538
Datum
537539
overlaps_time(PG_FUNCTION_ARGS)
538540
{
539-
TimeADT ts1 = PG_GETARG_TIMEADT(0);
540-
TimeADT te1 = PG_GETARG_TIMEADT(1);
541-
TimeADT ts2 = PG_GETARG_TIMEADT(2);
542-
TimeADT te2 = PG_GETARG_TIMEADT(3);
541+
/* The arguments are TimeADT, but we leave them as generic Datums
542+
* to avoid dereferencing nulls (TimeADT is pass-by-reference!)
543+
*/
544+
Datum ts1 = PG_GETARG_DATUM(0);
545+
Datum te1 = PG_GETARG_DATUM(1);
546+
Datum ts2 = PG_GETARG_DATUM(2);
547+
Datum te2 = PG_GETARG_DATUM(3);
548+
bool ts1IsNull = PG_ARGISNULL(0);
549+
bool te1IsNull = PG_ARGISNULL(1);
550+
bool ts2IsNull = PG_ARGISNULL(2);
551+
bool te2IsNull = PG_ARGISNULL(3);
543552

544-
/* Make sure we have ordered pairs... */
545-
if (ts1 > te1)
546-
{
547-
TimeADT tt = ts1;
553+
#define TIMEADT_GT(t1,t2) \
554+
(DatumGetTimeADT(t1) > DatumGetTimeADT(t2))
555+
#define TIMEADT_LT(t1,t2) \
556+
(DatumGetTimeADT(t1) < DatumGetTimeADT(t2))
548557

558+
/*
559+
* If both endpoints of interval 1 are null, the result is null (unknown).
560+
* If just one endpoint is null, take ts1 as the non-null one.
561+
* Otherwise, take ts1 as the lesser endpoint.
562+
*/
563+
if (ts1IsNull)
564+
{
565+
if (te1IsNull)
566+
PG_RETURN_NULL();
567+
/* swap null for non-null */
549568
ts1 = te1;
550-
te1 = tt;
569+
te1IsNull = true;
551570
}
552-
if (ts2 > te2)
571+
else if (!te1IsNull)
553572
{
554-
TimeADT tt = ts2;
573+
if (TIMEADT_GT(ts1, te1))
574+
{
575+
Datum tt = ts1;
576+
577+
ts1 = te1;
578+
te1 = tt;
579+
}
580+
}
555581

582+
/* Likewise for interval 2. */
583+
if (ts2IsNull)
584+
{
585+
if (te2IsNull)
586+
PG_RETURN_NULL();
587+
/* swap null for non-null */
556588
ts2 = te2;
557-
te2 = tt;
589+
te2IsNull = true;
590+
}
591+
else if (!te2IsNull)
592+
{
593+
if (TIMEADT_GT(ts2, te2))
594+
{
595+
Datum tt = ts2;
596+
597+
ts2 = te2;
598+
te2 = tt;
599+
}
558600
}
559601

560-
PG_RETURN_BOOL((ts1 > ts2 && (ts1 < te2 || te1 < te2)) ||
561-
(ts1 < ts2 && (ts2 < te1 || te2 < te1)) ||
562-
(ts1 == ts2));
602+
/*
603+
* At this point neither ts1 nor ts2 is null, so we can consider three
604+
* cases: ts1 > ts2, ts1 < ts2, ts1 = ts2
605+
*/
606+
if (TIMEADT_GT(ts1, ts2))
607+
{
608+
/* This case is ts1 < te2 OR te1 < te2, which may look redundant
609+
* but in the presence of nulls it's not quite completely so.
610+
*/
611+
if (te2IsNull)
612+
PG_RETURN_NULL();
613+
if (TIMEADT_LT(ts1, te2))
614+
PG_RETURN_BOOL(true);
615+
if (te1IsNull)
616+
PG_RETURN_NULL();
617+
/* If te1 is not null then we had ts1 <= te1 above, and we just
618+
* found ts1 >= te2, hence te1 >= te2.
619+
*/
620+
PG_RETURN_BOOL(false);
621+
}
622+
else if (TIMEADT_LT(ts1, ts2))
623+
{
624+
/* This case is ts2 < te1 OR te2 < te1 */
625+
if (te1IsNull)
626+
PG_RETURN_NULL();
627+
if (TIMEADT_LT(ts2, te1))
628+
PG_RETURN_BOOL(true);
629+
if (te2IsNull)
630+
PG_RETURN_NULL();
631+
/* If te2 is not null then we had ts2 <= te2 above, and we just
632+
* found ts2 >= te1, hence te2 >= te1.
633+
*/
634+
PG_RETURN_BOOL(false);
635+
}
636+
else
637+
{
638+
/* For ts1 = ts2 the spec says te1 <> te2 OR te1 = te2, which is a
639+
* rather silly way of saying "true if both are nonnull, else null".
640+
*/
641+
if (te1IsNull || te2IsNull)
642+
PG_RETURN_NULL();
643+
PG_RETURN_BOOL(true);
644+
}
645+
646+
#undef TIMEADT_GT
647+
#undef TIMEADT_LT
563648
}
564649

565650
/* timestamp_time()
@@ -964,53 +1049,122 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
9641049
PG_RETURN_TIMETZADT_P(result);
9651050
}
9661051

967-
/* overlaps_timetz()
968-
* Implements the SQL92 OVERLAPS operator.
969-
* Algorithm from Date and Darwen, 1997
1052+
/* overlaps_timetz() --- implements the SQL92 OVERLAPS operator.
1053+
*
1054+
* Algorithm is per SQL92 spec. This is much harder than you'd think
1055+
* because the spec requires us to deliver a non-null answer in some cases
1056+
* where some of the inputs are null.
9701057
*/
9711058
Datum
9721059
overlaps_timetz(PG_FUNCTION_ARGS)
9731060
{
9741061
/* The arguments are TimeTzADT *, but we leave them as generic Datums
975-
* for convenience of notation.
1062+
* for convenience of notation --- and to avoid dereferencing nulls.
9761063
*/
9771064
Datum ts1 = PG_GETARG_DATUM(0);
9781065
Datum te1 = PG_GETARG_DATUM(1);
9791066
Datum ts2 = PG_GETARG_DATUM(2);
9801067
Datum te2 = PG_GETARG_DATUM(3);
1068+
bool ts1IsNull = PG_ARGISNULL(0);
1069+
bool te1IsNull = PG_ARGISNULL(1);
1070+
bool ts2IsNull = PG_ARGISNULL(2);
1071+
bool te2IsNull = PG_ARGISNULL(3);
9811072

9821073
#define TIMETZ_GT(t1,t2) \
9831074
DatumGetBool(DirectFunctionCall2(timetz_gt,t1,t2))
9841075
#define TIMETZ_LT(t1,t2) \
9851076
DatumGetBool(DirectFunctionCall2(timetz_lt,t1,t2))
986-
#define TIMETZ_EQ(t1,t2) \
987-
DatumGetBool(DirectFunctionCall2(timetz_eq,t1,t2))
9881077

989-
/* Make sure we have ordered pairs... */
990-
if (TIMETZ_GT(ts1, te1))
1078+
/*
1079+
* If both endpoints of interval 1 are null, the result is null (unknown).
1080+
* If just one endpoint is null, take ts1 as the non-null one.
1081+
* Otherwise, take ts1 as the lesser endpoint.
1082+
*/
1083+
if (ts1IsNull)
9911084
{
992-
Datum tt = ts1;
993-
1085+
if (te1IsNull)
1086+
PG_RETURN_NULL();
1087+
/* swap null for non-null */
9941088
ts1 = te1;
995-
te1 = tt;
1089+
te1IsNull = true;
9961090
}
997-
if (TIMETZ_GT(ts2, te2))
1091+
else if (!te1IsNull)
9981092
{
999-
Datum tt = ts2;
1093+
if (TIMETZ_GT(ts1, te1))
1094+
{
1095+
Datum tt = ts1;
1096+
1097+
ts1 = te1;
1098+
te1 = tt;
1099+
}
1100+
}
10001101

1102+
/* Likewise for interval 2. */
1103+
if (ts2IsNull)
1104+
{
1105+
if (te2IsNull)
1106+
PG_RETURN_NULL();
1107+
/* swap null for non-null */
10011108
ts2 = te2;
1002-
te2 = tt;
1109+
te2IsNull = true;
1110+
}
1111+
else if (!te2IsNull)
1112+
{
1113+
if (TIMETZ_GT(ts2, te2))
1114+
{
1115+
Datum tt = ts2;
1116+
1117+
ts2 = te2;
1118+
te2 = tt;
1119+
}
10031120
}
10041121

1005-
PG_RETURN_BOOL((TIMETZ_GT(ts1, ts2) &&
1006-
(TIMETZ_LT(ts1, te2) || TIMETZ_LT(te1, te2))) ||
1007-
(TIMETZ_GT(ts2, ts1) &&
1008-
(TIMETZ_LT(ts2, te1) || TIMETZ_LT(te2, te1))) ||
1009-
TIMETZ_EQ(ts1, ts2));
1122+
/*
1123+
* At this point neither ts1 nor ts2 is null, so we can consider three
1124+
* cases: ts1 > ts2, ts1 < ts2, ts1 = ts2
1125+
*/
1126+
if (TIMETZ_GT(ts1, ts2))
1127+
{
1128+
/* This case is ts1 < te2 OR te1 < te2, which may look redundant
1129+
* but in the presence of nulls it's not quite completely so.
1130+
*/
1131+
if (te2IsNull)
1132+
PG_RETURN_NULL();
1133+
if (TIMETZ_LT(ts1, te2))
1134+
PG_RETURN_BOOL(true);
1135+
if (te1IsNull)
1136+
PG_RETURN_NULL();
1137+
/* If te1 is not null then we had ts1 <= te1 above, and we just
1138+
* found ts1 >= te2, hence te1 >= te2.
1139+
*/
1140+
PG_RETURN_BOOL(false);
1141+
}
1142+
else if (TIMETZ_LT(ts1, ts2))
1143+
{
1144+
/* This case is ts2 < te1 OR te2 < te1 */
1145+
if (te1IsNull)
1146+
PG_RETURN_NULL();
1147+
if (TIMETZ_LT(ts2, te1))
1148+
PG_RETURN_BOOL(true);
1149+
if (te2IsNull)
1150+
PG_RETURN_NULL();
1151+
/* If te2 is not null then we had ts2 <= te2 above, and we just
1152+
* found ts2 >= te1, hence te2 >= te1.
1153+
*/
1154+
PG_RETURN_BOOL(false);
1155+
}
1156+
else
1157+
{
1158+
/* For ts1 = ts2 the spec says te1 <> te2 OR te1 = te2, which is a
1159+
* rather silly way of saying "true if both are nonnull, else null".
1160+
*/
1161+
if (te1IsNull || te2IsNull)
1162+
PG_RETURN_NULL();
1163+
PG_RETURN_BOOL(true);
1164+
}
10101165

10111166
#undef TIMETZ_GT
10121167
#undef TIMETZ_LT
1013-
#undef TIMETZ_EQ
10141168
}
10151169

10161170
/* timestamp_timetz()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy