On Sun, Mar 22, 2015 at 1:01 PM, Andrew Gierth
<and...@tao11.riddles.org.uk> wrote:
> The substance of the code is unchanged from my original patch.  I didn't
> add diagnostic output to numeric_abbrev_abort, see my separate post
> about the suggestion of a GUC for that.

I don't think that V2 really changed the substance, which seems to be
the implication of your remarks here. You disagreed with my decision
on NULL values - causing me to reconsider my position (so that's now
irrelevant) - and you disagreed with not including support for 32-bit
platforms. Those were the only non-stylistic changes, though. I
certainly didn't change any details of the algorithm that you
proposed, which, FWIW, I think is rather clever. I added a few
defensive assertions to the encoding/conversion routine (which I see
you've removed in V3, a long with a couple of other helpful
assertions), and restructured and expanded upon the comments, but
that's all.

You haven't really taken into my account my V2 feedback with this V3
revision. Even after you yourself specifically called out your
non-explanation of excess-44 as a possible point of confusion for
readers of your patch, you didn't change or expand upon your remarks
on that one iota.

I see that code like this (from your V1) appears in your V3, as if V2
never happened:

+/*
+ * non-fmgr interface to the comparison routine to allow sortsupport to elide
+ * the fmgr call.  The saving here is small given how slow numeric
+ * comparisons are, but there's no reason not to implement it.
+ */

This comment is totally redundant at best, and misleading at worst. Of
course we have a non-fmgr interface - it's needed to make abbreviation
work. And of course *any* opclass that performs abbreviation won't get
any great saving from cases where only the fmgr interface is elided
(e.g. numeric is not the leading sorttuple attribute). Text is unusual
in that there is a small additional saving over fmgr-elision for
obscure reasons.

Ditto for comments like this, which re-appear in V3:

+/*
+ * Ordinary (non-sortsupport) comparisons follow.
+ */

Your V3 has obsolete comments here:

+ nss = palloc(sizeof(NumericSortSupport));
+
+ /*
+ * palloc a buffer for handling unaligned packed values in addition to
+ * the support struct
+ */
+ nss->buf = palloc(VARATT_SHORT_MAX + VARHDRSZ + 1);

I still don't think you should be referencing the text opclass behavior here:

+ * number.  We make no attempt to estimate the cardinality of the real values,
+ * since it plays no part in the cost model here (if the abbreviation is equal,
+ * the cost of comparing equal and unequal underlying values is comparable).
+ * We discontinue even checking for abort (saving us the hashing overhead) if
+ * the estimated cardinality gets to 100k; that would be enough to support many
+ * billions of rows while doing no worse than breaking even.

This is dubious:

+#if DEC_DIGITS != 4
+#error "Numeric bases other than 10000 are no longer supported"
+#endif

Because there is a bunch of code within numeric.c that deals with the
DEC_DIGITS != 4 case. For example, this code has been within numeric.c
forever:

#if DEC_DIGITS == 4 || DEC_DIGITS == 2
static NumericDigit const_ten_data[1] = {10};
static NumericVar const_ten =
{1, 0, NUMERIC_POS, 0, NULL, const_ten_data};
#elif DEC_DIGITS == 1
static NumericDigit const_ten_data[1] = {1};
static NumericVar const_ten =
{1, 1, NUMERIC_POS, 0, NULL, const_ten_data};
#endif

As has this:

while (ndigits-- > 0)
{
#if DEC_DIGITS == 4
    *digits++ = ((decdigits[i] * 10 + decdigits[i + 1]) * 10 +
    decdigits[i + 2]) * 10 + decdigits[i + 3];
#elif DEC_DIGITS == 2
    *digits++ = decdigits[i] * 10 + decdigits[i + 1];
#elif DEC_DIGITS == 1
    *digits++ = decdigits[i];
#else
#error unsupported NBASE
#endif
    i += DEC_DIGITS;
}

I tend to think that when Tom wrote this code back in 2003, he thought
it might be useful to change DEC_DIGITS on certain builds. And so, we
ought to continue to support it to the extent that we already do,
allowing these cases to opt out of abbreviation in an informed manner
(since it seems hard to make abbreviation work with DEC_DIGITS != 4
builds). In any case, you should have deleted all this code (which
there is rather a lot of) in proposing to not support DEC_DIGITS != 4
builds generally.

Attached revision, V4, incorporates some of your V3 changes. As I
mentioned, I changed my mind on the counting of non-NULL values, which
V4 reflects...but there are also comments that now make it clear why
that might be useful.

I've retained your new allocate once approach to buffer sizing, which
seems sound if a little obscure.

This abort function code (from your V1 + V3) seems misleading:

+ if (memtupcount < 10000 || nss->input_count < 10000 || !nss->estimating)
+     return false;

It's impossible for "memtupcount < 10000" while "nss->input_count <
10000" as well, since the latter counts a subset of what the former
counts. So I only check the latter now.

I have not added back 32-bit support, which, IMV isn't worth it at
this time - it is, after all, almost a fully independent abbreviation
scheme to the 64-bit scheme. Right now we ought to be cutting scope,
or 9.5 will never reach feature freeze. I have already spent
considerably more time on this patch than I had intended to. I need to
catch a flight to New York at the crack of dawn tomorrow, to get to
pgConf US, and I have much work to do on my UPSERT patch, so I've
simply run out of time. If the committer that picks this up wants to
look at 32-bit support, that may make sense. I think that given the
current lack of cycles from everyone, we'll be doing well to even get
64-bit numeric abbreviation support in 9.5. I ask that you have a some
perspective on cutting 32-bit support, Andrew. In any case, I've
personally run out of time for this for 9.5.

We may add a patch to change things so that a GUC controls
abbreviation debug output, and we may add a patch that standardizes
that INT64_MIN and INT64_MAX are available everywhere. But unless and
until those other patches are committed, I see no reason to assume
that they will be, and so those aspects remain unchanged from my V2.
Unless there is strong opposition, or bugs come to light, or the
GUC/macros are added by independent commits to the master branch, I
expect to mark this revision "Ready for Committer" shortly.

Thanks
-- 
Peter Geoghegan
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index ff9bfcc..0e64198 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -29,6 +29,7 @@
 #include "access/hash.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
+#include "lib/hyperloglog.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
@@ -36,6 +37,19 @@
 #include "utils/builtins.h"
 #include "utils/int8.h"
 #include "utils/numeric.h"
+#include "utils/sortsupport.h"
+
+#ifndef INT64_MIN
+#define INT64_MIN	(-INT64CONST(0x7FFFFFFFFFFFFFFF) - 1)
+#endif
+#ifndef INT64_MAX
+#define INT64_MAX	INT64CONST(0x7FFFFFFFFFFFFFFF)
+#endif
+
+/* Abbreviation sortsupport encoding scheme supported? */
+#ifndef USE_FLOAT8_BYVAL
+#define DISABLE_NUMERIC_ABBREV
+#endif
 
 /* ----------
  * Uncomment the following to enable compilation of dump_numeric()
@@ -275,6 +289,19 @@ typedef struct
 
 
 /* ----------
+ * sortsupport data
+ * ----------
+ */
+typedef struct
+{
+	bool				estimating;	/* Still estimating cardinality? */
+	void			   *buf;		/* Scratch, for handling unaligned packed values */
+	int64				ntuples;	/* number of non-null values seen */
+	hyperLogLogState	abbr_card;	/* Abbreviated key cardinality state */
+} NumericSortSupport;
+
+
+/* ----------
  * Some preinitialized constants
  * ----------
  */
@@ -410,6 +437,14 @@ static double numeric_to_double_no_overflow(Numeric num);
 static double numericvar_to_double_no_overflow(NumericVar *var);
 
 static int	cmp_numerics(Numeric num1, Numeric num2);
+static int	numericfastcmp(Datum x, Datum y, SortSupport ssup);
+#ifndef DISABLE_NUMERIC_ABBREV
+static Datum numeric_abbrev_convert(Datum original, SortSupport ssup);
+static bool numeric_abbrev_abort(int memtupcount, SortSupport ssup);
+static int	numericcmp_abbrev(Datum x, Datum y, SortSupport ssup);
+static Datum numeric_abbrev_convert_worker(NumericVar *var,
+			   NumericSortSupport *nss);
+#endif
 static int	cmp_var(NumericVar *var1, NumericVar *var2);
 static int cmp_var_common(const NumericDigit *var1digits, int var1ndigits,
 			   int var1weight, int var1sign,
@@ -1507,8 +1542,31 @@ compute_bucket(Numeric operand, Numeric bound1, Numeric bound2,
  * Note: btree indexes need these routines not to leak memory; therefore,
  * be careful to free working copies of toasted datums.  Most places don't
  * need to be so careful.
+ *
+ * sortsupport:
+ *
+ * Numeric uses a sortsupport routine, which is mostly effective in speeding up
+ * sorts because of its abbreviation capability.
+ *
+ * The abbreviation code makes assumptions about word sizes, such as that the
+ * value of a 4-decimal-digit field can fit in 14 bits rather than needing 16.
+ * int64 is used as the underlying representation if USE_FLOAT8_BYVAL is set
+ * (which, despite its name, indicates if int64 is pass-by-value), and if
+ * abbreviation is not otherwise disabled.  Otherwise, abbreviation isn't
+ * performed at all.
  * ----------------------------------------------------------------------
  */
+#ifdef DEBUG_ABBREV_KEYS
+#define DEBUG_elog_output	DEBUG1
+#endif
+
+/* Abbreviation related constants */
+/* NaN is encoded as lowest possible negative integer value */
+#define NUMERIC_ABBREV_NAN				((int64) INT64_MIN)
+/* "Negative infinity" representation for large negative scales, and zero */
+#define NUMERIC_ABBREV_NEG_INFINITY		((int64) 0)
+/* "Positive infinity" representation for large positive scales */
+#define NUMERIC_ABBREV_POS_INFINITY		((int64) INT64_MAX)
 
 
 Datum
@@ -1650,6 +1708,352 @@ cmp_numerics(Numeric num1, Numeric num2)
 }
 
 Datum
+numericsortsupport(PG_FUNCTION_ARGS)
+{
+	SortSupport		ssup = (SortSupport) PG_GETARG_POINTER(0);
+
+#ifndef DISABLE_NUMERIC_ABBREV
+
+	StaticAssertStmt(DEC_DIGITS == 4,
+					 "numericsortsupport assumes 4 dec digits/NBASE digits");
+
+	if (ssup->abbreviate)
+	{
+		NumericSortSupport *nss;
+		MemoryContext		oldcontext = MemoryContextSwitchTo(ssup->ssup_cxt);
+
+		ssup->abbrev_full_comparator = numericfastcmp;
+		ssup->comparator = numericcmp_abbrev;
+		ssup->abbrev_converter = numeric_abbrev_convert;
+		ssup->abbrev_abort = numeric_abbrev_abort;
+
+		nss = palloc(sizeof(NumericSortSupport));
+		nss->estimating = true;
+		/* Allocate a buffer for handling unaligned packed values */
+		nss->buf = palloc(MAXALIGN(VARATT_SHORT_MAX) + VARHDRSZ);
+		nss->ntuples = 0;
+		initHyperLogLog(&nss->abbr_card, 10);
+		ssup->ssup_extra = nss;
+
+		MemoryContextSwitchTo(oldcontext);
+	}
+	else
+#endif		/* DISABLE_NUMERIC_ABBREV */
+	{
+		/*
+		 * Set ssup->comparator to a function which can be used to directly
+		 * compare two datums, avoiding the overhead of a trip through the fmgr
+		 * layer for every comparison, which can be substantial.
+		 */
+		ssup->comparator = numericfastcmp;
+	}
+
+	PG_RETURN_VOID();
+}
+
+/*
+ * sortsupport comparison func
+ */
+static int
+numericfastcmp(Datum x, Datum y, SortSupport ssup)
+{
+	Numeric		num1 = DatumGetNumeric(x);
+	Numeric		num2 = DatumGetNumeric(y);
+	int			result;
+
+	result = cmp_numerics(num1, num2);
+
+	if ((Pointer) num1 != DatumGetPointer(x))
+		pfree(num1);
+	if ((Pointer) num2 != DatumGetPointer(y))
+		pfree(num2);
+
+	return result;
+}
+
+#ifndef DISABLE_NUMERIC_ABBREV
+/*
+ * Conversion routine for sortsupport.  Converts original numeric to
+ * abbreviated key representation.
+ */
+static Datum
+numeric_abbrev_convert(Datum original, SortSupport ssup)
+{
+	NumericSortSupport *nss = (NumericSortSupport *) ssup->ssup_extra;
+	void			   *authoritative_data = PG_DETOAST_DATUM_PACKED(original);
+
+	/* working state */
+	Numeric				value;
+	Datum				res;
+
+	/* Count a non-NULL value */
+	nss->ntuples += 1;
+
+	if (VARATT_IS_SHORT(authoritative_data))
+	{
+		void	   *buf = nss->buf;
+		Size		sz = VARSIZE_SHORT(authoritative_data) - VARHDRSZ_SHORT;
+
+		/*
+		 * This is to handle packed datums without needing a palloc/pfree
+		 * cycle;  we keep and reuse a buffer large enough to handle any short
+		 * datum.
+		 */
+		Assert(sz <= VARATT_SHORT_MAX - VARHDRSZ_SHORT);
+
+		SET_VARSIZE(buf, VARHDRSZ + sz);
+		memcpy(VARDATA(buf), VARDATA_SHORT(authoritative_data), sz);
+
+		value = (Numeric) buf;
+	}
+	else
+	{
+		value = (Numeric) authoritative_data;
+	}
+
+	if (NUMERIC_IS_NAN(value))
+	{
+		res = NUMERIC_ABBREV_NAN;
+	}
+	else
+	{
+		NumericVar	var;
+
+		init_var_from_num(value, &var);
+
+		/*
+		 * Worker routine operates on NumericVar representation, to generate
+		 * abbreviated int64 representation
+		 */
+		res = numeric_abbrev_convert_worker(&var, nss);
+
+		if (nss->estimating)
+		{
+			/* Hash abbreviated key */
+			uint32				hash;
+			uint32				lohalf,
+								hihalf;
+
+			StaticAssertStmt(SIZEOF_DATUM == 8,
+							 "numeric abbreviation assumes 8 byte Datum size");
+
+			lohalf = (uint32) res;
+			hihalf = (uint32) (res >> 32);
+			hash = hash_uint32(lohalf ^ hihalf);
+
+			addHyperLogLog(&nss->abbr_card, hash);
+		}
+	}
+
+	/* Don't leak memory here */
+	if ((Pointer) authoritative_data != DatumGetPointer(original))
+		pfree(authoritative_data);
+
+	return res;
+}
+
+static bool
+numeric_abbrev_abort(int memtupcount, SortSupport ssup)
+{
+	NumericSortSupport	   *nss = (NumericSortSupport *) ssup->ssup_extra;
+	double					abbrev_distinct;
+
+	Assert(ssup->abbreviate);
+
+	/*
+	 * Have some patience, or defer to previous decision to no longer estimate
+	 */
+	if (nss->ntuples < 10000 || !nss->estimating)
+		return false;
+
+	abbrev_distinct = estimateHyperLogLog(&nss->abbr_card);
+
+#ifdef DEBUG_ABBREV_KEYS
+	{
+		double norm_abbrev_card = abbrev_distinct / (double) memtupcount;
+
+		elog(DEBUG_elog_output, "abbrev_distinct after %d: %f (norm_abbrev_card: %f)",
+			 memtupcount, abbrev_distinct, norm_abbrev_card);
+	}
+#endif
+
+	/*
+	 * If we have > 100k distinct values, then even if we were sorting many
+	 * billions of rows we'd likely still break even.  Besides, the additional
+	 * penalty of actually undoing abbreviation would probably not be worth it.
+	 * Stop even counting at 100k tuples.
+	 */
+	if (abbrev_distinct > 100000.0)
+	{
+		nss->estimating = false;
+#ifdef DEBUG_ABBREV_KEYS
+	elog(DEBUG_elog_output, "gave up on considering aborting at %d. abbrev_distinct: %f",
+		 memtupcount, abbrev_distinct);
+#endif
+		return false;
+	}
+
+	/*
+	 * Target minimum cardinality is 1 per ~10k tuples.  (The break even point
+	 * is somewhere between one per 100k tuples, where abbreviation has a very
+	 * slight penalty, and 1 per 10k, where it wins by a measurable
+	 * percentage).  We make a point of specially tracking the non-NULL numeric
+	 * tuple count so that NULL values are not weighed against the count of
+	 * distinct actual values;  this avoids an expensive abort when the
+	 * absolute cost of abbreviation is low, even if the projected benefits are
+	 * even lower (due to having very few non-NULL values).  Besides, the
+	 * observation that most values seen so far are NULL doesn't mean that that
+	 * won't change.  While it's probably true that we have little to gain by
+	 * proceeding with abbreviation in the event of many NULLs, this might
+	 * still turn out to be dramatically wrong due to skew in the data.  On the
+	 * other hand, it's definitely true that we have relatively little to lose
+	 * by proceeding, since NULL values are not abbreviated.
+	 *
+	 * We use the relatively pessimistic 10k threshold, and add a
+	 * 0.5 tuple fudge factor, because it allows us to abort earlier on
+	 * genuinely pathological data where we've had exactly one abbreviated
+	 * value in the first 10k (non-null) rows.
+	 */
+	if (abbrev_distinct > nss->ntuples / 10000.0 + 0.5)
+		return false;
+
+#ifdef DEBUG_ABBREV_KEYS
+	elog(DEBUG_elog_output, "would have aborted abbreviation due to worst-case at %d. abbrev_distinct: %f",
+		 memtupcount, abbrev_distinct);
+	/* Actually abort only when debugging is disabled */
+	return false;
+#endif
+
+	return true;
+}
+
+/*
+ * Abbreviated key comparison func
+ */
+static int
+numericcmp_abbrev(Datum x, Datum y, SortSupport ssup)
+{
+	int64		a = DatumGetInt64(x);
+	int64		b = DatumGetInt64(y);
+
+	/*
+	 * NB:  This is intentionally backwards, because the abbreviated
+	 * representation has its sign inverted (this is used to seamlessly handle
+	 * NaN comparisons, and representational saturation)
+	 */
+	if (a < b)
+		return 1;
+	else if (a > b)
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Guts of encoding scheme
+ */
+static Datum
+numeric_abbrev_convert_worker(NumericVar *var, NumericSortSupport *nss)
+{
+	int		ndigits = var->ndigits;
+	int		weight = var->weight;
+	int64	res;
+
+	Assert(var->sign != NUMERIC_NAN);
+
+	if (ndigits == 0 || weight < -44)
+	{
+		res = NUMERIC_ABBREV_NEG_INFINITY;
+	}
+	else if (weight > 83)
+	{
+		res = NUMERIC_ABBREV_POS_INFINITY;
+	}
+	else
+	{
+		int64	exponent, abs;
+
+		/*
+		 * First, store "weight" in first 7 binary digits (7 binary digits is
+		 * the smallest number of digits that allows us to represent weights
+		 * -44 to 83 inclusive).
+		 *
+		 * The weight is stored in excess-44 -- the original weight in digit
+		 * words (i.e. powers of NBASE/10000).  The "excess-K"/offset binary
+		 * technique is also used with IEEE-754 floating point numbers.  As
+		 * with IEEE-754, we use an exponent without a sign (a 7-bit exponent
+		 * without a sign).  And as with IEEE-754, the lack of a sign does not
+		 * imply that the exponent must be *logically* positive.
+		 *
+		 * Adding 44 to "weight" makes the smallest possible "exponent" 7
+		 * binary digit number (where "res" hasn't already saturated to
+		 * NUMERIC_ABBREV_NEG_INFINITY) have the value zero in this
+		 * representation (since the smallest possible "weight" is -44).  If
+		 * the "weight" is 83, the largest possible, then "exponent" will have
+		 * 0b1111111 as its first 7 binary digits.  The 7-digit exponent is
+		 * always positive (forgetting for a moment that it's really
+		 * excess-44), and thus the bits could be equivalently interpreted as
+		 * either signed or unsigned.
+		 *
+		 * We left shift 56 bits in order to have this 7-bit representation
+		 * stored at the beginning of "exponent", a two's complement/signed
+		 * 64-bit integer.  In doing so, an eighth bit is reserved, since we
+		 * need it for the sign.
+		 */
+		exponent = ((int64) (weight + 44) << 56);
+		abs = 0;
+
+		/*
+		 * Append 4 x 14-bit packed digit words into remaining 56 bits.
+		 * 14-bits of storage should be enough to represent the largest
+		 * possible base-NBASE digit, despite the fact that those are stored as
+		 * int16.
+		 */
+		switch (ndigits)
+		{
+			default:
+				abs |= ((int64) var->digits[3]);
+				/* FALLTHROUGH */
+			case 3:
+				abs |= ((int64) var->digits[2]) << 14;
+				/* FALLTHROUGH */
+			case 2:
+				abs |= ((int64) var->digits[1]) << 28;
+				/* FALLTHROUGH */
+			case 1:
+				abs |= ((int64) var->digits[0]) << 42;
+				break;
+		}
+
+		res = exponent | abs;
+	}
+
+	Assert(res >= 0);
+
+	/*
+	 * Flip the abbreviated int64 representation's sign, for positive numerics
+	 * only, since the 63-bit value is currently the absolute value.  With
+	 * this, the last detail of encoding things such that int64 comparisons can
+	 * be interpreted backwards (within the abbreviated comparator, as a proxy
+	 * for actual numeric comparisons) is complete.
+	 */
+	if (var->sign == NUMERIC_POS)
+		res = -res;
+
+	Assert(res > NUMERIC_ABBREV_NAN);
+
+	/*
+	 * The representable range is 10^-176 to 10^332, which is considered to be
+	 * good enough for most practical purposes.  Comparison of
+	 * 4 words means that at least 13 decimal digits are compared, which is
+	 * considered to be a reasonable compromise between effectiveness in
+	 * concentrating entropy, and efficiency in computing abbreviations.
+	 */
+	return Int64GetDatum(res);
+}
+#endif /* #ifndef DISABLE_NUMERIC_ABBREV */
+
+Datum
 hash_numeric(PG_FUNCTION_ARGS)
 {
 	Numeric		key = PG_GETARG_NUMERIC(0);
diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h
index 49d3d13..2461593 100644
--- a/src/include/catalog/pg_amproc.h
+++ b/src/include/catalog/pg_amproc.h
@@ -118,6 +118,7 @@ DATA(insert (	1984   829 829 1 836 ));
 DATA(insert (	1986   19 19 1 359 ));
 DATA(insert (	1986   19 19 2 3135 ));
 DATA(insert (	1988   1700 1700 1 1769 ));
+DATA(insert (	1988   1700 1700 2 3280 ));
 DATA(insert (	1989   26 26 1 356 ));
 DATA(insert (	1989   26 26 2 3134 ));
 DATA(insert (	1991   30 30 1 404 ));
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 3c218a3..bc2c8e3 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -2366,6 +2366,8 @@ DATA(insert OID = 1767 ( numeric_larger			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2
 DESCR("larger of two");
 DATA(insert OID = 1769 ( numeric_cmp			PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "1700 1700" _null_ _null_ _null_ _null_ numeric_cmp _null_ _null_ _null_ ));
 DESCR("less-equal-greater");
+DATA(insert OID = 3280 ( numericsortsupport		PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2278 "2281" _null_ _null_ _null_ _null_ numericsortsupport _null_ _null_ _null_ ));
+DESCR("sort support");
 DATA(insert OID = 1771 ( numeric_uminus			PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 1700 "1700" _null_ _null_ _null_ _null_ numeric_uminus _null_ _null_ _null_ ));
 DATA(insert OID = 1779 ( int8					PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 20 "1700" _null_ _null_ _null_ _null_ numeric_int8 _null_ _null_ _null_ ));
 DESCR("convert numeric to int8");
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 6310641..f518111 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -985,6 +985,7 @@ extern Datum numeric_gt(PG_FUNCTION_ARGS);
 extern Datum numeric_ge(PG_FUNCTION_ARGS);
 extern Datum numeric_lt(PG_FUNCTION_ARGS);
 extern Datum numeric_le(PG_FUNCTION_ARGS);
+extern Datum numericsortsupport(PG_FUNCTION_ARGS);
 extern Datum numeric_add(PG_FUNCTION_ARGS);
 extern Datum numeric_sub(PG_FUNCTION_ARGS);
 extern Datum numeric_mul(PG_FUNCTION_ARGS);
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to