| 1 | /*------------------------------------------------------------------------- |
| 2 | * |
| 3 | * spginsert.c |
| 4 | * Externally visible index creation/insertion routines |
| 5 | * |
| 6 | * All the actual insertion logic is in spgdoinsert.c. |
| 7 | * |
| 8 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
| 9 | * Portions Copyright (c) 1994, Regents of the University of California |
| 10 | * |
| 11 | * IDENTIFICATION |
| 12 | * src/backend/access/spgist/spginsert.c |
| 13 | * |
| 14 | *------------------------------------------------------------------------- |
| 15 | */ |
| 16 | |
| 17 | #include "postgres.h" |
| 18 | |
| 19 | #include "access/genam.h" |
| 20 | #include "access/spgist_private.h" |
| 21 | #include "access/spgxlog.h" |
| 22 | #include "access/tableam.h" |
| 23 | #include "access/xlog.h" |
| 24 | #include "access/xloginsert.h" |
| 25 | #include "catalog/index.h" |
| 26 | #include "miscadmin.h" |
| 27 | #include "storage/bufmgr.h" |
| 28 | #include "storage/smgr.h" |
| 29 | #include "utils/memutils.h" |
| 30 | #include "utils/rel.h" |
| 31 | |
| 32 | |
| 33 | typedef struct |
| 34 | { |
| 35 | SpGistState spgstate; /* SPGiST's working state */ |
| 36 | int64 indtuples; /* total number of tuples indexed */ |
| 37 | MemoryContext tmpCtx; /* per-tuple temporary context */ |
| 38 | } SpGistBuildState; |
| 39 | |
| 40 | |
| 41 | /* Callback to process one heap tuple during table_index_build_scan */ |
| 42 | static void |
| 43 | spgistBuildCallback(Relation index, HeapTuple htup, Datum *values, |
| 44 | bool *isnull, bool tupleIsAlive, void *state) |
| 45 | { |
| 46 | SpGistBuildState *buildstate = (SpGistBuildState *) state; |
| 47 | MemoryContext oldCtx; |
| 48 | |
| 49 | /* Work in temp context, and reset it after each tuple */ |
| 50 | oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx); |
| 51 | |
| 52 | /* |
| 53 | * Even though no concurrent insertions can be happening, we still might |
| 54 | * get a buffer-locking failure due to bgwriter or checkpointer taking a |
| 55 | * lock on some buffer. So we need to be willing to retry. We can flush |
| 56 | * any temp data when retrying. |
| 57 | */ |
| 58 | while (!spgdoinsert(index, &buildstate->spgstate, &htup->t_self, |
| 59 | *values, *isnull)) |
| 60 | { |
| 61 | MemoryContextReset(buildstate->tmpCtx); |
| 62 | } |
| 63 | |
| 64 | /* Update total tuple count */ |
| 65 | buildstate->indtuples += 1; |
| 66 | |
| 67 | MemoryContextSwitchTo(oldCtx); |
| 68 | MemoryContextReset(buildstate->tmpCtx); |
| 69 | } |
| 70 | |
| 71 | /* |
| 72 | * Build an SP-GiST index. |
| 73 | */ |
| 74 | IndexBuildResult * |
| 75 | spgbuild(Relation heap, Relation index, IndexInfo *indexInfo) |
| 76 | { |
| 77 | IndexBuildResult *result; |
| 78 | double reltuples; |
| 79 | SpGistBuildState buildstate; |
| 80 | Buffer metabuffer, |
| 81 | rootbuffer, |
| 82 | nullbuffer; |
| 83 | |
| 84 | if (RelationGetNumberOfBlocks(index) != 0) |
| 85 | elog(ERROR, "index \"%s\" already contains data" , |
| 86 | RelationGetRelationName(index)); |
| 87 | |
| 88 | /* |
| 89 | * Initialize the meta page and root pages |
| 90 | */ |
| 91 | metabuffer = SpGistNewBuffer(index); |
| 92 | rootbuffer = SpGistNewBuffer(index); |
| 93 | nullbuffer = SpGistNewBuffer(index); |
| 94 | |
| 95 | Assert(BufferGetBlockNumber(metabuffer) == SPGIST_METAPAGE_BLKNO); |
| 96 | Assert(BufferGetBlockNumber(rootbuffer) == SPGIST_ROOT_BLKNO); |
| 97 | Assert(BufferGetBlockNumber(nullbuffer) == SPGIST_NULL_BLKNO); |
| 98 | |
| 99 | START_CRIT_SECTION(); |
| 100 | |
| 101 | SpGistInitMetapage(BufferGetPage(metabuffer)); |
| 102 | MarkBufferDirty(metabuffer); |
| 103 | SpGistInitBuffer(rootbuffer, SPGIST_LEAF); |
| 104 | MarkBufferDirty(rootbuffer); |
| 105 | SpGistInitBuffer(nullbuffer, SPGIST_LEAF | SPGIST_NULLS); |
| 106 | MarkBufferDirty(nullbuffer); |
| 107 | |
| 108 | |
| 109 | END_CRIT_SECTION(); |
| 110 | |
| 111 | UnlockReleaseBuffer(metabuffer); |
| 112 | UnlockReleaseBuffer(rootbuffer); |
| 113 | UnlockReleaseBuffer(nullbuffer); |
| 114 | |
| 115 | /* |
| 116 | * Now insert all the heap data into the index |
| 117 | */ |
| 118 | initSpGistState(&buildstate.spgstate, index); |
| 119 | buildstate.spgstate.isBuild = true; |
| 120 | buildstate.indtuples = 0; |
| 121 | |
| 122 | buildstate.tmpCtx = AllocSetContextCreate(CurrentMemoryContext, |
| 123 | "SP-GiST build temporary context" , |
| 124 | ALLOCSET_DEFAULT_SIZES); |
| 125 | |
| 126 | reltuples = table_index_build_scan(heap, index, indexInfo, true, true, |
| 127 | spgistBuildCallback, (void *) &buildstate, |
| 128 | NULL); |
| 129 | |
| 130 | MemoryContextDelete(buildstate.tmpCtx); |
| 131 | |
| 132 | SpGistUpdateMetaPage(index); |
| 133 | |
| 134 | /* |
| 135 | * We didn't write WAL records as we built the index, so if WAL-logging is |
| 136 | * required, write all pages to the WAL now. |
| 137 | */ |
| 138 | if (RelationNeedsWAL(index)) |
| 139 | { |
| 140 | log_newpage_range(index, MAIN_FORKNUM, |
| 141 | 0, RelationGetNumberOfBlocks(index), |
| 142 | true); |
| 143 | } |
| 144 | |
| 145 | result = (IndexBuildResult *) palloc0(sizeof(IndexBuildResult)); |
| 146 | result->heap_tuples = reltuples; |
| 147 | result->index_tuples = buildstate.indtuples; |
| 148 | |
| 149 | return result; |
| 150 | } |
| 151 | |
| 152 | /* |
| 153 | * Build an empty SPGiST index in the initialization fork |
| 154 | */ |
| 155 | void |
| 156 | spgbuildempty(Relation index) |
| 157 | { |
| 158 | Page page; |
| 159 | |
| 160 | /* Construct metapage. */ |
| 161 | page = (Page) palloc(BLCKSZ); |
| 162 | SpGistInitMetapage(page); |
| 163 | |
| 164 | /* |
| 165 | * Write the page and log it unconditionally. This is important |
| 166 | * particularly for indexes created on tablespaces and databases whose |
| 167 | * creation happened after the last redo pointer as recovery removes any |
| 168 | * of their existing content when the corresponding create records are |
| 169 | * replayed. |
| 170 | */ |
| 171 | PageSetChecksumInplace(page, SPGIST_METAPAGE_BLKNO); |
| 172 | smgrwrite(index->rd_smgr, INIT_FORKNUM, SPGIST_METAPAGE_BLKNO, |
| 173 | (char *) page, true); |
| 174 | log_newpage(&index->rd_smgr->smgr_rnode.node, INIT_FORKNUM, |
| 175 | SPGIST_METAPAGE_BLKNO, page, true); |
| 176 | |
| 177 | /* Likewise for the root page. */ |
| 178 | SpGistInitPage(page, SPGIST_LEAF); |
| 179 | |
| 180 | PageSetChecksumInplace(page, SPGIST_ROOT_BLKNO); |
| 181 | smgrwrite(index->rd_smgr, INIT_FORKNUM, SPGIST_ROOT_BLKNO, |
| 182 | (char *) page, true); |
| 183 | log_newpage(&index->rd_smgr->smgr_rnode.node, INIT_FORKNUM, |
| 184 | SPGIST_ROOT_BLKNO, page, true); |
| 185 | |
| 186 | /* Likewise for the null-tuples root page. */ |
| 187 | SpGistInitPage(page, SPGIST_LEAF | SPGIST_NULLS); |
| 188 | |
| 189 | PageSetChecksumInplace(page, SPGIST_NULL_BLKNO); |
| 190 | smgrwrite(index->rd_smgr, INIT_FORKNUM, SPGIST_NULL_BLKNO, |
| 191 | (char *) page, true); |
| 192 | log_newpage(&index->rd_smgr->smgr_rnode.node, INIT_FORKNUM, |
| 193 | SPGIST_NULL_BLKNO, page, true); |
| 194 | |
| 195 | /* |
| 196 | * An immediate sync is required even if we xlog'd the pages, because the |
| 197 | * writes did not go through shared buffers and therefore a concurrent |
| 198 | * checkpoint may have moved the redo pointer past our xlog record. |
| 199 | */ |
| 200 | smgrimmedsync(index->rd_smgr, INIT_FORKNUM); |
| 201 | } |
| 202 | |
| 203 | /* |
| 204 | * Insert one new tuple into an SPGiST index. |
| 205 | */ |
| 206 | bool |
| 207 | spginsert(Relation index, Datum *values, bool *isnull, |
| 208 | ItemPointer ht_ctid, Relation heapRel, |
| 209 | IndexUniqueCheck checkUnique, |
| 210 | IndexInfo *indexInfo) |
| 211 | { |
| 212 | SpGistState spgstate; |
| 213 | MemoryContext oldCtx; |
| 214 | MemoryContext insertCtx; |
| 215 | |
| 216 | insertCtx = AllocSetContextCreate(CurrentMemoryContext, |
| 217 | "SP-GiST insert temporary context" , |
| 218 | ALLOCSET_DEFAULT_SIZES); |
| 219 | oldCtx = MemoryContextSwitchTo(insertCtx); |
| 220 | |
| 221 | initSpGistState(&spgstate, index); |
| 222 | |
| 223 | /* |
| 224 | * We might have to repeat spgdoinsert() multiple times, if conflicts |
| 225 | * occur with concurrent insertions. If so, reset the insertCtx each time |
| 226 | * to avoid cumulative memory consumption. That means we also have to |
| 227 | * redo initSpGistState(), but it's cheap enough not to matter. |
| 228 | */ |
| 229 | while (!spgdoinsert(index, &spgstate, ht_ctid, *values, *isnull)) |
| 230 | { |
| 231 | MemoryContextReset(insertCtx); |
| 232 | initSpGistState(&spgstate, index); |
| 233 | } |
| 234 | |
| 235 | SpGistUpdateMetaPage(index); |
| 236 | |
| 237 | MemoryContextSwitchTo(oldCtx); |
| 238 | MemoryContextDelete(insertCtx); |
| 239 | |
| 240 | /* return false since we've not done any unique check */ |
| 241 | return false; |
| 242 | } |
| 243 | |