diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 4b27d4317fb09eb1dbe23b4f034ef4d5bab61017..d0f890c28aafc46813bfdfd7b5c6b3d04d0739dd 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -699,6 +699,8 @@ range_adjacent(PG_FUNCTION_ARGS)
 				upper2;
 	bool		empty1,
 				empty2;
+	RangeType  *r3;
+	int			cmp;
 
 	/* Different types should be prevented by ANYRANGE matching rules */
 	if (RangeTypeGetOid(r1) != RangeTypeGetOid(r2))
@@ -714,23 +716,59 @@ range_adjacent(PG_FUNCTION_ARGS)
 		PG_RETURN_BOOL(false);
 
 	/*
-	 * For two ranges to be adjacent, the lower boundary of one range has to
-	 * match the upper boundary of the other. However, the inclusivity of
-	 * those two boundaries must also be different.
+	 * Given two ranges A..B and C..D, where B < C, the ranges are adjacent
+	 * if and only if the range B..C is empty, where inclusivity of these two
+	 * bounds is inverted compared to the original bounds.  For discrete
+	 * ranges, we have to rely on the canonicalization function to normalize
+	 * B..C to empty if it contains no elements of the subtype.  (If there is
+	 * no canonicalization function, it's impossible for such a range to
+	 * normalize to empty, so we needn't bother to try.)
+	 *
+	 * If B == C, the ranges are adjacent only if these bounds have different
+	 * inclusive flags (i.e., exactly one of the ranges includes the common
+	 * boundary point).
 	 *
-	 * The semantics for range_cmp_bounds aren't quite what we need here, so
-	 * we do the comparison more directly.
+	 * And if B > C then the ranges cannot be adjacent in this order, but we
+	 * must consider the other order (i.e., check D <= A).
 	 */
-	if (lower1.inclusive != upper2.inclusive)
+	cmp = range_cmp_bound_values(typcache, &upper1, &lower2);
+	if (cmp < 0)
+	{
+		/* in a continuous subtype, there are assumed to be points between */
+		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
+			PG_RETURN_BOOL(false);
+		/* flip the inclusion flags */
+		upper1.inclusive = !upper1.inclusive;
+		lower2.inclusive = !lower2.inclusive;
+		/* change upper/lower labels to avoid Assert failures */
+		upper1.lower = true;
+		lower2.lower = false;
+		r3 = make_range(typcache, &upper1, &lower2, false);
+		PG_RETURN_BOOL(RangeIsEmpty(r3));
+	}
+	if (cmp == 0)
 	{
-		if (range_cmp_bound_values(typcache, &lower1, &upper2) == 0)
-			PG_RETURN_BOOL(true);
+		PG_RETURN_BOOL(upper1.inclusive != lower2.inclusive);
 	}
 
-	if (upper1.inclusive != lower2.inclusive)
+	cmp = range_cmp_bound_values(typcache, &upper2, &lower1);
+	if (cmp < 0)
+	{
+		/* in a continuous subtype, there are assumed to be points between */
+		if (!OidIsValid(typcache->rng_canonical_finfo.fn_oid))
+			PG_RETURN_BOOL(false);
+		/* flip the inclusion flags */
+		upper2.inclusive = !upper2.inclusive;
+		lower1.inclusive = !lower1.inclusive;
+		/* change upper/lower labels to avoid Assert failures */
+		upper2.lower = true;
+		lower1.lower = false;
+		r3 = make_range(typcache, &upper2, &lower1, false);
+		PG_RETURN_BOOL(RangeIsEmpty(r3));
+	}
+	if (cmp == 0)
 	{
-		if (range_cmp_bound_values(typcache, &upper1, &lower2) == 0)
-			PG_RETURN_BOOL(true);
+		PG_RETURN_BOOL(upper2.inclusive != lower1.inclusive);
 	}
 
 	PG_RETURN_BOOL(false);