-
+
Indexes
after a database crash.
For these reasons, hash index use is presently discouraged.
-
+
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
PostgreSQL includes GIN operator classes
for one-dimensional arrays, which support indexed
- 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
PostgreSQL; see the
file pg_config_manual.h.)
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.
+ 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.
+
+
Of course, each column must be used with operators appropriate to the index
type; clauses that involve other operators will not be considered.
PostgreSQL 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.
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
- x < ?
which will never imply
- x < 2
for all possible values of the parameter.
+ example a prepared query with a parameter might specify
+ x < ?
which will never imply
+ x < 2
for all possible values of the parameter.
- 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
PostgreSQL.) Only B-tree currently
the optional clauses ASC>, DESC>, NULLS
FIRST>, and/or 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 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; iginstate.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; inatts;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;ird_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;