| /*------------------------------------------------------------------------- |
| * |
| * enum.c |
| * I/O functions, operators, aggregates etc for enum types |
| * |
| * Copyright (c) 2006-2023, 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); |
| Node *escontext = fcinfo->context; |
| Oid enumoid; |
| HeapTuple tup; |
| |
| /* must check length to prevent Assert failure within SearchSysCache */ |
| if (strlen(name) >= NAMEDATALEN) |
| ereturn(escontext, (Datum) 0, |
| (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)) |
| ereturn(escontext, (Datum) 0, |
| (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. Perhaps we should take the trouble to |
| * report "unsafe use" softly; but it's unclear that it's worth the |
| * trouble, or indeed that that is a legitimate bad-input case at all |
| * rather than an implementation shortcoming. |
| */ |
| 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; |
| } |