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-2025, 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 : :
534 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 : 208030 : itemptr_cmp(const void *left, const void *right)
53 : : {
54 : : BlockNumber lblk,
55 : : rblk;
56 : : OffsetNumber loff,
57 : : roff;
58 : :
59 : 208030 : lblk = ItemPointerGetBlockNumber((ItemPointer) left);
60 : 208030 : rblk = ItemPointerGetBlockNumber((ItemPointer) right);
61 : :
62 [ + + ]: 208030 : if (lblk < rblk)
63 : 64026 : return -1;
64 [ + + ]: 144004 : if (lblk > rblk)
65 : 64680 : return 1;
66 : :
67 : 79324 : loff = ItemPointerGetOffsetNumber((ItemPointer) left);
68 : 79324 : roff = ItemPointerGetOffsetNumber((ItemPointer) right);
69 : :
70 [ + + ]: 79324 : if (loff < roff)
71 : 35152 : return -1;
72 [ + + ]: 44172 : if (loff > roff)
73 : 13686 : return 1;
74 : :
75 : 30486 : return 0;
76 : : }
77 : :
78 : : /*
79 : : * Create a TidStore. If shared is false, the tidstore is created
80 : : * on TopMemoryContext, otherwise on DSA. Although the tidstore
81 : : * is created on DSA, only the same process can subsequently use
82 : : * the tidstore. The tidstore handle is not shared anywhere.
83 : : */
84 : : Datum
85 : 3 : test_create(PG_FUNCTION_ARGS)
86 : : {
87 : 3 : bool shared = PG_GETARG_BOOL(0);
88 : : MemoryContext old_ctx;
89 : :
90 : : /* doesn't really matter, since it's just a hint */
91 : 3 : size_t tidstore_max_size = 2 * 1024 * 1024;
92 : 3 : size_t array_init_size = 1024;
93 : :
94 [ - + ]: 3 : Assert(tidstore == NULL);
95 : :
96 : : /*
97 : : * Create the TidStore on TopMemoryContext so that the same process use it
98 : : * for subsequent tests.
99 : : */
100 : 3 : old_ctx = MemoryContextSwitchTo(TopMemoryContext);
101 : :
102 [ + + ]: 3 : if (shared)
103 : : {
104 : : int tranche_id;
105 : :
3 nathan@postgresql.or 106 :GNC 1 : tranche_id = LWLockNewTrancheId("test_tidstore");
107 : :
527 msawada@postgresql.o 108 :CBC 1 : tidstore = TidStoreCreateShared(tidstore_max_size, tranche_id);
109 : :
110 : : /*
111 : : * Remain attached until end of backend or explicitly detached so that
112 : : * the same process use the tidstore for subsequent tests.
113 : : */
114 : 1 : dsa_pin_mapping(TidStoreGetDSA(tidstore));
115 : : }
116 : : else
117 : : /* VACUUM uses insert only, so we test the other option. */
517 john.naylor@postgres 118 : 2 : tidstore = TidStoreCreateLocal(tidstore_max_size, false);
119 : :
534 msawada@postgresql.o 120 : 3 : tidstore_empty_size = TidStoreMemoryUsage(tidstore);
121 : :
122 : 3 : items.num_tids = 0;
123 : 3 : items.max_tids = array_init_size / sizeof(ItemPointerData);
124 : 3 : items.insert_tids = (ItemPointerData *) palloc0(array_init_size);
125 : 3 : items.lookup_tids = (ItemPointerData *) palloc0(array_init_size);
126 : 3 : items.iter_tids = (ItemPointerData *) palloc0(array_init_size);
127 : :
128 : 3 : MemoryContextSwitchTo(old_ctx);
129 : :
130 : 3 : PG_RETURN_VOID();
131 : : }
132 : :
133 : : static void
134 : 1121 : sanity_check_array(ArrayType *ta)
135 : : {
136 [ - + - - ]: 1121 : if (ARR_HASNULL(ta) && array_contains_nulls(ta))
534 msawada@postgresql.o 137 [ # # ]:UBC 0 : ereport(ERROR,
138 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
139 : : errmsg("array must not contain nulls")));
140 : :
534 msawada@postgresql.o 141 [ - + ]:CBC 1121 : if (ARR_NDIM(ta) > 1)
534 msawada@postgresql.o 142 [ # # ]:UBC 0 : ereport(ERROR,
143 : : (errcode(ERRCODE_DATA_EXCEPTION),
144 : : errmsg("argument must be empty or one-dimensional array")));
534 msawada@postgresql.o 145 :CBC 1121 : }
146 : :
147 : : static void
451 148 : 1138 : check_tidstore_available(void)
149 : : {
150 [ - + ]: 1138 : if (tidstore == NULL)
451 msawada@postgresql.o 151 [ # # ]:UBC 0 : elog(ERROR, "tidstore is not created");
451 msawada@postgresql.o 152 :CBC 1138 : }
153 : :
154 : : static void
499 155 : 1120 : purge_from_verification_array(BlockNumber blkno)
156 : : {
157 : 1120 : int dst = 0;
158 : :
159 [ + + ]: 3491493 : for (int src = 0; src < items.num_tids; src++)
160 [ + + ]: 3490373 : if (ItemPointerGetBlockNumber(&items.insert_tids[src]) != blkno)
161 : 3490349 : items.insert_tids[dst++] = items.insert_tids[src];
162 : 1120 : items.num_tids = dst;
163 : 1120 : }
164 : :
165 : :
166 : : /* Set the given block and offsets pairs */
167 : : Datum
534 168 : 1121 : do_set_block_offsets(PG_FUNCTION_ARGS)
169 : : {
170 : 1121 : BlockNumber blkno = PG_GETARG_INT64(0);
171 : 1121 : ArrayType *ta = PG_GETARG_ARRAYTYPE_P_COPY(1);
172 : : OffsetNumber *offs;
173 : : int noffs;
174 : :
451 175 : 1121 : check_tidstore_available();
534 176 : 1121 : sanity_check_array(ta);
177 : :
178 : 1121 : noffs = ArrayGetNItems(ARR_NDIM(ta), ARR_DIMS(ta));
179 [ - + ]: 1121 : offs = ((OffsetNumber *) ARR_DATA_PTR(ta));
180 : :
181 : : /* Set TIDs in the store */
182 : 1121 : TidStoreLockExclusive(tidstore);
183 : 1121 : TidStoreSetBlockOffsets(tidstore, blkno, offs, noffs);
184 : 1120 : TidStoreUnlock(tidstore);
185 : :
186 : : /* Remove the existing items of blkno from the verification array */
499 187 : 1120 : purge_from_verification_array(blkno);
188 : :
189 : : /* Set TIDs in verification array */
534 190 [ + + ]: 16363 : for (int i = 0; i < noffs; i++)
191 : : {
192 : : ItemPointer tid;
193 : 15243 : int idx = items.num_tids + i;
194 : :
195 : : /* Enlarge the TID arrays if necessary */
196 [ + + ]: 15243 : if (idx >= items.max_tids)
197 : : {
198 : 12 : items.max_tids *= 2;
199 : 12 : items.insert_tids = repalloc(items.insert_tids, sizeof(ItemPointerData) * items.max_tids);
200 : 12 : items.lookup_tids = repalloc(items.lookup_tids, sizeof(ItemPointerData) * items.max_tids);
201 : 12 : items.iter_tids = repalloc(items.iter_tids, sizeof(ItemPointerData) * items.max_tids);
202 : : }
203 : :
204 : 15243 : tid = &(items.insert_tids[idx]);
205 : 15243 : ItemPointerSet(tid, blkno, offs[i]);
206 : : }
207 : :
208 : : /* Update statistics */
209 : 1120 : items.num_tids += noffs;
210 : :
211 : 1120 : PG_RETURN_INT64(blkno);
212 : : }
213 : :
214 : : /*
215 : : * Verify TIDs in store against the array.
216 : : */
217 : : Datum
218 : 12 : check_set_block_offsets(PG_FUNCTION_ARGS)
219 : : {
220 : : TidStoreIter *iter;
221 : : TidStoreIterResult *iter_result;
222 : 12 : int num_iter_tids = 0;
223 : 12 : int num_lookup_tids = 0;
526 dgustafsson@postgres 224 : 12 : BlockNumber prevblkno = 0;
225 : :
451 msawada@postgresql.o 226 : 12 : check_tidstore_available();
227 : :
228 : : /* lookup each member in the verification array */
534 229 [ + + ]: 15255 : for (int i = 0; i < items.num_tids; i++)
230 [ - + ]: 15243 : if (!TidStoreIsMember(tidstore, &items.insert_tids[i]))
534 msawada@postgresql.o 231 [ # # ]:UBC 0 : elog(ERROR, "missing TID with block %u, offset %u",
232 : : ItemPointerGetBlockNumber(&items.insert_tids[i]),
233 : : ItemPointerGetOffsetNumber(&items.insert_tids[i]));
234 : :
235 : : /*
236 : : * Lookup all possible TIDs for each distinct block in the verification
237 : : * array and save successful lookups in the lookup array.
238 : : */
239 : :
534 msawada@postgresql.o 240 [ + + ]:CBC 15255 : for (int i = 0; i < items.num_tids; i++)
241 : : {
242 : 15243 : BlockNumber blkno = ItemPointerGetBlockNumber(&items.insert_tids[i]);
243 : :
244 [ + + + + ]: 15243 : if (i > 0 && blkno == prevblkno)
245 : 14123 : continue;
246 : :
247 [ + + ]: 2293760 : for (OffsetNumber offset = FirstOffsetNumber; offset < MaxOffsetNumber; offset++)
248 : : {
249 : : ItemPointerData tid;
250 : :
251 : 2292640 : ItemPointerSet(&tid, blkno, offset);
252 : :
253 : 2292640 : TidStoreLockShare(tidstore);
254 [ + + ]: 2292640 : if (TidStoreIsMember(tidstore, &tid))
255 : 15243 : ItemPointerSet(&items.lookup_tids[num_lookup_tids++], blkno, offset);
256 : 2292640 : TidStoreUnlock(tidstore);
257 : : }
258 : :
259 : 1120 : prevblkno = blkno;
260 : : }
261 : :
262 : : /* Collect TIDs stored in the tidstore, in order */
263 : :
264 : 12 : TidStoreLockShare(tidstore);
265 : 12 : iter = TidStoreBeginIterate(tidstore);
266 [ + + ]: 1132 : while ((iter_result = TidStoreIterateNext(iter)) != NULL)
267 : : {
268 : : OffsetNumber offsets[MaxOffsetNumber];
269 : : int num_offsets;
270 : :
409 tmunro@postgresql.or 271 : 1120 : num_offsets = TidStoreGetBlockOffsets(iter_result, offsets, lengthof(offsets));
272 [ - + ]: 1120 : Assert(num_offsets <= lengthof(offsets));
273 [ + + ]: 16363 : for (int i = 0; i < num_offsets; i++)
534 msawada@postgresql.o 274 : 15243 : ItemPointerSet(&(items.iter_tids[num_iter_tids++]), iter_result->blkno,
409 tmunro@postgresql.or 275 : 15243 : offsets[i]);
276 : : }
534 msawada@postgresql.o 277 : 12 : TidStoreEndIterate(iter);
278 : 12 : TidStoreUnlock(tidstore);
279 : :
280 : : /*
281 : : * Sort verification and lookup arrays and test that all arrays are the
282 : : * same.
283 : : */
284 : :
285 [ - + ]: 12 : if (num_lookup_tids != items.num_tids)
534 msawada@postgresql.o 286 [ # # ]:UBC 0 : elog(ERROR, "should have %d TIDs, have %d", items.num_tids, num_lookup_tids);
534 msawada@postgresql.o 287 [ - + ]:CBC 12 : if (num_iter_tids != items.num_tids)
534 msawada@postgresql.o 288 [ # # ]:UBC 0 : elog(ERROR, "should have %d TIDs, have %d", items.num_tids, num_iter_tids);
289 : :
534 msawada@postgresql.o 290 :CBC 12 : qsort(items.insert_tids, items.num_tids, sizeof(ItemPointerData), itemptr_cmp);
291 : 12 : qsort(items.lookup_tids, items.num_tids, sizeof(ItemPointerData), itemptr_cmp);
292 [ + + ]: 15255 : for (int i = 0; i < items.num_tids; i++)
293 : : {
274 peter@eisentraut.org 294 [ - + ]: 15243 : if (itemptr_cmp(&items.insert_tids[i], &items.iter_tids[i]) != 0)
534 msawada@postgresql.o 295 [ # # ]:UBC 0 : elog(ERROR, "TID iter array doesn't match verification array, got (%u,%u) expected (%u,%u)",
296 : : ItemPointerGetBlockNumber(&items.iter_tids[i]),
297 : : ItemPointerGetOffsetNumber(&items.iter_tids[i]),
298 : : ItemPointerGetBlockNumber(&items.insert_tids[i]),
299 : : ItemPointerGetOffsetNumber(&items.insert_tids[i]));
274 peter@eisentraut.org 300 [ - + ]:CBC 15243 : if (itemptr_cmp(&items.insert_tids[i], &items.lookup_tids[i]) != 0)
534 msawada@postgresql.o 301 [ # # ]:UBC 0 : elog(ERROR, "TID lookup array doesn't match verification array, got (%u,%u) expected (%u,%u)",
302 : : ItemPointerGetBlockNumber(&items.lookup_tids[i]),
303 : : ItemPointerGetOffsetNumber(&items.lookup_tids[i]),
304 : : ItemPointerGetBlockNumber(&items.insert_tids[i]),
305 : : ItemPointerGetOffsetNumber(&items.insert_tids[i]));
306 : : }
307 : :
534 msawada@postgresql.o 308 :CBC 12 : PG_RETURN_VOID();
309 : : }
310 : :
311 : : /*
312 : : * In real world use, we care if the memory usage is greater than
313 : : * some configured limit. Here we just want to verify that
314 : : * TidStoreMemoryUsage is not broken.
315 : : */
316 : : Datum
317 : 2 : test_is_full(PG_FUNCTION_ARGS)
318 : : {
319 : : bool is_full;
320 : :
451 321 : 2 : check_tidstore_available();
322 : :
534 323 : 2 : is_full = (TidStoreMemoryUsage(tidstore) > tidstore_empty_size);
324 : :
325 : 2 : PG_RETURN_BOOL(is_full);
326 : : }
327 : :
328 : : /* Free the tidstore */
329 : : Datum
330 : 3 : test_destroy(PG_FUNCTION_ARGS)
331 : : {
451 332 : 3 : check_tidstore_available();
333 : :
534 334 : 3 : TidStoreDestroy(tidstore);
335 : 3 : tidstore = NULL;
336 : 3 : items.num_tids = 0;
337 : 3 : pfree(items.insert_tids);
338 : 3 : pfree(items.lookup_tids);
339 : 3 : pfree(items.iter_tids);
340 : :
341 : 3 : PG_RETURN_VOID();
342 : : }
|