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
33typedef 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 */
42static void
43spgistBuildCallback(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 */
74IndexBuildResult *
75spgbuild(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 */
155void
156spgbuildempty(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 */
206bool
207spginsert(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