From eb2011413eb96147f6c7b7ee30268fb26af932e4 Mon Sep 17 00:00:00 2001
From: Blake Smith <blakesmith0@gmail.com>
Date: Mon, 9 Sep 2013 09:31:42 -0500
Subject: [PATCH] Add 'gin_hstore_combined_ops' hstore indexing opclass.

This gin indexing strategy produces a combined key and value index entry
to support @> queries, and a key only index entry to support ? queries.
This can produce a significant speedup in @> queries, at the cost of a
larger index size.
---
 contrib/hstore/expected/hstore.out |  73 +++++++++++++++++++
 contrib/hstore/hstore--1.1.sql     |  25 +++++++
 contrib/hstore/hstore_gin.c        | 140 +++++++++++++++++++++++++++++++++++--
 contrib/hstore/sql/hstore.sql      |  16 +++++
 4 files changed, 247 insertions(+), 7 deletions(-)

diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out
index 2114143..7668ded 100644
--- a/contrib/hstore/expected/hstore.out
+++ b/contrib/hstore/expected/hstore.out
@@ -1437,6 +1437,79 @@ select distinct * from (values (hstore '' || ''),('')) v(h);
 (1 row)
 
 set enable_sort = true;
+-- gin combined opclass
+drop index hidx;
+create index hidx on testhstore using gin (h gin_hstore_combined_ops);
+set enable_seqscan=off;
+select count(*) from testhstore where h @> 'wait=>NULL';
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from testhstore where h @> 'wait=>CC';
+ count 
+-------
+    15
+(1 row)
+
+select count(*) from testhstore where h @> 'wait=>CC, public=>t';
+ count 
+-------
+     2
+(1 row)
+
+select count(*) from testhstore where h ? 'public';
+ count 
+-------
+   194
+(1 row)
+
+select count(*) from testhstore where h ?| ARRAY['public','disabled'];
+ count 
+-------
+   337
+(1 row)
+
+select count(*) from testhstore where h ?& ARRAY['public','disabled'];
+ count 
+-------
+    42
+(1 row)
+
+select count(*) from (select (each(h)).key from testhstore) as wow ;
+ count 
+-------
+  4781
+(1 row)
+
+select key, count(*) from (select (each(h)).key from testhstore) as wow group by key order by count desc, key;
+    key    | count 
+-----------+-------
+ line      |   884
+ query     |   207
+ pos       |   203
+ node      |   202
+ space     |   197
+ status    |   195
+ public    |   194
+ title     |   190
+ wait      |   190
+ org       |   189
+ user      |   189
+ coauthors |   188
+ disabled  |   185
+ indexed   |   184
+ cleaned   |   180
+ bad       |   179
+ date      |   179
+ world     |   176
+ state     |   172
+ subtitle  |   169
+ auth      |   168
+ abstract  |   161
+(22 rows)
+
 -- btree
 drop index hidx;
 create index hidx on testhstore using btree (h);
diff --git a/contrib/hstore/hstore--1.1.sql b/contrib/hstore/hstore--1.1.sql
index f415a72..af8e58b 100644
--- a/contrib/hstore/hstore--1.1.sql
+++ b/contrib/hstore/hstore--1.1.sql
@@ -535,3 +535,28 @@ AS
 	FUNCTION        3       gin_extract_hstore_query(internal, internal, int2, internal, internal),
 	FUNCTION        4       gin_consistent_hstore(internal, int2, internal, int4, internal, internal),
 	STORAGE         text;
+
+-- Gin combined opclass
+
+CREATE FUNCTION gin_extract_hstore_combined(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gin_extract_hstore_query_combined(internal, internal, int2, internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE OPERATOR CLASS gin_hstore_combined_ops
+FOR TYPE hstore USING gin
+AS
+	OPERATOR        7       @>,
+	OPERATOR        9       ?(hstore,text),
+	OPERATOR        10      ?|(hstore,text[]),
+	OPERATOR        11      ?&(hstore,text[]),
+	FUNCTION        1       bttextcmp(text,text),
+	FUNCTION        2       gin_extract_hstore_combined(internal, internal),
+	FUNCTION        3       gin_extract_hstore_query_combined(internal, internal, int2, internal, internal),
+	FUNCTION        4       gin_consistent_hstore(internal, int2, internal, int4, internal, internal),
+	STORAGE         text;
diff --git a/contrib/hstore/hstore_gin.c b/contrib/hstore/hstore_gin.c
index 2007801..f6c27fd 100644
--- a/contrib/hstore/hstore_gin.c
+++ b/contrib/hstore/hstore_gin.c
@@ -75,12 +75,8 @@ gin_extract_hstore(PG_FUNCTION_ARGS)
 	PG_RETURN_POINTER(entries);
 }
 
-PG_FUNCTION_INFO_V1(gin_extract_hstore_query);
-Datum		gin_extract_hstore_query(PG_FUNCTION_ARGS);
-
-Datum
-gin_extract_hstore_query(PG_FUNCTION_ARGS)
-{
+static Datum
+extract_hstore_query(PG_FUNCTION_ARGS, Datum (*extract_fn)(FunctionCallInfo)) {
 	int32	   *nentries = (int32 *) PG_GETARG_POINTER(1);
 	StrategyNumber strategy = PG_GETARG_UINT16(2);
 	int32	   *searchMode = (int32 *) PG_GETARG_POINTER(6);
@@ -90,7 +86,7 @@ gin_extract_hstore_query(PG_FUNCTION_ARGS)
 	{
 		/* Query is an hstore, so just apply gin_extract_hstore... */
 		entries = (Datum *)
-			DatumGetPointer(DirectFunctionCall2(gin_extract_hstore,
+			DatumGetPointer(DirectFunctionCall2(extract_fn,
 												PG_GETARG_DATUM(0),
 												PointerGetDatum(nentries)));
 		/* ... except that "contains {}" requires a full index scan */
@@ -147,6 +143,19 @@ gin_extract_hstore_query(PG_FUNCTION_ARGS)
 	PG_RETURN_POINTER(entries);
 }
 
+PG_FUNCTION_INFO_V1(gin_extract_hstore_query);
+Datum		gin_extract_hstore_query(PG_FUNCTION_ARGS);
+
+Datum
+gin_extract_hstore_query(PG_FUNCTION_ARGS)
+{
+	Datum	   *entries;
+
+	entries = (Datum *) DatumGetPointer(extract_hstore_query(fcinfo, &gin_extract_hstore));
+
+	PG_RETURN_POINTER(entries);
+}
+
 PG_FUNCTION_INFO_V1(gin_consistent_hstore);
 Datum		gin_consistent_hstore(PG_FUNCTION_ARGS);
 
@@ -211,3 +220,120 @@ gin_consistent_hstore(PG_FUNCTION_ARGS)
 
 	PG_RETURN_BOOL(res);
 }
+
+/*
+ * 'gin_hstore_combined_ops' indexing opclass.
+ *
+ * This gin indexing strategy produces a combined key and value index entry to
+ * support @> queries, and a key only index entry to support ? queries. This
+ * can produce a significant speedup in @> queries, at the cost of a larger
+ * index size.
+ */
+
+static text *
+make_combined_item(HEntry *hsent, char *ptr, int i) {
+	int keylen;
+	int vallen;
+	int len;
+	int valflag;
+	text	   *item;
+
+	keylen = HS_KEYLEN(hsent, i);
+
+	if (HS_VALISNULL(hsent, i)) {
+		valflag = NULLFLAG;
+		vallen = 0;
+	} else {
+		valflag = VALFLAG;
+		vallen = HS_VALLEN(hsent, i);
+	}
+
+	len = keylen + vallen + VARHDRSZ + 2;
+	item = (text *) palloc(len);
+	SET_VARSIZE(item, len);
+
+	*VARDATA(item) = KEYFLAG;
+
+	memcpy(VARDATA(item) + 1,
+		   HS_KEY(hsent, ptr, i), keylen);
+
+	*(VARDATA(item) + 1 + keylen) = valflag;
+
+	memcpy(VARDATA(item) + 2 + keylen,
+		   HS_VAL(hsent, ptr, i), vallen);
+
+	return item;
+}
+
+static Datum
+gin_extract_hstore_contains(PG_FUNCTION_ARGS)
+{
+	HStore	   *hs = PG_GETARG_HS(0);
+	int32	   *nentries = (int32 *) PG_GETARG_POINTER(1);
+	Datum	   *entries = NULL;
+	HEntry	   *hsent = ARRPTR(hs);
+	char	   *ptr = STRPTR(hs);
+	int			count = HS_COUNT(hs);
+	int			i;
+
+	*nentries = count;
+	if (count)
+		entries = (Datum *) palloc(sizeof(Datum) * count);
+
+	for (i = 0; i < count; ++i)
+	{
+		text       *item;
+
+		item = make_combined_item(hsent, ptr, i);
+		entries[i] = PointerGetDatum(item);
+	}
+
+	PG_RETURN_POINTER(entries);
+}
+
+PG_FUNCTION_INFO_V1(gin_extract_hstore_query_combined);
+Datum		gin_extract_hstore_query_combined(PG_FUNCTION_ARGS);
+
+Datum
+gin_extract_hstore_query_combined(PG_FUNCTION_ARGS)
+{
+	Datum	   *entries;
+
+	entries = (Datum *) DatumGetPointer(extract_hstore_query(fcinfo, &gin_extract_hstore_contains));
+
+	PG_RETURN_POINTER(entries);
+}
+
+PG_FUNCTION_INFO_V1(gin_extract_hstore_combined);
+Datum		gin_extract_hstore_combined(PG_FUNCTION_ARGS);
+
+Datum
+gin_extract_hstore_combined(PG_FUNCTION_ARGS)
+{
+	HStore	   *hs = PG_GETARG_HS(0);
+	int32	   *nentries = (int32 *) PG_GETARG_POINTER(1);
+	Datum	   *entries = NULL;
+	HEntry	   *hsent = ARRPTR(hs);
+	char	   *ptr = STRPTR(hs);
+	int			count = HS_COUNT(hs);
+	int			i;
+
+	*nentries = 2 * count;
+	if (count)
+		entries = (Datum *) palloc(sizeof(Datum) * 2 * count);
+
+	for (i = 0; i < count; ++i)
+	{
+		text       *combined;
+		text       *keyonly;
+
+		combined = make_combined_item(hsent, ptr, i);
+		keyonly = makeitem(HS_KEY(hsent, ptr, i), HS_KEYLEN(hsent, i),
+						   KEYFLAG);
+
+		entries[2 * i] = PointerGetDatum(combined);
+		entries[2 * i + 1] = PointerGetDatum(keyonly);
+	}
+
+	PG_RETURN_POINTER(entries);
+}
diff --git a/contrib/hstore/sql/hstore.sql b/contrib/hstore/sql/hstore.sql
index 68b74bf..e7e8bcd 100644
--- a/contrib/hstore/sql/hstore.sql
+++ b/contrib/hstore/sql/hstore.sql
@@ -323,6 +323,22 @@ select count(*) from (select h from (select * from testhstore union all select *
 select distinct * from (values (hstore '' || ''),('')) v(h);
 set enable_sort = true;
 
+-- gin combined opclass
+
+drop index hidx;
+create index hidx on testhstore using gin (h gin_hstore_combined_ops);
+set enable_seqscan=off;
+
+select count(*) from testhstore where h @> 'wait=>NULL';
+select count(*) from testhstore where h @> 'wait=>CC';
+select count(*) from testhstore where h @> 'wait=>CC, public=>t';
+select count(*) from testhstore where h ? 'public';
+select count(*) from testhstore where h ?| ARRAY['public','disabled'];
+select count(*) from testhstore where h ?& ARRAY['public','disabled'];
+
+select count(*) from (select (each(h)).key from testhstore) as wow ;
+select key, count(*) from (select (each(h)).key from testhstore) as wow group by key order by count desc, key;
+
 -- btree
 drop index hidx;
 create index hidx on testhstore using btree (h);
-- 
1.7.12.4 (Apple Git-37)

