Coverage Report

Created: 2026-10-16 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/access/gin/ginbulk.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * ginbulk.c
4
 *    routines for fast build of inverted index
5
 *
6
 *
7
 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
8
 * Portions Copyright (c) 1994, Regents of the University of California
9
 *
10
 * IDENTIFICATION
11
 *      src/backend/access/gin/ginbulk.c
12
 *-------------------------------------------------------------------------
13
 */
14
15
#include "postgres.h"
16
17
#include <limits.h>
18
19
#include "access/gin_private.h"
20
#include "utils/datum.h"
21
#include "utils/memutils.h"
22
23
24
0
#define DEF_NENTRY  2048    /* GinEntryAccumulator allocation quantum */
25
0
#define DEF_NPTR  5      /* ItemPointer initial allocation quantum */
26
27
28
/* Combiner function for rbtree.c */
29
static void
30
ginCombineData(RBTNode *existing, const RBTNode *newdata, void *arg)
31
0
{
32
0
  GinEntryAccumulator *eo = (GinEntryAccumulator *) existing;
33
0
  const GinEntryAccumulator *en = (const GinEntryAccumulator *) newdata;
34
0
  BuildAccumulator *accum = (BuildAccumulator *) arg;
35
36
  /*
37
   * Note this code assumes that newdata contains only one itempointer.
38
   */
39
0
  if (eo->count >= eo->maxcount)
40
0
  {
41
0
    if (eo->maxcount > INT_MAX)
42
0
      ereport(ERROR,
43
0
          (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
44
0
           errmsg("posting list is too long"),
45
0
           errhint("Reduce \"maintenance_work_mem\".")));
46
47
0
    accum->allocatedMemory -= GetMemoryChunkSpace(eo->list);
48
0
    eo->maxcount *= 2;
49
0
    eo->list = (ItemPointerData *)
50
0
      repalloc_huge(eo->list, sizeof(ItemPointerData) * eo->maxcount);
51
0
    accum->allocatedMemory += GetMemoryChunkSpace(eo->list);
52
0
  }
53
54
  /* If item pointers are not ordered, they will need to be sorted later */
55
0
  if (eo->shouldSort == false)
56
0
  {
57
0
    int     res;
58
59
0
    res = ginCompareItemPointers(eo->list + eo->count - 1, en->list);
60
0
    Assert(res != 0);
61
62
0
    if (res > 0)
63
0
      eo->shouldSort = true;
64
0
  }
65
66
0
  eo->list[eo->count] = en->list[0];
67
0
  eo->count++;
68
0
}
69
70
/* Comparator function for rbtree.c */
71
static int
72
cmpEntryAccumulator(const RBTNode *a, const RBTNode *b, void *arg)
73
0
{
74
0
  const GinEntryAccumulator *ea = (const GinEntryAccumulator *) a;
75
0
  const GinEntryAccumulator *eb = (const GinEntryAccumulator *) b;
76
0
  BuildAccumulator *accum = (BuildAccumulator *) arg;
77
78
0
  return ginCompareAttEntries(accum->ginstate,
79
0
                ea->attnum, ea->key, ea->category,
80
0
                eb->attnum, eb->key, eb->category);
81
0
}
82
83
/* Allocator function for rbtree.c */
84
static RBTNode *
85
ginAllocEntryAccumulator(void *arg)
86
0
{
87
0
  BuildAccumulator *accum = (BuildAccumulator *) arg;
88
0
  GinEntryAccumulator *ea;
89
90
  /*
91
   * Allocate memory by rather big chunks to decrease overhead.  We have no
92
   * need to reclaim RBTNodes individually, so this costs nothing.
93
   */
94
0
  if (accum->entryallocator == NULL || accum->eas_used >= DEF_NENTRY)
95
0
  {
96
0
    accum->entryallocator = palloc_array(GinEntryAccumulator, DEF_NENTRY);
97
0
    accum->allocatedMemory += GetMemoryChunkSpace(accum->entryallocator);
98
0
    accum->eas_used = 0;
99
0
  }
100
101
  /* Allocate new RBTNode from current chunk */
102
0
  ea = accum->entryallocator + accum->eas_used;
103
0
  accum->eas_used++;
104
105
0
  return (RBTNode *) ea;
106
0
}
107
108
void
109
ginInitBA(BuildAccumulator *accum)
110
0
{
111
  /* accum->ginstate is intentionally not set here */
112
0
  accum->allocatedMemory = 0;
113
0
  accum->entryallocator = NULL;
114
0
  accum->eas_used = 0;
115
0
  accum->tree = rbt_create(sizeof(GinEntryAccumulator),
116
0
               cmpEntryAccumulator,
117
0
               ginCombineData,
118
0
               ginAllocEntryAccumulator,
119
0
               NULL, /* no freefunc needed */
120
0
               accum);
121
0
}
122
123
/*
124
 * This is basically the same as datumCopy(), but extended to count
125
 * palloc'd space in accum->allocatedMemory.
126
 */
127
static Datum
128
getDatumCopy(BuildAccumulator *accum, OffsetNumber attnum, Datum value)
129
0
{
130
0
  CompactAttribute *att;
131
0
  Datum   res;
132
133
0
  att = TupleDescCompactAttr(accum->ginstate->origTupdesc, attnum - 1);
134
0
  if (att->attbyval)
135
0
    res = value;
136
0
  else
137
0
  {
138
0
    res = datumCopy(value, false, att->attlen);
139
0
    accum->allocatedMemory += GetMemoryChunkSpace(DatumGetPointer(res));
140
0
  }
141
0
  return res;
142
0
}
143
144
/*
145
 * Find/store one entry from indexed value.
146
 */
147
static void
148
ginInsertBAEntry(BuildAccumulator *accum,
149
         ItemPointer heapptr, OffsetNumber attnum,
150
         Datum key, GinNullCategory category)
151
0
{
152
0
  GinEntryAccumulator eatmp;
153
0
  GinEntryAccumulator *ea;
154
0
  bool    isNew;
155
156
  /*
157
   * For the moment, fill only the fields of eatmp that will be looked at by
158
   * cmpEntryAccumulator or ginCombineData.
159
   */
160
0
  eatmp.attnum = attnum;
161
0
  eatmp.key = key;
162
0
  eatmp.category = category;
163
  /* temporarily set up single-entry itempointer list */
164
0
  eatmp.list = heapptr;
165
166
0
  ea = (GinEntryAccumulator *) rbt_insert(accum->tree, (RBTNode *) &eatmp,
167
0
                      &isNew);
168
169
0
  if (isNew)
170
0
  {
171
    /*
172
     * Finish initializing new tree entry, including making permanent
173
     * copies of the datum (if it's not null) and itempointer.
174
     */
175
0
    if (category == GIN_CAT_NORM_KEY)
176
0
      ea->key = getDatumCopy(accum, attnum, key);
177
0
    ea->maxcount = DEF_NPTR;
178
0
    ea->count = 1;
179
0
    ea->shouldSort = false;
180
0
    ea->list = palloc_array(ItemPointerData, DEF_NPTR);
181
0
    ea->list[0] = *heapptr;
182
0
    accum->allocatedMemory += GetMemoryChunkSpace(ea->list);
183
0
  }
184
0
  else
185
0
  {
186
    /*
187
     * ginCombineData did everything needed.
188
     */
189
0
  }
190
0
}
191
192
/*
193
 * Insert the entries for one heap pointer.
194
 *
195
 * Since the entries are being inserted into a balanced binary tree, you
196
 * might think that the order of insertion wouldn't be critical, but it turns
197
 * out that inserting the entries in sorted order results in a lot of
198
 * rebalancing operations and is slow.  To prevent this, we attempt to insert
199
 * the nodes in an order that will produce a nearly-balanced tree if the input
200
 * is in fact sorted.
201
 *
202
 * We do this as follows.  First, we imagine that we have an array whose size
203
 * is the smallest power of two greater than or equal to the actual array
204
 * size.  Second, we insert the middle entry of our virtual array into the
205
 * tree; then, we insert the middles of each half of our virtual array, then
206
 * middles of quarters, etc.
207
 */
208
void
209
ginInsertBAEntries(BuildAccumulator *accum,
210
           ItemPointer heapptr, OffsetNumber attnum,
211
           Datum *entries, GinNullCategory *categories,
212
           int32 nentries)
213
0
{
214
0
  uint32    step = nentries;
215
216
0
  if (nentries <= 0)
217
0
    return;
218
219
0
  Assert(ItemPointerIsValid(heapptr) && attnum >= FirstOffsetNumber);
220
221
  /*
222
   * step will contain largest power of 2 and <= nentries
223
   */
224
0
  step |= (step >> 1);
225
0
  step |= (step >> 2);
226
0
  step |= (step >> 4);
227
0
  step |= (step >> 8);
228
0
  step |= (step >> 16);
229
0
  step >>= 1;
230
0
  step++;
231
232
0
  while (step > 0)
233
0
  {
234
0
    int     i;
235
236
0
    for (i = step - 1; i < nentries && i >= 0; i += step << 1 /* *2 */ )
237
0
      ginInsertBAEntry(accum, heapptr, attnum,
238
0
               entries[i], categories[i]);
239
240
0
    step >>= 1;       /* /2 */
241
0
  }
242
0
}
243
244
static int
245
qsortCompareItemPointers(const void *a, const void *b)
246
0
{
247
0
  int     res = ginCompareItemPointers((const ItemPointerData *) a, (const ItemPointerData *) b);
248
249
  /* Assert that there are no equal item pointers being sorted */
250
0
  Assert(res != 0);
251
0
  return res;
252
0
}
253
254
/* Prepare to read out the rbtree contents using ginGetBAEntry */
255
void
256
ginBeginBAScan(BuildAccumulator *accum)
257
0
{
258
0
  rbt_begin_iterate(accum->tree, LeftRightWalk, &accum->tree_walk);
259
0
}
260
261
/*
262
 * Get the next entry in sequence from the BuildAccumulator's rbtree.
263
 * This consists of a single key datum and a list (array) of one or more
264
 * heap TIDs in which that key is found.  The list is guaranteed sorted.
265
 */
266
ItemPointerData *
267
ginGetBAEntry(BuildAccumulator *accum,
268
        OffsetNumber *attnum, Datum *key, GinNullCategory *category,
269
        uint32 *n)
270
0
{
271
0
  GinEntryAccumulator *entry;
272
0
  ItemPointerData *list;
273
274
0
  entry = (GinEntryAccumulator *) rbt_iterate(&accum->tree_walk);
275
276
0
  if (entry == NULL)
277
0
    return NULL;     /* no more entries */
278
279
0
  *attnum = entry->attnum;
280
0
  *key = entry->key;
281
0
  *category = entry->category;
282
0
  list = entry->list;
283
0
  *n = entry->count;
284
285
0
  Assert(list != NULL && entry->count > 0);
286
287
0
  if (entry->shouldSort && entry->count > 1)
288
0
    qsort(list, entry->count, sizeof(ItemPointerData),
289
0
        qsortCompareItemPointers);
290
291
0
  return list;
292
0
}