/*-------------------------------------------------------------------------
 *
 * enum.c
 *    I/O functions, operators, aggregates etc for enum types
 *
 * Copyright (c) 2006-2007, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *    $PostgreSQL: pgsql/src/backend/utils/adt/enum.c,v 1.4 2007/09/04 16:41:42 adunstan Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "catalog/pg_enum.h"
#include "fmgr.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"


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

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 = SearchSysCache(ENUMTYPOIDNAME,
						 ObjectIdGetDatum(enumtypoid),
						 CStringGetDatum(name),
						 0, 0);
	if (!HeapTupleIsValid(tup))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid input value for enum %s: \"%s\"",
						format_type_be(enumtypoid),
						name)));

	enumoid = HeapTupleGetOid(tup);

	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 = SearchSysCache(ENUMOID,
						 ObjectIdGetDatum(enumval),
						 0, 0, 0);
	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 = SearchSysCache(ENUMTYPOIDNAME,
						 ObjectIdGetDatum(enumtypoid),
						 CStringGetDatum(name),
						 0, 0);
	if (!HeapTupleIsValid(tup))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("invalid input value for enum %s: \"%s\"",
						format_type_be(enumtypoid),
						name)));

	enumoid = HeapTupleGetOid(tup);

	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 = SearchSysCache(ENUMOID,
						 ObjectIdGetDatum(enumval),
						 0, 0, 0);
	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 */

Datum
enum_lt(PG_FUNCTION_ARGS)
{
	Oid a = PG_GETARG_OID(0);
	Oid b = PG_GETARG_OID(1);

	PG_RETURN_BOOL(a < b);
}

Datum
enum_le(PG_FUNCTION_ARGS)
{
	Oid a = PG_GETARG_OID(0);
	Oid b = PG_GETARG_OID(1);

	PG_RETURN_BOOL(a <= b);
}

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(a >= b);
}

Datum
enum_gt(PG_FUNCTION_ARGS)
{
	Oid a = PG_GETARG_OID(0);
	Oid b = PG_GETARG_OID(1);

	PG_RETURN_BOOL(a > b);
}

Datum
enum_smaller(PG_FUNCTION_ARGS)
{
	Oid a = PG_GETARG_OID(0);
	Oid b = PG_GETARG_OID(1);

	PG_RETURN_OID(a <= b ? a : b);
}

Datum
enum_larger(PG_FUNCTION_ARGS)
{
	Oid a = PG_GETARG_OID(0);
	Oid b = PG_GETARG_OID(1);

	PG_RETURN_OID(a >= b ? a : b);
}

Datum
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)
		PG_RETURN_INT32(0);
	else
		PG_RETURN_INT32(-1);
}

/* Enum programming support functions */

Datum
enum_first(PG_FUNCTION_ARGS)
{
	Oid enumtypoid;
	Oid min = InvalidOid;
	CatCList *list;
	int num, i;

	/*
	 * 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")));

	list = SearchSysCacheList(ENUMTYPOIDNAME, 1,
							  ObjectIdGetDatum(enumtypoid),
							  0, 0, 0);
	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);

	if (!OidIsValid(min))		/* should not happen */
		elog(ERROR, "no values found for enum %s",
			 format_type_be(enumtypoid));

	PG_RETURN_OID(min);
}

Datum
enum_last(PG_FUNCTION_ARGS)
{
	Oid enumtypoid;
	Oid max = InvalidOid;
	CatCList *list;
	int num, i;

	/*
	 * 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")));

	list = SearchSysCacheList(ENUMTYPOIDNAME, 1,
							  ObjectIdGetDatum(enumtypoid),
							  0, 0, 0);
	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);

	if (!OidIsValid(max))		/* should not happen */
		elog(ERROR, "no values found for enum %s",
			 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;
	CatCList *list;
	int total, i, j;
	Datum *elems;

	list = SearchSysCacheList(ENUMTYPOIDNAME, 1,
							  ObjectIdGetDatum(enumtypoid),
							  0, 0, 0);
	total = list->n_members;

	elems = (Datum *) palloc(total * sizeof(Datum));

	j = 0;
	for (i = 0; i < total; i++)
	{
		Oid val = HeapTupleGetOid(&(list->members[i]->tuple));

		if ((!OidIsValid(lower) || lower <= val) &&
			(!OidIsValid(upper) || val <= upper))
			elems[j++] = ObjectIdGetDatum(val);
	}

	/* shouldn't need the cache anymore */
	ReleaseCatCacheList(list);

	/* sort results into OID order */
	qsort(elems, j, sizeof(Datum), enum_elem_cmp);

	/* note this hardwires some details about the representation of Oid */
	result = construct_array(elems, j, 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;
}