/*--------------------------------------------------------------------------
 *
 * cdblegacyhash.c
 *	  Hash opclass implementations compatible with legacy DISTRIBUTED BY
 *	  hashing.
 *
 * These are compatibility routines, which implement a hashing that's
 * compatible with the "cdbhash" functions in GPDB 5 and below.
 *
 * Portions Copyright (c) 2005-2008, Greenplum inc
 * Portions Copyright (c) 2012-Present VMware, Inc. or its affiliates.
 *
 *
 * IDENTIFICATION
 *	    src/backend/cdb/cdblegacyhash.c
 *
 *--------------------------------------------------------------------------
 */
#include "postgres.h"

#include "cdb/cdbhash.h"
#include "catalog/pg_am.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/cash.h"
#include "utils/complex_type.h"
#include "utils/date.h"
#include "utils/datetime.h"
#include "utils/fmgroids.h"
#include "utils/inet.h"
#include "utils/lsyscache.h"
#include "utils/numeric.h"
#include "utils/timestamp.h"
#include "utils/uuid.h"
#include "utils/varbit.h"

#define PG_GETARG_ITEMPOINTER(n) DatumGetItemPointer(PG_GETARG_DATUM(n))

/*
 * FNV1_32_INIT is defined in cdbhash.h, so that it can be used directly
 * from cdbhash.c
 */

/* Constant prime value used for an FNV1 hash */
#define FNV_32_PRIME ((uint32)0x01000193)

/* Constant used for hashing a NULL value */
#define NULL_VAL ((uint32)0XF0F0F0F1)

/* Constant used for hashing a NAN value  */
#define NAN_VAL ((uint32)0XE0E0E0E1)

/* Constant used for hashing an invalid value  */
#define INVALID_VAL ((uint32)0XD0D0D0D1)

/* local function declarations */
static uint32 fnv1_32_buf(void *buf, size_t len, uint32 hashval);
static int	inet_getkey(inet *addr, unsigned char *inet_key, int key_size);
static int	ignoreblanks(char *data, int len);

/*
 * The normal PostgreSQL hash functions take one value as argument, and
 * return a hashcode, i.e. hashcode = hashfunc(arg). When multiple columns
 * need to be hashed together, the hash function is called separately for
 * each column, and the results are combined, with a 1 bit rotation to for
 * better mixing:
 *
 * hashcode =  hashfunc2(col2) ^ rot(hashfunc1(col1))
 *
 * The legacy hashing scheme, however, uses the already-computed hash code
 * as part of the computation:
 *
 * hashcode = hashfunc1(col2, hashfunc(col1, FNV1_32_INIT))
 *
 * Note that the hash function effectively has two arguments: the new
 * datum to be hashed, and the hash code computed so far. That's incompatible
 * with Postgres hash functions, which only have one column.
 *
 * We could represent the legacy hash functions as two-argument functions,
 * and store them in pg_amproc entries with a different strategy number than
 * the normal hash functions use (they use HASHPROC, which is 1). But that
 * seems like it might cause different problems. So instead, we pass the
 * "previous" hash value in this global variable, 'magic_hash_stash'. That
 * way, the legacy hash opclasses work like any other hash opclass, except
 * when the hash function is called from cdbhash.c. cdbhash.c needs to treat
 * them specially anyway, since the "reduction" algorithm used to derive the
 * segment number from the hash code is different too. (We could represent the
 * reduction algorithm as yet another support function, but we don't need the
 * flexibility, so let's not make things more complicated for the sake of it.)
 *
 * The 'magic_hash_stash' is initialized at FNV1_32_INIT, and reset back to
 * that value after any hash computation in cdbhash.c. That way, the result
 * you get if you call one of the legacy functions directly, is the same as
 * cdbhash would compute for a single column.
 */
uint32	magic_hash_stash = FNV1_32_INIT;

static uint32
hashFn(char *buf, int len)
{
	return fnv1_32_buf(buf, len, magic_hash_stash);
}

Datum cdblegacyhash_int2(PG_FUNCTION_ARGS);
Datum cdblegacyhash_int4(PG_FUNCTION_ARGS);
Datum cdblegacyhash_int8(PG_FUNCTION_ARGS);
Datum cdblegacyhash_float4(PG_FUNCTION_ARGS);
Datum cdblegacyhash_float8(PG_FUNCTION_ARGS);
Datum cdblegacyhash_numeric(PG_FUNCTION_ARGS);
Datum cdblegacyhash_char(PG_FUNCTION_ARGS);
Datum cdblegacyhash_text(PG_FUNCTION_ARGS);
Datum cdblegacyhash_bpchar(PG_FUNCTION_ARGS);
Datum cdblegacyhash_bytea(PG_FUNCTION_ARGS);
Datum cdblegacyhash_name(PG_FUNCTION_ARGS);
Datum cdblegacyhash_oid(PG_FUNCTION_ARGS);
Datum cdblegacyhash_tid(PG_FUNCTION_ARGS);
Datum cdblegacyhash_timestamp(PG_FUNCTION_ARGS);
Datum cdblegacyhash_timestamptz(PG_FUNCTION_ARGS);
Datum cdblegacyhash_date(PG_FUNCTION_ARGS);
Datum cdblegacyhash_time(PG_FUNCTION_ARGS);
Datum cdblegacyhash_timetz(PG_FUNCTION_ARGS);
Datum cdblegacyhash_interval(PG_FUNCTION_ARGS);
Datum cdblegacyhash_inet(PG_FUNCTION_ARGS);
Datum cdblegacyhash_macaddr(PG_FUNCTION_ARGS);
Datum cdblegacyhash_bit(PG_FUNCTION_ARGS);
Datum cdblegacyhash_bool(PG_FUNCTION_ARGS);
Datum cdblegacyhash_array(PG_FUNCTION_ARGS);
Datum cdblegacyhash_oidvector(PG_FUNCTION_ARGS);
Datum cdblegacyhash_cash(PG_FUNCTION_ARGS);
Datum cdblegacyhash_uuid(PG_FUNCTION_ARGS);
Datum cdblegacyhash_complex(PG_FUNCTION_ARGS);
Datum cdblegacyhash_anyenum(PG_FUNCTION_ARGS);

Oid
get_legacy_cdbhash_opclass_for_base_type(Oid orig_typid)
{
	Oid			typid;
	char	   *opclass_name;

	/* look through domains */
	typid = getBaseType(orig_typid);

	/*
	 * These are the datatypes that were "GPDB-hashable" in GPDB 5.
	 *
	 * NOTE: We don't support array types, even though GPDB 5 used to implement
	 * hashing for them. The reason is that the hash function for those was
	 * actually broken, see https://github.com/greenplum-db/gpdb/issues/5467.
	 */
	switch (typid)
	{
		case INT2OID:
			opclass_name = "cdbhash_int2_ops";
			break;
		case INT4OID:
			opclass_name = "cdbhash_int4_ops";
			break;
		case INT8OID:
			opclass_name = "cdbhash_int8_ops";
			break;
		case FLOAT4OID:
			opclass_name = "cdbhash_float4_ops";
			break;
		case FLOAT8OID:
			opclass_name = "cdbhash_float8_ops";
			break;
		case NUMERICOID:
			opclass_name = "cdbhash_numeric_ops";
			break;
		case CHAROID:
			opclass_name = "cdbhash_char_ops";
			break;
		case BPCHAROID:
			opclass_name = "cdbhash_bpchar_ops";
			break;
		case TEXTOID:
		case VARCHAROID:
			opclass_name = "cdbhash_text_ops";
			break;
		case BYTEAOID:
			opclass_name = "cdbhash_bytea_ops";
			break;
		case NAMEOID:
			opclass_name = "cdbhash_name_ops";
			break;
		case OIDOID:
			opclass_name = "cdbhash_oid_ops";
			break;
		case TIDOID:
			opclass_name = "cdbhash_tid_ops";
			break;
		case REGPROCOID:
		case REGPROCEDUREOID:
		case REGOPEROID:
		case REGOPERATOROID:
		case REGCLASSOID:
		case REGTYPEOID:
			opclass_name = "cdbhash_oid_ops";
			break;
		case TIMESTAMPOID:
			opclass_name = "cdbhash_timestamp_ops";
			break;
		case TIMESTAMPTZOID:
			opclass_name = "cdbhash_timestamptz_ops";
			break;
		case DATEOID:
			opclass_name = "cdbhash_date_ops";
			break;
		case TIMEOID:
			opclass_name = "cdbhash_time_ops";
			break;
		case TIMETZOID:
			opclass_name = "cdbhash_timetz_ops";
			break;
		case INTERVALOID:
			opclass_name = "cdbhash_interval_ops";
			break;
		case INETOID:
		case CIDROID:
			opclass_name = "cdbhash_inet_ops";
			break;
		case MACADDROID:
			opclass_name = "cdbhash_macaddr_ops";
			break;
		case BITOID:
		case VARBITOID:
			opclass_name = "cdbhash_bit_ops";
			break;
		case BOOLOID:
			opclass_name = "cdbhash_bool_ops";
			break;
		case OIDVECTOROID:
			opclass_name = "cdbhash_oidvector_ops";
			break;
		case CASHOID:
			opclass_name = "cdbhash_cash_ops";
			break;
		case UUIDOID:
			opclass_name = "cdbhash_uuid_ops";
			break;
		case COMPLEXOID:
			opclass_name = "cdbhash_complex_ops";
			break;

		case ANYENUMOID:
			opclass_name = "cdbhash_enum_ops";
			break;

		default:
			/* Is it an an enum? */
			if (type_is_enum(typid))
				opclass_name = "cdbhash_enum_ops";
			else
				opclass_name = NULL;
			break;
	}

	if (!opclass_name)
		return InvalidOid;

	return get_opclass_oid(HASH_AM_OID,
						   list_make2(makeString("pg_catalog"),
									  makeString(opclass_name)),
						   false);
}

bool
isLegacyCdbHashFunction(Oid funcid)
{
	switch (funcid)
	{
		case F_CDBLEGACYHASH_INT2:
		case F_CDBLEGACYHASH_INT4:
		case F_CDBLEGACYHASH_INT8:
		case F_CDBLEGACYHASH_FLOAT4:
		case F_CDBLEGACYHASH_FLOAT8:
		case F_CDBLEGACYHASH_NUMERIC:
		case F_CDBLEGACYHASH_CHAR:
		case F_CDBLEGACYHASH_TEXT:
		case F_CDBLEGACYHASH_BPCHAR:
		case F_CDBLEGACYHASH_BYTEA:
		case F_CDBLEGACYHASH_NAME:
		case F_CDBLEGACYHASH_OID:
		case F_CDBLEGACYHASH_TID:
		case F_CDBLEGACYHASH_TIMESTAMP:
		case F_CDBLEGACYHASH_TIMESTAMPTZ:
		case F_CDBLEGACYHASH_DATE:
		case F_CDBLEGACYHASH_TIME:
		case F_CDBLEGACYHASH_TIMETZ:
		case F_CDBLEGACYHASH_INTERVAL:
		case F_CDBLEGACYHASH_INET:
		case F_CDBLEGACYHASH_MACADDR:
		case F_CDBLEGACYHASH_BIT:
		case F_CDBLEGACYHASH_BOOL:
		case F_CDBLEGACYHASH_ARRAY:
		case F_CDBLEGACYHASH_OIDVECTOR:
		case F_CDBLEGACYHASH_CASH:
		case F_CDBLEGACYHASH_UUID:
		case F_CDBLEGACYHASH_COMPLEX:
		case F_CDBLEGACYHASH_ANYENUM:
			return true;

		default:
			return false;
	}
}

Datum
cdblegacyhash_int2(PG_FUNCTION_ARGS)
{
	/* an 8 byte buffer for all integer sizes */
	int64		intbuf = (int64)  PG_GETARG_INT16(0);

	/* do the hash using the selected algorithm */
	PG_RETURN_UINT32(hashFn((char *) &intbuf, sizeof(intbuf)));
}

Datum
cdblegacyhash_int4(PG_FUNCTION_ARGS)
{
	/* an 8 byte buffer for all integer sizes */
	int64		intbuf = (int64)  PG_GETARG_INT32(0);

	/* do the hash using the selected algorithm */
	PG_RETURN_UINT32(hashFn((char *) &intbuf, sizeof(intbuf)));
}

Datum
cdblegacyhash_int8(PG_FUNCTION_ARGS)
{
	/* an 8 byte buffer for all integer sizes */
	int64		intbuf = PG_GETARG_INT64(0);

	/* do the hash using the selected algorithm */
	PG_RETURN_UINT32(hashFn((char *) &intbuf, sizeof(intbuf)));
}

Datum
cdblegacyhash_float4(PG_FUNCTION_ARGS)
{
	float4		buf_f4 = PG_GETARG_FLOAT4(0);

	/*
	 * On IEEE-float machines, minus zero and zero have different bit
	 * patterns but should compare as equal.  We must ensure that they
	 * have the same hash value, which is most easily done this way:
	 */
	if (buf_f4 == (float4) 0)
		buf_f4 = 0.0;

	PG_RETURN_UINT32(hashFn((char *) &buf_f4, sizeof(buf_f4)));
}

Datum
cdblegacyhash_float8(PG_FUNCTION_ARGS)
{
	float8		buf_f8 = PG_GETARG_FLOAT8(0);

	/*
	 * On IEEE-float machines, minus zero and zero have different bit
	 * patterns but should compare as equal.  We must ensure that they
	 * have the same hash value, which is most easily done this way:
	 */
	if (buf_f8 == (float8) 0)
		buf_f8 = 0.0;

	PG_RETURN_UINT32(hashFn((char *) &buf_f8, sizeof(buf_f8)));
}

Datum
cdblegacyhash_numeric(PG_FUNCTION_ARGS)
{
	Numeric		num = PG_GETARG_NUMERIC(0);
	void	   *buf;		/* pointer to the data */
	size_t		len;		/* length for the data buffer */
	uint32		hash;

	if (NUMERIC_IS_NAN(num))
	{
		static const uint32 nanbuf = NAN_VAL;

		buf = (void *) &nanbuf;
		len = sizeof(nanbuf);
	}
	else
		/* not a nan */
	{
		buf = NUMERIC_DIGITS(num);
		len = NUMERIC_NDIGITS(num) * sizeof(NumericDigit);
	}

	/* do the hash using the selected algorithm */
	hash = hashFn(buf, len);

	/* Avoid leaking memory for toasted inputs */
	PG_FREE_IF_COPY(num, 0);

	PG_RETURN_UINT32(hash);
}

Datum
cdblegacyhash_char(PG_FUNCTION_ARGS)
{
	char		char_buf = PG_GETARG_CHAR(0);

	PG_RETURN_UINT32(hashFn(&char_buf, 1));
}

/* also for BPCHAR and VARCHAR */
Datum
cdblegacyhash_text(PG_FUNCTION_ARGS)
{
	text	   *text_buf = PG_GETARG_TEXT_PP(0);
	int			len;
	void	   *buf;		/* pointer to the data */
	uint32		hash;

	buf = (void *) VARDATA_ANY(text_buf);
	len = VARSIZE_ANY_EXHDR(text_buf);
	/* adjust length to not include trailing blanks */
	if (len > 1)
		len = ignoreblanks((char *) buf, len);

	hash = hashFn(buf, len);

	/* Avoid leaking memory for toasted inputs */
	PG_FREE_IF_COPY(text_buf, 0);

	PG_RETURN_UINT32(hash);
}

Datum
cdblegacyhash_bpchar(PG_FUNCTION_ARGS)
{
	return cdblegacyhash_text(fcinfo);
}

Datum
cdblegacyhash_bytea(PG_FUNCTION_ARGS)
{
	bytea	   *bytea_buf = PG_GETARG_BYTEA_PP(0);
	int			len;
	void	   *buf;		/* pointer to the data */
	uint32		hash;

	buf = (void *) VARDATA_ANY(bytea_buf);
	len = VARSIZE_ANY_EXHDR(bytea_buf);

	hash = hashFn(buf, len);

	/* Avoid leaking memory for toasted inputs */
	PG_FREE_IF_COPY(bytea_buf, 0);

	PG_RETURN_UINT32(hash);
}

Datum
cdblegacyhash_name(PG_FUNCTION_ARGS)
{
	char	   *namebuf = NameStr(*PG_GETARG_NAME(0));
	int			len;

	/* adjust length to not include trailing blanks */
	len = ignoreblanks((char *) namebuf, NAMEDATALEN);

	PG_RETURN_UINT32(hashFn(namebuf, len));
}

Datum
cdblegacyhash_oid(PG_FUNCTION_ARGS)
{
	/* an 8 byte buffer for all integer sizes */
	int64		intbuf = (int64)  PG_GETARG_INT32(0);

	/* do the hash using the selected algorithm */
	PG_RETURN_UINT32(hashFn((char *) &intbuf, sizeof(intbuf)));
}

Datum
cdblegacyhash_tid(PG_FUNCTION_ARGS)
{
	ItemPointer	tid = PG_GETARG_ITEMPOINTER(0);

	/* See hashtid() for why we're not using sizeof(ItemPointerData) here */
	PG_RETURN_UINT32(hashFn((char *) tid,
							sizeof(BlockIdData) + sizeof(OffsetNumber)));
}

Datum
cdblegacyhash_timestamp(PG_FUNCTION_ARGS)
{
	Timestamp tsbuf = PG_GETARG_TIMESTAMP(0);

	PG_RETURN_UINT32(hashFn((char *) &tsbuf, sizeof(tsbuf)));
}

Datum
cdblegacyhash_timestamptz(PG_FUNCTION_ARGS)
{
	TimestampTz tstzbuf = PG_GETARG_TIMESTAMPTZ(0);

	PG_RETURN_UINT32(hashFn((char *) &tstzbuf, sizeof(tstzbuf)));
}

Datum
cdblegacyhash_date(PG_FUNCTION_ARGS)
{
	DateADT	datebuf = PG_GETARG_DATEADT(0);

	PG_RETURN_UINT32(hashFn((char *) &datebuf, sizeof(datebuf)));
}

Datum
cdblegacyhash_time(PG_FUNCTION_ARGS)
{
	TimeADT timebuf = PG_GETARG_TIMEADT(0);

	PG_RETURN_UINT32(hashFn((char *) &timebuf, sizeof(timebuf)));
}

Datum
cdblegacyhash_timetz(PG_FUNCTION_ARGS)
{
	TimeTzADT *timetzptr = PG_GETARG_TIMETZADT_P(0);
	int			len;
	char	   *buf;		/* pointer to the data */

	/*
	 * will not compare to TIMEOID on equal values. Postgres never
	 * attempts to compare the two as well.
	 */
	buf = (char *) timetzptr;

	/*
	 * Specify hash length as sizeof(double) + sizeof(int4), not as
	 * sizeof(TimeTzADT), so that any garbage pad bytes in the
	 * structure won't be included in the hash!
	 */
	len = sizeof(timetzptr->time) + sizeof(timetzptr->zone);

	PG_RETURN_UINT32(hashFn(buf, len));
}

Datum
cdblegacyhash_interval(PG_FUNCTION_ARGS)
{
	Interval *intervalptr = PG_GETARG_INTERVAL_P(0);
	int			len;
	void	   *buf;		/* pointer to the data */

	buf = (unsigned char *) intervalptr;

	/*
	 * Specify hash length as sizeof(double) + sizeof(int4), not as
	 * sizeof(Interval), so that any garbage pad bytes in the
	 * structure won't be included in the hash!
	 */
	len = sizeof(intervalptr->time) + sizeof(intervalptr->month);

	PG_RETURN_UINT32(hashFn(buf, len));
}

Datum
cdblegacyhash_inet(PG_FUNCTION_ARGS)
{
	/* inet/cidr */
	inet	   *inetptr = PG_GETARG_INET_PP(0);
	int			len;
	void	   *buf;		/* pointer to the data */
	unsigned char inet_hkey[sizeof(inet_struct)];
	uint32		hash;

	len = inet_getkey(inetptr, inet_hkey, sizeof(inet_hkey));	/* fill-in inet_key &
																 * get len */
	buf = inet_hkey;
	hash = hashFn(buf, len);

	/* Avoid leaking memory for toasted inputs */
	PG_FREE_IF_COPY(inetptr, 0);

	PG_RETURN_UINT32(hash);
}

Datum
cdblegacyhash_macaddr(PG_FUNCTION_ARGS)
{
	macaddr *macptr = PG_GETARG_MACADDR_P(0);

	PG_RETURN_UINT32(hashFn((char *) macptr, sizeof(macaddr)));
}

Datum
cdblegacyhash_bit(PG_FUNCTION_ARGS)
{
	/*
	 * Note that these are essentially strings. we don't need to worry
	 * about '10' and '010' to compare, b/c they will not, by design.
	 * (see SQL standard, and varbit.c)
	 */
	VarBit *vbitptr = PG_GETARG_VARBIT_P(0);
	int			len;
	void	   *buf;		/* pointer to the data */
	uint32		hash;

	len = VARBITBYTES(vbitptr);
	buf = (char *) VARBITS(vbitptr);

	hash = hashFn(buf, len);

	/* Avoid leaking memory for toasted inputs */
	PG_FREE_IF_COPY(vbitptr, 0);

	PG_RETURN_UINT32(hash);
}

Datum
cdblegacyhash_bool(PG_FUNCTION_ARGS)
{
	char		bool_buf = PG_GETARG_BOOL(0);

	PG_RETURN_UINT32(hashFn(&bool_buf, sizeof(bool_buf)));
}

Datum
cdblegacyhash_array(PG_FUNCTION_ARGS)
{
	ArrayType  *arrbuf = PG_GETARG_ARRAYTYPE_P(0);
	int			len;
	void	   *buf;		/* pointer to the data */

	len = VARSIZE(arrbuf) - VARHDRSZ;
	buf = VARDATA(arrbuf);

	PG_RETURN_UINT32(hashFn(buf, len));
}

Datum
cdblegacyhash_oidvector(PG_FUNCTION_ARGS)
{
	oidvector  *oidvec_buf = (oidvector *) PG_GETARG_POINTER(0);
	int			len;
	void	   *buf;		/* pointer to the data */

	len = oidvec_buf->dim1 * sizeof(Oid);
	buf = oidvec_buf->values;

	PG_RETURN_UINT32(hashFn(buf, len));
}

Datum
cdblegacyhash_cash(PG_FUNCTION_ARGS)
{
	Cash		cash_buf = PG_GETARG_CASH(0);

	PG_RETURN_UINT32(hashFn((char *) &cash_buf, sizeof(Cash)));
}

Datum
cdblegacyhash_uuid(PG_FUNCTION_ARGS)
{
	pg_uuid_t  *uuid_buf = PG_GETARG_UUID_P(0);

	PG_RETURN_UINT32(hashFn((char *) uuid_buf, UUID_LEN));
}

Datum
cdblegacyhash_complex(PG_FUNCTION_ARGS)
{
	Complex *complex_ptr = PG_GETARG_COMPLEX_P(0);
	double		complex_real;
	double		complex_imag;
	Complex		complex_buf;

	complex_real = re(complex_ptr);
	complex_imag = im(complex_ptr);

	/*
	 * On IEEE-float machines, minus zero and zero have different bit
	 * patterns but should compare as equal.  We must ensure that they
	 * have the same hash value, which is most easily done this way:
	 */
	if (complex_real == (float8) 0)
	{
		complex_real = 0.0;
	}
	if (complex_imag == (float8) 0)
	{
		complex_imag = 0.0;
	}

	INIT_COMPLEX(&complex_buf, complex_real, complex_imag);

	PG_RETURN_UINT32(hashFn((char *) &complex_buf, sizeof(Complex)));
}

Datum
cdblegacyhash_anyenum(PG_FUNCTION_ARGS)
{
	/* an 8 byte buffer for all integer sizes */
	int64		intbuf = (int64)  PG_GETARG_INT32(0);

	/* do the hash using the selected algorithm */
	PG_RETURN_UINT32(hashFn((char *) &intbuf, sizeof(intbuf)));
}

/*
 * Update the hash value for a null Datum, for any datatype.
 */
uint32
cdblegacyhash_null(void)
{
	uint32		nullbuf = NULL_VAL; /* stores the constant value that
									 * represents a NULL */
	return hashFn((char *) &nullbuf, sizeof(nullbuf));
}

/*
 * fnv1_32_buf - perform a 32 bit FNV 1 hash on a buffer
 *
 * input:
 *	buf - start of buffer to hash
 *	len - length of buffer in octets (bytes)
 *	hval	- previous hash value or FNV1_32_INIT if first call.
 *
 * returns:
 *	32 bit hash as a static hash type
 */
static uint32
fnv1_32_buf(void *buf, size_t len, uint32 hval)
{
	unsigned char *bp = (unsigned char *) buf;	/* start of buffer */
	unsigned char *be = bp + len;	/* beyond end of buffer */

	/*
	 * FNV-1 hash each octet in the buffer
	 */
	while (bp < be)
	{
		/* multiply by the 32 bit FNV magic prime mod 2^32 */
#if defined(NO_FNV_GCC_OPTIMIZATION)
		hval *= FNV_32_PRIME;
#else
		hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
#endif

		/* xor the bottom with the current octet */
		hval ^= (uint32) *bp++;
	}

	/* return our new hash value */
	return hval;
}

/*
 * Support function for hashing on inet/cidr (see network.c)
 *
 * Since network_cmp considers only ip_family, ip_bits, and ip_addr,
 * only these fields may be used in the hash; in particular don't use type.
 */
static int
inet_getkey(inet *addr, unsigned char *inet_key, int key_size)
{
	int			addrsize;

	switch (((inet_struct *) VARDATA_ANY(addr))->family)
	{
		case PGSQL_AF_INET:
			addrsize = 4;
			break;
		case PGSQL_AF_INET6:
			addrsize = 16;
			break;
		default:
			addrsize = 0;
	}

	Assert(addrsize + 2 <= key_size);
	inet_key[0] = ((inet_struct *) VARDATA_ANY(addr))->family;
	inet_key[1] = ((inet_struct *) VARDATA_ANY(addr))->bits;
	memcpy(inet_key + 2, ((inet_struct *) VARDATA_ANY(addr))->ipaddr, addrsize);

	return (addrsize + 2);
}

/*================================================================
 *
 * GENERAL PURPOSE UTILS
 *
 *================================================================
 */

/*
 * Given the original length of the data array this function is
 * recalculating the length after ignoring any trailing blanks. The
 * actual data remains unmodified.
 */
static int
ignoreblanks(char *data, int len)
{
	/* look for trailing blanks and skip them in the hash calculation */
	while (data[len - 1] == ' ')
	{
		len--;

		/*
		 * If only 1 char is left, leave it alone! The string is either empty
		 * or has 1 char
		 */
		if (len == 1)
			break;
	}

	return len;
}
