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 | |