postgresql/src/backend/utils/adt/enum.c

617 lines
16 KiB
C

/*-------------------------------------------------------------------------
*
* 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;
}