Age Owner Branch data TLA Line data Source code
1 : : /*--------------------------------------------------------------------------
2 : : *
3 : : * test_tidstore.c
4 : : * Test TidStore data structure.
5 : : *
6 : : * Note: all locking in this test module is useless since there is only
7 : : * a single process to use the TidStore. It is meant to be an example of
8 : : * usage.
9 : : *
10 : : * Copyright (c) 2024-2026, PostgreSQL Global Development Group
11 : : *
12 : : * IDENTIFICATION
13 : : * src/test/modules/test_tidstore/test_tidstore.c
14 : : *
15 : : * -------------------------------------------------------------------------
16 : : */
17 : : #include "postgres.h"
18 : :
19 : : #include "access/tidstore.h"
20 : : #include "fmgr.h"
21 : : #include "storage/block.h"
22 : : #include "storage/itemptr.h"
23 : : #include "storage/lwlock.h"
24 : : #include "utils/array.h"
25 : : #include "utils/memutils.h"
26 : :
775 msawada@postgresql.o 27 :CBC 1 : PG_MODULE_MAGIC;
28 : :
29 : 2 : PG_FUNCTION_INFO_V1(test_create);
30 : 2 : PG_FUNCTION_INFO_V1(do_set_block_offsets);
31 : 2 : PG_FUNCTION_INFO_V1(check_set_block_offsets);
32 : 2 : PG_FUNCTION_INFO_V1(test_is_full);
33 : 2 : PG_FUNCTION_INFO_V1(test_destroy);
34 : :
35 : : static TidStore *tidstore = NULL;
36 : : static size_t tidstore_empty_size;
37 : :
38 : : /* array for verification of some tests */
39 : : typedef struct ItemArray
40 : : {
41 : : ItemPointerData *insert_tids;
42 : : ItemPointerData *lookup_tids;
43 : : ItemPointerData *iter_tids;
44 : : int max_tids;
45 : : int num_tids;
46 : : } ItemArray;
47 : :
48 : : static ItemArray items;
49 : :
50 : : /* comparator routine for ItemPointer */
51 : : static int
52 : 207992 : itemptr_cmp(const void *left, const void *right)
53 : : {
54 : : BlockNumber lblk,
55 : : rblk;
56 : : OffsetNumber loff,
57 : : roff;
58 : :
99 peter@eisentraut.org 59 :GNC 207992 : lblk = ItemPointerGetBlockNumber((const ItemPointerData *) left);
60 : 207992 : rblk = ItemPointerGetBlockNumber((const ItemPointerData *) right);
61 : :
775 msawada@postgresql.o 62 [ + + ]:CBC 207992 : if (lblk < rblk)
63 : 64084 : return -1;
64 [ + + ]: 143908 : if (lblk > rblk)
65 : 64510 : return 1;
66 : :
99 peter@eisentraut.org 67 :GNC 79398 : loff = ItemPointerGetOffsetNumber((const ItemPointerData *) left);
68 : 79398 : roff = ItemPointerGetOffsetNumber((const ItemPointerData *) right);
69 : :
775 msawada@postgresql.o 70 [ + + ]:CBC 79398 : if (loff < roff)
71 : 35082 : return -1;
72 [ + + ]: 44316 : if (loff > roff)
73 : 13842 : return 1;
74 : :
75 : 30474 : return 0;
76 : : }
77 : :
78 : : static int
6 79 : 14117 : offsetnumber_cmp(const void *a, const void *b)
80 : : {
81 : 14117 : OffsetNumber l = *(const OffsetNumber *) a;
82 : 14117 : OffsetNumber r = *(const OffsetNumber *) b;
83 : :
84 [ + - ]: 14117 : if (l < r)
85 : 14117 : return -1;
6 msawada@postgresql.o 86 [ # # ]:UBC 0 : else if (l > r)
87 : 0 : return 1;
88 : 0 : return 0;
89 : : }
90 : :
91 : : /*
92 : : * Create a TidStore. If shared is false, the tidstore is created
93 : : * on TopMemoryContext, otherwise on DSA. Although the tidstore
94 : : * is created on DSA, only the same process can subsequently use
95 : : * the tidstore. The tidstore handle is not shared anywhere.
96 : : */
97 : : Datum
775 msawada@postgresql.o 98 :CBC 3 : test_create(PG_FUNCTION_ARGS)
99 : : {
100 : 3 : bool shared = PG_GETARG_BOOL(0);
101 : : MemoryContext old_ctx;
102 : :
103 : : /* doesn't really matter, since it's just a hint */
104 : 3 : size_t tidstore_max_size = 2 * 1024 * 1024;
105 : 3 : size_t array_init_size = 1024;
106 : :
107 [ - + ]: 3 : Assert(tidstore == NULL);
108 : :
109 : : /*
110 : : * Create the TidStore on TopMemoryContext so that the same process use it
111 : : * for subsequent tests.
112 : : */
113 : 3 : old_ctx = MemoryContextSwitchTo(TopMemoryContext);
114 : :
115 [ + + ]: 3 : if (shared)
116 : : {
117 : : int tranche_id;
118 : :
244 nathan@postgresql.or 119 :GNC 1 : tranche_id = LWLockNewTrancheId("test_tidstore");
120 : :
768 msawada@postgresql.o 121 :CBC 1 : tidstore = TidStoreCreateShared(tidstore_max_size, tranche_id);
122 : :
123 : : /*
124 : : * Remain attached until end of backend or explicitly detached so that
125 : : * the same process use the tidstore for subsequent tests.
126 : : */
127 : 1 : dsa_pin_mapping(TidStoreGetDSA(tidstore));
128 : : }
129 : : else
130 : : /* VACUUM uses insert only, so we test the other option. */
758 john.naylor@postgres 131 : 2 : tidstore = TidStoreCreateLocal(tidstore_max_size, false);
132 : :
775 msawada@postgresql.o 133 : 3 : tidstore_empty_size = TidStoreMemoryUsage(tidstore);
134 : :
135 : 3 : items.num_tids = 0;
136 : 3 : items.max_tids = array_init_size / sizeof(ItemPointerData);
137 : 3 : items.insert_tids = (ItemPointerData *) palloc0(array_init_size);
138 : 3 : items.lookup_tids = (ItemPointerData *) palloc0(array_init_size);
139 : 3 : items.iter_tids = (ItemPointerData *) palloc0(array_init_size);
140 : :
141 : 3 : MemoryContextSwitchTo(old_ctx);
142 : :
143 : 3 : PG_RETURN_VOID();
144 : : }
145 : :
146 : : static void
147 : 1121 : sanity_check_array(ArrayType *ta)
148 : : {
149 [ - + - - ]: 1121 : if (ARR_HASNULL(ta) && array_contains_nulls(ta))
775 msawada@postgresql.o 150 [ # # ]:UBC 0 : ereport(ERROR,
151 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
152 : : errmsg("array must not contain nulls")));
153 : :
775 msawada@postgresql.o 154 [ - + ]:CBC 1121 : if (ARR_NDIM(ta) > 1)
775 msawada@postgresql.o 155 [ # # ]:UBC 0 : ereport(ERROR,
156 : : (errcode(ERRCODE_DATA_EXCEPTION),
157 : : errmsg("argument must be empty or one-dimensional array")));
775 msawada@postgresql.o 158 :CBC 1121 : }
159 : :
160 : : static void
692 161 : 1138 : check_tidstore_available(void)
162 : : {
163 [ - + ]: 1138 : if (tidstore == NULL)
692 msawada@postgresql.o 164 [ # # ]:UBC 0 : elog(ERROR, "tidstore is not created");
692 msawada@postgresql.o 165 :CBC 1138 : }
166 : :
167 : : static void
740 168 : 1120 : purge_from_verification_array(BlockNumber blkno)
169 : : {
170 : 1120 : int dst = 0;
171 : :
172 [ + + ]: 3491540 : for (int src = 0; src < items.num_tids; src++)
173 [ + + ]: 3490420 : if (ItemPointerGetBlockNumber(&items.insert_tids[src]) != blkno)
174 : 3490396 : items.insert_tids[dst++] = items.insert_tids[src];
175 : 1120 : items.num_tids = dst;
176 : 1120 : }
177 : :
178 : :
179 : : /* Set the given block and offsets pairs */
180 : : Datum
775 181 : 1121 : do_set_block_offsets(PG_FUNCTION_ARGS)
182 : : {
183 : 1121 : BlockNumber blkno = PG_GETARG_INT64(0);
184 : 1121 : ArrayType *ta = PG_GETARG_ARRAYTYPE_P_COPY(1);
185 : : OffsetNumber *offs;
186 : : int noffs;
187 : :
692 188 : 1121 : check_tidstore_available();
775 189 : 1121 : sanity_check_array(ta);
190 : :
191 : 1121 : noffs = ArrayGetNItems(ARR_NDIM(ta), ARR_DIMS(ta));
192 [ - + ]: 1121 : offs = ((OffsetNumber *) ARR_DATA_PTR(ta));
193 : :
194 : : /* TidStoreSetBlockOffsets() requires offsets to be strictly ascending. */
6 195 : 1121 : qsort(offs, noffs, sizeof(OffsetNumber), offsetnumber_cmp);
196 : :
197 : : /* Set TIDs in the store */
775 198 : 1121 : TidStoreLockExclusive(tidstore);
199 : 1121 : TidStoreSetBlockOffsets(tidstore, blkno, offs, noffs);
200 : 1120 : TidStoreUnlock(tidstore);
201 : :
202 : : /* Remove the existing items of blkno from the verification array */
740 203 : 1120 : purge_from_verification_array(blkno);
204 : :
205 : : /* Set TIDs in verification array */
775 206 [ + + ]: 16357 : for (int i = 0; i < noffs; i++)
207 : : {
208 : : ItemPointer tid;
209 : 15237 : int idx = items.num_tids + i;
210 : :
211 : : /* Enlarge the TID arrays if necessary */
212 [ + + ]: 15237 : if (idx >= items.max_tids)
213 : : {
214 : 12 : items.max_tids *= 2;
215 : 12 : items.insert_tids = repalloc(items.insert_tids, sizeof(ItemPointerData) * items.max_tids);
216 : 12 : items.lookup_tids = repalloc(items.lookup_tids, sizeof(ItemPointerData) * items.max_tids);
217 : 12 : items.iter_tids = repalloc(items.iter_tids, sizeof(ItemPointerData) * items.max_tids);
218 : : }
219 : :
220 : 15237 : tid = &(items.insert_tids[idx]);
221 : 15237 : ItemPointerSet(tid, blkno, offs[i]);
222 : : }
223 : :
224 : : /* Update statistics */
225 : 1120 : items.num_tids += noffs;
226 : :
227 : 1120 : PG_RETURN_INT64(blkno);
228 : : }
229 : :
230 : : /*
231 : : * Verify TIDs in store against the array.
232 : : */
233 : : Datum
234 : 12 : check_set_block_offsets(PG_FUNCTION_ARGS)
235 : : {
236 : : TidStoreIter *iter;
237 : : TidStoreIterResult *iter_result;
238 : 12 : int num_iter_tids = 0;
239 : 12 : int num_lookup_tids = 0;
767 dgustafsson@postgres 240 : 12 : BlockNumber prevblkno = 0;
241 : :
692 msawada@postgresql.o 242 : 12 : check_tidstore_available();
243 : :
244 : : /* lookup each member in the verification array */
775 245 [ + + ]: 15249 : for (int i = 0; i < items.num_tids; i++)
246 [ - + ]: 15237 : if (!TidStoreIsMember(tidstore, &items.insert_tids[i]))
775 msawada@postgresql.o 247 [ # # ]:UBC 0 : elog(ERROR, "missing TID with block %u, offset %u",
248 : : ItemPointerGetBlockNumber(&items.insert_tids[i]),
249 : : ItemPointerGetOffsetNumber(&items.insert_tids[i]));
250 : :
251 : : /*
252 : : * Lookup all possible TIDs for each distinct block in the verification
253 : : * array and save successful lookups in the lookup array.
254 : : */
255 : :
775 msawada@postgresql.o 256 [ + + ]:CBC 15249 : for (int i = 0; i < items.num_tids; i++)
257 : : {
258 : 15237 : BlockNumber blkno = ItemPointerGetBlockNumber(&items.insert_tids[i]);
259 : :
260 [ + + + + ]: 15237 : if (i > 0 && blkno == prevblkno)
261 : 14117 : continue;
262 : :
263 [ + + ]: 2293760 : for (OffsetNumber offset = FirstOffsetNumber; offset < MaxOffsetNumber; offset++)
264 : : {
265 : : ItemPointerData tid;
266 : :
267 : 2292640 : ItemPointerSet(&tid, blkno, offset);
268 : :
269 : 2292640 : TidStoreLockShare(tidstore);
270 [ + + ]: 2292640 : if (TidStoreIsMember(tidstore, &tid))
271 : 15237 : ItemPointerSet(&items.lookup_tids[num_lookup_tids++], blkno, offset);
272 : 2292640 : TidStoreUnlock(tidstore);
273 : : }
274 : :
275 : 1120 : prevblkno = blkno;
276 : : }
277 : :
278 : : /* Collect TIDs stored in the tidstore, in order */
279 : :
280 : 12 : TidStoreLockShare(tidstore);
281 : 12 : iter = TidStoreBeginIterate(tidstore);
282 [ + + ]: 1132 : while ((iter_result = TidStoreIterateNext(iter)) != NULL)
283 : : {
284 : : OffsetNumber offsets[MaxOffsetNumber];
285 : : int num_offsets;
286 : :
650 tmunro@postgresql.or 287 : 1120 : num_offsets = TidStoreGetBlockOffsets(iter_result, offsets, lengthof(offsets));
288 [ - + ]: 1120 : Assert(num_offsets <= lengthof(offsets));
289 [ + + ]: 16357 : for (int i = 0; i < num_offsets; i++)
775 msawada@postgresql.o 290 : 15237 : ItemPointerSet(&(items.iter_tids[num_iter_tids++]), iter_result->blkno,
650 tmunro@postgresql.or 291 : 15237 : offsets[i]);
292 : : }
775 msawada@postgresql.o 293 : 12 : TidStoreEndIterate(iter);
294 : 12 : TidStoreUnlock(tidstore);
295 : :
296 : : /*
297 : : * Sort verification and lookup arrays and test that all arrays are the
298 : : * same.
299 : : */
300 : :
301 [ - + ]: 12 : if (num_lookup_tids != items.num_tids)
775 msawada@postgresql.o 302 [ # # ]:UBC 0 : elog(ERROR, "should have %d TIDs, have %d", items.num_tids, num_lookup_tids);
775 msawada@postgresql.o 303 [ - + ]:CBC 12 : if (num_iter_tids != items.num_tids)
775 msawada@postgresql.o 304 [ # # ]:UBC 0 : elog(ERROR, "should have %d TIDs, have %d", items.num_tids, num_iter_tids);
305 : :
775 msawada@postgresql.o 306 :CBC 12 : qsort(items.insert_tids, items.num_tids, sizeof(ItemPointerData), itemptr_cmp);
307 : 12 : qsort(items.lookup_tids, items.num_tids, sizeof(ItemPointerData), itemptr_cmp);
308 [ + + ]: 15249 : for (int i = 0; i < items.num_tids; i++)
309 : : {
515 peter@eisentraut.org 310 [ - + ]: 15237 : if (itemptr_cmp(&items.insert_tids[i], &items.iter_tids[i]) != 0)
775 msawada@postgresql.o 311 [ # # ]:UBC 0 : elog(ERROR, "TID iter array doesn't match verification array, got (%u,%u) expected (%u,%u)",
312 : : ItemPointerGetBlockNumber(&items.iter_tids[i]),
313 : : ItemPointerGetOffsetNumber(&items.iter_tids[i]),
314 : : ItemPointerGetBlockNumber(&items.insert_tids[i]),
315 : : ItemPointerGetOffsetNumber(&items.insert_tids[i]));
515 peter@eisentraut.org 316 [ - + ]:CBC 15237 : if (itemptr_cmp(&items.insert_tids[i], &items.lookup_tids[i]) != 0)
775 msawada@postgresql.o 317 [ # # ]:UBC 0 : elog(ERROR, "TID lookup array doesn't match verification array, got (%u,%u) expected (%u,%u)",
318 : : ItemPointerGetBlockNumber(&items.lookup_tids[i]),
319 : : ItemPointerGetOffsetNumber(&items.lookup_tids[i]),
320 : : ItemPointerGetBlockNumber(&items.insert_tids[i]),
321 : : ItemPointerGetOffsetNumber(&items.insert_tids[i]));
322 : : }
323 : :
775 msawada@postgresql.o 324 :CBC 12 : PG_RETURN_VOID();
325 : : }
326 : :
327 : : /*
328 : : * In real world use, we care if the memory usage is greater than
329 : : * some configured limit. Here we just want to verify that
330 : : * TidStoreMemoryUsage is not broken.
331 : : */
332 : : Datum
333 : 2 : test_is_full(PG_FUNCTION_ARGS)
334 : : {
335 : : bool is_full;
336 : :
692 337 : 2 : check_tidstore_available();
338 : :
775 339 : 2 : is_full = (TidStoreMemoryUsage(tidstore) > tidstore_empty_size);
340 : :
341 : 2 : PG_RETURN_BOOL(is_full);
342 : : }
343 : :
344 : : /* Free the tidstore */
345 : : Datum
346 : 3 : test_destroy(PG_FUNCTION_ARGS)
347 : : {
692 348 : 3 : check_tidstore_available();
349 : :
775 350 : 3 : TidStoreDestroy(tidstore);
351 : 3 : tidstore = NULL;
352 : 3 : items.num_tids = 0;
353 : 3 : pfree(items.insert_tids);
354 : 3 : pfree(items.lookup_tids);
355 : 3 : pfree(items.iter_tids);
356 : :
357 : 3 : PG_RETURN_VOID();
358 : : }
|