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