diff options
Diffstat (limited to 'src/backend/utils')
-rw-r--r-- | src/backend/utils/adt/rowtypes.c | 249 | ||||
-rw-r--r-- | src/backend/utils/cache/lsyscache.c | 7 | ||||
-rw-r--r-- | src/backend/utils/cache/typcache.c | 78 |
3 files changed, 310 insertions, 24 deletions
diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c index 674cf0a55d8..5c4648bccff 100644 --- a/src/backend/utils/adt/rowtypes.c +++ b/src/backend/utils/adt/rowtypes.c @@ -19,6 +19,7 @@ #include "access/detoast.h" #include "access/htup_details.h" #include "catalog/pg_type.h" +#include "common/hashfn.h" #include "funcapi.h" #include "libpq/pqformat.h" #include "miscadmin.h" @@ -1766,3 +1767,251 @@ btrecordimagecmp(PG_FUNCTION_ARGS) { PG_RETURN_INT32(record_image_cmp(fcinfo)); } + + +/* + * Row type hash functions + */ + +Datum +hash_record(PG_FUNCTION_ARGS) +{ + HeapTupleHeader record = PG_GETARG_HEAPTUPLEHEADER(0); + uint32 result = 0; + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc; + HeapTupleData tuple; + int ncolumns; + RecordCompareData *my_extra; + Datum *values; + bool *nulls; + + check_stack_depth(); /* recurses for record-type columns */ + + /* Extract type info from tuple */ + tupType = HeapTupleHeaderGetTypeId(record); + tupTypmod = HeapTupleHeaderGetTypMod(record); + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + ncolumns = tupdesc->natts; + + /* Build temporary HeapTuple control structure */ + tuple.t_len = HeapTupleHeaderGetDatumLength(record); + ItemPointerSetInvalid(&(tuple.t_self)); + tuple.t_tableOid = InvalidOid; + tuple.t_data = record; + + /* + * We arrange to look up the needed hashing info just once per series + * of calls, assuming the record type doesn't change underneath us. + */ + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL || + my_extra->ncolumns < ncolumns) + { + fcinfo->flinfo->fn_extra = + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(RecordCompareData, columns) + + ncolumns * sizeof(ColumnCompareData)); + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; + my_extra->ncolumns = ncolumns; + my_extra->record1_type = InvalidOid; + my_extra->record1_typmod = 0; + } + + if (my_extra->record1_type != tupType || + my_extra->record1_typmod != tupTypmod) + { + MemSet(my_extra->columns, 0, ncolumns * sizeof(ColumnCompareData)); + my_extra->record1_type = tupType; + my_extra->record1_typmod = tupTypmod; + } + + /* Break down the tuple into fields */ + values = (Datum *) palloc(ncolumns * sizeof(Datum)); + nulls = (bool *) palloc(ncolumns * sizeof(bool)); + heap_deform_tuple(&tuple, tupdesc, values, nulls); + + for (int i = 0; i < ncolumns; i++) + { + Form_pg_attribute att; + TypeCacheEntry *typentry; + uint32 element_hash; + + att = TupleDescAttr(tupdesc, i); + + if (att->attisdropped) + continue; + + /* + * Lookup the hash function if not done already + */ + typentry = my_extra->columns[i].typentry; + if (typentry == NULL || + typentry->type_id != att->atttypid) + { + typentry = lookup_type_cache(att->atttypid, + TYPECACHE_HASH_PROC_FINFO); + if (!OidIsValid(typentry->hash_proc_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a hash function for type %s", + format_type_be(typentry->type_id)))); + my_extra->columns[i].typentry = typentry; + } + + /* Compute hash of element */ + if (nulls[i]) + { + element_hash = 0; + } + else + { + LOCAL_FCINFO(locfcinfo, 1); + + InitFunctionCallInfoData(*locfcinfo, &typentry->hash_proc_finfo, 1, + att->attcollation, NULL, NULL); + locfcinfo->args[0].value = values[i]; + locfcinfo->args[0].isnull = false; + element_hash = DatumGetUInt32(FunctionCallInvoke(locfcinfo)); + + /* We don't expect hash support functions to return null */ + Assert(!locfcinfo->isnull); + } + + /* see hash_array() */ + result = (result << 5) - result + element_hash; + } + + pfree(values); + pfree(nulls); + ReleaseTupleDesc(tupdesc); + + /* Avoid leaking memory when handed toasted input. */ + PG_FREE_IF_COPY(record, 0); + + PG_RETURN_UINT32(result); +} + +Datum +hash_record_extended(PG_FUNCTION_ARGS) +{ + HeapTupleHeader record = PG_GETARG_HEAPTUPLEHEADER(0); + uint64 seed = PG_GETARG_INT64(1); + uint64 result = 0; + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc; + HeapTupleData tuple; + int ncolumns; + RecordCompareData *my_extra; + Datum *values; + bool *nulls; + + check_stack_depth(); /* recurses for record-type columns */ + + /* Extract type info from tuple */ + tupType = HeapTupleHeaderGetTypeId(record); + tupTypmod = HeapTupleHeaderGetTypMod(record); + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + ncolumns = tupdesc->natts; + + /* Build temporary HeapTuple control structure */ + tuple.t_len = HeapTupleHeaderGetDatumLength(record); + ItemPointerSetInvalid(&(tuple.t_self)); + tuple.t_tableOid = InvalidOid; + tuple.t_data = record; + + /* + * We arrange to look up the needed hashing info just once per series + * of calls, assuming the record type doesn't change underneath us. + */ + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL || + my_extra->ncolumns < ncolumns) + { + fcinfo->flinfo->fn_extra = + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(RecordCompareData, columns) + + ncolumns * sizeof(ColumnCompareData)); + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; + my_extra->ncolumns = ncolumns; + my_extra->record1_type = InvalidOid; + my_extra->record1_typmod = 0; + } + + if (my_extra->record1_type != tupType || + my_extra->record1_typmod != tupTypmod) + { + MemSet(my_extra->columns, 0, ncolumns * sizeof(ColumnCompareData)); + my_extra->record1_type = tupType; + my_extra->record1_typmod = tupTypmod; + } + + /* Break down the tuple into fields */ + values = (Datum *) palloc(ncolumns * sizeof(Datum)); + nulls = (bool *) palloc(ncolumns * sizeof(bool)); + heap_deform_tuple(&tuple, tupdesc, values, nulls); + + for (int i = 0; i < ncolumns; i++) + { + Form_pg_attribute att; + TypeCacheEntry *typentry; + uint64 element_hash; + + att = TupleDescAttr(tupdesc, i); + + if (att->attisdropped) + continue; + + /* + * Lookup the hash function if not done already + */ + typentry = my_extra->columns[i].typentry; + if (typentry == NULL || + typentry->type_id != att->atttypid) + { + typentry = lookup_type_cache(att->atttypid, + TYPECACHE_HASH_EXTENDED_PROC_FINFO); + if (!OidIsValid(typentry->hash_extended_proc_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an extended hash function for type %s", + format_type_be(typentry->type_id)))); + my_extra->columns[i].typentry = typentry; + } + + /* Compute hash of element */ + if (nulls[i]) + { + element_hash = 0; + } + else + { + LOCAL_FCINFO(locfcinfo, 2); + + InitFunctionCallInfoData(*locfcinfo, &typentry->hash_extended_proc_finfo, 2, + att->attcollation, NULL, NULL); + locfcinfo->args[0].value = values[i]; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = Int64GetDatum(seed); + locfcinfo->args[0].isnull = false; + element_hash = DatumGetUInt64(FunctionCallInvoke(locfcinfo)); + + /* We don't expect hash support functions to return null */ + Assert(!locfcinfo->isnull); + } + + /* see hash_array_extended() */ + result = (result << 5) - result + element_hash; + } + + pfree(values); + pfree(nulls); + ReleaseTupleDesc(tupdesc); + + /* Avoid leaking memory when handed toasted input. */ + PG_FREE_IF_COPY(record, 0); + + PG_RETURN_UINT64(result); +} diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 140339073b6..ae232991623 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -1358,13 +1358,18 @@ op_hashjoinable(Oid opno, Oid inputtype) TypeCacheEntry *typentry; /* As in op_mergejoinable, let the typcache handle the hard cases */ - /* Eventually we'll need a similar case for record_eq ... */ if (opno == ARRAY_EQ_OP) { typentry = lookup_type_cache(inputtype, TYPECACHE_HASH_PROC); if (typentry->hash_proc == F_HASH_ARRAY) result = true; } + else if (opno == RECORD_EQ_OP) + { + typentry = lookup_type_cache(inputtype, TYPECACHE_HASH_PROC); + if (typentry->hash_proc == F_HASH_RECORD) + result = true; + } else { /* For all other operators, rely on pg_operator.oprcanhash */ diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index 98ab14ace2a..dca1d48e895 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -97,8 +97,10 @@ static TypeCacheEntry *firstDomainTypeEntry = NULL; #define TCFLAGS_CHECKED_FIELD_PROPERTIES 0x004000 #define TCFLAGS_HAVE_FIELD_EQUALITY 0x008000 #define TCFLAGS_HAVE_FIELD_COMPARE 0x010000 -#define TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS 0x020000 -#define TCFLAGS_DOMAIN_BASE_IS_COMPOSITE 0x040000 +#define TCFLAGS_HAVE_FIELD_HASHING 0x020000 +#define TCFLAGS_HAVE_FIELD_EXTENDED_HASHING 0x040000 +#define TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS 0x080000 +#define TCFLAGS_DOMAIN_BASE_IS_COMPOSITE 0x100000 /* The flags associated with equality/comparison/hashing are all but these: */ #define TCFLAGS_OPERATOR_FLAGS \ @@ -297,6 +299,8 @@ static bool array_element_has_extended_hashing(TypeCacheEntry *typentry); static void cache_array_element_properties(TypeCacheEntry *typentry); static bool record_fields_have_equality(TypeCacheEntry *typentry); static bool record_fields_have_compare(TypeCacheEntry *typentry); +static bool record_fields_have_hashing(TypeCacheEntry *typentry); +static bool record_fields_have_extended_hashing(TypeCacheEntry *typentry); static void cache_record_field_properties(TypeCacheEntry *typentry); static bool range_element_has_hashing(TypeCacheEntry *typentry); static bool range_element_has_extended_hashing(TypeCacheEntry *typentry); @@ -677,18 +681,16 @@ lookup_type_cache(Oid type_id, int flags) HASHSTANDARD_PROC); /* - * As above, make sure hash_array will succeed. We don't currently - * support hashing for composite types, but when we do, we'll need - * more logic here to check that case too. + * As above, make sure hash_array, hash_record, or hash_range will + * succeed. */ if (hash_proc == F_HASH_ARRAY && !array_element_has_hashing(typentry)) hash_proc = InvalidOid; - - /* - * Likewise for hash_range. - */ - if (hash_proc == F_HASH_RANGE && + else if (hash_proc == F_HASH_RECORD && + !record_fields_have_hashing(typentry)) + hash_proc = InvalidOid; + else if (hash_proc == F_HASH_RANGE && !range_element_has_hashing(typentry)) hash_proc = InvalidOid; @@ -721,18 +723,16 @@ lookup_type_cache(Oid type_id, int flags) HASHEXTENDED_PROC); /* - * As above, make sure hash_array_extended will succeed. We don't - * currently support hashing for composite types, but when we do, - * we'll need more logic here to check that case too. + * As above, make sure hash_array_extended, hash_record_extended, or + * hash_range_extended will succeed. */ if (hash_extended_proc == F_HASH_ARRAY_EXTENDED && !array_element_has_extended_hashing(typentry)) hash_extended_proc = InvalidOid; - - /* - * Likewise for hash_range_extended. - */ - if (hash_extended_proc == F_HASH_RANGE_EXTENDED && + else if (hash_extended_proc == F_HASH_RECORD_EXTENDED && + !record_fields_have_extended_hashing(typentry)) + hash_extended_proc = InvalidOid; + else if (hash_extended_proc == F_HASH_RANGE_EXTENDED && !range_element_has_extended_hashing(typentry)) hash_extended_proc = InvalidOid; @@ -1447,6 +1447,22 @@ record_fields_have_compare(TypeCacheEntry *typentry) return (typentry->flags & TCFLAGS_HAVE_FIELD_COMPARE) != 0; } +static bool +record_fields_have_hashing(TypeCacheEntry *typentry) +{ + if (!(typentry->flags & TCFLAGS_CHECKED_FIELD_PROPERTIES)) + cache_record_field_properties(typentry); + return (typentry->flags & TCFLAGS_HAVE_FIELD_HASHING) != 0; +} + +static bool +record_fields_have_extended_hashing(TypeCacheEntry *typentry) +{ + if (!(typentry->flags & TCFLAGS_CHECKED_FIELD_PROPERTIES)) + cache_record_field_properties(typentry); + return (typentry->flags & TCFLAGS_HAVE_FIELD_EXTENDED_HASHING) != 0; +} + static void cache_record_field_properties(TypeCacheEntry *typentry) { @@ -1456,8 +1472,12 @@ cache_record_field_properties(TypeCacheEntry *typentry) * everything will (we may get a failure at runtime ...) */ if (typentry->type_id == RECORDOID) + { typentry->flags |= (TCFLAGS_HAVE_FIELD_EQUALITY | - TCFLAGS_HAVE_FIELD_COMPARE); + TCFLAGS_HAVE_FIELD_COMPARE | + TCFLAGS_HAVE_FIELD_HASHING | + TCFLAGS_HAVE_FIELD_EXTENDED_HASHING); + } else if (typentry->typtype == TYPTYPE_COMPOSITE) { TupleDesc tupdesc; @@ -1474,7 +1494,9 @@ cache_record_field_properties(TypeCacheEntry *typentry) /* Have each property if all non-dropped fields have the property */ newflags = (TCFLAGS_HAVE_FIELD_EQUALITY | - TCFLAGS_HAVE_FIELD_COMPARE); + TCFLAGS_HAVE_FIELD_COMPARE | + TCFLAGS_HAVE_FIELD_HASHING | + TCFLAGS_HAVE_FIELD_EXTENDED_HASHING); for (i = 0; i < tupdesc->natts; i++) { TypeCacheEntry *fieldentry; @@ -1485,11 +1507,17 @@ cache_record_field_properties(TypeCacheEntry *typentry) fieldentry = lookup_type_cache(attr->atttypid, TYPECACHE_EQ_OPR | - TYPECACHE_CMP_PROC); + TYPECACHE_CMP_PROC | + TYPECACHE_HASH_PROC | + TYPECACHE_HASH_EXTENDED_PROC); if (!OidIsValid(fieldentry->eq_opr)) newflags &= ~TCFLAGS_HAVE_FIELD_EQUALITY; if (!OidIsValid(fieldentry->cmp_proc)) newflags &= ~TCFLAGS_HAVE_FIELD_COMPARE; + if (!OidIsValid(fieldentry->hash_proc)) + newflags &= ~TCFLAGS_HAVE_FIELD_HASHING; + if (!OidIsValid(fieldentry->hash_extended_proc)) + newflags &= ~TCFLAGS_HAVE_FIELD_EXTENDED_HASHING; /* We can drop out of the loop once we disprove all bits */ if (newflags == 0) @@ -1514,12 +1542,16 @@ cache_record_field_properties(TypeCacheEntry *typentry) } baseentry = lookup_type_cache(typentry->domainBaseType, TYPECACHE_EQ_OPR | - TYPECACHE_CMP_PROC); + TYPECACHE_CMP_PROC | + TYPECACHE_HASH_PROC | + TYPECACHE_HASH_EXTENDED_PROC); if (baseentry->typtype == TYPTYPE_COMPOSITE) { typentry->flags |= TCFLAGS_DOMAIN_BASE_IS_COMPOSITE; typentry->flags |= baseentry->flags & (TCFLAGS_HAVE_FIELD_EQUALITY | - TCFLAGS_HAVE_FIELD_COMPARE); + TCFLAGS_HAVE_FIELD_COMPARE | + TCFLAGS_HAVE_FIELD_HASHING | + TCFLAGS_HAVE_FIELD_EXTENDED_HASHING); } } typentry->flags |= TCFLAGS_CHECKED_FIELD_PROPERTIES; |