From 27cb66fdfe862f395cefa0d498b681ce142f59d8 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 11 Jul 2008 21:06:29 +0000 Subject: [PATCH] Multi-column GIN indexes. Teodor Sigaev --- doc/src/sgml/indices.sgml | 37 +++-- doc/src/sgml/ref/create_index.sgml | 6 +- src/backend/access/gin/ginbulk.c | 38 ++--- src/backend/access/gin/ginentrypage.c | 60 ++++---- src/backend/access/gin/ginget.c | 33 +++-- src/backend/access/gin/gininsert.c | 59 ++++---- src/backend/access/gin/ginscan.c | 24 +-- src/backend/access/gin/ginutil.c | 161 ++++++++++++++++----- src/backend/access/gin/ginvacuum.c | 11 +- src/backend/access/gin/ginxlog.c | 4 +- src/include/access/gin.h | 57 +++++--- src/include/catalog/catversion.h | 4 +- src/include/catalog/pg_am.h | 4 +- src/test/regress/expected/create_index.out | 126 +++++++++++++++- src/test/regress/sql/create_index.sql | 29 +++- 15 files changed, 468 insertions(+), 185 deletions(-) diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 3de0541922..2ab713c39b 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -1,4 +1,4 @@ - + Indexes @@ -198,7 +198,7 @@ CREATE INDEX name ON table after a database crash. For these reasons, hash index use is presently discouraged. - + @@ -250,9 +250,9 @@ CREATE INDEX name ON table 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 @@ -306,7 +306,7 @@ CREATE INDEX test2_mm_idx ON test2 (major, minor); - 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.) @@ -336,14 +336,21 @@ CREATE INDEX test2_mm_idx ON test2 (major, minor); 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. @@ -551,7 +558,7 @@ CREATE UNIQUE INDEX name ON table 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. @@ -798,9 +805,9 @@ SELECT * FROM orders WHERE order_nr = 3501; 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. diff --git a/doc/src/sgml/ref/create_index.sgml b/doc/src/sgml/ref/create_index.sgml index 32b7bbebd9..030fa1bb00 100644 --- a/doc/src/sgml/ref/create_index.sgml +++ b/doc/src/sgml/ref/create_index.sgml @@ -1,5 +1,5 @@ @@ -394,7 +394,7 @@ Indexes: - 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 @@ -423,7 +423,7 @@ Indexes: 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 diff --git a/src/backend/access/gin/ginbulk.c b/src/backend/access/gin/ginbulk.c index b17e87cdb1..7f50b52602 100644 --- a/src/backend/access/gin/ginbulk.c +++ b/src/backend/access/gin/ginbulk.c @@ -8,7 +8,7 @@ * 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 $ *------------------------------------------------------------------------- */ @@ -82,16 +82,16 @@ ginInsertData(BuildAccumulator *accum, EntryAccumulator *entry, ItemPointer heap * 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; @@ -101,7 +101,7 @@ getDatumCopy(BuildAccumulator *accum, Datum value) * 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; @@ -110,7 +110,7 @@ ginInsertEntry(BuildAccumulator *accum, ItemPointer heapptr, Datum entry) 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 @@ -132,7 +132,8 @@ ginInsertEntry(BuildAccumulator *accum, ItemPointer heapptr, Datum entry) 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; @@ -160,7 +161,8 @@ ginInsertEntry(BuildAccumulator *accum, ItemPointer heapptr, Datum entry) * 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; @@ -168,15 +170,15 @@ ginChooseElem(BuildAccumulator *accum, ItemPointer heapptr, Datum *entries, uint 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); } /* @@ -185,7 +187,8 @@ ginChooseElem(BuildAccumulator *accum, ItemPointer heapptr, Datum *entries, uint * 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, @@ -201,8 +204,8 @@ ginInsertRecordBA(BuildAccumulator *accum, ItemPointer heapptr, Datum *entries, 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 @@ -259,7 +262,7 @@ walkTree(BuildAccumulator *accum) } ItemPointerData * -ginGetEntry(BuildAccumulator *accum, Datum *value, uint32 *n) +ginGetEntry(BuildAccumulator *accum, OffsetNumber *attnum, Datum *value, uint32 *n) { EntryAccumulator *entry; ItemPointerData *list; @@ -299,6 +302,7 @@ ginGetEntry(BuildAccumulator *accum, Datum *value, uint32 *n) return NULL; *n = entry->number; + *attnum = entry->attnum; *value = entry->value; list = entry->list; diff --git a/src/backend/access/gin/ginentrypage.c b/src/backend/access/gin/ginentrypage.c index 96be4ee94b..ef9c9ba2a4 100644 --- a/src/backend/access/gin/ginentrypage.c +++ b/src/backend/access/gin/ginentrypage.c @@ -8,7 +8,7 @@ * 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 $ *------------------------------------------------------------------------- */ @@ -38,14 +38,27 @@ * - 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)); @@ -88,28 +101,20 @@ getRightMostTuple(Page page) 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; @@ -154,11 +159,11 @@ entryLocateEntry(GinBtree btree, GinBtreeStack *stack) 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) @@ -217,13 +222,13 @@ entryLocateLeafEntry(GinBtree btree, GinBtreeStack *stack) 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; @@ -587,7 +592,7 @@ entryFillRoot(GinBtree btree, Buffer root, Buffer lbuf, Buffer rbuf) } 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)); @@ -603,6 +608,7 @@ prepareEntryScan(GinBtree btree, Relation index, Datum value, GinState *ginstate btree->index = index; btree->ginstate = ginstate; + btree->entryAttnum = attnum; btree->entryValue = value; btree->isDelete = FALSE; diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c index 76d937740f..9648d5ea28 100644 --- a/src/backend/access/gin/ginget.c +++ b/src/backend/access/gin/ginget.c @@ -8,7 +8,7 @@ * 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 $ *------------------------------------------------------------------------- */ @@ -138,7 +138,6 @@ computePartialMatchList( GinBtreeData *btree, GinBtreeStack *stack, GinScanEntry Page page; IndexTuple itup; Datum idatum; - bool isnull; int32 cmp; scanEntry->partialMatch = tbm_create( work_mem * 1024L ); @@ -153,8 +152,15 @@ computePartialMatchList( GinBtreeData *btree, GinBtreeStack *stack, GinScanEntry 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. @@ -163,7 +169,7 @@ computePartialMatchList( GinBtreeData *btree, GinBtreeStack *stack, GinScanEntry * 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))); @@ -182,8 +188,8 @@ computePartialMatchList( GinBtreeData *btree, GinBtreeStack *stack, GinScanEntry 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 @@ -220,12 +226,15 @@ computePartialMatchList( GinBtreeData *btree, GinBtreeStack *stack, GinScanEntry 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; } @@ -270,7 +279,7 @@ startScanEntry(Relation index, GinState *ginstate, GinScanEntry entry) * 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); @@ -705,7 +714,7 @@ keyGetItem(Relation index, GinState *ginstate, MemoryContext tempCtx, *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, diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c index 654bf137e2..ac35069d7f 100644 --- a/src/backend/access/gin/gininsert.c +++ b/src/backend/access/gin/gininsert.c @@ -8,7 +8,7 @@ * 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 $ *------------------------------------------------------------------------- */ @@ -99,9 +99,9 @@ static IndexTuple 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) { @@ -119,7 +119,7 @@ addItemPointersToTuple(Relation index, GinState *ginstate, GinBtreeStack *stack, 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); @@ -138,14 +138,15 @@ addItemPointersToTuple(Relation index, GinState *ginstate, GinBtreeStack *stack, * 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); @@ -180,7 +181,7 @@ ginEntryInsert(Relation index, GinState *ginstate, Datum value, ItemPointerData 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"); @@ -203,21 +204,21 @@ ginEntryInsert(Relation index, GinState *ginstate, Datum value, ItemPointerData * 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); @@ -230,13 +231,15 @@ ginBuildCallback(Relation index, HeapTuple htup, Datum *values, { 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) @@ -244,12 +247,13 @@ ginBuildCallback(Relation index, HeapTuple htup, Datum *values, 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); @@ -273,6 +277,7 @@ ginbuild(PG_FUNCTION_ARGS) Datum entry; uint32 nlist; MemoryContext oldCtx; + OffsetNumber attnum; if (RelationGetNumberOfBlocks(index) != 0) elog(ERROR, "index \"%s\" already contains data", @@ -337,11 +342,11 @@ ginbuild(PG_FUNCTION_ARGS) /* 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); @@ -362,20 +367,20 @@ ginbuild(PG_FUNCTION_ARGS) * 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; } @@ -395,10 +400,8 @@ gininsert(PG_FUNCTION_ARGS) 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", @@ -410,7 +413,9 @@ gininsert(PG_FUNCTION_ARGS) 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); diff --git a/src/backend/access/gin/ginscan.c b/src/backend/access/gin/ginscan.c index 21eb1d8cbb..b81ba0c12e 100644 --- a/src/backend/access/gin/ginscan.c +++ b/src/backend/access/gin/ginscan.c @@ -8,7 +8,7 @@ * 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 $ *------------------------------------------------------------------------- */ @@ -36,7 +36,7 @@ ginbeginscan(PG_FUNCTION_ARGS) } 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) { @@ -47,6 +47,7 @@ fillScanKey(GinState *ginstate, GinScanKey key, Datum query, 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); @@ -55,19 +56,20 @@ fillScanKey(GinState *ginstate, GinScanKey key, Datum query, { 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; @@ -164,19 +166,17 @@ newScanKey(IndexScanDesc scan) 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) { /* @@ -194,7 +194,7 @@ newScanKey(IndexScanDesc scan) /* 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++; } diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index 36105e20d2..86b2650c75 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -8,7 +8,7 @@ * 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 $ *------------------------------------------------------------------------- */ @@ -16,6 +16,7 @@ #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" @@ -23,40 +24,116 @@ 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; } /* @@ -136,16 +213,26 @@ GinInitBuffer(Buffer b, uint32 f) } 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; @@ -165,13 +252,13 @@ cmpEntries(const Datum *a, const Datum *b, cmpEntriesData *arg) } 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) )); @@ -184,7 +271,7 @@ extractEntriesS(GinState *ginstate, Datum value, int32 *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); @@ -195,10 +282,10 @@ extractEntriesS(GinState *ginstate, Datum value, int32 *nentries, 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) @@ -210,7 +297,7 @@ extractEntriesSU(GinState *ginstate, Datum value, int32 *nentries) while (ptr - entries < *nentries) { - if (compareEntries(ginstate, *ptr, *res) != 0) + if (compareEntries(ginstate, attnum, *ptr, *res) != 0) *(++res) = *ptr++; else ptr++; diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c index b6411f4b9b..249f612dd1 100644 --- a/src/backend/access/gin/ginvacuum.c +++ b/src/backend/access/gin/ginvacuum.c @@ -8,7 +8,7 @@ * 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 $ *------------------------------------------------------------------------- */ @@ -515,8 +515,8 @@ ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint3 if (GinGetNPosting(itup) != newN) { - bool isnull; - Datum value; + Datum value; + OffsetNumber attnum; /* * Some ItemPointers was deleted, so we should remake our @@ -544,8 +544,9 @@ ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint3 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) diff --git a/src/backend/access/gin/ginxlog.c b/src/backend/access/gin/ginxlog.c index 428c956c7c..b777e56536 100644 --- a/src/backend/access/gin/ginxlog.c +++ b/src/backend/access/gin/ginxlog.c @@ -8,7 +8,7 @@ * 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" @@ -549,7 +549,7 @@ ginContinueSplit(ginIncompleteSplit *split) if (split->rootBlkno == GIN_ROOT_BLKNO) { - prepareEntryScan(&btree, reln, (Datum) 0, NULL); + prepareEntryScan(&btree, reln, InvalidOffsetNumber, (Datum) 0, NULL); btree.entry = ginPageGetLinkItup(buffer); } else diff --git a/src/include/access/gin.h b/src/include/access/gin.h index d47d7ac2d6..f6a786d3db 100644 --- a/src/include/access/gin.h +++ b/src/include/access/gin.h @@ -4,7 +4,7 @@ * * 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 $ *-------------------------------------------------------------------------- */ @@ -140,15 +140,18 @@ typedef struct 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 */ @@ -235,12 +238,16 @@ extern void initGinState(GinState *state, Relation index); 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); @@ -291,6 +298,7 @@ typedef struct GinBtreeData BlockNumber rightblkno; /* Entry options */ + OffsetNumber entryAttnum; Datum entryValue; IndexTuple entry; bool isDelete; @@ -310,9 +318,10 @@ extern void ginInsertValue(GinBtree btree, GinBtreeStack *stack); 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); @@ -359,6 +368,7 @@ typedef struct GinScanEntryData /* entry, got from extractQueryFn */ Datum entry; + OffsetNumber attnum; /* Current page in posting tree */ Buffer buffer; @@ -396,6 +406,7 @@ typedef struct GinScanKeyData /* for calling consistentFn(GinScanKey->entryRes, strategy, query) */ StrategyNumber strategy; Datum query; + OffsetNumber attnum; ItemPointerData curItem; bool firstCall; @@ -450,11 +461,12 @@ extern Datum ginarrayconsistent(PG_FUNCTION_ARGS); /* 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; @@ -474,7 +486,8 @@ typedef struct 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 diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index e7ba550c48..d74a5eb582 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200807031 +#define CATALOG_VERSION_NO 200807111 #endif diff --git a/src/include/catalog/pg_am.h b/src/include/catalog/pg_am.h index 0fe5d05e7c..712a409633 100644 --- a/src/include/catalog/pg_am.h +++ b/src/include/catalog/pg_am.h @@ -8,7 +8,7 @@ * 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 @@ -114,7 +114,7 @@ DESCR("hash index access method"); 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 diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index 52ba630a56..e43ee6aa53 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -170,7 +170,7 @@ RESET enable_seqscan; RESET enable_indexscan; RESET enable_bitmapscan; -- --- GIN over int[] +-- GIN over int[] and text[] -- SET enable_seqscan = OFF; SET enable_indexscan = ON; @@ -416,6 +416,130 @@ SELECT * FROM array_index_op_test WHERE i = '{47,77}' ORDER BY seqno; 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; diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql index 447c5df37a..9638b23312 100644 --- a/src/test/regress/sql/create_index.sql +++ b/src/test/regress/sql/create_index.sql @@ -138,7 +138,7 @@ RESET enable_indexscan; RESET enable_bitmapscan; -- --- GIN over int[] +-- GIN over int[] and text[] -- SET enable_seqscan = OFF; @@ -180,6 +180,33 @@ SELECT * FROM array_index_op_test WHERE i && '{32,17}' ORDER BY seqno; 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; -- GitLab