|
19 | 19 | #include "access/detoast.h"
|
20 | 20 | #include "access/htup_details.h"
|
21 | 21 | #include "catalog/pg_type.h"
|
| 22 | +#include "common/hashfn.h" |
22 | 23 | #include "funcapi.h"
|
23 | 24 | #include "libpq/pqformat.h"
|
24 | 25 | #include "miscadmin.h"
|
@@ -1766,3 +1767,251 @@ btrecordimagecmp(PG_FUNCTION_ARGS)
|
1766 | 1767 | {
|
1767 | 1768 | PG_RETURN_INT32(record_image_cmp(fcinfo));
|
1768 | 1769 | }
|
| 1770 | + |
| 1771 | + |
| 1772 | +/* |
| 1773 | + * Row type hash functions |
| 1774 | + */ |
| 1775 | + |
| 1776 | +Datum |
| 1777 | +hash_record(PG_FUNCTION_ARGS) |
| 1778 | +{ |
| 1779 | + HeapTupleHeader record = PG_GETARG_HEAPTUPLEHEADER(0); |
| 1780 | + uint32 result = 0; |
| 1781 | + Oid tupType; |
| 1782 | + int32 tupTypmod; |
| 1783 | + TupleDesc tupdesc; |
| 1784 | + HeapTupleData tuple; |
| 1785 | + int ncolumns; |
| 1786 | + RecordCompareData *my_extra; |
| 1787 | + Datum *values; |
| 1788 | + bool *nulls; |
| 1789 | + |
| 1790 | + check_stack_depth(); /* recurses for record-type columns */ |
| 1791 | + |
| 1792 | + /* Extract type info from tuple */ |
| 1793 | + tupType = HeapTupleHeaderGetTypeId(record); |
| 1794 | + tupTypmod = HeapTupleHeaderGetTypMod(record); |
| 1795 | + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); |
| 1796 | + ncolumns = tupdesc->natts; |
| 1797 | + |
| 1798 | + /* Build temporary HeapTuple control structure */ |
| 1799 | + tuple.t_len = HeapTupleHeaderGetDatumLength(record); |
| 1800 | + ItemPointerSetInvalid(&(tuple.t_self)); |
| 1801 | + tuple.t_tableOid = InvalidOid; |
| 1802 | + tuple.t_data = record; |
| 1803 | + |
| 1804 | + /* |
| 1805 | + * We arrange to look up the needed hashing info just once per series |
| 1806 | + * of calls, assuming the record type doesn't change underneath us. |
| 1807 | + */ |
| 1808 | + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; |
| 1809 | + if (my_extra == NULL || |
| 1810 | + my_extra->ncolumns < ncolumns) |
| 1811 | + { |
| 1812 | + fcinfo->flinfo->fn_extra = |
| 1813 | + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, |
| 1814 | + offsetof(RecordCompareData, columns) + |
| 1815 | + ncolumns * sizeof(ColumnCompareData)); |
| 1816 | + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; |
| 1817 | + my_extra->ncolumns = ncolumns; |
| 1818 | + my_extra->record1_type = InvalidOid; |
| 1819 | + my_extra->record1_typmod = 0; |
| 1820 | + } |
| 1821 | + |
| 1822 | + if (my_extra->record1_type != tupType || |
| 1823 | + my_extra->record1_typmod != tupTypmod) |
| 1824 | + { |
| 1825 | + MemSet(my_extra->columns, 0, ncolumns * sizeof(ColumnCompareData)); |
| 1826 | + my_extra->record1_type = tupType; |
| 1827 | + my_extra->record1_typmod = tupTypmod; |
| 1828 | + } |
| 1829 | + |
| 1830 | + /* Break down the tuple into fields */ |
| 1831 | + values = (Datum *) palloc(ncolumns * sizeof(Datum)); |
| 1832 | + nulls = (bool *) palloc(ncolumns * sizeof(bool)); |
| 1833 | + heap_deform_tuple(&tuple, tupdesc, values, nulls); |
| 1834 | + |
| 1835 | + for (int i = 0; i < ncolumns; i++) |
| 1836 | + { |
| 1837 | + Form_pg_attribute att; |
| 1838 | + TypeCacheEntry *typentry; |
| 1839 | + uint32 element_hash; |
| 1840 | + |
| 1841 | + att = TupleDescAttr(tupdesc, i); |
| 1842 | + |
| 1843 | + if (att->attisdropped) |
| 1844 | + continue; |
| 1845 | + |
| 1846 | + /* |
| 1847 | + * Lookup the hash function if not done already |
| 1848 | + */ |
| 1849 | + typentry = my_extra->columns[i].typentry; |
| 1850 | + if (typentry == NULL || |
| 1851 | + typentry->type_id != att->atttypid) |
| 1852 | + { |
| 1853 | + typentry = lookup_type_cache(att->atttypid, |
| 1854 | + TYPECACHE_HASH_PROC_FINFO); |
| 1855 | + if (!OidIsValid(typentry->hash_proc_finfo.fn_oid)) |
| 1856 | + ereport(ERROR, |
| 1857 | + (errcode(ERRCODE_UNDEFINED_FUNCTION), |
| 1858 | + errmsg("could not identify a hash function for type %s", |
| 1859 | + format_type_be(typentry->type_id)))); |
| 1860 | + my_extra->columns[i].typentry = typentry; |
| 1861 | + } |
| 1862 | + |
| 1863 | + /* Compute hash of element */ |
| 1864 | + if (nulls[i]) |
| 1865 | + { |
| 1866 | + element_hash = 0; |
| 1867 | + } |
| 1868 | + else |
| 1869 | + { |
| 1870 | + LOCAL_FCINFO(locfcinfo, 1); |
| 1871 | + |
| 1872 | + InitFunctionCallInfoData(*locfcinfo, &typentry->hash_proc_finfo, 1, |
| 1873 | + att->attcollation, NULL, NULL); |
| 1874 | + locfcinfo->args[0].value = values[i]; |
| 1875 | + locfcinfo->args[0].isnull = false; |
| 1876 | + element_hash = DatumGetUInt32(FunctionCallInvoke(locfcinfo)); |
| 1877 | + |
| 1878 | + /* We don't expect hash support functions to return null */ |
| 1879 | + Assert(!locfcinfo->isnull); |
| 1880 | + } |
| 1881 | + |
| 1882 | + /* see hash_array() */ |
| 1883 | + result = (result << 5) - result + element_hash; |
| 1884 | + } |
| 1885 | + |
| 1886 | + pfree(values); |
| 1887 | + pfree(nulls); |
| 1888 | + ReleaseTupleDesc(tupdesc); |
| 1889 | + |
| 1890 | + /* Avoid leaking memory when handed toasted input. */ |
| 1891 | + PG_FREE_IF_COPY(record, 0); |
| 1892 | + |
| 1893 | + PG_RETURN_UINT32(result); |
| 1894 | +} |
| 1895 | + |
| 1896 | +Datum |
| 1897 | +hash_record_extended(PG_FUNCTION_ARGS) |
| 1898 | +{ |
| 1899 | + HeapTupleHeader record = PG_GETARG_HEAPTUPLEHEADER(0); |
| 1900 | + uint64 seed = PG_GETARG_INT64(1); |
| 1901 | + uint64 result = 0; |
| 1902 | + Oid tupType; |
| 1903 | + int32 tupTypmod; |
| 1904 | + TupleDesc tupdesc; |
| 1905 | + HeapTupleData tuple; |
| 1906 | + int ncolumns; |
| 1907 | + RecordCompareData *my_extra; |
| 1908 | + Datum *values; |
| 1909 | + bool *nulls; |
| 1910 | + |
| 1911 | + check_stack_depth(); /* recurses for record-type columns */ |
| 1912 | + |
| 1913 | + /* Extract type info from tuple */ |
| 1914 | + tupType = HeapTupleHeaderGetTypeId(record); |
| 1915 | + tupTypmod = HeapTupleHeaderGetTypMod(record); |
| 1916 | + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); |
| 1917 | + ncolumns = tupdesc->natts; |
| 1918 | + |
| 1919 | + /* Build temporary HeapTuple control structure */ |
| 1920 | + tuple.t_len = HeapTupleHeaderGetDatumLength(record); |
| 1921 | + ItemPointerSetInvalid(&(tuple.t_self)); |
| 1922 | + tuple.t_tableOid = InvalidOid; |
| 1923 | + tuple.t_data = record; |
| 1924 | + |
| 1925 | + /* |
| 1926 | + * We arrange to look up the needed hashing info just once per series |
| 1927 | + * of calls, assuming the record type doesn't change underneath us. |
| 1928 | + */ |
| 1929 | + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; |
| 1930 | + if (my_extra == NULL || |
| 1931 | + my_extra->ncolumns < ncolumns) |
| 1932 | + { |
| 1933 | + fcinfo->flinfo->fn_extra = |
| 1934 | + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, |
| 1935 | + offsetof(RecordCompareData, columns) + |
| 1936 | + ncolumns * sizeof(ColumnCompareData)); |
| 1937 | + my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; |
| 1938 | + my_extra->ncolumns = ncolumns; |
| 1939 | + my_extra->record1_type = InvalidOid; |
| 1940 | + my_extra->record1_typmod = 0; |
| 1941 | + } |
| 1942 | + |
| 1943 | + if (my_extra->record1_type != tupType || |
| 1944 | + my_extra->record1_typmod != tupTypmod) |
| 1945 | + { |
| 1946 | + MemSet(my_extra->columns, 0, ncolumns * sizeof(ColumnCompareData)); |
| 1947 | + my_extra->record1_type = tupType; |
| 1948 | + my_extra->record1_typmod = tupTypmod; |
| 1949 | + } |
| 1950 | + |
| 1951 | + /* Break down the tuple into fields */ |
| 1952 | + values = (Datum *) palloc(ncolumns * sizeof(Datum)); |
| 1953 | + nulls = (bool *) palloc(ncolumns * sizeof(bool)); |
| 1954 | + heap_deform_tuple(&tuple, tupdesc, values, nulls); |
| 1955 | + |
| 1956 | + for (int i = 0; i < ncolumns; i++) |
| 1957 | + { |
| 1958 | + Form_pg_attribute att; |
| 1959 | + TypeCacheEntry *typentry; |
| 1960 | + uint64 element_hash; |
| 1961 | + |
| 1962 | + att = TupleDescAttr(tupdesc, i); |
| 1963 | + |
| 1964 | + if (att->attisdropped) |
| 1965 | + continue; |
| 1966 | + |
| 1967 | + /* |
| 1968 | + * Lookup the hash function if not done already |
| 1969 | + */ |
| 1970 | + typentry = my_extra->columns[i].typentry; |
| 1971 | + if (typentry == NULL || |
| 1972 | + typentry->type_id != att->atttypid) |
| 1973 | + { |
| 1974 | + typentry = lookup_type_cache(att->atttypid, |
| 1975 | + TYPECACHE_HASH_EXTENDED_PROC_FINFO); |
| 1976 | + if (!OidIsValid(typentry->hash_extended_proc_finfo.fn_oid)) |
| 1977 | + ereport(ERROR, |
| 1978 | + (errcode(ERRCODE_UNDEFINED_FUNCTION), |
| 1979 | + errmsg("could not identify an extended hash function for type %s", |
| 1980 | + format_type_be(typentry->type_id)))); |
| 1981 | + my_extra->columns[i].typentry = typentry; |
| 1982 | + } |
| 1983 | + |
| 1984 | + /* Compute hash of element */ |
| 1985 | + if (nulls[i]) |
| 1986 | + { |
| 1987 | + element_hash = 0; |
| 1988 | + } |
| 1989 | + else |
| 1990 | + { |
| 1991 | + LOCAL_FCINFO(locfcinfo, 2); |
| 1992 | + |
| 1993 | + InitFunctionCallInfoData(*locfcinfo, &typentry->hash_extended_proc_finfo, 2, |
| 1994 | + att->attcollation, NULL, NULL); |
| 1995 | + locfcinfo->args[0].value = values[i]; |
| 1996 | + locfcinfo->args[0].isnull = false; |
| 1997 | + locfcinfo->args[1].value = Int64GetDatum(seed); |
| 1998 | + locfcinfo->args[0].isnull = false; |
| 1999 | + element_hash = DatumGetUInt64(FunctionCallInvoke(locfcinfo)); |
| 2000 | + |
| 2001 | + /* We don't expect hash support functions to return null */ |
| 2002 | + Assert(!locfcinfo->isnull); |
| 2003 | + } |
| 2004 | + |
| 2005 | + /* see hash_array_extended() */ |
| 2006 | + result = (result << 5) - result + element_hash; |
| 2007 | + } |
| 2008 | + |
| 2009 | + pfree(values); |
| 2010 | + pfree(nulls); |
| 2011 | + ReleaseTupleDesc(tupdesc); |
| 2012 | + |
| 2013 | + /* Avoid leaking memory when handed toasted input. */ |
| 2014 | + PG_FREE_IF_COPY(record, 0); |
| 2015 | + |
| 2016 | + PG_RETURN_UINT64(result); |
| 2017 | +} |
0 commit comments