-<!-- $PostgreSQL: pgsql/doc/src/sgml/indices.sgml,v 1.73 2008/05/27 00:13:08 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/indices.sgml,v 1.74 2008/07/11 21:06:28 tgl Exp $ -->
<chapter id="indexes">
<title id="indexes-title">Indexes</title>
after a database crash.
For these reasons, hash index use is presently discouraged.
</para>
- </note>
+ </note>
<para>
<indexterm>
</indexterm>
GIN indexes are inverted indexes which can handle values that contain more
than one key, arrays for example. Like GiST, GIN can support
- many different user-defined indexing strategies and the particular
- operators with which a GIN index can be used vary depending on the
- indexing strategy.
+ many different user-defined indexing strategies and the particular
+ operators with which a GIN index can be used vary depending on the
+ indexing strategy.
As an example, the standard distribution of
<productname>PostgreSQL</productname> includes GIN operator classes
for one-dimensional arrays, which support indexed
</para>
<para>
- Currently, only the B-tree and GiST index types support multicolumn
+ Currently, only the B-tree, GiST and GIN index types support multicolumn
indexes. Up to 32 columns can be specified. (This limit can be
altered when building <productname>PostgreSQL</productname>; see the
file <filename>pg_config_manual.h</filename>.)
<para>
A multicolumn GiST index can be used with query conditions that
- involve any subset of the index's columns. Conditions on additional
- columns restrict the entries returned by the index, but the condition on
- the first column is the most important one for determining how much of
- the index needs to be scanned. A GiST index will be relatively
- ineffective if its first column has only a few distinct values, even if
+ involve any subset of the index's columns. Conditions on additional
+ columns restrict the entries returned by the index, but the condition on
+ the first column is the most important one for determining how much of
+ the index needs to be scanned. A GiST index will be relatively
+ ineffective if its first column has only a few distinct values, even if
there are many distinct values in additional columns.
</para>
+ <para>
+ A multicolumn GIN index can be used with query conditions that
+ involve any subset of the index's columns. Unlike B-tree or GiST,
+ index search effectiveness is the same regardless of which index column(s)
+ the query conditions use.
+ </para>
+
<para>
Of course, each column must be used with operators appropriate to the index
type; clauses that involve other operators will not be considered.
<para>
<productname>PostgreSQL</productname> automatically creates a unique
index when a unique constraint or a primary key is defined for a table.
- The index covers the columns that make up the primary key or unique
+ The index covers the columns that make up the primary key or unique
columns (a multicolumn index, if appropriate), and is the mechanism
that enforces the constraint.
</para>
or the index will not be recognized to be usable. Matching takes
place at query planning time, not at run time. As a result,
parameterized query clauses will not work with a partial index. For
- example a prepared query with a parameter might specify
- <quote>x < ?</quote> which will never imply
- <quote>x < 2</quote> for all possible values of the parameter.
+ example a prepared query with a parameter might specify
+ <quote>x < ?</quote> which will never imply
+ <quote>x < 2</quote> for all possible values of the parameter.
</para>
<para>
<!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/create_index.sgml,v 1.67 2008/03/16 23:57:51 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/create_index.sgml,v 1.68 2008/07/11 21:06:29 tgl Exp $
PostgreSQL documentation
-->
</para>
<para>
- Currently, only the B-tree and GiST index methods support
+ Currently, only the B-tree, GiST and GIN index methods support
multicolumn indexes. Up to 32 fields can be specified by default.
(This limit can be altered when building
<productname>PostgreSQL</productname>.) Only B-tree currently
the optional clauses <literal>ASC</>, <literal>DESC</>, <literal>NULLS
FIRST</>, and/or <literal>NULLS LAST</> can be specified to reverse
the normal sort direction of the index. Since an ordered index can be
- scanned either forward or backward, it is not normally useful to create a
+ scanned either forward or backward, it is not normally useful to create a
single-column <literal>DESC</> index — that sort ordering is already
available with a regular index. The value of these options is that
multicolumn indexes can be created that match the sort ordering requested
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/gin/ginbulk.c,v 1.12 2008/06/29 21:04:01 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/access/gin/ginbulk.c,v 1.13 2008/07/11 21:06:29 tgl Exp $
*-------------------------------------------------------------------------
*/
* palloc'd space in accum.
*/
static Datum
-getDatumCopy(BuildAccumulator *accum, Datum value)
+getDatumCopy(BuildAccumulator *accum, OffsetNumber attnum, Datum value)
{
- Form_pg_attribute *att = accum->ginstate->tupdesc->attrs;
+ Form_pg_attribute att = accum->ginstate->origTupdesc->attrs[ attnum - 1 ];
Datum res;
- if (att[0]->attbyval)
+ if (att->attbyval)
res = value;
else
{
- res = datumCopy(value, false, att[0]->attlen);
+ res = datumCopy(value, false, att->attlen);
accum->allocatedMemory += GetMemoryChunkSpace(DatumGetPointer(res));
}
return res;
* Find/store one entry from indexed value.
*/
static void
-ginInsertEntry(BuildAccumulator *accum, ItemPointer heapptr, Datum entry)
+ginInsertEntry(BuildAccumulator *accum, ItemPointer heapptr, OffsetNumber attnum, Datum entry)
{
EntryAccumulator *ea = accum->entries,
*pea = NULL;
while (ea)
{
- res = compareEntries(accum->ginstate, entry, ea->value);
+ res = compareAttEntries(accum->ginstate, attnum, entry, ea->attnum, ea->value);
if (res == 0)
break; /* found */
else
ea = EAAllocate(accum);
ea->left = ea->right = NULL;
- ea->value = getDatumCopy(accum, entry);
+ ea->attnum = attnum;
+ ea->value = getDatumCopy(accum, attnum, entry);
ea->length = DEF_NPTR;
ea->number = 1;
ea->shouldSort = FALSE;
* then calls itself for each parts
*/
static void
-ginChooseElem(BuildAccumulator *accum, ItemPointer heapptr, Datum *entries, uint32 nentry,
+ginChooseElem(BuildAccumulator *accum, ItemPointer heapptr, OffsetNumber attnum,
+ Datum *entries, uint32 nentry,
uint32 low, uint32 high, uint32 offset)
{
uint32 pos;
pos = (low + middle) >> 1;
if (low != middle && pos >= offset && pos - offset < nentry)
- ginInsertEntry(accum, heapptr, entries[pos - offset]);
+ ginInsertEntry(accum, heapptr, attnum, entries[pos - offset]);
pos = (high + middle + 1) >> 1;
if (middle + 1 != high && pos >= offset && pos - offset < nentry)
- ginInsertEntry(accum, heapptr, entries[pos - offset]);
+ ginInsertEntry(accum, heapptr, attnum, entries[pos - offset]);
if (low != middle)
- ginChooseElem(accum, heapptr, entries, nentry, low, middle, offset);
+ ginChooseElem(accum, heapptr, attnum, entries, nentry, low, middle, offset);
if (high != middle + 1)
- ginChooseElem(accum, heapptr, entries, nentry, middle + 1, high, offset);
+ ginChooseElem(accum, heapptr, attnum, entries, nentry, middle + 1, high, offset);
}
/*
* next middle on left part and middle of right part.
*/
void
-ginInsertRecordBA(BuildAccumulator *accum, ItemPointer heapptr, Datum *entries, int32 nentry)
+ginInsertRecordBA(BuildAccumulator *accum, ItemPointer heapptr, OffsetNumber attnum,
+ Datum *entries, int32 nentry)
{
uint32 i,
nbit = 0,
nbit = 1 << nbit;
offset = (nbit - nentry) / 2;
- ginInsertEntry(accum, heapptr, entries[(nbit >> 1) - offset]);
- ginChooseElem(accum, heapptr, entries, nentry, 0, nbit, offset);
+ ginInsertEntry(accum, heapptr, attnum, entries[(nbit >> 1) - offset]);
+ ginChooseElem(accum, heapptr, attnum, entries, nentry, 0, nbit, offset);
}
static int
}
ItemPointerData *
-ginGetEntry(BuildAccumulator *accum, Datum *value, uint32 *n)
+ginGetEntry(BuildAccumulator *accum, OffsetNumber *attnum, Datum *value, uint32 *n)
{
EntryAccumulator *entry;
ItemPointerData *list;
return NULL;
*n = entry->number;
+ *attnum = entry->attnum;
*value = entry->value;
list = entry->list;
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/gin/ginentrypage.c,v 1.16 2008/06/19 00:46:03 alvherre Exp $
+ * $PostgreSQL: pgsql/src/backend/access/gin/ginentrypage.c,v 1.17 2008/07/11 21:06:29 tgl Exp $
*-------------------------------------------------------------------------
*/
* - ItemPointerGetBlockNumber(&itup->t_tid) contains block number of
* root of posting tree
* - ItemPointerGetOffsetNumber(&itup->t_tid) contains magic number GIN_TREE_POSTING
+ *
+ * Storage of attributes of tuple are different for single and multicolumn index.
+ * For single-column index tuple stores only value to be indexed and for
+ * multicolumn variant it stores two attributes: column number of value and value.
*/
IndexTuple
-GinFormTuple(GinState *ginstate, Datum key, ItemPointerData *ipd, uint32 nipd)
+GinFormTuple(GinState *ginstate, OffsetNumber attnum, Datum key, ItemPointerData *ipd, uint32 nipd)
{
- bool isnull = FALSE;
+ bool isnull[2] = {FALSE,FALSE};
IndexTuple itup;
- itup = index_form_tuple(ginstate->tupdesc, &key, &isnull);
+ if ( ginstate->oneCol )
+ itup = index_form_tuple(ginstate->origTupdesc, &key, isnull);
+ else
+ {
+ Datum datums[2];
+
+ datums[0] = UInt16GetDatum(attnum);
+ datums[1] = key;
+ itup = index_form_tuple(ginstate->tupdesc[attnum-1], datums, isnull);
+ }
GinSetOrigSizePosting(itup, IndexTupleSize(itup));
return (IndexTuple) PageGetItem(page, PageGetItemId(page, maxoff));
}
-Datum
-ginGetHighKey(GinState *ginstate, Page page)
-{
- IndexTuple itup;
- bool isnull;
-
- itup = getRightMostTuple(page);
-
- return index_getattr(itup, FirstOffsetNumber, ginstate->tupdesc, &isnull);
-}
-
static bool
entryIsMoveRight(GinBtree btree, Page page)
{
- Datum highkey;
+ IndexTuple itup;
if (GinPageRightMost(page))
return FALSE;
- highkey = ginGetHighKey(btree->ginstate, page);
+ itup = getRightMostTuple(page);
- if (compareEntries(btree->ginstate, btree->entryValue, highkey) > 0)
+ if (compareAttEntries(btree->ginstate,
+ btree->entryAttnum, btree->entryValue,
+ gintuple_get_attrnum(btree->ginstate, itup),
+ gin_index_getattr(btree->ginstate, itup)) > 0)
return TRUE;
return FALSE;
result = -1;
else
{
- bool isnull;
-
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, mid));
- result = compareEntries(btree->ginstate, btree->entryValue,
- index_getattr(itup, FirstOffsetNumber, btree->ginstate->tupdesc, &isnull));
+ result = compareAttEntries(btree->ginstate,
+ btree->entryAttnum, btree->entryValue,
+ gintuple_get_attrnum(btree->ginstate, itup),
+ gin_index_getattr(btree->ginstate, itup));
}
if (result == 0)
while (high > low)
{
OffsetNumber mid = low + ((high - low) / 2);
- bool isnull;
int result;
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, mid));
- result = compareEntries(btree->ginstate, btree->entryValue,
- index_getattr(itup, FirstOffsetNumber, btree->ginstate->tupdesc, &isnull));
-
+ result = compareAttEntries(btree->ginstate,
+ btree->entryAttnum, btree->entryValue,
+ gintuple_get_attrnum(btree->ginstate, itup),
+ gin_index_getattr(btree->ginstate, itup));
if (result == 0)
{
stack->off = mid;
}
void
-prepareEntryScan(GinBtree btree, Relation index, Datum value, GinState *ginstate)
+prepareEntryScan(GinBtree btree, Relation index, OffsetNumber attnum, Datum value, GinState *ginstate)
{
memset(btree, 0, sizeof(GinBtreeData));
btree->index = index;
btree->ginstate = ginstate;
+ btree->entryAttnum = attnum;
btree->entryValue = value;
btree->isDelete = FALSE;
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/gin/ginget.c,v 1.17 2008/06/19 00:46:03 alvherre Exp $
+ * $PostgreSQL: pgsql/src/backend/access/gin/ginget.c,v 1.18 2008/07/11 21:06:29 tgl Exp $
*-------------------------------------------------------------------------
*/
Page page;
IndexTuple itup;
Datum idatum;
- bool isnull;
int32 cmp;
scanEntry->partialMatch = tbm_create( work_mem * 1024L );
page = BufferGetPage(stack->buffer);
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off));
- idatum = index_getattr(itup, 1, btree->ginstate->tupdesc, &isnull);
- Assert(!isnull);
+
+ /*
+ * If tuple stores another attribute then stop scan
+ */
+ if ( gintuple_get_attrnum( btree->ginstate, itup ) != scanEntry->attnum )
+ return true;
+
+ idatum = gin_index_getattr( btree->ginstate, itup );
+
/*----------
* Check of partial match.
* case cmp < 0 => not match and continue scan
*----------
*/
- cmp = DatumGetInt32(FunctionCall3(&btree->ginstate->comparePartialFn,
+ cmp = DatumGetInt32(FunctionCall3(&btree->ginstate->comparePartialFn[scanEntry->attnum-1],
scanEntry->entry,
idatum,
UInt16GetDatum(scanEntry->strategy)));
Datum newDatum,
savedDatum = datumCopy (
idatum,
- btree->ginstate->tupdesc->attrs[0]->attbyval,
- btree->ginstate->tupdesc->attrs[0]->attlen
+ btree->ginstate->origTupdesc->attrs[scanEntry->attnum-1]->attbyval,
+ btree->ginstate->origTupdesc->attrs[scanEntry->attnum-1]->attlen
);
/*
* We should unlock current page (but not unpin) during
page = BufferGetPage(stack->buffer);
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off));
- newDatum = index_getattr(itup, FirstOffsetNumber, btree->ginstate->tupdesc, &isnull);
+ newDatum = gin_index_getattr( btree->ginstate, itup );
+
+ if ( gintuple_get_attrnum( btree->ginstate, itup ) != scanEntry->attnum )
+ elog(ERROR, "lost saved point in index"); /* must not happen !!! */
- if ( compareEntries(btree->ginstate, newDatum, savedDatum) == 0 )
+ if ( compareEntries(btree->ginstate, scanEntry->attnum, newDatum, savedDatum) == 0 )
{
/* Found! */
- if ( btree->ginstate->tupdesc->attrs[0]->attbyval == false )
+ if ( btree->ginstate->origTupdesc->attrs[scanEntry->attnum-1]->attbyval == false )
pfree( DatumGetPointer(savedDatum) );
break;
}
* or just store posting list in memory
*/
- prepareEntryScan(&btreeEntry, index, entry->entry, ginstate);
+ prepareEntryScan(&btreeEntry, index, entry->attnum, entry->entry, ginstate);
btreeEntry.searchMode = TRUE;
stackEntry = ginFindLeafPage(&btreeEntry, NULL);
page = BufferGetPage(stackEntry->buffer);
*keyrecheck = true;
oldCtx = MemoryContextSwitchTo(tempCtx);
- res = DatumGetBool(FunctionCall4(&ginstate->consistentFn,
+ res = DatumGetBool(FunctionCall4(&ginstate->consistentFn[key->attnum-1],
PointerGetDatum(key->entryRes),
UInt16GetDatum(key->strategy),
key->query,
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/gin/gininsert.c,v 1.13 2008/05/16 01:27:06 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/access/gin/gininsert.c,v 1.14 2008/07/11 21:06:29 tgl Exp $
*-------------------------------------------------------------------------
*/
addItemPointersToTuple(Relation index, GinState *ginstate, GinBtreeStack *stack,
IndexTuple old, ItemPointerData *items, uint32 nitem, bool isBuild)
{
- bool isnull;
- Datum key = index_getattr(old, FirstOffsetNumber, ginstate->tupdesc, &isnull);
- IndexTuple res = GinFormTuple(ginstate, key, NULL, nitem + GinGetNPosting(old));
+ Datum key = gin_index_getattr(ginstate, old);
+ OffsetNumber attnum = gintuple_get_attrnum(ginstate, old);
+ IndexTuple res = GinFormTuple(ginstate, attnum, key, NULL, nitem + GinGetNPosting(old));
if (res)
{
GinPostingTreeScan *gdi;
/* posting list becomes big, so we need to make posting's tree */
- res = GinFormTuple(ginstate, key, NULL, 0);
+ res = GinFormTuple(ginstate, attnum, key, NULL, 0);
postingRoot = createPostingTree(index, GinGetPosting(old), GinGetNPosting(old));
GinSetPostingTree(res, postingRoot);
* Inserts only one entry to the index, but it can add more than 1 ItemPointer.
*/
static void
-ginEntryInsert(Relation index, GinState *ginstate, Datum value, ItemPointerData *items, uint32 nitem, bool isBuild)
+ginEntryInsert(Relation index, GinState *ginstate, OffsetNumber attnum, Datum value,
+ ItemPointerData *items, uint32 nitem, bool isBuild)
{
GinBtreeData btree;
GinBtreeStack *stack;
IndexTuple itup;
Page page;
- prepareEntryScan(&btree, index, value, ginstate);
+ prepareEntryScan(&btree, index, attnum, value, ginstate);
stack = ginFindLeafPage(&btree, NULL);
page = BufferGetPage(stack->buffer);
else
{
/* We suppose, that tuple can store at list one itempointer */
- itup = GinFormTuple(ginstate, value, items, 1);
+ itup = GinFormTuple(ginstate, attnum, value, items, 1);
if (itup == NULL || IndexTupleSize(itup) >= GinMaxItemSize)
elog(ERROR, "huge tuple");
* Function isn't used during normal insert
*/
static uint32
-ginHeapTupleBulkInsert(GinBuildState *buildstate, Datum value, ItemPointer heapptr)
+ginHeapTupleBulkInsert(GinBuildState *buildstate, OffsetNumber attnum, Datum value, ItemPointer heapptr)
{
Datum *entries;
int32 nentries;
MemoryContext oldCtx;
oldCtx = MemoryContextSwitchTo(buildstate->funcCtx);
- entries = extractEntriesSU(buildstate->accum.ginstate, value, &nentries);
+ entries = extractEntriesSU(buildstate->accum.ginstate, attnum, value, &nentries);
MemoryContextSwitchTo(oldCtx);
if (nentries == 0)
/* nothing to insert */
return 0;
- ginInsertRecordBA(&buildstate->accum, heapptr, entries, nentries);
+ ginInsertRecordBA(&buildstate->accum, heapptr, attnum, entries, nentries);
MemoryContextReset(buildstate->funcCtx);
{
GinBuildState *buildstate = (GinBuildState *) state;
MemoryContext oldCtx;
-
- if (*isnull)
- return;
+ int i;
oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
- buildstate->indtuples += ginHeapTupleBulkInsert(buildstate, *values, &htup->t_self);
+ for(i=0; i<buildstate->ginstate.origTupdesc->natts;i++)
+ if ( !isnull[i] )
+ buildstate->indtuples += ginHeapTupleBulkInsert(buildstate,
+ (OffsetNumber)(i+1), values[i],
+ &htup->t_self);
/* If we've maxed out our available memory, dump everything to the index */
if (buildstate->accum.allocatedMemory >= maintenance_work_mem * 1024L)
ItemPointerData *list;
Datum entry;
uint32 nlist;
+ OffsetNumber attnum;
- while ((list = ginGetEntry(&buildstate->accum, &entry, &nlist)) != NULL)
+ while ((list = ginGetEntry(&buildstate->accum, &attnum, &entry, &nlist)) != NULL)
{
/* there could be many entries, so be willing to abort here */
CHECK_FOR_INTERRUPTS();
- ginEntryInsert(index, &buildstate->ginstate, entry, list, nlist, TRUE);
+ ginEntryInsert(index, &buildstate->ginstate, attnum, entry, list, nlist, TRUE);
}
MemoryContextReset(buildstate->tmpCtx);
Datum entry;
uint32 nlist;
MemoryContext oldCtx;
+ OffsetNumber attnum;
if (RelationGetNumberOfBlocks(index) != 0)
elog(ERROR, "index \"%s\" already contains data",
/* dump remaining entries to the index */
oldCtx = MemoryContextSwitchTo(buildstate.tmpCtx);
- while ((list = ginGetEntry(&buildstate.accum, &entry, &nlist)) != NULL)
+ while ((list = ginGetEntry(&buildstate.accum, &attnum, &entry, &nlist)) != NULL)
{
/* there could be many entries, so be willing to abort here */
CHECK_FOR_INTERRUPTS();
- ginEntryInsert(index, &buildstate.ginstate, entry, list, nlist, TRUE);
+ ginEntryInsert(index, &buildstate.ginstate, attnum, entry, list, nlist, TRUE);
}
MemoryContextSwitchTo(oldCtx);
* Inserts value during normal insertion
*/
static uint32
-ginHeapTupleInsert(Relation index, GinState *ginstate, Datum value, ItemPointer item)
+ginHeapTupleInsert(Relation index, GinState *ginstate, OffsetNumber attnum, Datum value, ItemPointer item)
{
Datum *entries;
int32 i,
nentries;
- entries = extractEntriesSU(ginstate, value, &nentries);
+ entries = extractEntriesSU(ginstate, attnum, value, &nentries);
if (nentries == 0)
/* nothing to insert */
return 0;
for (i = 0; i < nentries; i++)
- ginEntryInsert(index, ginstate, entries[i], item, 1, FALSE);
+ ginEntryInsert(index, ginstate, attnum, entries[i], item, 1, FALSE);
return nentries;
}
GinState ginstate;
MemoryContext oldCtx;
MemoryContext insertCtx;
- uint32 res;
-
- if (*isnull)
- PG_RETURN_BOOL(false);
+ uint32 res = 0;
+ int i;
insertCtx = AllocSetContextCreate(CurrentMemoryContext,
"Gin insert temporary context",
initGinState(&ginstate, index);
- res = ginHeapTupleInsert(index, &ginstate, *values, ht_ctid);
+ for(i=0; i<ginstate.origTupdesc->natts;i++)
+ if ( !isnull[i] )
+ res += ginHeapTupleInsert(index, &ginstate, (OffsetNumber)(i+1), values[i], ht_ctid);
MemoryContextSwitchTo(oldCtx);
MemoryContextDelete(insertCtx);
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/gin/ginscan.c,v 1.16 2008/07/04 13:21:18 teodor Exp $
+ * $PostgreSQL: pgsql/src/backend/access/gin/ginscan.c,v 1.17 2008/07/11 21:06:29 tgl Exp $
*-------------------------------------------------------------------------
*/
}
static void
-fillScanKey(GinState *ginstate, GinScanKey key, Datum query,
+fillScanKey(GinState *ginstate, GinScanKey key, OffsetNumber attnum, Datum query,
Datum *entryValues, bool *partial_matches, uint32 nEntryValues,
StrategyNumber strategy)
{
key->entryRes = (bool *) palloc0(sizeof(bool) * nEntryValues);
key->scanEntry = (GinScanEntry) palloc(sizeof(GinScanEntryData) * nEntryValues);
key->strategy = strategy;
+ key->attnum = attnum;
key->query = query;
key->firstCall = TRUE;
ItemPointerSet(&(key->curItem), InvalidBlockNumber, InvalidOffsetNumber);
{
key->scanEntry[i].pval = key->entryRes + i;
key->scanEntry[i].entry = entryValues[i];
+ key->scanEntry[i].attnum = attnum;
ItemPointerSet(&(key->scanEntry[i].curItem), InvalidBlockNumber, InvalidOffsetNumber);
key->scanEntry[i].offset = InvalidOffsetNumber;
key->scanEntry[i].buffer = InvalidBuffer;
key->scanEntry[i].partialMatch = NULL;
key->scanEntry[i].list = NULL;
key->scanEntry[i].nlist = 0;
- key->scanEntry[i].isPartialMatch = ( ginstate->canPartialMatch && partial_matches )
+ key->scanEntry[i].isPartialMatch = ( ginstate->canPartialMatch[attnum - 1] && partial_matches )
? partial_matches[i] : false;
/* link to the equals entry in current scan key */
key->scanEntry[i].master = NULL;
for (j = 0; j < i; j++)
- if (compareEntries(ginstate, entryValues[i], entryValues[j]) == 0)
+ if (compareEntries(ginstate, attnum, entryValues[i], entryValues[j]) == 0)
{
key->scanEntry[i].master = key->scanEntry + j;
break;
int32 nEntryValues;
bool *partial_matches = NULL;
- Assert(scankey[i].sk_attno == 1);
-
/* XXX can't we treat nulls by just setting isVoidRes? */
/* This would amount to assuming that all GIN operators are strict */
if (scankey[i].sk_flags & SK_ISNULL)
elog(ERROR, "GIN doesn't support NULL as scan key");
entryValues = (Datum *) DatumGetPointer(FunctionCall4(
- &so->ginstate.extractQueryFn,
- scankey[i].sk_argument,
- PointerGetDatum(&nEntryValues),
- UInt16GetDatum(scankey[i].sk_strategy),
- PointerGetDatum(&partial_matches)));
+ &so->ginstate.extractQueryFn[scankey[i].sk_attno - 1],
+ scankey[i].sk_argument,
+ PointerGetDatum(&nEntryValues),
+ UInt16GetDatum(scankey[i].sk_strategy),
+ PointerGetDatum(&partial_matches)));
if (nEntryValues < 0)
{
/*
/* full scan... */
continue;
- fillScanKey(&so->ginstate, &(so->keys[nkeys]), scankey[i].sk_argument,
+ fillScanKey(&so->ginstate, &(so->keys[nkeys]), scankey[i].sk_attno, scankey[i].sk_argument,
entryValues, partial_matches, nEntryValues, scankey[i].sk_strategy);
nkeys++;
}
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/gin/ginutil.c,v 1.15 2008/05/16 16:31:01 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/access/gin/ginutil.c,v 1.16 2008/07/11 21:06:29 tgl Exp $
*-------------------------------------------------------------------------
*/
#include "access/genam.h"
#include "access/gin.h"
#include "access/reloptions.h"
+#include "catalog/pg_type.h"
#include "storage/bufmgr.h"
#include "storage/freespace.h"
#include "storage/lmgr.h"
void
initGinState(GinState *state, Relation index)
{
- if (index->rd_att->natts != 1)
- elog(ERROR, "numberOfAttributes %d != 1",
- index->rd_att->natts);
-
- state->tupdesc = index->rd_att;
-
- fmgr_info_copy(&(state->compareFn),
- index_getprocinfo(index, 1, GIN_COMPARE_PROC),
- CurrentMemoryContext);
- fmgr_info_copy(&(state->extractValueFn),
- index_getprocinfo(index, 1, GIN_EXTRACTVALUE_PROC),
- CurrentMemoryContext);
- fmgr_info_copy(&(state->extractQueryFn),
- index_getprocinfo(index, 1, GIN_EXTRACTQUERY_PROC),
- CurrentMemoryContext);
- fmgr_info_copy(&(state->consistentFn),
- index_getprocinfo(index, 1, GIN_CONSISTENT_PROC),
- CurrentMemoryContext);
-
- /*
- * Check opclass capability to do partial match.
- */
- if ( index_getprocid(index, 1, GIN_COMPARE_PARTIAL_PROC) != InvalidOid )
+ int i;
+
+ state->origTupdesc = index->rd_att;
+
+ state->oneCol = (index->rd_att->natts == 1) ? true : false;
+
+ for(i=0;i<index->rd_att->natts;i++)
+ {
+ state->tupdesc[i] = CreateTemplateTupleDesc(2,false);
+
+ TupleDescInitEntry( state->tupdesc[i], (AttrNumber) 1, NULL,
+ INT2OID, -1, 0);
+ TupleDescInitEntry( state->tupdesc[i], (AttrNumber) 2, NULL,
+ index->rd_att->attrs[i]->atttypid,
+ index->rd_att->attrs[i]->atttypmod,
+ index->rd_att->attrs[i]->attndims
+ );
+
+ fmgr_info_copy(&(state->compareFn[i]),
+ index_getprocinfo(index, i+1, GIN_COMPARE_PROC),
+ CurrentMemoryContext);
+ fmgr_info_copy(&(state->extractValueFn[i]),
+ index_getprocinfo(index, i+1, GIN_EXTRACTVALUE_PROC),
+ CurrentMemoryContext);
+ 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.
+ */
+ if ( index_getprocid(index, i+1, GIN_COMPARE_PARTIAL_PROC) != InvalidOid )
+ {
+ fmgr_info_copy(&(state->comparePartialFn[i]),
+ index_getprocinfo(index, i+1, GIN_COMPARE_PARTIAL_PROC),
+ CurrentMemoryContext);
+
+ state->canPartialMatch[i] = true;
+ }
+ else
+ {
+ state->canPartialMatch[i] = false;
+ }
+ }
+}
+
+/*
+ * Extract attribute (column) number of stored entry from GIN tuple
+ */
+OffsetNumber
+gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple)
+{
+ OffsetNumber colN = FirstOffsetNumber;
+
+ if ( !ginstate->oneCol )
{
- fmgr_info_copy(&(state->comparePartialFn),
- index_getprocinfo(index, 1, GIN_COMPARE_PARTIAL_PROC),
- CurrentMemoryContext);
+ Datum res;
+ bool isnull;
+
+ /*
+ * First attribute is always int16, so we can safely use any
+ * tuple descriptor to obtain first attribute of tuple
+ */
+ res = index_getattr(tuple, FirstOffsetNumber, ginstate->tupdesc[0],
+ &isnull);
+ Assert(!isnull);
- state->canPartialMatch = true;
+ colN = DatumGetUInt16(res);
+ Assert( colN >= FirstOffsetNumber && colN <= ginstate->origTupdesc->natts );
+ }
+
+ return colN;
+}
+
+/*
+ * Extract stored datum from GIN tuple
+ */
+Datum
+gin_index_getattr(GinState *ginstate, IndexTuple tuple)
+{
+ bool isnull;
+ Datum res;
+
+ if ( ginstate->oneCol )
+ {
+ /*
+ * Single column index doesn't store attribute numbers in tuples
+ */
+ res = index_getattr(tuple, FirstOffsetNumber, ginstate->origTupdesc,
+ &isnull);
}
else
{
- state->canPartialMatch = false;
+ /*
+ * Since the datum type depends on which index column it's from,
+ * we must be careful to use the right tuple descriptor here.
+ */
+ OffsetNumber colN = gintuple_get_attrnum(ginstate, tuple);
+
+ res = index_getattr(tuple, OffsetNumberNext(FirstOffsetNumber),
+ ginstate->tupdesc[colN - 1],
+ &isnull);
}
+
+ Assert(!isnull);
+
+ return res;
}
/*
}
int
-compareEntries(GinState *ginstate, Datum a, Datum b)
+compareEntries(GinState *ginstate, OffsetNumber attnum, Datum a, Datum b)
{
return DatumGetInt32(
FunctionCall2(
- &ginstate->compareFn,
+ &ginstate->compareFn[attnum-1],
a, b
)
);
}
+int
+compareAttEntries(GinState *ginstate, OffsetNumber attnum_a, Datum a,
+ OffsetNumber attnum_b, Datum b)
+{
+ if ( attnum_a == attnum_b )
+ return compareEntries( ginstate, attnum_a, a, b);
+
+ return ( attnum_a < attnum_b ) ? -1 : 1;
+}
+
typedef struct
{
FmgrInfo *cmpDatumFunc;
}
Datum *
-extractEntriesS(GinState *ginstate, Datum value, int32 *nentries,
+extractEntriesS(GinState *ginstate, OffsetNumber attnum, Datum value, int32 *nentries,
bool *needUnique)
{
Datum *entries;
entries = (Datum *) DatumGetPointer(FunctionCall2(
- &ginstate->extractValueFn,
+ &ginstate->extractValueFn[attnum-1],
value,
PointerGetDatum(nentries)
));
{
cmpEntriesData arg;
- arg.cmpDatumFunc = &ginstate->compareFn;
+ arg.cmpDatumFunc = &ginstate->compareFn[attnum-1];
arg.needUnique = needUnique;
qsort_arg(entries, *nentries, sizeof(Datum),
(qsort_arg_comparator) cmpEntries, (void *) &arg);
Datum *
-extractEntriesSU(GinState *ginstate, Datum value, int32 *nentries)
+extractEntriesSU(GinState *ginstate, OffsetNumber attnum, Datum value, int32 *nentries)
{
bool needUnique;
- Datum *entries = extractEntriesS(ginstate, value, nentries,
+ Datum *entries = extractEntriesS(ginstate, attnum, value, nentries,
&needUnique);
if (needUnique)
while (ptr - entries < *nentries)
{
- if (compareEntries(ginstate, *ptr, *res) != 0)
+ if (compareEntries(ginstate, attnum, *ptr, *res) != 0)
*(++res) = *ptr++;
else
ptr++;
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/gin/ginvacuum.c,v 1.20 2008/05/12 00:00:44 alvherre Exp $
+ * $PostgreSQL: pgsql/src/backend/access/gin/ginvacuum.c,v 1.21 2008/07/11 21:06:29 tgl Exp $
*-------------------------------------------------------------------------
*/
if (GinGetNPosting(itup) != newN)
{
- bool isnull;
- Datum value;
+ Datum value;
+ OffsetNumber attnum;
/*
* Some ItemPointers was deleted, so we should remake our
itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
}
- value = index_getattr(itup, FirstOffsetNumber, gvs->ginstate.tupdesc, &isnull);
- itup = GinFormTuple(&gvs->ginstate, value, GinGetPosting(itup), newN);
+ value = gin_index_getattr(&gvs->ginstate, itup);
+ attnum = gintuple_get_attrnum(&gvs->ginstate, itup);
+ itup = GinFormTuple(&gvs->ginstate, attnum, value, GinGetPosting(itup), newN);
PageIndexTupleDelete(tmppage, i);
if (PageAddItem(tmppage, (Item) itup, IndexTupleSize(itup), i, false, false) != i)
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/gin/ginxlog.c,v 1.14 2008/06/12 09:12:29 heikki Exp $
+ * $PostgreSQL: pgsql/src/backend/access/gin/ginxlog.c,v 1.15 2008/07/11 21:06:29 tgl Exp $
*-------------------------------------------------------------------------
*/
#include "postgres.h"
if (split->rootBlkno == GIN_ROOT_BLKNO)
{
- prepareEntryScan(&btree, reln, (Datum) 0, NULL);
+ prepareEntryScan(&btree, reln, InvalidOffsetNumber, (Datum) 0, NULL);
btree.entry = ginPageGetLinkItup(buffer);
}
else
*
* Copyright (c) 2006-2008, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/include/access/gin.h,v 1.22 2008/06/19 00:46:05 alvherre Exp $
+ * $PostgreSQL: pgsql/src/include/access/gin.h,v 1.23 2008/07/11 21:06:29 tgl Exp $
*--------------------------------------------------------------------------
*/
typedef struct GinState
{
- FmgrInfo compareFn;
- FmgrInfo extractValueFn;
- FmgrInfo extractQueryFn;
- FmgrInfo consistentFn;
- FmgrInfo comparePartialFn; /* optional method */
-
- bool canPartialMatch; /* can opclass perform partial
- * match (prefix search)? */
- TupleDesc tupdesc;
+ FmgrInfo compareFn[INDEX_MAX_KEYS];
+ FmgrInfo extractValueFn[INDEX_MAX_KEYS];
+ FmgrInfo extractQueryFn[INDEX_MAX_KEYS];
+ FmgrInfo consistentFn[INDEX_MAX_KEYS];
+ FmgrInfo comparePartialFn[INDEX_MAX_KEYS]; /* optional method */
+
+ bool canPartialMatch[INDEX_MAX_KEYS]; /* can opclass perform partial
+ * match (prefix search)? */
+
+ TupleDesc tupdesc[INDEX_MAX_KEYS];
+ TupleDesc origTupdesc;
+ bool oneCol;
} GinState;
/* XLog stuff */
extern Buffer GinNewBuffer(Relation index);
extern void GinInitBuffer(Buffer b, uint32 f);
extern void GinInitPage(Page page, uint32 f, Size pageSize);
-extern int compareEntries(GinState *ginstate, Datum a, Datum b);
-extern Datum *extractEntriesS(GinState *ginstate, Datum value,
+extern int compareEntries(GinState *ginstate, OffsetNumber attnum, Datum a, Datum b);
+extern int compareAttEntries(GinState *ginstate, OffsetNumber attnum_a, Datum a,
+ OffsetNumber attnum_b, Datum b);
+extern Datum *extractEntriesS(GinState *ginstate, OffsetNumber attnum, Datum value,
int32 *nentries, bool *needUnique);
-extern Datum *extractEntriesSU(GinState *ginstate, Datum value, int32 *nentries);
+extern Datum *extractEntriesSU(GinState *ginstate, OffsetNumber attnum, Datum value, int32 *nentries);
extern Page GinPageGetCopyPage(Page page);
+extern Datum gin_index_getattr(GinState *ginstate, IndexTuple tuple);
+extern OffsetNumber gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple);
/* gininsert.c */
extern Datum ginbuild(PG_FUNCTION_ARGS);
extern Datum gininsert(PG_FUNCTION_ARGS);
BlockNumber rightblkno;
/* Entry options */
+ OffsetNumber entryAttnum;
Datum entryValue;
IndexTuple entry;
bool isDelete;
extern void findParents(GinBtree btree, GinBtreeStack *stack, BlockNumber rootBlkno);
/* ginentrypage.c */
-extern IndexTuple GinFormTuple(GinState *ginstate, Datum key, ItemPointerData *ipd, uint32 nipd);
-extern Datum ginGetHighKey(GinState *ginstate, Page page);
-extern void prepareEntryScan(GinBtree btree, Relation index, Datum value, GinState *ginstate);
+extern IndexTuple GinFormTuple(GinState *ginstate, OffsetNumber attnum, Datum key,
+ ItemPointerData *ipd, uint32 nipd);
+extern void prepareEntryScan(GinBtree btree, Relation index, OffsetNumber attnum,
+ Datum value, GinState *ginstate);
extern void entryFillRoot(GinBtree btree, Buffer root, Buffer lbuf, Buffer rbuf);
extern IndexTuple ginPageGetLinkItup(Buffer buf);
/* entry, got from extractQueryFn */
Datum entry;
+ OffsetNumber attnum;
/* Current page in posting tree */
Buffer buffer;
/* for calling consistentFn(GinScanKey->entryRes, strategy, query) */
StrategyNumber strategy;
Datum query;
+ OffsetNumber attnum;
ItemPointerData curItem;
bool firstCall;
/* ginbulk.c */
typedef struct EntryAccumulator
{
- Datum value;
- uint32 length;
- uint32 number;
+ OffsetNumber attnum;
+ Datum value;
+ uint32 length;
+ uint32 number;
ItemPointerData *list;
- bool shouldSort;
+ bool shouldSort;
struct EntryAccumulator *left;
struct EntryAccumulator *right;
} EntryAccumulator;
extern void ginInitBA(BuildAccumulator *accum);
extern void ginInsertRecordBA(BuildAccumulator *accum,
- ItemPointer heapptr, Datum *entries, int32 nentry);
-extern ItemPointerData *ginGetEntry(BuildAccumulator *accum, Datum *entry, uint32 *n);
+ ItemPointer heapptr,
+ OffsetNumber attnum, Datum *entries, int32 nentry);
+extern ItemPointerData *ginGetEntry(BuildAccumulator *accum, OffsetNumber *attnum, Datum *entry, uint32 *n);
#endif
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.465 2008/07/03 20:58:46 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.466 2008/07/11 21:06:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 200807031
+#define CATALOG_VERSION_NO 200807111
#endif
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/pg_am.h,v 1.56 2008/05/16 16:31:01 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_am.h,v 1.57 2008/07/11 21:06:29 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
DATA(insert OID = 783 ( gist 0 7 f f t t t t t t gistinsert gistbeginscan gistgettuple gistgetbitmap gistrescan gistendscan gistmarkpos gistrestrpos gistbuild 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 f f t f gininsert ginbeginscan gingettuple gingetbitmap ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbulkdelete ginvacuumcleanup gincostestimate ginoptions ));
+DATA(insert OID = 2742 ( gin 0 5 f f t t f f t f gininsert ginbeginscan gingettuple gingetbitmap ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbulkdelete ginvacuumcleanup gincostestimate ginoptions ));
DESCR("GIN index access method");
#define GIN_AM_OID 2742
RESET enable_indexscan;
RESET enable_bitmapscan;
--
--- GIN over int[]
+-- GIN over int[] and text[]
--
SET enable_seqscan = OFF;
SET enable_indexscan = ON;
95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483}
(1 row)
+-- And try it with a multicolumn GIN index
+DROP INDEX intarrayidx, textarrayidx;
+CREATE INDEX botharrayidx ON array_index_op_test USING gin (i, t);
+SET enable_seqscan = OFF;
+SET enable_indexscan = ON;
+SET enable_bitmapscan = OFF;
+SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAA80240}' ORDER BY seqno;
+ seqno | i | t
+-------+--------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240}
+ 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240}
+ 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104}
+ 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433}
+ 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(7 rows)
+
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAA80240}' ORDER BY seqno;
+ seqno | i | t
+-------+--------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240}
+ 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240}
+ 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104}
+ 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433}
+ 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(7 rows)
+
+SELECT * FROM array_index_op_test WHERE i @> '{32}' AND t && '{AAAAAAA80240}' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------------+------------------------------------------------------------------------------
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(1 row)
+
+SELECT * FROM array_index_op_test WHERE i && '{32}' AND t @> '{AAAAAAA80240}' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------------+------------------------------------------------------------------------------
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(1 row)
+
+SET enable_indexscan = OFF;
+SET enable_bitmapscan = ON;
+SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAA80240}' ORDER BY seqno;
+ seqno | i | t
+-------+--------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240}
+ 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240}
+ 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104}
+ 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433}
+ 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(7 rows)
+
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAA80240}' ORDER BY seqno;
+ seqno | i | t
+-------+--------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240}
+ 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240}
+ 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104}
+ 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433}
+ 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(7 rows)
+
+SELECT * FROM array_index_op_test WHERE i @> '{32}' AND t && '{AAAAAAA80240}' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------------+------------------------------------------------------------------------------
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(1 row)
+
+SELECT * FROM array_index_op_test WHERE i && '{32}' AND t @> '{AAAAAAA80240}' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------------+------------------------------------------------------------------------------
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(1 row)
+
RESET enable_seqscan;
RESET enable_indexscan;
RESET enable_bitmapscan;
RESET enable_bitmapscan;
--
--- GIN over int[]
+-- GIN over int[] and text[]
--
SET enable_seqscan = OFF;
SELECT * FROM array_index_op_test WHERE i <@ '{38,34,32,89}' ORDER BY seqno;
SELECT * FROM array_index_op_test WHERE i = '{47,77}' ORDER BY seqno;
+-- And try it with a multicolumn GIN index
+
+DROP INDEX intarrayidx, textarrayidx;
+
+CREATE INDEX botharrayidx ON array_index_op_test USING gin (i, t);
+
+SET enable_seqscan = OFF;
+SET enable_indexscan = ON;
+SET enable_bitmapscan = OFF;
+
+SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAA80240}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAA80240}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i @> '{32}' AND t && '{AAAAAAA80240}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i && '{32}' AND t @> '{AAAAAAA80240}' ORDER BY seqno;
+
+SET enable_indexscan = OFF;
+SET enable_bitmapscan = ON;
+
+SELECT * FROM array_index_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i && '{32}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t @> '{AAAAAAA80240}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE t && '{AAAAAAA80240}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i @> '{32}' AND t && '{AAAAAAA80240}' ORDER BY seqno;
+SELECT * FROM array_index_op_test WHERE i && '{32}' AND t @> '{AAAAAAA80240}' ORDER BY seqno;
+
RESET enable_seqscan;
RESET enable_indexscan;
RESET enable_bitmapscan;