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 :
28 2 : PG_MODULE_MAGIC;
29 :
30 4 : PG_FUNCTION_INFO_V1(test_create);
31 4 : PG_FUNCTION_INFO_V1(do_set_block_offsets);
32 4 : PG_FUNCTION_INFO_V1(check_set_block_offsets);
33 4 : PG_FUNCTION_INFO_V1(test_is_full);
34 4 : 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 416296 : itemptr_cmp(const void *left, const void *right)
54 : {
55 : BlockNumber lblk,
56 : rblk;
57 : OffsetNumber loff,
58 : roff;
59 :
60 416296 : lblk = ItemPointerGetBlockNumber((ItemPointer) left);
61 416296 : rblk = ItemPointerGetBlockNumber((ItemPointer) right);
62 :
63 416296 : if (lblk < rblk)
64 128168 : return -1;
65 288128 : if (lblk > rblk)
66 129020 : return 1;
67 :
68 159108 : loff = ItemPointerGetOffsetNumber((ItemPointer) left);
69 159108 : roff = ItemPointerGetOffsetNumber((ItemPointer) right);
70 :
71 159108 : if (loff < roff)
72 70320 : return -1;
73 88788 : if (loff > roff)
74 27684 : return 1;
75 :
76 61104 : 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 6 : test_create(PG_FUNCTION_ARGS)
87 : {
88 6 : bool shared = PG_GETARG_BOOL(0);
89 : MemoryContext old_ctx;
90 :
91 : /* doesn't really matter, since it's just a hint */
92 6 : size_t tidstore_max_size = 2 * 1024 * 1024;
93 6 : size_t array_init_size = 1024;
94 :
95 : Assert(tidstore == NULL);
96 :
97 : /*
98 : * Create the TidStore on TopMemoryContext so that the same process use it
99 : * for subsequent tests.
100 : */
101 6 : old_ctx = MemoryContextSwitchTo(TopMemoryContext);
102 :
103 6 : if (shared)
104 : {
105 : int tranche_id;
106 :
107 2 : tranche_id = LWLockNewTrancheId();
108 2 : LWLockRegisterTranche(tranche_id, "test_tidstore");
109 :
110 2 : 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 2 : dsa_pin_mapping(TidStoreGetDSA(tidstore));
117 : }
118 : else
119 : /* VACUUM uses insert only, so we test the other option. */
120 4 : tidstore = TidStoreCreateLocal(tidstore_max_size, false);
121 :
122 6 : tidstore_empty_size = TidStoreMemoryUsage(tidstore);
123 :
124 6 : items.num_tids = 0;
125 6 : items.max_tids = array_init_size / sizeof(ItemPointerData);
126 6 : items.insert_tids = (ItemPointerData *) palloc0(array_init_size);
127 6 : items.lookup_tids = (ItemPointerData *) palloc0(array_init_size);
128 6 : items.iter_tids = (ItemPointerData *) palloc0(array_init_size);
129 :
130 6 : MemoryContextSwitchTo(old_ctx);
131 :
132 6 : PG_RETURN_VOID();
133 : }
134 :
135 : static void
136 2242 : sanity_check_array(ArrayType *ta)
137 : {
138 2242 : if (ARR_HASNULL(ta) && array_contains_nulls(ta))
139 0 : ereport(ERROR,
140 : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
141 : errmsg("array must not contain nulls")));
142 :
143 2242 : if (ARR_NDIM(ta) > 1)
144 0 : ereport(ERROR,
145 : (errcode(ERRCODE_DATA_EXCEPTION),
146 : errmsg("argument must be empty or one-dimensional array")));
147 2242 : }
148 :
149 : static void
150 2240 : purge_from_verification_array(BlockNumber blkno)
151 : {
152 2240 : int dst = 0;
153 :
154 6987154 : for (int src = 0; src < items.num_tids; src++)
155 6984914 : if (ItemPointerGetBlockNumber(&items.insert_tids[src]) != blkno)
156 6984866 : items.insert_tids[dst++] = items.insert_tids[src];
157 2240 : items.num_tids = dst;
158 2240 : }
159 :
160 :
161 : /* Set the given block and offsets pairs */
162 : Datum
163 2242 : do_set_block_offsets(PG_FUNCTION_ARGS)
164 : {
165 2242 : BlockNumber blkno = PG_GETARG_INT64(0);
166 2242 : ArrayType *ta = PG_GETARG_ARRAYTYPE_P_COPY(1);
167 : OffsetNumber *offs;
168 : int noffs;
169 :
170 2242 : sanity_check_array(ta);
171 :
172 2242 : noffs = ArrayGetNItems(ARR_NDIM(ta), ARR_DIMS(ta));
173 2242 : offs = ((OffsetNumber *) ARR_DATA_PTR(ta));
174 :
175 : /* Set TIDs in the store */
176 2242 : TidStoreLockExclusive(tidstore);
177 2242 : TidStoreSetBlockOffsets(tidstore, blkno, offs, noffs);
178 2240 : TidStoreUnlock(tidstore);
179 :
180 : /* Remove the existing items of blkno from the verification array */
181 2240 : purge_from_verification_array(blkno);
182 :
183 : /* Set TIDs in verification array */
184 32792 : for (int i = 0; i < noffs; i++)
185 : {
186 : ItemPointer tid;
187 30552 : int idx = items.num_tids + i;
188 :
189 : /* Enlarge the TID arrays if necessary */
190 30552 : if (idx >= items.max_tids)
191 : {
192 24 : items.max_tids *= 2;
193 24 : items.insert_tids = repalloc(items.insert_tids, sizeof(ItemPointerData) * items.max_tids);
194 24 : items.lookup_tids = repalloc(items.lookup_tids, sizeof(ItemPointerData) * items.max_tids);
195 24 : items.iter_tids = repalloc(items.iter_tids, sizeof(ItemPointerData) * items.max_tids);
196 : }
197 :
198 30552 : tid = &(items.insert_tids[idx]);
199 30552 : ItemPointerSet(tid, blkno, offs[i]);
200 : }
201 :
202 : /* Update statistics */
203 2240 : items.num_tids += noffs;
204 :
205 2240 : PG_RETURN_INT64(blkno);
206 : }
207 :
208 : /*
209 : * Verify TIDs in store against the array.
210 : */
211 : Datum
212 24 : check_set_block_offsets(PG_FUNCTION_ARGS)
213 : {
214 : TidStoreIter *iter;
215 : TidStoreIterResult *iter_result;
216 24 : int num_iter_tids = 0;
217 24 : int num_lookup_tids = 0;
218 24 : BlockNumber prevblkno = 0;
219 :
220 : /* lookup each member in the verification array */
221 30576 : for (int i = 0; i < items.num_tids; i++)
222 30552 : if (!TidStoreIsMember(tidstore, &items.insert_tids[i]))
223 0 : elog(ERROR, "missing TID with block %u, offset %u",
224 : ItemPointerGetBlockNumber(&items.insert_tids[i]),
225 : ItemPointerGetOffsetNumber(&items.insert_tids[i]));
226 :
227 : /*
228 : * Lookup all possible TIDs for each distinct block in the verification
229 : * array and save successful lookups in the lookup array.
230 : */
231 :
232 30576 : for (int i = 0; i < items.num_tids; i++)
233 : {
234 30552 : BlockNumber blkno = ItemPointerGetBlockNumber(&items.insert_tids[i]);
235 :
236 30552 : if (i > 0 && blkno == prevblkno)
237 28312 : continue;
238 :
239 4587520 : for (OffsetNumber offset = FirstOffsetNumber; offset < MaxOffsetNumber; offset++)
240 : {
241 : ItemPointerData tid;
242 :
243 4585280 : ItemPointerSet(&tid, blkno, offset);
244 :
245 4585280 : TidStoreLockShare(tidstore);
246 4585280 : if (TidStoreIsMember(tidstore, &tid))
247 30552 : ItemPointerSet(&items.lookup_tids[num_lookup_tids++], blkno, offset);
248 4585280 : TidStoreUnlock(tidstore);
249 : }
250 :
251 2240 : prevblkno = blkno;
252 : }
253 :
254 : /* Collect TIDs stored in the tidstore, in order */
255 :
256 24 : TidStoreLockShare(tidstore);
257 24 : iter = TidStoreBeginIterate(tidstore);
258 2264 : while ((iter_result = TidStoreIterateNext(iter)) != NULL)
259 : {
260 32792 : for (int i = 0; i < iter_result->num_offsets; i++)
261 30552 : ItemPointerSet(&(items.iter_tids[num_iter_tids++]), iter_result->blkno,
262 30552 : iter_result->offsets[i]);
263 : }
264 24 : TidStoreEndIterate(iter);
265 24 : TidStoreUnlock(tidstore);
266 :
267 : /*
268 : * Sort verification and lookup arrays and test that all arrays are the
269 : * same.
270 : */
271 :
272 24 : if (num_lookup_tids != items.num_tids)
273 0 : elog(ERROR, "should have %d TIDs, have %d", items.num_tids, num_lookup_tids);
274 24 : if (num_iter_tids != items.num_tids)
275 0 : elog(ERROR, "should have %d TIDs, have %d", items.num_tids, num_iter_tids);
276 :
277 24 : qsort(items.insert_tids, items.num_tids, sizeof(ItemPointerData), itemptr_cmp);
278 24 : qsort(items.lookup_tids, items.num_tids, sizeof(ItemPointerData), itemptr_cmp);
279 30576 : for (int i = 0; i < items.num_tids; i++)
280 : {
281 30552 : if (itemptr_cmp((const void *) &items.insert_tids[i], (const void *) &items.iter_tids[i]) != 0)
282 0 : elog(ERROR, "TID iter array doesn't match verification array, got (%u,%u) expected (%u,%u)",
283 : ItemPointerGetBlockNumber(&items.iter_tids[i]),
284 : ItemPointerGetOffsetNumber(&items.iter_tids[i]),
285 : ItemPointerGetBlockNumber(&items.insert_tids[i]),
286 : ItemPointerGetOffsetNumber(&items.insert_tids[i]));
287 30552 : if (itemptr_cmp((const void *) &items.insert_tids[i], (const void *) &items.lookup_tids[i]) != 0)
288 0 : elog(ERROR, "TID lookup array doesn't match verification array, got (%u,%u) expected (%u,%u)",
289 : ItemPointerGetBlockNumber(&items.lookup_tids[i]),
290 : ItemPointerGetOffsetNumber(&items.lookup_tids[i]),
291 : ItemPointerGetBlockNumber(&items.insert_tids[i]),
292 : ItemPointerGetOffsetNumber(&items.insert_tids[i]));
293 : }
294 :
295 24 : PG_RETURN_VOID();
296 : }
297 :
298 : /*
299 : * In real world use, we care if the memory usage is greater than
300 : * some configured limit. Here we just want to verify that
301 : * TidStoreMemoryUsage is not broken.
302 : */
303 : Datum
304 4 : test_is_full(PG_FUNCTION_ARGS)
305 : {
306 : bool is_full;
307 :
308 4 : is_full = (TidStoreMemoryUsage(tidstore) > tidstore_empty_size);
309 :
310 4 : PG_RETURN_BOOL(is_full);
311 : }
312 :
313 : /* Free the tidstore */
314 : Datum
315 6 : test_destroy(PG_FUNCTION_ARGS)
316 : {
317 6 : TidStoreDestroy(tidstore);
318 6 : tidstore = NULL;
319 6 : items.num_tids = 0;
320 6 : pfree(items.insert_tids);
321 6 : pfree(items.lookup_tids);
322 6 : pfree(items.iter_tids);
323 :
324 6 : PG_RETURN_VOID();
325 : }
|