/src/postgres/src/backend/access/spgist/spginsert.c
Line | Count | Source (jump to first uncovered line) |
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-2025, 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/tableam.h" |
22 | | #include "access/xloginsert.h" |
23 | | #include "miscadmin.h" |
24 | | #include "nodes/execnodes.h" |
25 | | #include "storage/bufmgr.h" |
26 | | #include "storage/bulk_write.h" |
27 | | #include "utils/memutils.h" |
28 | | #include "utils/rel.h" |
29 | | |
30 | | |
31 | | typedef struct |
32 | | { |
33 | | SpGistState spgstate; /* SPGiST's working state */ |
34 | | int64 indtuples; /* total number of tuples indexed */ |
35 | | MemoryContext tmpCtx; /* per-tuple temporary context */ |
36 | | } SpGistBuildState; |
37 | | |
38 | | |
39 | | /* Callback to process one heap tuple during table_index_build_scan */ |
40 | | static void |
41 | | spgistBuildCallback(Relation index, ItemPointer tid, Datum *values, |
42 | | bool *isnull, bool tupleIsAlive, void *state) |
43 | 0 | { |
44 | 0 | SpGistBuildState *buildstate = (SpGistBuildState *) state; |
45 | 0 | MemoryContext oldCtx; |
46 | | |
47 | | /* Work in temp context, and reset it after each tuple */ |
48 | 0 | oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx); |
49 | | |
50 | | /* |
51 | | * Even though no concurrent insertions can be happening, we still might |
52 | | * get a buffer-locking failure due to bgwriter or checkpointer taking a |
53 | | * lock on some buffer. So we need to be willing to retry. We can flush |
54 | | * any temp data when retrying. |
55 | | */ |
56 | 0 | while (!spgdoinsert(index, &buildstate->spgstate, tid, |
57 | 0 | values, isnull)) |
58 | 0 | { |
59 | 0 | MemoryContextReset(buildstate->tmpCtx); |
60 | 0 | } |
61 | | |
62 | | /* Update total tuple count */ |
63 | 0 | buildstate->indtuples += 1; |
64 | |
|
65 | 0 | MemoryContextSwitchTo(oldCtx); |
66 | 0 | MemoryContextReset(buildstate->tmpCtx); |
67 | 0 | } |
68 | | |
69 | | /* |
70 | | * Build an SP-GiST index. |
71 | | */ |
72 | | IndexBuildResult * |
73 | | spgbuild(Relation heap, Relation index, IndexInfo *indexInfo) |
74 | 0 | { |
75 | 0 | IndexBuildResult *result; |
76 | 0 | double reltuples; |
77 | 0 | SpGistBuildState buildstate; |
78 | 0 | Buffer metabuffer, |
79 | 0 | rootbuffer, |
80 | 0 | nullbuffer; |
81 | |
|
82 | 0 | if (RelationGetNumberOfBlocks(index) != 0) |
83 | 0 | elog(ERROR, "index \"%s\" already contains data", |
84 | 0 | RelationGetRelationName(index)); |
85 | | |
86 | | /* |
87 | | * Initialize the meta page and root pages |
88 | | */ |
89 | 0 | metabuffer = SpGistNewBuffer(index); |
90 | 0 | rootbuffer = SpGistNewBuffer(index); |
91 | 0 | nullbuffer = SpGistNewBuffer(index); |
92 | |
|
93 | 0 | Assert(BufferGetBlockNumber(metabuffer) == SPGIST_METAPAGE_BLKNO); |
94 | 0 | Assert(BufferGetBlockNumber(rootbuffer) == SPGIST_ROOT_BLKNO); |
95 | 0 | Assert(BufferGetBlockNumber(nullbuffer) == SPGIST_NULL_BLKNO); |
96 | |
|
97 | 0 | START_CRIT_SECTION(); |
98 | |
|
99 | 0 | SpGistInitMetapage(BufferGetPage(metabuffer)); |
100 | 0 | MarkBufferDirty(metabuffer); |
101 | 0 | SpGistInitBuffer(rootbuffer, SPGIST_LEAF); |
102 | 0 | MarkBufferDirty(rootbuffer); |
103 | 0 | SpGistInitBuffer(nullbuffer, SPGIST_LEAF | SPGIST_NULLS); |
104 | 0 | MarkBufferDirty(nullbuffer); |
105 | | |
106 | |
|
107 | 0 | END_CRIT_SECTION(); |
108 | |
|
109 | 0 | UnlockReleaseBuffer(metabuffer); |
110 | 0 | UnlockReleaseBuffer(rootbuffer); |
111 | 0 | UnlockReleaseBuffer(nullbuffer); |
112 | | |
113 | | /* |
114 | | * Now insert all the heap data into the index |
115 | | */ |
116 | 0 | initSpGistState(&buildstate.spgstate, index); |
117 | 0 | buildstate.spgstate.isBuild = true; |
118 | 0 | buildstate.indtuples = 0; |
119 | |
|
120 | 0 | buildstate.tmpCtx = AllocSetContextCreate(CurrentMemoryContext, |
121 | 0 | "SP-GiST build temporary context", |
122 | 0 | ALLOCSET_DEFAULT_SIZES); |
123 | |
|
124 | 0 | reltuples = table_index_build_scan(heap, index, indexInfo, true, true, |
125 | 0 | spgistBuildCallback, &buildstate, |
126 | 0 | NULL); |
127 | |
|
128 | 0 | MemoryContextDelete(buildstate.tmpCtx); |
129 | |
|
130 | 0 | SpGistUpdateMetaPage(index); |
131 | | |
132 | | /* |
133 | | * We didn't write WAL records as we built the index, so if WAL-logging is |
134 | | * required, write all pages to the WAL now. |
135 | | */ |
136 | 0 | if (RelationNeedsWAL(index)) |
137 | 0 | { |
138 | 0 | log_newpage_range(index, MAIN_FORKNUM, |
139 | 0 | 0, RelationGetNumberOfBlocks(index), |
140 | 0 | true); |
141 | 0 | } |
142 | |
|
143 | 0 | result = (IndexBuildResult *) palloc0(sizeof(IndexBuildResult)); |
144 | 0 | result->heap_tuples = reltuples; |
145 | 0 | result->index_tuples = buildstate.indtuples; |
146 | |
|
147 | 0 | return result; |
148 | 0 | } |
149 | | |
150 | | /* |
151 | | * Build an empty SPGiST index in the initialization fork |
152 | | */ |
153 | | void |
154 | | spgbuildempty(Relation index) |
155 | 0 | { |
156 | 0 | BulkWriteState *bulkstate; |
157 | 0 | BulkWriteBuffer buf; |
158 | |
|
159 | 0 | bulkstate = smgr_bulk_start_rel(index, INIT_FORKNUM); |
160 | | |
161 | | /* Construct metapage. */ |
162 | 0 | buf = smgr_bulk_get_buf(bulkstate); |
163 | 0 | SpGistInitMetapage((Page) buf); |
164 | 0 | smgr_bulk_write(bulkstate, SPGIST_METAPAGE_BLKNO, buf, true); |
165 | | |
166 | | /* Likewise for the root page. */ |
167 | 0 | buf = smgr_bulk_get_buf(bulkstate); |
168 | 0 | SpGistInitPage((Page) buf, SPGIST_LEAF); |
169 | 0 | smgr_bulk_write(bulkstate, SPGIST_ROOT_BLKNO, buf, true); |
170 | | |
171 | | /* Likewise for the null-tuples root page. */ |
172 | 0 | buf = smgr_bulk_get_buf(bulkstate); |
173 | 0 | SpGistInitPage((Page) buf, SPGIST_LEAF | SPGIST_NULLS); |
174 | 0 | smgr_bulk_write(bulkstate, SPGIST_NULL_BLKNO, buf, true); |
175 | |
|
176 | 0 | smgr_bulk_finish(bulkstate); |
177 | 0 | } |
178 | | |
179 | | /* |
180 | | * Insert one new tuple into an SPGiST index. |
181 | | */ |
182 | | bool |
183 | | spginsert(Relation index, Datum *values, bool *isnull, |
184 | | ItemPointer ht_ctid, Relation heapRel, |
185 | | IndexUniqueCheck checkUnique, |
186 | | bool indexUnchanged, |
187 | | IndexInfo *indexInfo) |
188 | 0 | { |
189 | 0 | SpGistState spgstate; |
190 | 0 | MemoryContext oldCtx; |
191 | 0 | MemoryContext insertCtx; |
192 | |
|
193 | 0 | insertCtx = AllocSetContextCreate(CurrentMemoryContext, |
194 | 0 | "SP-GiST insert temporary context", |
195 | 0 | ALLOCSET_DEFAULT_SIZES); |
196 | 0 | oldCtx = MemoryContextSwitchTo(insertCtx); |
197 | |
|
198 | 0 | initSpGistState(&spgstate, index); |
199 | | |
200 | | /* |
201 | | * We might have to repeat spgdoinsert() multiple times, if conflicts |
202 | | * occur with concurrent insertions. If so, reset the insertCtx each time |
203 | | * to avoid cumulative memory consumption. That means we also have to |
204 | | * redo initSpGistState(), but it's cheap enough not to matter. |
205 | | */ |
206 | 0 | while (!spgdoinsert(index, &spgstate, ht_ctid, values, isnull)) |
207 | 0 | { |
208 | 0 | MemoryContextReset(insertCtx); |
209 | 0 | initSpGistState(&spgstate, index); |
210 | 0 | } |
211 | |
|
212 | 0 | SpGistUpdateMetaPage(index); |
213 | |
|
214 | 0 | MemoryContextSwitchTo(oldCtx); |
215 | 0 | MemoryContextDelete(insertCtx); |
216 | | |
217 | | /* return false since we've not done any unique check */ |
218 | 0 | return false; |
219 | 0 | } |