diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
new file mode 100644
index 9ffa8be..b2c35b5
*** a/doc/src/sgml/gin.sgml
--- b/doc/src/sgml/gin.sgml
***************
*** 267,272 ****
--- 267,317 ----
       </listitem>
      </varlistentry>
    </variablelist>
+ 
+   Additionally or as replacement to regular binary logic <function>consistent</> function
+   operator class may provide ternary logic <function>triConsistent</> function. Optimal performance
+   can be achieved when both functions are provided. 
+ 
+   <variablelist>
+     <varlistentry>
+      <term><function>GinLogicValue triConsistent(GinLogicValue check[], StrategyNumber n, Datum query,
+         int32 nkeys, Pointer extra_data[],
+         Datum queryKeys[], bool nullFlags[])</></term>
+      <listitem>
+       <para>
+        <function>triConsistent</> is similar to <function>consistent</>
+        but it implements ternary logic instead of binary logic.
+        <literal>GinLogicValue</> datatype is used to represent ternary value.
+        It can have either <literal>GIN_FALSE</>, <literal>GIN_TRUE</> or
+        <literal>GIN_MAYBE</> value. <literal>GIN_FALSE</> and <literal>GIN_TRUE</>
+        have same notion as regular boolean values. <literal>GIN_MAYBE</>
+        represents value which can be either true or false. If some
+        of <literal>check</> values are <literal>GIN_MAYBE</> then
+        <function>triConsistent</> return value should represent result
+        of consistent check for whole set boolean <literal>check</>
+        vectors matching ternary <literal>check</> vector. That is if
+        <function>triConsistent</> returns <literal>GIN_FALSE</> or
+        <literal>GIN_TRUE</> then consistent check should be same for
+        any substitution of <literal>GIN_MAYBE</> values if
+        <literal>check</> vector with either true or false. However,
+        <function>triConsistent</> may also return <literal>GIN_MAYBE</>
+        if result of consistent check varies depending on substitution
+        of <literal>check</> vector <literal>GIN_MAYBE</> with
+        particular binary values. When all values in <literal>check</> vector
+        is <literal>GIN_FALSE</> or <literal>GIN_TRUE</> then
+        <literal>GIN_MAYBE</> return value is equivalent of setting
+        <literal>recheck</> flag in regular <function>consistent</> function.
+       </para>
+ 
+       <para>
+        Usually, there are ways for cheap calculations for
+        <function>triConsistent</> function. For instance, if underlying logic
+        of consistent check is boolean formula, then <function>triConsistent</>
+        function can evaluate it in ternary logic.
+       </para>
+      </listitem>
+     </varlistentry>
+   </variablelist>
   </para>
  
   <para>
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
new file mode 100644
index e02a91b..d04b851
*** a/src/backend/access/gin/ginarrayproc.c
--- b/src/backend/access/gin/ginarrayproc.c
*************** ginarrayconsistent(PG_FUNCTION_ARGS)
*** 218,220 ****
--- 218,304 ----
  
  	PG_RETURN_BOOL(res);
  }
+ 
+ /*
+  * triconsistent support function
+  */
+ Datum
+ ginarraytriconsistent(PG_FUNCTION_ARGS)
+ {
+ 	GinLogicValue *check = (GinLogicValue *) PG_GETARG_POINTER(0);
+ 	StrategyNumber strategy = PG_GETARG_UINT16(1);
+ 
+ 	/* ArrayType  *query = PG_GETARG_ARRAYTYPE_P(2); */
+ 	int32		nkeys = PG_GETARG_INT32(3);
+ 
+ 	/* Pointer	   *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+ 	/* Datum	   *queryKeys = (Datum *) PG_GETARG_POINTER(5); */
+ 	bool	   *nullFlags = (bool *) PG_GETARG_POINTER(6);
+ 	GinLogicValue	res;
+ 	int32		i;
+ 
+ 	switch (strategy)
+ 	{
+ 		case GinOverlapStrategy:
+ 			/* must have a match for at least one non-null element */
+ 			res = GIN_FALSE;
+ 			for (i = 0; i < nkeys; i++)
+ 			{
+ 				if (!nullFlags[i])
+ 				{
+ 					if (check[i] == GIN_TRUE)
+ 					{
+ 						res = GIN_TRUE;
+ 						break;
+ 					}
+ 					else if (check[i] == GIN_MAYBE && res == GIN_FALSE)
+ 					{
+ 						res = GIN_MAYBE;
+ 					}
+ 				}
+ 			}
+ 			break;
+ 		case GinContainsStrategy:
+ 			/* must have all elements in check[] true, and no nulls */
+ 			res = GIN_TRUE;
+ 			for (i = 0; i < nkeys; i++)
+ 			{
+ 				if (check[i] == GIN_FALSE || nullFlags[i])
+ 				{
+ 					res = GIN_FALSE;
+ 					break;
+ 				}
+ 				if (check[i] == GIN_MAYBE)
+ 				{
+ 					res = GIN_MAYBE;
+ 				}
+ 			}
+ 			break;
+ 		case GinContainedStrategy:
+ 			/* can't do anything else useful here */
+ 			res = GIN_MAYBE;
+ 			break;
+ 		case GinEqualStrategy:
+ 			/*
+ 			 * Must have all elements in check[] true; no discrimination
+ 			 * against nulls here.	This is because array_contain_compare and
+ 			 * array_eq handle nulls differently ...
+ 			 */
+ 			res = GIN_MAYBE;
+ 			for (i = 0; i < nkeys; i++)
+ 			{
+ 				if (check[i] == GIN_FALSE)
+ 				{
+ 					res = GIN_FALSE;
+ 					break;
+ 				}
+ 			}
+ 			break;
+ 		default:
+ 			elog(ERROR, "ginarrayconsistent: unknown strategy number: %d",
+ 				 strategy);
+ 			res = false;
+ 	}
+ 
+ 	PG_RETURN_GIN_LOGIC_VALUE(res);
+ }
diff --git a/src/backend/access/gin/ginlogic.c b/src/backend/access/gin/ginlogic.c
new file mode 100644
index dc8e630..c661408
*** a/src/backend/access/gin/ginlogic.c
--- b/src/backend/access/gin/ginlogic.c
*************** normalBoolConsistentFn(GinScanKey key)
*** 82,87 ****
--- 82,134 ----
  }
  
  /*
+  * A helper function for calling a native ternary logic consistent function.
+  */
+ static GinLogicValue
+ normalTriConsistentFn(GinScanKey key)
+ {
+ 	return DatumGetGinLogicValue(FunctionCall7Coll(key->triConsistentFmgrInfo,
+ 										  key->collation,
+ 										  PointerGetDatum(key->entryRes),
+ 										  UInt16GetDatum(key->strategy),
+ 										  key->query,
+ 										  UInt32GetDatum(key->nuserentries),
+ 										  PointerGetDatum(key->extra_data),
+ 										  PointerGetDatum(key->queryValues),
+ 									 PointerGetDatum(key->queryCategories)));
+ }
+ 
+ /*
+  * This function implements a binary logic consistency check, using a ternary
+  * logic consistent function provided by the opclass. GIN_MAYBE return value
+  * is interpreted as true with recheck flag.
+  */
+ static bool
+ shimBoolConsistentFn(GinScanKey key)
+ {
+ 	GinLogicValue result;
+ 	result = DatumGetGinLogicValue(FunctionCall7Coll(key->triConsistentFmgrInfo,
+ 										  key->collation,
+ 										  PointerGetDatum(key->entryRes),
+ 										  UInt16GetDatum(key->strategy),
+ 										  key->query,
+ 										  UInt32GetDatum(key->nuserentries),
+ 										  PointerGetDatum(key->extra_data),
+ 										  PointerGetDatum(key->queryValues),
+ 									 PointerGetDatum(key->queryCategories)));
+ 	if (result == GIN_MAYBE)
+ 	{
+ 		key->recheckCurItem = true;
+ 		return true;
+ 	}
+ 	else
+ 	{
+ 		key->recheckCurItem = false;
+ 		return result;
+ 	}
+ }
+ 
+ /*
   * This function implements a tri-state consistency check, using a boolean
   * consistent function provided by the opclass.
   *
*************** ginInitConsistentFunction(GinState *gins
*** 175,182 ****
  	else
  	{
  		key->consistentFmgrInfo = &ginstate->consistentFn[key->attnum - 1];
  		key->collation = ginstate->supportCollation[key->attnum - 1];
! 		key->boolConsistentFn = normalBoolConsistentFn;
! 		key->triConsistentFn = shimTriConsistentFn;
  	}
  }
--- 222,236 ----
  	else
  	{
  		key->consistentFmgrInfo = &ginstate->consistentFn[key->attnum - 1];
+ 		key->triConsistentFmgrInfo = &ginstate->triConsistentFn[key->attnum - 1];
  		key->collation = ginstate->supportCollation[key->attnum - 1];
! 		if (OidIsValid(ginstate->consistentFn[key->attnum - 1].fn_oid))
! 			key->boolConsistentFn = normalBoolConsistentFn;
! 		else
! 			key->boolConsistentFn = shimBoolConsistentFn;
! 		if (OidIsValid(ginstate->triConsistentFn[key->attnum - 1].fn_oid))
! 			key->triConsistentFn =  normalTriConsistentFn;
! 		else
! 			key->triConsistentFn =  shimTriConsistentFn;
  	}
  }
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
new file mode 100644
index 486f2ef..80790e8
*** a/src/backend/access/gin/ginutil.c
--- b/src/backend/access/gin/ginutil.c
*************** initGinState(GinState *state, Relation i
*** 67,75 ****
  		fmgr_info_copy(&(state->extractQueryFn[i]),
  					   index_getprocinfo(index, i + 1, GIN_EXTRACTQUERY_PROC),
  					   CurrentMemoryContext);
! 		fmgr_info_copy(&(state->consistentFn[i]),
! 					   index_getprocinfo(index, i + 1, GIN_CONSISTENT_PROC),
! 					   CurrentMemoryContext);
  
  		/*
  		 * Check opclass capability to do partial match.
--- 67,89 ----
  		fmgr_info_copy(&(state->extractQueryFn[i]),
  					   index_getprocinfo(index, i + 1, GIN_EXTRACTQUERY_PROC),
  					   CurrentMemoryContext);
! 		/*
! 		 * Check opclass capability to do tri-state or binary logic consistent
! 		 * check.
! 		 */
! 		if (index_getprocid(index, i + 1, GIN_TRICONSISTENT_PROC) != InvalidOid)
! 		{
! 			fmgr_info_copy(&(state->triConsistentFn[i]),
! 			   index_getprocinfo(index, i + 1, GIN_TRICONSISTENT_PROC),
! 						   CurrentMemoryContext);
! 		}
! 
! 		if (index_getprocid(index, i + 1, GIN_CONSISTENT_PROC) != InvalidOid)
! 		{
! 			fmgr_info_copy(&(state->consistentFn[i]),
! 						   index_getprocinfo(index, i + 1, GIN_CONSISTENT_PROC),
! 						   CurrentMemoryContext);
! 		}
  
  		/*
  		 * Check opclass capability to do partial match.
diff --git a/src/backend/utils/adt/tsginidx.c b/src/backend/utils/adt/tsginidx.c
new file mode 100644
index 9f6e8e9..f8a3ba5
*** a/src/backend/utils/adt/tsginidx.c
--- b/src/backend/utils/adt/tsginidx.c
***************
*** 15,20 ****
--- 15,21 ----
  
  #include "access/gin.h"
  #include "access/skey.h"
+ #include "miscadmin.h"
  #include "tsearch/ts_type.h"
  #include "tsearch/ts_utils.h"
  #include "utils/builtins.h"
*************** gin_extract_tsquery(PG_FUNCTION_ARGS)
*** 172,183 ****
  typedef struct
  {
  	QueryItem  *first_item;
! 	bool	   *check;
  	int		   *map_item_operand;
  	bool	   *need_recheck;
  } GinChkVal;
  
! static bool
  checkcondition_gin(void *checkval, QueryOperand *val)
  {
  	GinChkVal  *gcv = (GinChkVal *) checkval;
--- 173,184 ----
  typedef struct
  {
  	QueryItem  *first_item;
! 	GinLogicValue *check;
  	int		   *map_item_operand;
  	bool	   *need_recheck;
  } GinChkVal;
  
! static GinLogicValue
  checkcondition_gin(void *checkval, QueryOperand *val)
  {
  	GinChkVal  *gcv = (GinChkVal *) checkval;
*************** checkcondition_gin(void *checkval, Query
*** 194,199 ****
--- 195,260 ----
  	return gcv->check[j];
  }
  
+ /*
+  * Evaluate tsquery boolean expression using ternary login.
+  *
+  * chkcond is a callback function used to evaluate each VAL node in the query.
+  * checkval can be used to pass information to the callback. TS_execute doesn't
+  * do anything with it.
+  */
+ static GinLogicValue
+ TS_execute_ternary(QueryItem *curitem, void *checkval,
+ 		GinLogicValue (*chkcond) (void *checkval, QueryOperand *val))
+ {
+ 	GinLogicValue val1, val2, result;
+ 	/* since this function recurses, it could be driven to stack overflow */
+ 	check_stack_depth();
+ 
+ 	if (curitem->type == QI_VAL)
+ 		return chkcond(checkval, (QueryOperand *) curitem);
+ 
+ 	switch (curitem->qoperator.oper)
+ 	{
+ 		case OP_NOT:
+ 			result = TS_execute_ternary(curitem + 1, checkval, chkcond);
+ 			if (result == GIN_MAYBE)
+ 				return result;
+ 			return !result;
+ 
+ 		case OP_AND:
+ 			val1 = TS_execute_ternary(curitem + curitem->qoperator.left,
+ 															checkval, chkcond);
+ 			if (val1 == GIN_FALSE)
+ 				return GIN_FALSE;
+ 			val2 = TS_execute_ternary(curitem + 1, checkval, chkcond);
+ 			if (val2 == GIN_FALSE)
+ 				return GIN_FALSE;
+ 			if (val1 == GIN_TRUE && val2 == GIN_TRUE)
+ 				return GIN_TRUE;
+ 			else
+ 				return GIN_MAYBE;
+ 
+ 		case OP_OR:
+ 			val1 = TS_execute_ternary(curitem + curitem->qoperator.left,
+ 															checkval, chkcond);
+ 			if (val1 == GIN_TRUE)
+ 				return GIN_TRUE;
+ 			val2 = TS_execute_ternary(curitem + 1, checkval, chkcond);
+ 			if (val2 == GIN_TRUE)
+ 				return GIN_TRUE;
+ 			if (val1 == GIN_FALSE && val2 == GIN_FALSE)
+ 				return GIN_FALSE;
+ 			else
+ 				return GIN_MAYBE;
+ 
+ 		default:
+ 			elog(ERROR, "unrecognized operator: %d", curitem->qoperator.oper);
+ 	}
+ 
+ 	/* not reachable, but keep compiler quiet */
+ 	return false;
+ }
+ 
  Datum
  gin_tsquery_consistent(PG_FUNCTION_ARGS)
  {
*************** gin_tsquery_consistent(PG_FUNCTION_ARGS)
*** 233,238 ****
--- 294,338 ----
  	PG_RETURN_BOOL(res);
  }
  
+ Datum
+ gin_tsquery_triconsistent(PG_FUNCTION_ARGS)
+ {
+ 	GinLogicValue *check = (GinLogicValue *) PG_GETARG_POINTER(0);
+ 
+ 	/* StrategyNumber strategy = PG_GETARG_UINT16(1); */
+ 	TSQuery		query = PG_GETARG_TSQUERY(2);
+ 
+ 	/* int32	nkeys = PG_GETARG_INT32(3); */
+ 	Pointer    *extra_data = (Pointer *) PG_GETARG_POINTER(4);
+ 	GinLogicValue res = GIN_FALSE;
+ 	bool		recheck;
+ 
+ 	/* The query requires recheck only if it involves weights */
+ 	if (query->size > 0)
+ 	{
+ 		QueryItem  *item;
+ 		GinChkVal	gcv;
+ 
+ 		/*
+ 		 * check-parameter array has one entry for each value (operand) in the
+ 		 * query.
+ 		 */
+ 		gcv.first_item = item = GETQUERY(query);
+ 		gcv.check = check;
+ 		gcv.map_item_operand = (int *) (extra_data[0]);
+ 		gcv.need_recheck = &recheck;
+ 
+ 		res = TS_execute_ternary(GETQUERY(query),
+ 								   &gcv,
+ 								   checkcondition_gin);
+ 
+ 		if (res == GIN_TRUE && recheck)
+ 			res = GIN_MAYBE;
+ 	}
+ 
+ 	PG_RETURN_GIN_LOGIC_VALUE(res);
+ }
+ 
  /*
   * Formerly, gin_extract_tsvector had only two arguments.  Now it has three,
   * but we still need a pg_proc entry with two args to support reloading
diff --git a/src/include/access/gin.h b/src/include/access/gin.h
new file mode 100644
index 03e58c9..75a86c4
*** a/src/include/access/gin.h
--- b/src/include/access/gin.h
***************
*** 23,29 ****
  #define GIN_EXTRACTQUERY_PROC		   3
  #define GIN_CONSISTENT_PROC			   4
  #define GIN_COMPARE_PARTIAL_PROC	   5
! #define GINNProcs					   5
  
  /*
   * searchMode settings for extractQueryFn.
--- 23,30 ----
  #define GIN_EXTRACTQUERY_PROC		   3
  #define GIN_CONSISTENT_PROC			   4
  #define GIN_COMPARE_PARTIAL_PROC	   5
! #define GIN_TRICONSISTENT_PROC		   6
! #define GINNProcs					   6
  
  /*
   * searchMode settings for extractQueryFn.
*************** typedef struct GinStatsData
*** 46,51 ****
--- 47,67 ----
  	int32		ginVersion;
  } GinStatsData;
  
+ /* ginlogic.c */
+ enum
+ {
+ 	GIN_FALSE = 0,			/* item is present / matches */
+ 	GIN_TRUE = 1,			/* item is not present / does not match */
+ 	GIN_MAYBE = 2			/* don't know if item is present / don't know if
+ 							 * matches */
+ } GinLogicValueEnum;
+ 
+ typedef char GinLogicValue;
+ 
+ #define DatumGetGinLogicValue(X) ((GinLogicValue)(X))
+ #define GinLogicValueGetDatum(X) ((Datum)(X))
+ #define PG_RETURN_GIN_LOGIC_VALUE(x) return GinLogicValueGetDatum(x)
+ 
  /* GUC parameter */
  extern PGDLLIMPORT int GinFuzzySearchLimit;
  
diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h
new file mode 100644
index 2cf042a..0099272
*** a/src/include/access/gin_private.h
--- b/src/include/access/gin_private.h
*************** typedef struct GinState
*** 348,353 ****
--- 348,354 ----
  	FmgrInfo	extractValueFn[INDEX_MAX_KEYS];
  	FmgrInfo	extractQueryFn[INDEX_MAX_KEYS];
  	FmgrInfo	consistentFn[INDEX_MAX_KEYS];
+ 	FmgrInfo	triConsistentFn[INDEX_MAX_KEYS];
  	FmgrInfo	comparePartialFn[INDEX_MAX_KEYS];		/* optional method */
  	/* canPartialMatch[i] is true if comparePartialFn[i] is valid */
  	bool		canPartialMatch[INDEX_MAX_KEYS];
*************** typedef struct GinScanKeyData
*** 762,769 ****
  	/* array of check flags, reported to consistentFn */
  	bool	   *entryRes;
  	bool		(*boolConsistentFn) (GinScanKey key);
! 	bool		(*triConsistentFn) (GinScanKey key);
  	FmgrInfo   *consistentFmgrInfo;
  	Oid			collation;
  
  	/* other data needed for calling consistentFn */
--- 763,771 ----
  	/* array of check flags, reported to consistentFn */
  	bool	   *entryRes;
  	bool		(*boolConsistentFn) (GinScanKey key);
! 	GinLogicValue (*triConsistentFn) (GinScanKey key);
  	FmgrInfo   *consistentFmgrInfo;
+ 	FmgrInfo   *triConsistentFmgrInfo;
  	Oid			collation;
  
  	/* other data needed for calling consistentFn */
*************** extern void ginNewScanKey(IndexScanDesc 
*** 850,866 ****
  extern Datum gingetbitmap(PG_FUNCTION_ARGS);
  
  /* ginlogic.c */
- 
- enum
- {
- 	GIN_FALSE = 0,			/* item is present / matches */
- 	GIN_TRUE = 1,			/* item is not present / does not match */
- 	GIN_MAYBE = 2			/* don't know if item is present / don't know if
- 							 * matches */
- } GinLogicValueEnum;
- 
- typedef char GinLogicValue;
- 
  extern void ginInitConsistentFunction(GinState *ginstate, GinScanKey key);
  
  /* ginvacuum.c */
--- 852,857 ----
diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h
new file mode 100644
index 4f46ddd..759ea70
*** a/src/include/catalog/pg_am.h
--- b/src/include/catalog/pg_am.h
*************** DESCR("hash index access method");
*** 126,132 ****
  DATA(insert OID = 783 (  gist		0 8 f t f f t t f t t t f 0 gistinsert gistbeginscan gistgettuple gistgetbitmap gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbuildempty gistbulkdelete gistvacuumcleanup - gistcostestimate gistoptions ));
  DESCR("GiST index access method");
  #define GIST_AM_OID 783
! DATA(insert OID = 2742 (  gin		0 5 f f f f t t f f t f f 0 gininsert ginbeginscan - gingetbitmap ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbuildempty ginbulkdelete ginvacuumcleanup - gincostestimate ginoptions ));
  DESCR("GIN index access method");
  #define GIN_AM_OID 2742
  DATA(insert OID = 4000 (  spgist	0 5 f f f f f t f t f f f 0 spginsert spgbeginscan spggettuple spggetbitmap spgrescan spgendscan spgmarkpos spgrestrpos spgbuild spgbuildempty spgbulkdelete spgvacuumcleanup spgcanreturn spgcostestimate spgoptions ));
--- 126,132 ----
  DATA(insert OID = 783 (  gist		0 8 f t f f t t f t t t f 0 gistinsert gistbeginscan gistgettuple gistgetbitmap gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbuildempty gistbulkdelete gistvacuumcleanup - gistcostestimate gistoptions ));
  DESCR("GiST index access method");
  #define GIST_AM_OID 783
! DATA(insert OID = 2742 (  gin		0 6 f f f f t t f f t f f 0 gininsert ginbeginscan - gingetbitmap ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbuildempty ginbulkdelete ginvacuumcleanup - gincostestimate ginoptions ));
  DESCR("GIN index access method");
  #define GIN_AM_OID 2742
  DATA(insert OID = 4000 (  spgist	0 5 f f f f f t f t f f f 0 spginsert spgbeginscan spggettuple spggetbitmap spgrescan spgendscan spgmarkpos spgrestrpos spgbuild spgbuildempty spgbulkdelete spgvacuumcleanup spgcanreturn spgcostestimate spgoptions ));
diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h
new file mode 100644
index 66bd765..e3773e9
*** a/src/include/catalog/pg_amproc.h
--- b/src/include/catalog/pg_amproc.h
*************** DATA(insert (	2745   1007 1007 1	351 ));
*** 235,361 ****
--- 235,392 ----
  DATA(insert (	2745   1007 1007 2 2743 ));
  DATA(insert (	2745   1007 1007 3 2774 ));
  DATA(insert (	2745   1007 1007 4 2744 ));
+ DATA(insert (	2745   1007 1007 6 3920 ));
  DATA(insert (	2745   1009 1009 1	360 ));
  DATA(insert (	2745   1009 1009 2 2743 ));
  DATA(insert (	2745   1009 1009 3 2774 ));
  DATA(insert (	2745   1009 1009 4 2744 ));
+ DATA(insert (	2745   1009 1009 6 3920 ));
  DATA(insert (	2745   1015 1015 1	360 ));
  DATA(insert (	2745   1015 1015 2 2743 ));
  DATA(insert (	2745   1015 1015 3 2774 ));
  DATA(insert (	2745   1015 1015 4 2744 ));
+ DATA(insert (	2745   1015 1015 6 3920 ));
  DATA(insert (	2745   1023 1023 1 357 ));
  DATA(insert (	2745   1023 1023 2 2743 ));
  DATA(insert (	2745   1023 1023 3 2774 ));
  DATA(insert (	2745   1023 1023 4 2744 ));
+ DATA(insert (	2745   1023 1023 6 3920 ));
  DATA(insert (	2745   1561 1561 1 1596 ));
  DATA(insert (	2745   1561 1561 2 2743 ));
  DATA(insert (	2745   1561 1561 3 2774 ));
  DATA(insert (	2745   1561 1561 4 2744 ));
+ DATA(insert (	2745   1561 1561 6 3920 ));
  DATA(insert (	2745   1000 1000 1 1693 ));
  DATA(insert (	2745   1000 1000 2 2743 ));
  DATA(insert (	2745   1000 1000 3 2774 ));
  DATA(insert (	2745   1000 1000 4 2744 ));
+ DATA(insert (	2745   1000 1000 6 3920 ));
  DATA(insert (	2745   1014 1014 1 1078 ));
  DATA(insert (	2745   1014 1014 2 2743 ));
  DATA(insert (	2745   1014 1014 3 2774 ));
  DATA(insert (	2745   1014 1014 4 2744 ));
+ DATA(insert (	2745   1014 1014 6 3920 ));
  DATA(insert (	2745   1001 1001 1 1954 ));
  DATA(insert (	2745   1001 1001 2 2743 ));
  DATA(insert (	2745   1001 1001 3 2774 ));
  DATA(insert (	2745   1001 1001 4 2744 ));
+ DATA(insert (	2745   1001 1001 6 3920 ));
  DATA(insert (	2745   1002 1002 1 358 ));
  DATA(insert (	2745   1002 1002 2 2743 ));
  DATA(insert (	2745   1002 1002 3 2774 ));
  DATA(insert (	2745   1002 1002 4 2744 ));
+ DATA(insert (	2745   1002 1002 6 3920 ));
  DATA(insert (	2745   1182 1182 1 1092 ));
  DATA(insert (	2745   1182 1182 2 2743 ));
  DATA(insert (	2745   1182 1182 3 2774 ));
  DATA(insert (	2745   1182 1182 4 2744 ));
+ DATA(insert (	2745   1182 1182 6 3920 ));
  DATA(insert (	2745   1021 1021 1 354 ));
  DATA(insert (	2745   1021 1021 2 2743 ));
  DATA(insert (	2745   1021 1021 3 2774 ));
  DATA(insert (	2745   1021 1021 4 2744 ));
+ DATA(insert (	2745   1021 1021 6 3920 ));
  DATA(insert (	2745   1022 1022 1 355 ));
  DATA(insert (	2745   1022 1022 2 2743 ));
  DATA(insert (	2745   1022 1022 3 2774 ));
  DATA(insert (	2745   1022 1022 4 2744 ));
+ DATA(insert (	2745   1022 1022 6 3920 ));
  DATA(insert (	2745   1041 1041 1 926 ));
  DATA(insert (	2745   1041 1041 2 2743 ));
  DATA(insert (	2745   1041 1041 3 2774 ));
  DATA(insert (	2745   1041 1041 4 2744 ));
+ DATA(insert (	2745   1041 1041 6 3920 ));
  DATA(insert (	2745   651 651 1 926 ));
  DATA(insert (	2745   651 651 2 2743 ));
  DATA(insert (	2745   651 651 3 2774 ));
  DATA(insert (	2745   651 651 4 2744 ));
+ DATA(insert (	2745   651 651 6 3920 ));
  DATA(insert (	2745   1005 1005 1 350 ));
  DATA(insert (	2745   1005 1005 2 2743 ));
  DATA(insert (	2745   1005 1005 3 2774 ));
  DATA(insert (	2745   1005 1005 4 2744 ));
+ DATA(insert (	2745   1005 1005 6 3920 ));
  DATA(insert (	2745   1016 1016 1 842 ));
  DATA(insert (	2745   1016 1016 2 2743 ));
  DATA(insert (	2745   1016 1016 3 2774 ));
  DATA(insert (	2745   1016 1016 4 2744 ));
+ DATA(insert (	2745   1016 1016 6 3920 ));
  DATA(insert (	2745   1187 1187 1 1315 ));
  DATA(insert (	2745   1187 1187 2 2743 ));
  DATA(insert (	2745   1187 1187 3 2774 ));
  DATA(insert (	2745   1187 1187 4 2744 ));
+ DATA(insert (	2745   1187 1187 6 3920 ));
  DATA(insert (	2745   1040 1040 1 836 ));
  DATA(insert (	2745   1040 1040 2 2743 ));
  DATA(insert (	2745   1040 1040 3 2774 ));
  DATA(insert (	2745   1040 1040 4 2744 ));
+ DATA(insert (	2745   1040 1040 6 3920 ));
  DATA(insert (	2745   1003 1003 1 359 ));
  DATA(insert (	2745   1003 1003 2 2743 ));
  DATA(insert (	2745   1003 1003 3 2774 ));
  DATA(insert (	2745   1003 1003 4 2744 ));
+ DATA(insert (	2745   1003 1003 6 3920 ));
  DATA(insert (	2745   1231 1231 1 1769 ));
  DATA(insert (	2745   1231 1231 2 2743 ));
  DATA(insert (	2745   1231 1231 3 2774 ));
  DATA(insert (	2745   1231 1231 4 2744 ));
+ DATA(insert (	2745   1231 1231 6 3920 ));
  DATA(insert (	2745   1028 1028 1 356 ));
  DATA(insert (	2745   1028 1028 2 2743 ));
  DATA(insert (	2745   1028 1028 3 2774 ));
  DATA(insert (	2745   1028 1028 4 2744 ));
+ DATA(insert (	2745   1028 1028 6 3920 ));
  DATA(insert (	2745   1013 1013 1 404 ));
  DATA(insert (	2745   1013 1013 2 2743 ));
  DATA(insert (	2745   1013 1013 3 2774 ));
  DATA(insert (	2745   1013 1013 4 2744 ));
+ DATA(insert (	2745   1013 1013 6 3920 ));
  DATA(insert (	2745   1183 1183 1 1107 ));
  DATA(insert (	2745   1183 1183 2 2743 ));
  DATA(insert (	2745   1183 1183 3 2774 ));
  DATA(insert (	2745   1183 1183 4 2744 ));
+ DATA(insert (	2745   1183 1183 6 3920 ));
  DATA(insert (	2745   1185 1185 1 1314 ));
  DATA(insert (	2745   1185 1185 2 2743 ));
  DATA(insert (	2745   1185 1185 3 2774 ));
  DATA(insert (	2745   1185 1185 4 2744 ));
+ DATA(insert (	2745   1185 1185 6 3920 ));
  DATA(insert (	2745   1270 1270 1 1358 ));
  DATA(insert (	2745   1270 1270 2 2743 ));
  DATA(insert (	2745   1270 1270 3 2774 ));
  DATA(insert (	2745   1270 1270 4 2744 ));
+ DATA(insert (	2745   1270 1270 6 3920 ));
  DATA(insert (	2745   1563 1563 1 1672 ));
  DATA(insert (	2745   1563 1563 2 2743 ));
  DATA(insert (	2745   1563 1563 3 2774 ));
  DATA(insert (	2745   1563 1563 4 2744 ));
+ DATA(insert (	2745   1563 1563 6 3920 ));
  DATA(insert (	2745   1115 1115 1 2045 ));
  DATA(insert (	2745   1115 1115 2 2743 ));
  DATA(insert (	2745   1115 1115 3 2774 ));
  DATA(insert (	2745   1115 1115 4 2744 ));
+ DATA(insert (	2745   1115 1115 6 3920 ));
  DATA(insert (	2745   791 791 1 377 ));
  DATA(insert (	2745   791 791 2 2743 ));
  DATA(insert (	2745   791 791 3 2774 ));
  DATA(insert (	2745   791 791 4 2744 ));
+ DATA(insert (	2745   791 791 6 3920 ));
  DATA(insert (	2745   1024 1024 1 380 ));
  DATA(insert (	2745   1024 1024 2 2743 ));
  DATA(insert (	2745   1024 1024 3 2774 ));
  DATA(insert (	2745   1024 1024 4 2744 ));
+ DATA(insert (	2745   1024 1024 6 3920 ));
  DATA(insert (	2745   1025 1025 1 381 ));
  DATA(insert (	2745   1025 1025 2 2743 ));
  DATA(insert (	2745   1025 1025 3 2774 ));
  DATA(insert (	2745   1025 1025 4 2744 ));
+ DATA(insert (	2745   1025 1025 6 3920 ));
  DATA(insert (	3659   3614 3614 1 3724 ));
  DATA(insert (	3659   3614 3614 2 3656 ));
  DATA(insert (	3659   3614 3614 3 3657 ));
  DATA(insert (	3659   3614 3614 4 3658 ));
  DATA(insert (	3659   3614 3614 5 2700 ));
+ DATA(insert (	3659   3614 3614 6 3921 ));
  
  
  /* sp-gist */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
new file mode 100644
index e33670c..d087e42
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 2774 (  ginqueryarraye
*** 3991,3996 ****
--- 3991,3998 ----
  DESCR("GIN array support");
  DATA(insert OID = 2744 (  ginarrayconsistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 8 0 16 "2281 21 2277 23 2281 2281 2281 2281" _null_ _null_ _null_ _null_ ginarrayconsistent _null_ _null_ _null_ ));
  DESCR("GIN array support");
+ DATA(insert OID = 3920 (  ginarraytriconsistent	PGNSP PGUID 12 1 0 0 0 f f f f t f i 7 0 16 "2281 21 2277 23 2281 2281 2281" _null_ _null_ _null_ _null_ ginarraytriconsistent _null_ _null_ _null_ ));
+ DESCR("GIN array support");
  DATA(insert OID = 3076 (  ginarrayextract	 PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2277 2281" _null_ _null_ _null_ _null_	ginarrayextract_2args _null_ _null_ _null_ ));
  DESCR("GIN array support (obsolete)");
  
*************** DATA(insert OID = 3657 (  gin_extract_ts
*** 4334,4339 ****
--- 4336,4343 ----
  DESCR("GIN tsvector support");
  DATA(insert OID = 3658 (  gin_tsquery_consistent PGNSP PGUID 12 1 0 0 0 f f f f t f i 8 0 16 "2281 21 3615 23 2281 2281 2281 2281" _null_ _null_ _null_ _null_	gin_tsquery_consistent _null_ _null_ _null_ ));
  DESCR("GIN tsvector support");
+ DATA(insert OID = 3921 (  gin_tsquery_triconsistent PGNSP PGUID 12 1 0 0 0 f f f f t f i 7 0 16 "2281 21 3615 23 2281 2281 2281" _null_ _null_ _null_ _null_	gin_tsquery_triconsistent _null_ _null_ _null_ ));
+ DESCR("GIN tsvector support");
  DATA(insert OID = 3724 (  gin_cmp_tslexeme		PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "25 25" _null_ _null_ _null_ _null_ gin_cmp_tslexeme _null_ _null_ _null_ ));
  DESCR("GIN tsvector support");
  DATA(insert OID = 2700 (  gin_cmp_prefix		PGNSP PGUID 12 1 0 0 0 f f f f t f i 4 0 23 "25 25 21 2281" _null_ _null_ _null_ _null_ gin_cmp_prefix _null_ _null_ _null_ ));
diff --git a/src/include/tsearch/ts_utils.h b/src/include/tsearch/ts_utils.h
new file mode 100644
index 974e58c..2ffb3fb
*** a/src/include/tsearch/ts_utils.h
--- b/src/include/tsearch/ts_utils.h
*************** extern Datum gin_cmp_tslexeme(PG_FUNCTIO
*** 149,154 ****
--- 149,155 ----
  extern Datum gin_cmp_prefix(PG_FUNCTION_ARGS);
  extern Datum gin_extract_tsquery(PG_FUNCTION_ARGS);
  extern Datum gin_tsquery_consistent(PG_FUNCTION_ARGS);
+ extern Datum gin_tsquery_triconsistent(PG_FUNCTION_ARGS);
  extern Datum gin_extract_tsvector_2args(PG_FUNCTION_ARGS);
  extern Datum gin_extract_tsquery_5args(PG_FUNCTION_ARGS);
  extern Datum gin_tsquery_consistent_6args(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
new file mode 100644
index b90d88d..031a43a
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum ginarrayextract(PG_FUNCTION
*** 1161,1166 ****
--- 1161,1167 ----
  extern Datum ginarrayextract_2args(PG_FUNCTION_ARGS);
  extern Datum ginqueryarrayextract(PG_FUNCTION_ARGS);
  extern Datum ginarrayconsistent(PG_FUNCTION_ARGS);
+ extern Datum ginarraytriconsistent(PG_FUNCTION_ARGS);
  
  /* access/transam/twophase.c */
  extern Datum pg_prepared_xact(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
new file mode 100644
index 26abe8a..3b91877
*** a/src/test/regress/expected/opr_sanity.out
--- b/src/test/regress/expected/opr_sanity.out
*************** WHERE p2.opfmethod = p1.oid AND p3.ampro
*** 1306,1312 ****
             p4.amproclefttype = p3.amproclefttype AND
             p4.amprocrighttype = p3.amprocrighttype)
      NOT BETWEEN
!       (CASE WHEN p1.amname IN ('btree', 'gist', 'gin') THEN p1.amsupport - 1
              ELSE p1.amsupport END)
        AND p1.amsupport;
   amname | opfname | amproclefttype | amprocrighttype 
--- 1306,1313 ----
             p4.amproclefttype = p3.amproclefttype AND
             p4.amprocrighttype = p3.amprocrighttype)
      NOT BETWEEN
!       (CASE WHEN p1.amname IN ('btree', 'gist') THEN p1.amsupport - 1
!             WHEN p1.amname = 'gin' THEN p1.amsupport - 2
              ELSE p1.amsupport END)
        AND p1.amsupport;
   amname | opfname | amproclefttype | amprocrighttype 
*************** FROM pg_am am JOIN pg_opclass op ON opcm
*** 1333,1339 ****
           amproclefttype = amprocrighttype AND amproclefttype = opcintype
  WHERE am.amname = 'btree' OR am.amname = 'gist' OR am.amname = 'gin'
  GROUP BY amname, amsupport, opcname, amprocfamily
! HAVING (count(*) != amsupport AND count(*) != amsupport - 1)
      OR amprocfamily IS NULL;
   amname | opcname | count 
  --------+---------+-------
--- 1334,1341 ----
           amproclefttype = amprocrighttype AND amproclefttype = opcintype
  WHERE am.amname = 'btree' OR am.amname = 'gist' OR am.amname = 'gin'
  GROUP BY amname, amsupport, opcname, amprocfamily
! HAVING (count(*) != amsupport AND count(*) != amsupport - 1 AND
!        (count(*) != amsupport - 2 OR am.amname <> 'gin'))
      OR amprocfamily IS NULL;
   amname | opcname | count 
  --------+---------+-------
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
new file mode 100644
index 40e1be2..6dd93f9
*** a/src/test/regress/sql/opr_sanity.sql
--- b/src/test/regress/sql/opr_sanity.sql
*************** WHERE p2.opfmethod = p1.oid AND p3.ampro
*** 1002,1008 ****
             p4.amproclefttype = p3.amproclefttype AND
             p4.amprocrighttype = p3.amprocrighttype)
      NOT BETWEEN
!       (CASE WHEN p1.amname IN ('btree', 'gist', 'gin') THEN p1.amsupport - 1
              ELSE p1.amsupport END)
        AND p1.amsupport;
  
--- 1002,1009 ----
             p4.amproclefttype = p3.amproclefttype AND
             p4.amprocrighttype = p3.amprocrighttype)
      NOT BETWEEN
!       (CASE WHEN p1.amname IN ('btree', 'gist') THEN p1.amsupport - 1
!             WHEN p1.amname = 'gin' THEN p1.amsupport - 2
              ELSE p1.amsupport END)
        AND p1.amsupport;
  
*************** FROM pg_am am JOIN pg_opclass op ON opcm
*** 1024,1030 ****
           amproclefttype = amprocrighttype AND amproclefttype = opcintype
  WHERE am.amname = 'btree' OR am.amname = 'gist' OR am.amname = 'gin'
  GROUP BY amname, amsupport, opcname, amprocfamily
! HAVING (count(*) != amsupport AND count(*) != amsupport - 1)
      OR amprocfamily IS NULL;
  
  -- Unfortunately, we can't check the amproc link very well because the
--- 1025,1032 ----
           amproclefttype = amprocrighttype AND amproclefttype = opcintype
  WHERE am.amname = 'btree' OR am.amname = 'gist' OR am.amname = 'gin'
  GROUP BY amname, amsupport, opcname, amprocfamily
! HAVING (count(*) != amsupport AND count(*) != amsupport - 1 AND
!        (count(*) != amsupport - 2 OR am.amname <> 'gin'))
      OR amprocfamily IS NULL;
  
  -- Unfortunately, we can't check the amproc link very well because the
