Coverage Report

Created: 2025-06-13 06:06

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