From ad50934eaadb626de682defe0ad270bbf31e92a2 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 14 Nov 2011 21:42:04 -0500 Subject: [PATCH] Fix alignment and toasting bugs in range types. A range type whose element type has 'd' alignment must have 'd' alignment itself, else there is no guarantee that the element value can be used in-place. (Because range_deserialize uses att_align_pointer which forcibly aligns the given pointer, violations of this rule did not lead to SIGBUS but rather to garbage data being extracted, as in one of the added regression test cases.) Also, you can't put a toast pointer inside a range datum, since the referenced value could disappear with the range datum still present. For consistency with the handling of arrays and records, I also forced decompression of in-line-compressed bound values. It would work to store them as-is, but our policy is to avoid situations that might result in double compression. Add assorted regression tests for this, and bump catversion because of fixes to built-in pg_type entries. Also some marginal cleanup of inconsistent/unnecessary error checks. --- src/backend/commands/typecmds.c | 34 ++-- src/backend/utils/adt/rangetypes.c | 219 +++++++++++----------- src/include/catalog/catversion.h | 2 +- src/include/catalog/pg_type.h | 18 +- src/test/regress/expected/rangetypes.out | 10 +- src/test/regress/expected/type_sanity.out | 32 ++++ src/test/regress/sql/rangetypes.sql | 6 +- src/test/regress/sql/type_sanity.sql | 24 +++ 8 files changed, 215 insertions(+), 130 deletions(-) diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index c2f1160e1f..a1628bc098 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -1167,8 +1167,6 @@ DefineRange(CreateRangeStmt *stmt) Oid typoid; Oid rangeArrayOid; List *parameters = stmt->params; - - ListCell *lc; List *rangeSubOpclassName = NIL; List *rangeSubtypeDiffName = NIL; List *rangeCollationName = NIL; @@ -1178,8 +1176,12 @@ DefineRange(CreateRangeStmt *stmt) regproc rangeSubOpclass = InvalidOid; regproc rangeCanonical = InvalidOid; regproc rangeSubtypeDiff = InvalidOid; - + int16 subtyplen; + bool subtypbyval; + char subtypalign; + char alignment; AclResult aclresult; + ListCell *lc; /* Convert list of names to a name and namespace */ typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName, @@ -1314,14 +1316,21 @@ DefineRange(CreateRangeStmt *stmt) else if (rangeCollationName != NIL) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("range collation provided but subtype does not support collation"))); + errmsg("range collation specified but subtype does not support collation"))); rangeSubOpclass = findRangeSubOpclass(rangeSubOpclassName, rangeSubtype); if (rangeSubtypeDiffName != NIL) - rangeSubtypeDiff = findRangeSubtypeDiffFunction( - rangeSubtypeDiffName, rangeSubtype); + rangeSubtypeDiff = findRangeSubtypeDiffFunction(rangeSubtypeDiffName, + rangeSubtype); + get_typlenbyvalalign(rangeSubtype, + &subtyplen, &subtypbyval, &subtypalign); + + /* alignment must be 'i' or 'd' for ranges */ + alignment = (subtypalign == 'd') ? 'd' : 'i'; + + /* Allocate OID for array type */ rangeArrayOid = AssignTypeArrayOid(); /* Create the pg_type entry */ @@ -1332,7 +1341,7 @@ DefineRange(CreateRangeStmt *stmt) InvalidOid, /* relation oid (n/a here) */ 0, /* relation kind (ditto) */ GetUserId(), /* owner's ID */ - -1, /* internal size */ + -1, /* internal size (always varlena) */ TYPTYPE_RANGE, /* type-type (range type) */ TYPCATEGORY_RANGE, /* type-category (range type) */ false, /* range types are never preferred */ @@ -1343,16 +1352,16 @@ DefineRange(CreateRangeStmt *stmt) F_RANGE_SEND, /* send procedure */ InvalidOid, /* typmodin procedure - none */ InvalidOid, /* typmodout procedure - none */ - rangeAnalyze, /* analyze procedure - default */ - InvalidOid, /* element type ID */ + rangeAnalyze, /* analyze procedure */ + InvalidOid, /* element type ID - none */ false, /* this is not an array type */ rangeArrayOid, /* array type we are about to create */ InvalidOid, /* base type ID (only for domains) */ NULL, /* never a default type value */ NULL, /* binary default isn't sent either */ false, /* never passed by value */ - 'i', /* int alignment */ - 'x', /* TOAST strategy always plain */ + alignment, /* alignment */ + 'x', /* TOAST strategy (always extended) */ -1, /* typMod (Domains only) */ 0, /* Array dimensions of typbasetype */ false, /* Type NOT NULL */ @@ -1392,7 +1401,7 @@ DefineRange(CreateRangeStmt *stmt) NULL, /* never a default type value */ NULL, /* binary default isn't sent either */ false, /* never passed by value */ - 'i', /* align 'i' */ + alignment, /* alignment - same as range's */ 'x', /* ARRAY is always toastable */ -1, /* typMod (Domains only) */ 0, /* Array dimensions of typbasetype */ @@ -1401,6 +1410,7 @@ DefineRange(CreateRangeStmt *stmt) pfree(rangeArrayName); + /* And create the constructor functions for this range type */ makeRangeConstructor(typeName, typeNamespace, typoid, rangeSubtype); } diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c index de9b9a5efb..fd5d781038 100644 --- a/src/backend/utils/adt/rangetypes.c +++ b/src/backend/utils/adt/rangetypes.c @@ -33,29 +33,26 @@ #include "utils/typcache.h" -#define TYPE_IS_PACKABLE(typlen, typstorage) \ - (typlen == -1 && typstorage != 'p') - /* flags */ #define RANGE_EMPTY 0x01 #define RANGE_LB_INC 0x02 -#define RANGE_LB_NULL 0x04 /* NOT USED */ +#define RANGE_LB_NULL 0x04 /* NOT CURRENTLY USED */ #define RANGE_LB_INF 0x08 #define RANGE_UB_INC 0x10 -#define RANGE_UB_NULL 0x20 /* NOT USED */ +#define RANGE_UB_NULL 0x20 /* NOT CURRENTLY USED */ #define RANGE_UB_INF 0x40 -#define RANGE_HAS_LBOUND(flags) (!(flags & (RANGE_EMPTY | \ - RANGE_LB_NULL | \ - RANGE_LB_INF))) +#define RANGE_HAS_LBOUND(flags) (!((flags) & (RANGE_EMPTY | \ + RANGE_LB_NULL | \ + RANGE_LB_INF))) -#define RANGE_HAS_UBOUND(flags) (!(flags & (RANGE_EMPTY | \ - RANGE_UB_NULL | \ - RANGE_UB_INF))) +#define RANGE_HAS_UBOUND(flags) (!((flags) & (RANGE_EMPTY | \ + RANGE_UB_NULL | \ + RANGE_UB_INF))) #define RANGE_EMPTY_LITERAL "empty" -#define RANGE_DEFAULT_FLAGS "[)" +#define RANGE_DEFAULT_FLAGS "[)" static char range_parse_flags(const char *flags_str); @@ -151,18 +148,15 @@ range_out(PG_FUNCTION_ARGS) /* deserialize */ range_deserialize(fcinfo, range, &lower, &upper, &empty); - if (lower.rngtypid != upper.rngtypid) - elog(ERROR, "range types do not match"); - range_gettypinfo(fcinfo, lower.rngtypid, &rngtypinfo); if (empty) flags |= RANGE_EMPTY; - flags |= (lower.inclusive) ? RANGE_LB_INC : 0; - flags |= (lower.infinite) ? RANGE_LB_INF : 0; - flags |= (upper.inclusive) ? RANGE_UB_INC : 0; - flags |= (upper.infinite) ? RANGE_UB_INF : 0; + flags |= lower.inclusive ? RANGE_LB_INC : 0; + flags |= lower.infinite ? RANGE_LB_INF : 0; + flags |= upper.inclusive ? RANGE_UB_INC : 0; + flags |= upper.infinite ? RANGE_UB_INF : 0; /* output */ getTypeOutputInfo(rngtypinfo.subtype, &subOutput, &isVarlena); @@ -280,10 +274,10 @@ range_send(PG_FUNCTION_ARGS) if (empty) flags |= RANGE_EMPTY; - flags |= (lower.inclusive) ? RANGE_LB_INC : 0; - flags |= (lower.infinite) ? RANGE_LB_INF : 0; - flags |= (upper.inclusive) ? RANGE_UB_INC : 0; - flags |= (upper.infinite) ? RANGE_UB_INF : 0; + flags |= lower.inclusive ? RANGE_LB_INC : 0; + flags |= lower.infinite ? RANGE_LB_INF : 0; + flags |= upper.inclusive ? RANGE_UB_INC : 0; + flags |= upper.infinite ? RANGE_UB_INF : 0; range_gettypinfo(fcinfo, lower.rngtypid, &rngtypinfo); @@ -395,13 +389,13 @@ range_constructor2(PG_FUNCTION_ARGS) lower.rngtypid = rngtypid; lower.val = PG_ARGISNULL(0) ? (Datum) 0 : arg1; - lower.inclusive = flags & RANGE_LB_INC; + lower.inclusive = (flags & RANGE_LB_INC) != 0; lower.infinite = PG_ARGISNULL(0); lower.lower = true; upper.rngtypid = rngtypid; upper.val = PG_ARGISNULL(1) ? (Datum) 0 : arg2; - upper.inclusive = flags & RANGE_UB_INC; + upper.inclusive = (flags & RANGE_UB_INC) != 0; upper.infinite = PG_ARGISNULL(1); upper.lower = false; @@ -430,13 +424,13 @@ range_constructor3(PG_FUNCTION_ARGS) lower.rngtypid = rngtypid; lower.val = PG_ARGISNULL(0) ? (Datum) 0 : arg1; - lower.inclusive = flags & RANGE_LB_INC; + lower.inclusive = (flags & RANGE_LB_INC) != 0; lower.infinite = PG_ARGISNULL(0); lower.lower = true; upper.rngtypid = rngtypid; upper.val = PG_ARGISNULL(1) ? (Datum) 0 : arg2; - upper.inclusive = flags & RANGE_UB_INC; + upper.inclusive = (flags & RANGE_UB_INC) != 0; upper.infinite = PG_ARGISNULL(1); upper.lower = false; @@ -564,9 +558,7 @@ range_eq(PG_FUNCTION_ARGS) range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); - if (lower1.rngtypid != upper1.rngtypid || - lower1.rngtypid != lower2.rngtypid || - lower1.rngtypid != upper2.rngtypid) + if (lower1.rngtypid != lower2.rngtypid) elog(ERROR, "range types do not match"); if (empty1 && empty2) @@ -686,9 +678,7 @@ range_before(PG_FUNCTION_ARGS) range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); - if (lower1.rngtypid != upper1.rngtypid || - lower1.rngtypid != lower2.rngtypid || - lower1.rngtypid != upper2.rngtypid) + if (lower1.rngtypid != lower2.rngtypid) elog(ERROR, "range types do not match"); /* An empty range is neither before nor after any other range */ @@ -713,9 +703,7 @@ range_after(PG_FUNCTION_ARGS) range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); - if (lower1.rngtypid != upper1.rngtypid || - lower1.rngtypid != lower2.rngtypid || - lower1.rngtypid != upper2.rngtypid) + if (lower1.rngtypid != lower2.rngtypid) elog(ERROR, "range types do not match"); /* An empty range is neither before nor after any other range */ @@ -741,9 +729,7 @@ range_adjacent(PG_FUNCTION_ARGS) range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); - if (lower1.rngtypid != upper1.rngtypid || - lower1.rngtypid != lower2.rngtypid || - lower1.rngtypid != upper2.rngtypid) + if (lower1.rngtypid != lower2.rngtypid) elog(ERROR, "range types do not match"); /* An empty range is not adjacent to any other range */ @@ -795,9 +781,7 @@ range_overlaps(PG_FUNCTION_ARGS) range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); - if (lower1.rngtypid != upper1.rngtypid || - lower1.rngtypid != lower2.rngtypid || - lower1.rngtypid != upper2.rngtypid) + if (lower1.rngtypid != lower2.rngtypid) elog(ERROR, "range types do not match"); /* An empty range does not overlap any other range */ @@ -830,9 +814,7 @@ range_overleft(PG_FUNCTION_ARGS) range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); - if (lower1.rngtypid != upper1.rngtypid || - lower1.rngtypid != lower2.rngtypid || - lower1.rngtypid != upper2.rngtypid) + if (lower1.rngtypid != lower2.rngtypid) elog(ERROR, "range types do not match"); /* An empty range is neither before nor after any other range */ @@ -860,9 +842,7 @@ range_overright(PG_FUNCTION_ARGS) range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); - if (lower1.rngtypid != upper1.rngtypid || - lower1.rngtypid != lower2.rngtypid || - lower1.rngtypid != upper2.rngtypid) + if (lower1.rngtypid != lower2.rngtypid) elog(ERROR, "range types do not match"); /* An empty range is neither before nor after any other range */ @@ -896,9 +876,7 @@ range_minus(PG_FUNCTION_ARGS) range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); - if (lower1.rngtypid != upper1.rngtypid || - lower1.rngtypid != lower2.rngtypid || - lower1.rngtypid != upper2.rngtypid) + if (lower1.rngtypid != lower2.rngtypid) elog(ERROR, "range types do not match"); /* if either is empty, r1 is the correct answer */ @@ -956,6 +934,9 @@ range_union(PG_FUNCTION_ARGS) range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + if (lower1.rngtypid != lower2.rngtypid) + elog(ERROR, "range types do not match"); + /* if either is empty, the other is the correct answer */ if (empty1) PG_RETURN_RANGE(r2); @@ -998,6 +979,9 @@ range_intersect(PG_FUNCTION_ARGS) range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); + if (lower1.rngtypid != lower2.rngtypid) + elog(ERROR, "range types do not match"); + if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo))) PG_RETURN_RANGE(make_empty_range(fcinfo, lower1.rngtypid)); @@ -1032,9 +1016,7 @@ range_cmp(PG_FUNCTION_ARGS) range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); - if (lower1.rngtypid != upper1.rngtypid || - lower1.rngtypid != lower2.rngtypid || - lower1.rngtypid != upper2.rngtypid) + if (lower1.rngtypid != lower2.rngtypid) elog(ERROR, "range types do not match"); /* For b-tree use, empty ranges sort before all else */ @@ -1103,16 +1085,13 @@ hash_range(PG_FUNCTION_ARGS) range_deserialize(fcinfo, r, &lower, &upper, &empty); - if (lower.rngtypid != upper.rngtypid) - elog(ERROR, "range types do not match"); - if (empty) flags |= RANGE_EMPTY; - flags |= (lower.inclusive) ? RANGE_LB_INC : 0; - flags |= (lower.infinite) ? RANGE_LB_INF : 0; - flags |= (upper.inclusive) ? RANGE_UB_INC : 0; - flags |= (upper.infinite) ? RANGE_UB_INF : 0; + flags |= lower.inclusive ? RANGE_LB_INC : 0; + flags |= lower.infinite ? RANGE_LB_INF : 0; + flags |= upper.inclusive ? RANGE_UB_INC : 0; + flags |= upper.infinite ? RANGE_UB_INF : 0; range_gettypinfo(fcinfo, lower.rngtypid, &rngtypinfo); subtype = rngtypinfo.subtype; @@ -1370,8 +1349,10 @@ tstzrange_subdiff(PG_FUNCTION_ARGS) */ /* - * This serializes a range, but does not canonicalize it. This should - * only be called by a canonicalization function. + * range_serialize: construct a range value from bounds and empty-flag + * + * This does not force canonicalization of the range value. In most cases, + * external callers should only be canonicalization functions. */ Datum range_serialize(FunctionCallInfo fcinfo, RangeBound *lower, RangeBound *upper, @@ -1404,28 +1385,45 @@ range_serialize(FunctionCallInfo fcinfo, RangeBound *lower, RangeBound *upper, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("range lower bound must be less than or equal to range upper bound"))); - flags |= (lower->inclusive) ? RANGE_LB_INC : 0; - flags |= (lower->infinite) ? RANGE_LB_INF : 0; - flags |= (upper->inclusive) ? RANGE_UB_INC : 0; - flags |= (upper->infinite) ? RANGE_UB_INF : 0; + flags |= lower->inclusive ? RANGE_LB_INC : 0; + flags |= lower->infinite ? RANGE_LB_INF : 0; + flags |= upper->inclusive ? RANGE_UB_INC : 0; + flags |= upper->infinite ? RANGE_UB_INF : 0; msize = VARHDRSZ; msize += sizeof(Oid); if (RANGE_HAS_LBOUND(flags)) { + /* + * Make sure item to be inserted is not toasted. It is essential that + * we not insert an out-of-line toast value pointer into a range + * object, for the same reasons that arrays and records can't contain + * them. It would work to store a compressed-in-line value, but we + * prefer to decompress and then let compression be applied to the + * whole range object if necessary. But, unlike arrays, we do allow + * short-header varlena objects to stay as-is. + */ + if (typlen == -1) + lower->val = PointerGetDatum(PG_DETOAST_DATUM_PACKED(lower->val)); + msize = datum_compute_size(msize, lower->val, typbyval, typalign, typlen, typstorage); } if (RANGE_HAS_UBOUND(flags)) { + /* Make sure item to be inserted is not toasted */ + if (typlen == -1) + upper->val = PointerGetDatum(PG_DETOAST_DATUM_PACKED(upper->val)); + msize = datum_compute_size(msize, upper->val, typbyval, typalign, typlen, typstorage); } msize += sizeof(char); + /* Note: zero-fill is required here, just as in heap tuples */ ptr = palloc0(msize); range = (Datum) ptr; @@ -1455,6 +1453,15 @@ range_serialize(FunctionCallInfo fcinfo, RangeBound *lower, RangeBound *upper, PG_RETURN_RANGE(range); } +/* + * range_deserialize: deconstruct a range value + * + * NB: the given range object must be fully detoasted; it cannot have a + * short varlena header. + * + * Note that if the element type is pass-by-reference, the datums in the + * RangeBound structs will be pointers into the given range object. + */ void range_deserialize(FunctionCallInfo fcinfo, RangeType *range, RangeBound *lower, RangeBound *upper, bool *empty) @@ -1467,64 +1474,55 @@ range_deserialize(FunctionCallInfo fcinfo, RangeType *range, RangeBound *lower, Oid rngtypid; Datum lbound; Datum ubound; - Pointer flags_ptr; RangeTypeInfo rngtypinfo; - memset(lower, 0, sizeof(RangeBound)); - memset(upper, 0, sizeof(RangeBound)); + /* fetch the flag byte from datum's last byte */ + flags = *((char *) (ptr + VARSIZE(range) - VARHDRSZ - 1)); - /* peek at last byte to read the flag byte */ - flags_ptr = ptr + VARSIZE(range) - VARHDRSZ - 1; - memcpy(&flags, flags_ptr, sizeof(char)); - - memcpy(&rngtypid, ptr, sizeof(Oid)); + /* fetch and advance over the range type OID */ + rngtypid = *((Oid *) ptr); ptr += sizeof(Oid); - if (rngtypid == ANYRANGEOID) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot output a value of type anyrange"))); - + /* fetch information about range type */ range_gettypinfo(fcinfo, rngtypid, &rngtypinfo); - typalign = rngtypinfo.subtypalign; typlen = rngtypinfo.subtyplen; typbyval = rngtypinfo.subtypbyval; + /* fetch lower bound, if any */ if (RANGE_HAS_LBOUND(flags)) { - ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr); + /* att_align_pointer cannot be necessary here */ lbound = fetch_att(ptr, typbyval, typlen); - ptr = (Pointer) att_addlength_datum(ptr, typlen, PointerGetDatum(ptr)); - if (typlen == -1) - lbound = PointerGetDatum(PG_DETOAST_DATUM(lbound)); + ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr); } else lbound = (Datum) 0; + /* fetch upper bound, if any */ if (RANGE_HAS_UBOUND(flags)) { ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr); ubound = fetch_att(ptr, typbyval, typlen); - ptr = (Pointer) att_addlength_datum(ptr, typlen, PointerGetDatum(ptr)); - if (typlen == -1) - ubound = PointerGetDatum(PG_DETOAST_DATUM(ubound)); + /* no need for att_addlength_pointer */ } else ubound = (Datum) 0; + /* emit results */ + *empty = flags & RANGE_EMPTY; lower->rngtypid = rngtypid; lower->val = lbound; - lower->inclusive = flags & RANGE_LB_INC; - lower->infinite = flags & RANGE_LB_INF; + lower->inclusive = (flags & RANGE_LB_INC) != 0; + lower->infinite = (flags & RANGE_LB_INF) != 0; lower->lower = true; upper->rngtypid = rngtypid; upper->val = ubound; - upper->inclusive = flags & RANGE_UB_INC; - upper->infinite = flags & RANGE_UB_INF; + upper->inclusive = (flags & RANGE_UB_INC) != 0; + upper->infinite = (flags & RANGE_UB_INF) != 0; upper->lower = false; } @@ -1541,9 +1539,6 @@ make_range(FunctionCallInfo fcinfo, RangeBound *lower, RangeBound *upper, range_gettypinfo(fcinfo, lower->rngtypid, &rngtypinfo); - if (lower->rngtypid != upper->rngtypid) - elog(ERROR, "range types do not match"); - range = range_serialize(fcinfo, lower, upper, empty); if (rngtypinfo.canonicalFn.fn_addr != NULL) @@ -2033,9 +2028,7 @@ range_contains_internal(FunctionCallInfo fcinfo, RangeType *r1, RangeType *r2) range_deserialize(fcinfo, r1, &lower1, &upper1, &empty1); range_deserialize(fcinfo, r2, &lower2, &upper2, &empty2); - if (lower1.rngtypid != upper1.rngtypid || - lower1.rngtypid != lower2.rngtypid || - lower1.rngtypid != upper2.rngtypid) + if (lower1.rngtypid != lower2.rngtypid) elog(ERROR, "range types do not match"); if (empty2) @@ -2051,11 +2044,23 @@ range_contains_internal(FunctionCallInfo fcinfo, RangeType *r1, RangeType *r2) return true; } + /* - * datum_compute_size() and datum_write() are modeled after - * heap_compute_data_size() and heap_fill_tuple(). + * datum_compute_size() and datum_write() are used to insert the bound + * values into a range object. They are modeled after heaptuple.c's + * heap_compute_data_size() and heap_fill_tuple(), but we need not handle + * null values here. TYPE_IS_PACKABLE must test the same conditions as + * heaptuple.c's ATT_IS_PACKABLE macro. */ +/* Does datatype allow packing into the 1-byte-header varlena format? */ +#define TYPE_IS_PACKABLE(typlen, typstorage) \ + ((typlen) == -1 && (typstorage) != 'p') + +/* + * Increment data_length by the space needed by the datum, including any + * preceding alignment padding. + */ static Size datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign, int16 typlen, char typstorage) @@ -2079,9 +2084,8 @@ datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign, } /* - * Modified version of the code in heap_fill_tuple(). Writes the datum to ptr - * using the correct alignment, and also uses short varlena header if - * applicable. + * Write the given datum beginning at ptr (after advancing to correct + * alignment, if needed). Return the pointer incremented by space used. */ static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign, @@ -2103,9 +2107,12 @@ datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign, if (VARATT_IS_EXTERNAL(val)) { - /* no alignment, since it's short by definition */ - data_length = VARSIZE_EXTERNAL(val); - memcpy(ptr, val, data_length); + /* + * Throw error, because we must never put a toast pointer inside a + * range object. Caller should have detoasted it. + */ + elog(ERROR, "cannot store a toast pointer inside a range"); + data_length = 0; /* keep compiler quiet */ } else if (VARATT_IS_SHORT(val)) { diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index ece718d939..6e2a060a12 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201111061 +#define CATALOG_VERSION_NO 201111141 #endif diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index b24fbc97f4..a062d1e248 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -594,24 +594,24 @@ DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 a /* range types */ DATA(insert OID = 3904 ( int4range PGNSP PGUID -1 f r R f t \054 0 0 3905 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); -DESCR("range of int4s"); +DESCR("range of integers"); #define INT4RANGEOID 3904 DATA(insert OID = 3905 ( _int4range PGNSP PGUID -1 f b A f t \054 0 3904 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); DATA(insert OID = 3906 ( numrange PGNSP PGUID -1 f r R f t \054 0 0 3907 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); DESCR("range of numerics"); DATA(insert OID = 3907 ( _numrange PGNSP PGUID -1 f b A f t \054 0 3906 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); -DATA(insert OID = 3908 ( tsrange PGNSP PGUID -1 f r R f t \054 0 0 3909 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); -DESCR("range of timestamps"); -DATA(insert OID = 3909 ( _tsrange PGNSP PGUID -1 f b A f t \054 0 3908 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); -DATA(insert OID = 3910 ( tstzrange PGNSP PGUID -1 f r R f t \054 0 0 3911 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3908 ( tsrange PGNSP PGUID -1 f r R f t \054 0 0 3909 range_in range_out range_recv range_send - - - d x f 0 -1 0 0 _null_ _null_ )); +DESCR("range of timestamps without time zone"); +DATA(insert OID = 3909 ( _tsrange PGNSP PGUID -1 f b A f t \054 0 3908 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3910 ( tstzrange PGNSP PGUID -1 f r R f t \054 0 0 3911 range_in range_out range_recv range_send - - - d x f 0 -1 0 0 _null_ _null_ )); DESCR("range of timestamps with time zone"); -DATA(insert OID = 3911 ( _tstzrange PGNSP PGUID -1 f b A f t \054 0 3910 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3911 ( _tstzrange PGNSP PGUID -1 f b A f t \054 0 3910 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 0 _null_ _null_ )); DATA(insert OID = 3912 ( daterange PGNSP PGUID -1 f r R f t \054 0 0 3913 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); DESCR("range of dates"); DATA(insert OID = 3913 ( _daterange PGNSP PGUID -1 f b A f t \054 0 3912 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); -DATA(insert OID = 3926 ( int8range PGNSP PGUID -1 f r R f t \054 0 0 3927 range_in range_out range_recv range_send - - - i x f 0 -1 0 0 _null_ _null_ )); -DESCR("range of int8s"); -DATA(insert OID = 3927 ( _int8range PGNSP PGUID -1 f b A f t \054 0 3926 0 array_in array_out array_recv array_send - - - i x f 0 -1 0 0 _null_ _null_ )); +DATA(insert OID = 3926 ( int8range PGNSP PGUID -1 f r R f t \054 0 0 3927 range_in range_out range_recv range_send - - - d x f 0 -1 0 0 _null_ _null_ )); +DESCR("range of bigints"); +DATA(insert OID = 3927 ( _int8range PGNSP PGUID -1 f b A f t \054 0 3926 0 array_in array_out array_recv array_send - - - d x f 0 -1 0 0 _null_ _null_ )); /* * pseudo-types diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out index 9b87948617..b2258b9045 100644 --- a/src/test/regress/expected/rangetypes.out +++ b/src/test/regress/expected/rangetypes.out @@ -822,7 +822,6 @@ select * from float8range_test; (1 row) drop table float8range_test; -drop type float8range; -- -- Test range types over domains -- @@ -909,6 +908,15 @@ select ARRAY[numrange(1.1), numrange(12.3,155.5)]; {"[1.1,1.1]","[12.3,155.5)"} (1 row) +create table i8r_array (f1 int, f2 int8range[]); +insert into i8r_array values (42, array[int8range(1,10), int8range(2,20)]); +select * from i8r_array; + f1 | f2 +----+--------------------- + 42 | {"[1,10)","[2,20)"} +(1 row) + +drop table i8r_array; -- -- Ranges of arrays -- diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out index 7ca7a95ec6..a159ac718a 100644 --- a/src/test/regress/expected/type_sanity.out +++ b/src/test/regress/expected/type_sanity.out @@ -81,6 +81,28 @@ WHERE p1.typarray <> 0 AND -----+----------+-----------+---------+-------- (0 rows) +-- Look for range types that do not have a pg_range entry +SELECT p1.oid, p1.typname +FROM pg_type as p1 +WHERE p1.typtype = 'r' AND + NOT EXISTS(SELECT 1 FROM pg_range r WHERE rngtypid = p1.oid); + oid | typname +-----+--------- +(0 rows) + +-- Look for range types whose typalign isn't sufficient +SELECT p1.oid, p1.typname, p1.typalign, p2.typname, p2.typalign +FROM pg_type as p1 + LEFT JOIN pg_range as r ON rngtypid = p1.oid + LEFT JOIN pg_type as p2 ON rngsubtype = p2.oid +WHERE p1.typtype = 'r' AND + (p1.typalign != (CASE WHEN p2.typalign = 'd' THEN 'd'::"char" + ELSE 'i'::"char" END) + OR p2.oid IS NULL); + oid | typname | typalign | typname | typalign +-----+---------+----------+---------+---------- +(0 rows) + -- Text conversion routines must be provided. SELECT p1.oid, p1.typname FROM pg_type as p1 @@ -263,6 +285,16 @@ WHERE p1.typarray = p2.oid AND NOT (p1.typdelim = p2.typdelim); -----+---------+-----+--------- (0 rows) +-- Look for array types whose typalign isn't sufficient +SELECT p1.oid, p1.typname, p1.typalign, p2.typname, p2.typalign +FROM pg_type AS p1, pg_type AS p2 +WHERE p1.typarray = p2.oid AND + p2.typalign != (CASE WHEN p1.typalign = 'd' THEN 'd'::"char" + ELSE 'i'::"char" END); + oid | typname | typalign | typname | typalign +-----+---------+----------+---------+---------- +(0 rows) + -- Check for bogus typanalyze routines SELECT p1.oid, p1.typname, p2.oid, p2.proname FROM pg_type AS p1, pg_proc AS p2 diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql index 573e85ebb2..4b455f1d35 100644 --- a/src/test/regress/sql/rangetypes.sql +++ b/src/test/regress/sql/rangetypes.sql @@ -260,7 +260,6 @@ create table float8range_test(f8r float8range, i int); insert into float8range_test values(float8range(-100.00007, '1.111113e9')); select * from float8range_test; drop table float8range_test; -drop type float8range; -- -- Test range types over domains @@ -327,6 +326,11 @@ select range_add_bounds(numrange(1.0001, 123.123)); select ARRAY[numrange(1.1), numrange(12.3,155.5)]; +create table i8r_array (f1 int, f2 int8range[]); +insert into i8r_array values (42, array[int8range(1,10), int8range(2,20)]); +select * from i8r_array; +drop table i8r_array; + -- -- Ranges of arrays -- diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql index 1638861bc1..2ed03f39bc 100644 --- a/src/test/regress/sql/type_sanity.sql +++ b/src/test/regress/sql/type_sanity.sql @@ -67,6 +67,22 @@ FROM pg_type p1 LEFT JOIN pg_type p2 ON (p1.typarray = p2.oid) WHERE p1.typarray <> 0 AND (p2.oid IS NULL OR p2.typelem <> p1.oid OR p2.typlen <> -1); +-- Look for range types that do not have a pg_range entry +SELECT p1.oid, p1.typname +FROM pg_type as p1 +WHERE p1.typtype = 'r' AND + NOT EXISTS(SELECT 1 FROM pg_range r WHERE rngtypid = p1.oid); + +-- Look for range types whose typalign isn't sufficient +SELECT p1.oid, p1.typname, p1.typalign, p2.typname, p2.typalign +FROM pg_type as p1 + LEFT JOIN pg_range as r ON rngtypid = p1.oid + LEFT JOIN pg_type as p2 ON rngsubtype = p2.oid +WHERE p1.typtype = 'r' AND + (p1.typalign != (CASE WHEN p2.typalign = 'd' THEN 'd'::"char" + ELSE 'i'::"char" END) + OR p2.oid IS NULL); + -- Text conversion routines must be provided. SELECT p1.oid, p1.typname @@ -202,6 +218,14 @@ SELECT p1.oid, p1.typname, p2.oid, p2.typname FROM pg_type AS p1, pg_type AS p2 WHERE p1.typarray = p2.oid AND NOT (p1.typdelim = p2.typdelim); +-- Look for array types whose typalign isn't sufficient + +SELECT p1.oid, p1.typname, p1.typalign, p2.typname, p2.typalign +FROM pg_type AS p1, pg_type AS p2 +WHERE p1.typarray = p2.oid AND + p2.typalign != (CASE WHEN p1.typalign = 'd' THEN 'd'::"char" + ELSE 'i'::"char" END); + -- Check for bogus typanalyze routines SELECT p1.oid, p1.typname, p2.oid, p2.proname -- GitLab