diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
commit | 46651ce6fe013220ed397add242004d764fc0153 (patch) | |
tree | 6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/backend/utils/adt/enum.c | |
parent | Initial commit. (diff) | |
download | postgresql-14-46651ce6fe013220ed397add242004d764fc0153.tar.xz postgresql-14-46651ce6fe013220ed397add242004d764fc0153.zip |
Adding upstream version 14.5.upstream/14.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/utils/adt/enum.c')
-rw-r--r-- | src/backend/utils/adt/enum.c | 610 |
1 files changed, 610 insertions, 0 deletions
diff --git a/src/backend/utils/adt/enum.c b/src/backend/utils/adt/enum.c new file mode 100644 index 0000000..0d89213 --- /dev/null +++ b/src/backend/utils/adt/enum.c @@ -0,0 +1,610 @@ +/*------------------------------------------------------------------------- + * + * enum.c + * I/O functions, operators, aggregates etc for enum types + * + * Copyright (c) 2006-2021, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/enum.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/table.h" +#include "catalog/pg_enum.h" +#include "libpq/pqformat.h" +#include "storage/procarray.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" +#include "utils/typcache.h" + + +static Oid enum_endpoint(Oid enumtypoid, ScanDirection direction); +static ArrayType *enum_range_internal(Oid enumtypoid, Oid lower, Oid upper); + + +/* + * Disallow use of an uncommitted pg_enum tuple. + * + * We need to make sure that uncommitted enum values don't get into indexes. + * If they did, and if we then rolled back the pg_enum addition, we'd have + * broken the index because value comparisons will not work reliably without + * an underlying pg_enum entry. (Note that removal of the heap entry + * containing an enum value is not sufficient to ensure that it doesn't appear + * in upper levels of indexes.) To do this we prevent an uncommitted row from + * being used for any SQL-level purpose. This is stronger than necessary, + * since the value might not be getting inserted into a table or there might + * be no index on its column, but it's easy to enforce centrally. + * + * However, it's okay to allow use of uncommitted values belonging to enum + * types that were themselves created in the same transaction, because then + * any such index would also be new and would go away altogether on rollback. + * We don't implement that fully right now, but we do allow free use of enum + * values created during CREATE TYPE AS ENUM, which are surely of the same + * lifespan as the enum type. (This case is required by "pg_restore -1".) + * Values added by ALTER TYPE ADD VALUE are currently restricted, but could + * be allowed if the enum type could be proven to have been created earlier + * in the same transaction. (Note that comparing tuple xmins would not work + * for that, because the type tuple might have been updated in the current + * transaction. Subtransactions also create hazards to be accounted for.) + * + * This function needs to be called (directly or indirectly) in any of the + * functions below that could return an enum value to SQL operations. + */ +static void +check_safe_enum_use(HeapTuple enumval_tup) +{ + TransactionId xmin; + Form_pg_enum en = (Form_pg_enum) GETSTRUCT(enumval_tup); + + /* + * If the row is hinted as committed, it's surely safe. This provides a + * fast path for all normal use-cases. + */ + if (HeapTupleHeaderXminCommitted(enumval_tup->t_data)) + return; + + /* + * Usually, a row would get hinted as committed when it's read or loaded + * into syscache; but just in case not, let's check the xmin directly. + */ + xmin = HeapTupleHeaderGetXmin(enumval_tup->t_data); + if (!TransactionIdIsInProgress(xmin) && + TransactionIdDidCommit(xmin)) + return; + + /* + * Check if the enum value is uncommitted. If not, it's safe, because it + * was made during CREATE TYPE AS ENUM and can't be shorter-lived than its + * owning type. (This'd also be false for values made by other + * transactions; but the previous tests should have handled all of those.) + */ + if (!EnumUncommitted(en->oid)) + return; + + /* + * There might well be other tests we could do here to narrow down the + * unsafe conditions, but for now just raise an exception. + */ + ereport(ERROR, + (errcode(ERRCODE_UNSAFE_NEW_ENUM_VALUE_USAGE), + errmsg("unsafe use of new value \"%s\" of enum type %s", + NameStr(en->enumlabel), + format_type_be(en->enumtypid)), + errhint("New enum values must be committed before they can be used."))); +} + + +/* Basic I/O support */ + +Datum +enum_in(PG_FUNCTION_ARGS) +{ + char *name = PG_GETARG_CSTRING(0); + Oid enumtypoid = PG_GETARG_OID(1); + Oid enumoid; + HeapTuple tup; + + /* must check length to prevent Assert failure within SearchSysCache */ + if (strlen(name) >= NAMEDATALEN) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input value for enum %s: \"%s\"", + format_type_be(enumtypoid), + name))); + + tup = SearchSysCache2(ENUMTYPOIDNAME, + ObjectIdGetDatum(enumtypoid), + CStringGetDatum(name)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input value for enum %s: \"%s\"", + format_type_be(enumtypoid), + name))); + + /* check it's safe to use in SQL */ + check_safe_enum_use(tup); + + /* + * This comes from pg_enum.oid and stores system oids in user tables. This + * oid must be preserved by binary upgrades. + */ + enumoid = ((Form_pg_enum) GETSTRUCT(tup))->oid; + + ReleaseSysCache(tup); + + PG_RETURN_OID(enumoid); +} + +Datum +enum_out(PG_FUNCTION_ARGS) +{ + Oid enumval = PG_GETARG_OID(0); + char *result; + HeapTuple tup; + Form_pg_enum en; + + tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid internal value for enum: %u", + enumval))); + en = (Form_pg_enum) GETSTRUCT(tup); + + result = pstrdup(NameStr(en->enumlabel)); + + ReleaseSysCache(tup); + + PG_RETURN_CSTRING(result); +} + +/* Binary I/O support */ +Datum +enum_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + Oid enumtypoid = PG_GETARG_OID(1); + Oid enumoid; + HeapTuple tup; + char *name; + int nbytes; + + name = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); + + /* must check length to prevent Assert failure within SearchSysCache */ + if (strlen(name) >= NAMEDATALEN) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input value for enum %s: \"%s\"", + format_type_be(enumtypoid), + name))); + + tup = SearchSysCache2(ENUMTYPOIDNAME, + ObjectIdGetDatum(enumtypoid), + CStringGetDatum(name)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input value for enum %s: \"%s\"", + format_type_be(enumtypoid), + name))); + + /* check it's safe to use in SQL */ + check_safe_enum_use(tup); + + enumoid = ((Form_pg_enum) GETSTRUCT(tup))->oid; + + ReleaseSysCache(tup); + + pfree(name); + + PG_RETURN_OID(enumoid); +} + +Datum +enum_send(PG_FUNCTION_ARGS) +{ + Oid enumval = PG_GETARG_OID(0); + StringInfoData buf; + HeapTuple tup; + Form_pg_enum en; + + tup = SearchSysCache1(ENUMOID, ObjectIdGetDatum(enumval)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("invalid internal value for enum: %u", + enumval))); + en = (Form_pg_enum) GETSTRUCT(tup); + + pq_begintypsend(&buf); + pq_sendtext(&buf, NameStr(en->enumlabel), strlen(NameStr(en->enumlabel))); + + ReleaseSysCache(tup); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* 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; + + /* + * We don't need the typcache except in the hopefully-uncommon case that + * one or both Oids are odd. This means that cursory testing of code that + * fails to pass flinfo to an enum comparison function might not disclose + * the oversight. To make such errors more obvious, Assert that we have a + * place to cache even when we take a fast-path exit. + */ + Assert(fcinfo->flinfo != NULL); + + /* 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(enum_cmp_internal(a, b, fcinfo) < 0); +} + +Datum +enum_le(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) <= 0); +} + +Datum +enum_eq(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_BOOL(a == b); +} + +Datum +enum_ne(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_BOOL(a != b); +} + +Datum +enum_ge(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) >= 0); +} + +Datum +enum_gt(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_BOOL(enum_cmp_internal(a, b, fcinfo) > 0); +} + +Datum +enum_smaller(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) < 0 ? a : b); +} + +Datum +enum_larger(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_OID(enum_cmp_internal(a, b, fcinfo) > 0 ? a : b); +} + +Datum +enum_cmp(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + + PG_RETURN_INT32(enum_cmp_internal(a, b, fcinfo)); +} + +/* 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. 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 = table_open(EnumRelationId, AccessShareLock); + enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock); + enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL, + 1, &skey); + + enum_tuple = systable_getnext_ordered(enum_scan, direction); + if (HeapTupleIsValid(enum_tuple)) + { + /* check it's safe to use in SQL */ + check_safe_enum_use(enum_tuple); + minmax = ((Form_pg_enum) GETSTRUCT(enum_tuple))->oid; + } + else + { + /* should only happen with an empty enum */ + minmax = InvalidOid; + } + + systable_endscan_ordered(enum_scan); + index_close(enum_idx, AccessShareLock); + table_close(enum_rel, AccessShareLock); + + return minmax; +} + +Datum +enum_first(PG_FUNCTION_ARGS) +{ + Oid enumtypoid; + Oid min; + + /* + * We rely on being able to get the specific enum type from the calling + * expression tree. Notice that the actual value of the argument isn't + * examined at all; in particular it might be NULL. + */ + enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0); + if (enumtypoid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine actual enum type"))); + + /* Get the OID using the index */ + min = enum_endpoint(enumtypoid, ForwardScanDirection); + + 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); +} + +Datum +enum_last(PG_FUNCTION_ARGS) +{ + Oid enumtypoid; + Oid max; + + /* + * We rely on being able to get the specific enum type from the calling + * expression tree. Notice that the actual value of the argument isn't + * examined at all; in particular it might be NULL. + */ + enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0); + if (enumtypoid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine actual enum type"))); + + /* Get the OID using the index */ + max = enum_endpoint(enumtypoid, BackwardScanDirection); + + 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); +} + +/* 2-argument variant of enum_range */ +Datum +enum_range_bounds(PG_FUNCTION_ARGS) +{ + Oid lower; + Oid upper; + Oid enumtypoid; + + if (PG_ARGISNULL(0)) + lower = InvalidOid; + else + lower = PG_GETARG_OID(0); + if (PG_ARGISNULL(1)) + upper = InvalidOid; + else + upper = PG_GETARG_OID(1); + + /* + * We rely on being able to get the specific enum type from the calling + * expression tree. The generic type mechanism should have ensured that + * both are of the same type. + */ + enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0); + if (enumtypoid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine actual enum type"))); + + PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid, lower, upper)); +} + +/* 1-argument variant of enum_range */ +Datum +enum_range_all(PG_FUNCTION_ARGS) +{ + Oid enumtypoid; + + /* + * We rely on being able to get the specific enum type from the calling + * expression tree. Notice that the actual value of the argument isn't + * examined at all; in particular it might be NULL. + */ + enumtypoid = get_fn_expr_argtype(fcinfo->flinfo, 0); + if (enumtypoid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine actual enum type"))); + + PG_RETURN_ARRAYTYPE_P(enum_range_internal(enumtypoid, + InvalidOid, InvalidOid)); +} + +static ArrayType * +enum_range_internal(Oid enumtypoid, Oid lower, Oid upper) +{ + ArrayType *result; + Relation enum_rel; + Relation enum_idx; + SysScanDesc enum_scan; + HeapTuple enum_tuple; + ScanKeyData skey; + Datum *elems; + int max, + cnt; + bool left_found; + + /* + * Scan the enum members in order using pg_enum_typid_sortorder_index. + * Note we must not use the syscache. 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 = table_open(EnumRelationId, AccessShareLock); + enum_idx = index_open(EnumTypIdSortOrderIndexId, AccessShareLock); + enum_scan = systable_beginscan_ordered(enum_rel, enum_idx, NULL, 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 = ((Form_pg_enum) GETSTRUCT(enum_tuple))->oid; + + if (!left_found && lower == enum_oid) + left_found = true; + + if (left_found) + { + /* check it's safe to use in SQL */ + check_safe_enum_use(enum_tuple); + + if (cnt >= max) + { + max *= 2; + elems = (Datum *) repalloc(elems, max * sizeof(Datum)); + } + + elems[cnt++] = ObjectIdGetDatum(enum_oid); + } + + if (OidIsValid(upper) && upper == enum_oid) + break; + } + + systable_endscan_ordered(enum_scan); + index_close(enum_idx, AccessShareLock); + table_close(enum_rel, AccessShareLock); + + /* and build the result array */ + /* note this hardwires some details about the representation of Oid */ + result = construct_array(elems, cnt, enumtypoid, + sizeof(Oid), true, TYPALIGN_INT); + + pfree(elems); + + return result; +} |