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, 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 "funcapi.h"
22 : : #include "storage/block.h"
23 : : #include "storage/itemptr.h"
24 : : #include "storage/lwlock.h"
25 : : #include "utils/array.h"
26 : : #include "utils/memutils.h"
27 : :
24 msawada@postgresql.o 28 :GNC 1 : PG_MODULE_MAGIC;
29 : :
30 : 2 : PG_FUNCTION_INFO_V1(test_create);
31 : 2 : PG_FUNCTION_INFO_V1(do_set_block_offsets);
32 : 2 : PG_FUNCTION_INFO_V1(check_set_block_offsets);
33 : 2 : PG_FUNCTION_INFO_V1(test_is_full);
34 : 2 : PG_FUNCTION_INFO_V1(test_destroy);
35 : :
36 : : static TidStore *tidstore = NULL;
37 : : static size_t tidstore_empty_size;
38 : :
39 : : /* array for verification of some tests */
40 : : typedef struct ItemArray
41 : : {
42 : : ItemPointerData *insert_tids;
43 : : ItemPointerData *lookup_tids;
44 : : ItemPointerData *iter_tids;
45 : : int max_tids;
46 : : int num_tids;
47 : : } ItemArray;
48 : :
49 : : static ItemArray items;
50 : :
51 : : /* comparator routine for ItemPointer */
52 : : static int
53 : 208070 : itemptr_cmp(const void *left, const void *right)
54 : : {
55 : : BlockNumber lblk,
56 : : rblk;
57 : : OffsetNumber loff,
58 : : roff;
59 : :
60 : 208070 : lblk = ItemPointerGetBlockNumber((ItemPointer) left);
61 : 208070 : rblk = ItemPointerGetBlockNumber((ItemPointer) right);
62 : :
63 [ + + ]: 208070 : if (lblk < rblk)
64 : 64084 : return -1;
65 [ + + ]: 143986 : if (lblk > rblk)
66 : 64510 : return 1;
67 : :
68 : 79476 : loff = ItemPointerGetOffsetNumber((ItemPointer) left);
69 : 79476 : roff = ItemPointerGetOffsetNumber((ItemPointer) right);
70 : :
71 [ + + ]: 79476 : if (loff < roff)
72 : 35130 : return -1;
73 [ + + ]: 44346 : if (loff > roff)
74 : 13842 : return 1;
75 : :
76 : 30504 : return 0;
77 : : }
78 : :
79 : : /*
80 : : * Create a TidStore. If shared is false, the tidstore is created
81 : : * on TopMemoryContext, otherwise on DSA. Although the tidstore
82 : : * is created on DSA, only the same process can subsequently use
83 : : * the tidstore. The tidstore handle is not shared anywhere.
84 : : */
85 : : Datum
86 : 2 : test_create(PG_FUNCTION_ARGS)
87 : : {
88 : 2 : bool shared = PG_GETARG_BOOL(0);
89 : : MemoryContext old_ctx;
90 : :
91 : : /* doesn't really matter, since it's just a hint */
92 : 2 : size_t tidstore_max_size = 2 * 1024 * 1024;
93 : 2 : size_t array_init_size = 1024;
94 : :
95 [ - + ]: 2 : Assert(tidstore == NULL);
96 : :
97 : : /*
98 : : * Create the TidStore on TopMemoryContext so that the same process use it
99 : : * for subsequent tests.
100 : : */
101 : 2 : old_ctx = MemoryContextSwitchTo(TopMemoryContext);
102 : :
103 [ + + ]: 2 : if (shared)
104 : : {
105 : : int tranche_id;
106 : :
107 : 1 : tranche_id = LWLockNewTrancheId();
108 : 1 : LWLockRegisterTranche(tranche_id, "test_tidstore");
109 : :
17 110 : 1 : tidstore = TidStoreCreateShared(tidstore_max_size, tranche_id);
111 : :
112 : : /*
113 : : * Remain attached until end of backend or explicitly detached so that
114 : : * the same process use the tidstore for subsequent tests.
115 : : */
116 : 1 : dsa_pin_mapping(TidStoreGetDSA(tidstore));
117 : : }
118 : : else
119 : : /* VACUUM uses insert only, so we test the other option. */
7 john.naylor@postgres 120 : 1 : tidstore = TidStoreCreateLocal(tidstore_max_size, false);
121 : :
24 msawada@postgresql.o 122 : 2 : tidstore_empty_size = TidStoreMemoryUsage(tidstore);
123 : :
124 : 2 : items.num_tids = 0;
125 : 2 : items.max_tids = array_init_size / sizeof(ItemPointerData);
126 : 2 : items.insert_tids = (ItemPointerData *) palloc0(array_init_size);
127 : 2 : items.lookup_tids = (ItemPointerData *) palloc0(array_init_size);
128 : 2 : items.iter_tids = (ItemPointerData *) palloc0(array_init_size);
129 : :
130 : 2 : MemoryContextSwitchTo(old_ctx);
131 : :
132 : 2 : PG_RETURN_VOID();
133 : : }
134 : :
135 : : static void
136 : 1112 : sanity_check_array(ArrayType *ta)
137 : : {
138 [ - + - - ]: 1112 : if (ARR_HASNULL(ta) && array_contains_nulls(ta))
24 msawada@postgresql.o 139 [ # # ]:UNC 0 : ereport(ERROR,
140 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
141 : : errmsg("array must not contain nulls")));
142 : :
24 msawada@postgresql.o 143 [ - + ]:GNC 1112 : if (ARR_NDIM(ta) > 1)
24 msawada@postgresql.o 144 [ # # ]:UNC 0 : ereport(ERROR,
145 : : (errcode(ERRCODE_DATA_EXCEPTION),
146 : : errmsg("argument must be empty or one-dimensional array")));
24 msawada@postgresql.o 147 :GNC 1112 : }
148 : :
149 : : /* Set the given block and offsets pairs */
150 : : Datum
151 : 1112 : do_set_block_offsets(PG_FUNCTION_ARGS)
152 : : {
153 : 1112 : BlockNumber blkno = PG_GETARG_INT64(0);
154 : 1112 : ArrayType *ta = PG_GETARG_ARRAYTYPE_P_COPY(1);
155 : : OffsetNumber *offs;
156 : : int noffs;
157 : :
158 : 1112 : sanity_check_array(ta);
159 : :
160 : 1112 : noffs = ArrayGetNItems(ARR_NDIM(ta), ARR_DIMS(ta));
161 [ - + ]: 1112 : offs = ((OffsetNumber *) ARR_DATA_PTR(ta));
162 : :
163 : : /* Set TIDs in the store */
164 : 1112 : TidStoreLockExclusive(tidstore);
165 : 1112 : TidStoreSetBlockOffsets(tidstore, blkno, offs, noffs);
166 : 1111 : TidStoreUnlock(tidstore);
167 : :
168 : : /* Set TIDs in verification array */
169 [ + + ]: 16363 : for (int i = 0; i < noffs; i++)
170 : : {
171 : : ItemPointer tid;
172 : 15252 : int idx = items.num_tids + i;
173 : :
174 : : /* Enlarge the TID arrays if necessary */
175 [ + + ]: 15252 : if (idx >= items.max_tids)
176 : : {
177 : 12 : items.max_tids *= 2;
178 : 12 : items.insert_tids = repalloc(items.insert_tids, sizeof(ItemPointerData) * items.max_tids);
179 : 12 : items.lookup_tids = repalloc(items.lookup_tids, sizeof(ItemPointerData) * items.max_tids);
180 : 12 : items.iter_tids = repalloc(items.iter_tids, sizeof(ItemPointerData) * items.max_tids);
181 : : }
182 : :
183 : 15252 : tid = &(items.insert_tids[idx]);
184 : 15252 : ItemPointerSet(tid, blkno, offs[i]);
185 : : }
186 : :
187 : : /* Update statistics */
188 : 1111 : items.num_tids += noffs;
189 : :
190 : 1111 : PG_RETURN_INT64(blkno);
191 : : }
192 : :
193 : : /*
194 : : * Verify TIDs in store against the array.
195 : : */
196 : : Datum
197 : 3 : check_set_block_offsets(PG_FUNCTION_ARGS)
198 : : {
199 : : TidStoreIter *iter;
200 : : TidStoreIterResult *iter_result;
201 : 3 : int num_iter_tids = 0;
202 : 3 : int num_lookup_tids = 0;
16 dgustafsson@postgres 203 : 3 : BlockNumber prevblkno = 0;
204 : :
205 : : /* lookup each member in the verification array */
24 msawada@postgresql.o 206 [ + + ]: 15255 : for (int i = 0; i < items.num_tids; i++)
207 [ - + ]: 15252 : if (!TidStoreIsMember(tidstore, &items.insert_tids[i]))
24 msawada@postgresql.o 208 [ # # ]:UNC 0 : elog(ERROR, "missing TID with block %u, offset %u",
209 : : ItemPointerGetBlockNumber(&items.insert_tids[i]),
210 : : ItemPointerGetOffsetNumber(&items.insert_tids[i]));
211 : :
212 : : /*
213 : : * Lookup all possible TIDs for each distinct block in the verification
214 : : * array and save successful lookups in the lookup array.
215 : : */
216 : :
24 msawada@postgresql.o 217 [ + + ]:GNC 15255 : for (int i = 0; i < items.num_tids; i++)
218 : : {
219 : 15252 : BlockNumber blkno = ItemPointerGetBlockNumber(&items.insert_tids[i]);
220 : :
221 [ + + + + ]: 15252 : if (i > 0 && blkno == prevblkno)
222 : 14141 : continue;
223 : :
224 [ + + ]: 2275328 : for (OffsetNumber offset = FirstOffsetNumber; offset < MaxOffsetNumber; offset++)
225 : : {
226 : : ItemPointerData tid;
227 : :
228 : 2274217 : ItemPointerSet(&tid, blkno, offset);
229 : :
230 : 2274217 : TidStoreLockShare(tidstore);
231 [ + + ]: 2274217 : if (TidStoreIsMember(tidstore, &tid))
232 : 15252 : ItemPointerSet(&items.lookup_tids[num_lookup_tids++], blkno, offset);
233 : 2274217 : TidStoreUnlock(tidstore);
234 : : }
235 : :
236 : 1111 : prevblkno = blkno;
237 : : }
238 : :
239 : : /* Collect TIDs stored in the tidstore, in order */
240 : :
241 : 3 : TidStoreLockShare(tidstore);
242 : 3 : iter = TidStoreBeginIterate(tidstore);
243 [ + + ]: 1114 : while ((iter_result = TidStoreIterateNext(iter)) != NULL)
244 : : {
245 [ + + ]: 16363 : for (int i = 0; i < iter_result->num_offsets; i++)
246 : 15252 : ItemPointerSet(&(items.iter_tids[num_iter_tids++]), iter_result->blkno,
247 : 15252 : iter_result->offsets[i]);
248 : : }
249 : 3 : TidStoreEndIterate(iter);
250 : 3 : TidStoreUnlock(tidstore);
251 : :
252 : : /*
253 : : * Sort verification and lookup arrays and test that all arrays are the
254 : : * same.
255 : : */
256 : :
257 [ - + ]: 3 : if (num_lookup_tids != items.num_tids)
24 msawada@postgresql.o 258 [ # # ]:UNC 0 : elog(ERROR, "should have %d TIDs, have %d", items.num_tids, num_lookup_tids);
24 msawada@postgresql.o 259 [ - + ]:GNC 3 : if (num_iter_tids != items.num_tids)
24 msawada@postgresql.o 260 [ # # ]:UNC 0 : elog(ERROR, "should have %d TIDs, have %d", items.num_tids, num_iter_tids);
261 : :
24 msawada@postgresql.o 262 :GNC 3 : qsort(items.insert_tids, items.num_tids, sizeof(ItemPointerData), itemptr_cmp);
263 : 3 : qsort(items.lookup_tids, items.num_tids, sizeof(ItemPointerData), itemptr_cmp);
264 [ + + ]: 15255 : for (int i = 0; i < items.num_tids; i++)
265 : : {
266 [ - + ]: 15252 : if (itemptr_cmp((const void *) &items.insert_tids[i], (const void *) &items.iter_tids[i]) != 0)
24 msawada@postgresql.o 267 [ # # ]:UNC 0 : elog(ERROR, "TID iter array doesn't match verification array, got (%u,%u) expected (%u,%u)",
268 : : ItemPointerGetBlockNumber(&items.iter_tids[i]),
269 : : ItemPointerGetOffsetNumber(&items.iter_tids[i]),
270 : : ItemPointerGetBlockNumber(&items.insert_tids[i]),
271 : : ItemPointerGetOffsetNumber(&items.insert_tids[i]));
24 msawada@postgresql.o 272 [ - + ]:GNC 15252 : if (itemptr_cmp((const void *) &items.insert_tids[i], (const void *) &items.lookup_tids[i]) != 0)
24 msawada@postgresql.o 273 [ # # ]:UNC 0 : elog(ERROR, "TID lookup array doesn't match verification array, got (%u,%u) expected (%u,%u)",
274 : : ItemPointerGetBlockNumber(&items.lookup_tids[i]),
275 : : ItemPointerGetOffsetNumber(&items.lookup_tids[i]),
276 : : ItemPointerGetBlockNumber(&items.insert_tids[i]),
277 : : ItemPointerGetOffsetNumber(&items.insert_tids[i]));
278 : : }
279 : :
24 msawada@postgresql.o 280 :GNC 3 : PG_RETURN_VOID();
281 : : }
282 : :
283 : : /*
284 : : * In real world use, we care if the memory usage is greater than
285 : : * some configured limit. Here we just want to verify that
286 : : * TidStoreMemoryUsage is not broken.
287 : : */
288 : : Datum
289 : 2 : test_is_full(PG_FUNCTION_ARGS)
290 : : {
291 : : bool is_full;
292 : :
293 : 2 : is_full = (TidStoreMemoryUsage(tidstore) > tidstore_empty_size);
294 : :
295 : 2 : PG_RETURN_BOOL(is_full);
296 : : }
297 : :
298 : : /* Free the tidstore */
299 : : Datum
300 : 2 : test_destroy(PG_FUNCTION_ARGS)
301 : : {
302 : 2 : TidStoreDestroy(tidstore);
303 : 2 : tidstore = NULL;
304 : 2 : items.num_tids = 0;
305 : 2 : pfree(items.insert_tids);
306 : 2 : pfree(items.lookup_tids);
307 : 2 : pfree(items.iter_tids);
308 : :
309 : 2 : PG_RETURN_VOID();
310 : : }
|