diff options
Diffstat (limited to 'src/backend/utils')
| -rw-r--r-- | src/backend/utils/adt/enum.c | 259 | ||||
| -rw-r--r-- | src/backend/utils/cache/typcache.c | 322 |
2 files changed, 498 insertions, 83 deletions
diff --git a/src/backend/utils/adt/enum.c b/src/backend/utils/adt/enum.c index e5747a46bc..dd168dd973 100644 --- a/src/backend/utils/adt/enum.c +++ b/src/backend/utils/adt/enum.c @@ -13,18 +13,22 @@ */ #include "postgres.h" +#include "access/genam.h" +#include "access/heapam.h" +#include "catalog/indexing.h" #include "catalog/pg_enum.h" #include "fmgr.h" +#include "libpq/pqformat.h" #include "utils/array.h" #include "utils/builtins.h" -#include "utils/lsyscache.h" +#include "utils/fmgroids.h" +#include "utils/snapmgr.h" #include "utils/syscache.h" -#include "libpq/pqformat.h" -#include "miscadmin.h" +#include "utils/typcache.h" +static Oid enum_endpoint(Oid enumtypoid, ScanDirection direction); static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper); -static int enum_elem_cmp(const void *left, const void *right); /* Basic I/O support */ @@ -155,13 +159,63 @@ enum_send(PG_FUNCTION_ARGS) /* Comparison functions and related */ +/* + * enum_cmp_internal is the common engine for all the visible comparison + * functions, except for enum_eq and enum_ne which can just check for OID + * equality directly. + */ +static int +enum_cmp_internal(Oid arg1, Oid arg2, FunctionCallInfo fcinfo) +{ + TypeCacheEntry *tcache; + + /* Equal OIDs are equal no matter what */ + if (arg1 == arg2) + return 0; + + /* Fast path: even-numbered Oids are known to compare correctly */ + if ((arg1 & 1) == 0 && (arg2 & 1) == 0) + { + if (arg1 < arg2) + return -1; + else + return 1; + } + + /* Locate the typcache entry for the enum type */ + tcache = (TypeCacheEntry *) fcinfo->flinfo->fn_extra; + if (tcache == NULL) + { + HeapTuple enum_tup; + Form_pg_enum en; + Oid typeoid; + + /* Get the OID of the enum type containing arg1 */ + enum_tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(arg1)); + if (!HeapTupleIsValid(enum_tup)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid internal value for enum: %u", + arg1))); + en = (Form_pg_enum) GETSTRUCT(enum_tup); + typeoid = en->enumtypid; + ReleaseSysCache(enum_tup); + /* Now locate and remember the typcache entry */ + tcache = lookup_type_cache(typeoid, 0); + fcinfo->flinfo->fn_extra = (void *) tcache; + } + + /* The remaining comparison logic is in typcache.c */ + return compare_values_of_enum(tcache, arg1, arg2); +} + Datum enum_lt(PG_FUNCTION_ARGS) { Oid a = PG_GETARG_OID(0); Oid b = PG_GETARG_OID(1); - PG_RETURN_BOOL(a < b); + PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) < 0); } Datum @@ -170,7 +224,7 @@ enum_le(PG_FUNCTION_ARGS) Oid a = PG_GETARG_OID(0); Oid b = PG_GETARG_OID(1); - PG_RETURN_BOOL(a <= b); + PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) <= 0); } Datum @@ -197,7 +251,7 @@ enum_ge(PG_FUNCTION_ARGS) Oid a = PG_GETARG_OID(0); Oid b = PG_GETARG_OID(1); - PG_RETURN_BOOL(a >= b); + PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) >= 0); } Datum @@ -206,7 +260,7 @@ enum_gt(PG_FUNCTION_ARGS) Oid a = PG_GETARG_OID(0); Oid b = PG_GETARG_OID(1); - PG_RETURN_BOOL(a > b); + PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) > 0); } Datum @@ -215,7 +269,7 @@ enum_smaller(PG_FUNCTION_ARGS) Oid a = PG_GETARG_OID(0); Oid b = PG_GETARG_OID(1); - PG_RETURN_OID(a <= b ? a : b); + PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) < 0 ? a : b); } Datum @@ -224,7 +278,7 @@ enum_larger(PG_FUNCTION_ARGS) Oid a = PG_GETARG_OID(0); Oid b = PG_GETARG_OID(1); - PG_RETURN_OID(a >= b ? a : b); + PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) > 0 ? a : b); } Datum @@ -233,24 +287,63 @@ enum_cmp(PG_FUNCTION_ARGS) Oid a = PG_GETARG_OID(0); Oid b = PG_GETARG_OID(1); - if (a > b) - PG_RETURN_INT32(1); - else if (a == b) + if (a == b) PG_RETURN_INT32(0); + else if (enum_cmp_internal(a, b, fcinfo) > 0) + PG_RETURN_INT32(1); else PG_RETURN_INT32(-1); } /* Enum programming support functions */ +/* + * enum_endpoint: common code for enum_first/enum_last + */ +static Oid +enum_endpoint(Oid enumtypoid, ScanDirection direction) +{ + Relation enum_rel; + Relation enum_idx; + SysScanDesc enum_scan; + HeapTuple enum_tuple; + ScanKeyData skey; + Oid minmax; + + /* + * Find the first/last enum member using pg_enum_typid_sortorder_index. + * Note we must not use the syscache, and must use an MVCC snapshot here. + * See comments for RenumberEnumType in catalog/pg_enum.c for more info. + */ + ScanKeyInit(&skey, + Anum_pg_enum_enumtypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(enumtypoid)); + + enum_rel = heap_open(EnumRelationId, AccessShareLock); + enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock); + enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, + GetTransactionSnapshot(), + 1, &skey); + + enum_tuple = systable_getnext_ordered(enum_scan, direction); + if (HeapTupleIsValid(enum_tuple)) + minmax = HeapTupleGetOid(enum_tuple); + else + minmax = InvalidOid; + + systable_endscan_ordered(enum_scan); + index_close(enum_idx, AccessShareLock); + heap_close(enum_rel, AccessShareLock); + + return minmax; +} + Datum enum_first(PG_FUNCTION_ARGS) { Oid enumtypoid; - Oid min = InvalidOid; - CatCList *list; - int num, - i; + Oid min; /* * We rely on being able to get the specific enum type from the calling @@ -263,21 +356,14 @@ enum_first(PG_FUNCTION_ARGS) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("could not determine actual enum type"))); - list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid)); - num = list->n_members; - for (i = 0; i < num; i++) - { - Oid valoid = HeapTupleHeaderGetOid(list->members[i]->tuple.t_data); - - if (!OidIsValid(min) || valoid < min) - min = valoid; - } - - ReleaseCatCacheList(list); + /* Get the OID using the index */ + min = enum_endpoint(enumtypoid, ForwardScanDirection); - if (!OidIsValid(min)) /* should not happen */ - elog(ERROR, "no values found for enum %s", - format_type_be(enumtypoid)); + if (!OidIsValid(min)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("enum %s contains no values", + format_type_be(enumtypoid)))); PG_RETURN_OID(min); } @@ -286,10 +372,7 @@ Datum enum_last(PG_FUNCTION_ARGS) { Oid enumtypoid; - Oid max = InvalidOid; - CatCList *list; - int num, - i; + Oid max; /* * We rely on being able to get the specific enum type from the calling @@ -302,21 +385,14 @@ enum_last(PG_FUNCTION_ARGS) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("could not determine actual enum type"))); - list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid)); - num = list->n_members; - for (i = 0; i < num; i++) - { - Oid valoid = HeapTupleHeaderGetOid(list->members[i]->tuple.t_data); - - if (!OidIsValid(max) || valoid > max) - max = valoid; - } - - ReleaseCatCacheList(list); + /* Get the OID using the index */ + max = enum_endpoint(enumtypoid, BackwardScanDirection); - if (!OidIsValid(max)) /* should not happen */ - elog(ERROR, "no values found for enum %s", - format_type_be(enumtypoid)); + if (!OidIsValid(max)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("enum %s contains no values", + format_type_be(enumtypoid)))); PG_RETURN_OID(max); } @@ -377,51 +453,68 @@ static ArrayType * enum_range_internal(Oid enumtypoid, Oid lower, Oid upper) { ArrayType *result; - CatCList *list; - int total, - i, - j; + Relation enum_rel; + Relation enum_idx; + SysScanDesc enum_scan; + HeapTuple enum_tuple; + ScanKeyData skey; Datum *elems; + int max, + cnt; + bool left_found; - list = SearchSysCacheList1(ENUMTYPOIDNAME, ObjectIdGetDatum(enumtypoid)); - total = list->n_members; + /* + * Scan the enum members in order using pg_enum_typid_sortorder_index. + * Note we must not use the syscache, and must use an MVCC snapshot here. + * See comments for RenumberEnumType in catalog/pg_enum.c for more info. + */ + ScanKeyInit(&skey, + Anum_pg_enum_enumtypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(enumtypoid)); + + enum_rel = heap_open(EnumRelationId, AccessShareLock); + enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock); + enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, + GetTransactionSnapshot(), + 1, &skey); + + max = 64; + elems = (Datum *) palloc(max * sizeof(Datum)); + cnt = 0; + left_found = !OidIsValid(lower); + + while (HeapTupleIsValid(enum_tuple = systable_getnext_ordered(enum_scan, ForwardScanDirection))) + { + Oid enum_oid = HeapTupleGetOid(enum_tuple); - elems = (Datum *) palloc(total * sizeof(Datum)); + if (!left_found && lower == enum_oid) + left_found = true; - j = 0; - for (i = 0; i < total; i++) - { - Oid val = HeapTupleGetOid(&(list->members[i]->tuple)); + if (left_found) + { + if (cnt >= max) + { + max *= 2; + elems = (Datum *) repalloc(elems, max * sizeof(Datum)); + } - if ((!OidIsValid(lower) || lower <= val) && - (!OidIsValid(upper) || val <= upper)) - elems[j++] = ObjectIdGetDatum(val); - } + elems[cnt++] = ObjectIdGetDatum(enum_oid); + } - /* shouldn't need the cache anymore */ - ReleaseCatCacheList(list); + if (OidIsValid(upper) && upper == enum_oid) + break; + } - /* sort results into OID order */ - qsort(elems, j, sizeof(Datum), enum_elem_cmp); + systable_endscan_ordered(enum_scan); + index_close(enum_idx, AccessShareLock); + heap_close(enum_rel, AccessShareLock); + /* and build the result array */ /* note this hardwires some details about the representation of Oid */ - result = construct_array(elems, j, enumtypoid, sizeof(Oid), true, 'i'); + result = construct_array(elems, cnt, enumtypoid, sizeof(Oid), true, 'i'); pfree(elems); return result; } - -/* qsort comparison function for Datums that are OIDs */ -static int -enum_elem_cmp(const void *left, const void *right) -{ - Oid l = DatumGetObjectId(*((const Datum *) left)); - Oid r = DatumGetObjectId(*((const Datum *) right)); - - if (l < r) - return -1; - if (l > r) - return 1; - return 0; -} diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index 2009614489..bc4abc451a 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -42,22 +42,44 @@ */ #include "postgres.h" +#include <limits.h> + #include "access/hash.h" #include "access/heapam.h" #include "access/nbtree.h" +#include "catalog/indexing.h" +#include "catalog/pg_enum.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "utils/builtins.h" +#include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/rel.h" +#include "utils/snapmgr.h" #include "utils/syscache.h" +#include "utils/tqual.h" #include "utils/typcache.h" /* The main type cache hashtable searched by lookup_type_cache */ static HTAB *TypeCacheHash = NULL; +/* Private information to support comparisons of enum values */ +typedef struct +{ + Oid enum_oid; /* OID of one enum value */ + float4 sort_order; /* its sort position */ +} EnumItem; + +typedef struct TypeCacheEnumData +{ + Oid bitmap_base; /* OID corresponding to bit 0 of bitmapset */ + Bitmapset *sorted_values; /* Set of OIDs known to be in order */ + int num_values; /* total number of values in enum */ + EnumItem enum_values[1]; /* VARIABLE LENGTH ARRAY */ +} TypeCacheEnumData; + /* * We use a separate table for storing the definitions of non-anonymous * record types. Once defined, a record type will be remembered for the @@ -88,6 +110,9 @@ static int32 RecordCacheArrayLen = 0; /* allocated length of array */ static int32 NextRecordTypmod = 0; /* number of entries used */ static void TypeCacheRelCallback(Datum arg, Oid relid); +static void load_enum_cache_data(TypeCacheEntry *tcache); +static EnumItem *find_enumitem(TypeCacheEnumData *enumdata, Oid arg); +static int enum_oid_cmp(const void *left, const void *right); /* @@ -551,3 +576,300 @@ TypeCacheRelCallback(Datum arg, Oid relid) } } } + + +/* + * Check if given OID is part of the subset that's sortable by comparisons + */ +static inline bool +enum_known_sorted(TypeCacheEnumData *enumdata, Oid arg) +{ + Oid offset; + + if (arg < enumdata->bitmap_base) + return false; + offset = arg - enumdata->bitmap_base; + if (offset > (Oid) INT_MAX) + return false; + return bms_is_member((int) offset, enumdata->sorted_values); +} + + +/* + * compare_values_of_enum + * Compare two members of an enum type. + * Return <0, 0, or >0 according as arg1 <, =, or > arg2. + * + * Note: currently, the enumData cache is refreshed only if we are asked + * to compare an enum value that is not already in the cache. This is okay + * because there is no support for re-ordering existing values, so comparisons + * of previously cached values will return the right answer even if other + * values have been added since we last loaded the cache. + * + * Note: the enum logic has a special-case rule about even-numbered versus + * odd-numbered OIDs, but we take no account of that rule here; this + * routine shouldn't even get called when that rule applies. + */ +int +compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2) +{ + TypeCacheEnumData *enumdata; + EnumItem *item1; + EnumItem *item2; + + /* + * Equal OIDs are certainly equal --- this case was probably handled + * by our caller, but we may as well check. + */ + if (arg1 == arg2) + return 0; + + /* Load up the cache if first time through */ + if (tcache->enumData == NULL) + load_enum_cache_data(tcache); + enumdata = tcache->enumData; + + /* + * If both OIDs are known-sorted, we can just compare them directly. + */ + if (enum_known_sorted(enumdata, arg1) && + enum_known_sorted(enumdata, arg2)) + { + if (arg1 < arg2) + return -1; + else + return 1; + } + + /* + * Slow path: we have to identify their actual sort-order positions. + */ + item1 = find_enumitem(enumdata, arg1); + item2 = find_enumitem(enumdata, arg2); + + if (item1 == NULL || item2 == NULL) + { + /* + * We couldn't find one or both values. That means the enum has + * changed under us, so re-initialize the cache and try again. + * We don't bother retrying the known-sorted case in this path. + */ + load_enum_cache_data(tcache); + enumdata = tcache->enumData; + + item1 = find_enumitem(enumdata, arg1); + item2 = find_enumitem(enumdata, arg2); + + /* + * If we still can't find the values, complain: we must have + * corrupt data. + */ + if (item1 == NULL) + elog(ERROR, "enum value %u not found in cache for enum %s", + arg1, format_type_be(tcache->type_id)); + if (item2 == NULL) + elog(ERROR, "enum value %u not found in cache for enum %s", + arg2, format_type_be(tcache->type_id)); + } + + if (item1->sort_order < item2->sort_order) + return -1; + else if (item1->sort_order > item2->sort_order) + return 1; + else + return 0; +} + +/* + * Load (or re-load) the enumData member of the typcache entry. + */ +static void +load_enum_cache_data(TypeCacheEntry *tcache) +{ + TypeCacheEnumData *enumdata; + Relation enum_rel; + SysScanDesc enum_scan; + HeapTuple enum_tuple; + ScanKeyData skey; + EnumItem *items; + int numitems; + int maxitems; + Oid bitmap_base; + Bitmapset *bitmap; + MemoryContext oldcxt; + int bm_size, + start_pos; + + /* Check that this is actually an enum */ + if (tcache->typtype != TYPTYPE_ENUM) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("%s is not an enum", + format_type_be(tcache->type_id)))); + + /* + * Read all the information for members of the enum type. We collect + * the info in working memory in the caller's context, and then transfer + * it to permanent memory in CacheMemoryContext. This minimizes the risk + * of leaking memory from CacheMemoryContext in the event of an error + * partway through. + */ + maxitems = 64; + items = (EnumItem *) palloc(sizeof(EnumItem) * maxitems); + numitems = 0; + + /* + * Scan pg_enum for the members of the target enum type. We use a + * current MVCC snapshot, *not* SnapshotNow, so that we see a consistent + * set of rows even if someone commits a renumbering of the enum meanwhile. + * See comments for RenumberEnumType in catalog/pg_enum.c for more info. + */ + ScanKeyInit(&skey, + Anum_pg_enum_enumtypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(tcache->type_id)); + + enum_rel = heap_open(EnumRelationId, AccessShareLock); + enum_scan = systable_beginscan(enum_rel, + EnumTypIdLabelIndexId, + true, GetTransactionSnapshot(), + 1, &skey); + + while (HeapTupleIsValid(enum_tuple = systable_getnext(enum_scan))) + { + Form_pg_enum en = (Form_pg_enum) GETSTRUCT(enum_tuple); + + if (numitems >= maxitems) + { + maxitems *= 2; + items = (EnumItem *) repalloc(items, sizeof(EnumItem) * maxitems); + } + items[numitems].enum_oid = HeapTupleGetOid(enum_tuple); + items[numitems].sort_order = en->enumsortorder; + numitems++; + } + + systable_endscan(enum_scan); + heap_close(enum_rel, AccessShareLock); + + /* Sort the items into OID order */ + qsort(items, numitems, sizeof(EnumItem), enum_oid_cmp); + + /* + * Here, we create a bitmap listing a subset of the enum's OIDs that are + * known to be in order and can thus be compared with just OID comparison. + * + * The point of this is that the enum's initial OIDs were certainly in + * order, so there is some subset that can be compared via OID comparison; + * and we'd rather not do binary searches unnecessarily. + * + * This is somewhat heuristic, and might identify a subset of OIDs that + * isn't exactly what the type started with. That's okay as long as + * the subset is correctly sorted. + */ + bitmap_base = InvalidOid; + bitmap = NULL; + bm_size = 1; /* only save sets of at least 2 OIDs */ + + for (start_pos = 0; start_pos < numitems - 1; start_pos++) + { + /* + * Identify longest sorted subsequence starting at start_pos + */ + Bitmapset *this_bitmap = bms_make_singleton(0); + int this_bm_size = 1; + Oid start_oid = items[start_pos].enum_oid; + float4 prev_order = items[start_pos].sort_order; + int i; + + for (i = start_pos + 1; i < numitems; i++) + { + Oid offset; + + offset = items[i].enum_oid - start_oid; + /* quit if bitmap would be too large; cutoff is arbitrary */ + if (offset >= 8192) + break; + /* include the item if it's in-order */ + if (items[i].sort_order > prev_order) + { + prev_order = items[i].sort_order; + this_bitmap = bms_add_member(this_bitmap, (int) offset); + this_bm_size++; + } + } + + /* Remember it if larger than previous best */ + if (this_bm_size > bm_size) + { + bms_free(bitmap); + bitmap_base = start_oid; + bitmap = this_bitmap; + bm_size = this_bm_size; + } + else + bms_free(this_bitmap); + + /* + * Done if it's not possible to find a longer sequence in the rest + * of the list. In typical cases this will happen on the first + * iteration, which is why we create the bitmaps on the fly instead + * of doing a second pass over the list. + */ + if (bm_size >= (numitems - start_pos - 1)) + break; + } + + /* OK, copy the data into CacheMemoryContext */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + enumdata = (TypeCacheEnumData *) + palloc(offsetof(TypeCacheEnumData, enum_values) + + numitems * sizeof(EnumItem)); + enumdata->bitmap_base = bitmap_base; + enumdata->sorted_values = bms_copy(bitmap); + enumdata->num_values = numitems; + memcpy(enumdata->enum_values, items, numitems * sizeof(EnumItem)); + MemoryContextSwitchTo(oldcxt); + + pfree(items); + bms_free(bitmap); + + /* And link the finished cache struct into the typcache */ + if (tcache->enumData != NULL) + pfree(tcache->enumData); + tcache->enumData = enumdata; +} + +/* + * Locate the EnumItem with the given OID, if present + */ +static EnumItem * +find_enumitem(TypeCacheEnumData *enumdata, Oid arg) +{ + EnumItem srch; + + /* On some versions of Solaris, bsearch of zero items dumps core */ + if (enumdata->num_values <= 0) + return NULL; + + srch.enum_oid = arg; + return bsearch(&srch, enumdata->enum_values, enumdata->num_values, + sizeof(EnumItem), enum_oid_cmp); +} + +/* + * qsort comparison function for OID-ordered EnumItems + */ +static int +enum_oid_cmp(const void *left, const void *right) +{ + const EnumItem *l = (const EnumItem *) left; + const EnumItem *r = (const EnumItem *) right; + + if (l->enum_oid < r->enum_oid) + return -1; + else if (l->enum_oid > r->enum_oid) + return 1; + else + return 0; +} |
