reftable/reader: make table iterator reseekable

In 67ce50ba26 (Merge branch 'ps/reftable-reusable-iterator', 2024-05-30) we have refactored the interface of reftable iterators such that they can be reused in theory. This patch series only landed the required changes on the interface level, but didn't yet implement the actual logic to make iterators reusable. As it turns out almost all of the infrastructure already does support re-seeking. The only exception is the table iterator, which does not reset its `is_finished` bit. Do so and add a couple of tests that verify that we can re-seek iterators. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Sep 16, 2024 at 10:50 UTC 0a148a8eda7da6f7ff039ff2ea62b143ceb57f2f
4 files changed +174
Makefile
+1
@@ -1346,6 +1346,7 @@ UNIT_TEST_PROGRAMS += t-reftable-basics
1346 UNIT_TEST_PROGRAMS += t-reftable-block
1347 UNIT_TEST_PROGRAMS += t-reftable-merged
1348 UNIT_TEST_PROGRAMS += t-reftable-pq
1349 +UNIT_TEST_PROGRAMS += t-reftable-reader
1350 UNIT_TEST_PROGRAMS += t-reftable-readwrite
1351 UNIT_TEST_PROGRAMS += t-reftable-record
1352 UNIT_TEST_PROGRAMS += t-reftable-stack
reftable/reader.c
+1
@@ -328,6 +328,7 @@ static int table_iter_seek_to(struct table_iter *ti, uint64_t off, uint8_t typ)
328 ti->typ = block_reader_type(&ti->br);
329 ti->block_off = off;
330 block_iter_seek_start(&ti->bi, &ti->br);
331 + ti->is_finished = 0;
332 return 0;
333 }
334
t/unit-tests/t-reftable-merged.c
+76
@@ -194,6 +194,81 @@ static void t_merged_refs(void)
194 reftable_free(bs);
195 }
196
197 +static void t_merged_seek_multiple_times(void)
198 +{
199 + struct reftable_ref_record r1[] = {
200 + {
201 + .refname = (char *) "a",
202 + .update_index = 1,
203 + .value_type = REFTABLE_REF_VAL1,
204 + .value.val1 = { 1 },
205 + },
206 + {
207 + .refname = (char *) "c",
208 + .update_index = 1,
209 + .value_type = REFTABLE_REF_VAL1,
210 + .value.val1 = { 2 },
211 + }
212 + };
213 + struct reftable_ref_record r2[] = {
214 + {
215 + .refname = (char *) "b",
216 + .update_index = 2,
217 + .value_type = REFTABLE_REF_VAL1,
218 + .value.val1 = { 3 },
219 + },
220 + {
221 + .refname = (char *) "d",
222 + .update_index = 2,
223 + .value_type = REFTABLE_REF_VAL1,
224 + .value.val1 = { 4 },
225 + },
226 + };
227 + struct reftable_ref_record *refs[] = {
228 + r1, r2,
229 + };
230 + size_t sizes[] = {
231 + ARRAY_SIZE(r1), ARRAY_SIZE(r2),
232 + };
233 + struct strbuf bufs[] = {
234 + STRBUF_INIT, STRBUF_INIT,
235 + };
236 + struct reftable_block_source *sources = NULL;
237 + struct reftable_reader **readers = NULL;
238 + struct reftable_ref_record rec = { 0 };
239 + struct reftable_iterator it = { 0 };
240 + struct reftable_merged_table *mt;
241 +
242 + mt = merged_table_from_records(refs, &sources, &readers, sizes, bufs, 2);
243 + merged_table_init_iter(mt, &it, BLOCK_TYPE_REF);
244 +
245 + for (size_t i = 0; i < 5; i++) {
246 + int err = reftable_iterator_seek_ref(&it, "c");
247 + check(!err);
248 +
249 + err = reftable_iterator_next_ref(&it, &rec);
250 + check(!err);
251 + err = reftable_ref_record_equal(&rec, &r1[1], GIT_SHA1_RAWSZ);
252 + check(err == 1);
253 +
254 + err = reftable_iterator_next_ref(&it, &rec);
255 + check(!err);
256 + err = reftable_ref_record_equal(&rec, &r2[1], GIT_SHA1_RAWSZ);
257 + check(err == 1);
258 +
259 + err = reftable_iterator_next_ref(&it, &rec);
260 + check(err > 0);
261 + }
262 +
263 + for (size_t i = 0; i < ARRAY_SIZE(bufs); i++)
264 + strbuf_release(&bufs[i]);
265 + readers_destroy(readers, ARRAY_SIZE(refs));
266 + reftable_ref_record_release(&rec);
267 + reftable_iterator_destroy(&it);
268 + reftable_merged_table_free(mt);
269 + reftable_free(sources);
270 +}
271 +
272 static struct reftable_merged_table *
273 merged_table_from_log_records(struct reftable_log_record **logs,
274 struct reftable_block_source **source,
@@ -383,6 +458,7 @@ int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
458 TEST(t_default_write_opts(), "merged table with default write opts");
459 TEST(t_merged_logs(), "merged table with multiple log updates for same ref");
460 TEST(t_merged_refs(), "merged table with multiple updates to same ref");
461 + TEST(t_merged_seek_multiple_times(), "merged table can seek multiple times");
462 TEST(t_merged_single_record(), "ref ocurring in only one record can be fetched");
463
464 return test_done();
t/unit-tests/t-reftable-reader.c new
+96
@@ -0,0 +1,96 @@
1 +#include "test-lib.h"
2 +#include "lib-reftable.h"
3 +#include "reftable/blocksource.h"
4 +#include "reftable/reader.h"
5 +
6 +static int t_reader_seek_once(void)
7 +{
8 + struct reftable_ref_record records[] = {
9 + {
10 + .refname = (char *) "refs/heads/main",
11 + .value_type = REFTABLE_REF_VAL1,
12 + .value.val1 = { 42 },
13 + },
14 + };
15 + struct reftable_block_source source = { 0 };
16 + struct reftable_ref_record ref = { 0 };
17 + struct reftable_iterator it = { 0 };
18 + struct reftable_reader *reader;
19 + struct strbuf buf = STRBUF_INIT;
20 + int ret;
21 +
22 + t_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL);
23 + block_source_from_strbuf(&source, &buf);
24 +
25 + ret = reftable_reader_new(&reader, &source, "name");
26 + check(!ret);
27 +
28 + reftable_reader_init_ref_iterator(reader, &it);
29 + ret = reftable_iterator_seek_ref(&it, "");
30 + check(!ret);
31 + ret = reftable_iterator_next_ref(&it, &ref);
32 + check(!ret);
33 +
34 + ret = reftable_ref_record_equal(&ref, &records[0], GIT_SHA1_RAWSZ);
35 + check_int(ret, ==, 1);
36 +
37 + ret = reftable_iterator_next_ref(&it, &ref);
38 + check_int(ret, ==, 1);
39 +
40 + reftable_ref_record_release(&ref);
41 + reftable_iterator_destroy(&it);
42 + reftable_reader_decref(reader);
43 + strbuf_release(&buf);
44 + return 0;
45 +}
46 +
47 +static int t_reader_reseek(void)
48 +{
49 + struct reftable_ref_record records[] = {
50 + {
51 + .refname = (char *) "refs/heads/main",
52 + .value_type = REFTABLE_REF_VAL1,
53 + .value.val1 = { 42 },
54 + },
55 + };
56 + struct reftable_block_source source = { 0 };
57 + struct reftable_ref_record ref = { 0 };
58 + struct reftable_iterator it = { 0 };
59 + struct reftable_reader *reader;
60 + struct strbuf buf = STRBUF_INIT;
61 + int ret;
62 +
63 + t_reftable_write_to_buf(&buf, records, ARRAY_SIZE(records), NULL, 0, NULL);
64 + block_source_from_strbuf(&source, &buf);
65 +
66 + ret = reftable_reader_new(&reader, &source, "name");
67 + check(!ret);
68 +
69 + reftable_reader_init_ref_iterator(reader, &it);
70 +
71 + for (size_t i = 0; i < 5; i++) {
72 + ret = reftable_iterator_seek_ref(&it, "");
73 + check(!ret);
74 + ret = reftable_iterator_next_ref(&it, &ref);
75 + check(!ret);
76 +
77 + ret = reftable_ref_record_equal(&ref, &records[0], GIT_SHA1_RAWSZ);
78 + check_int(ret, ==, 1);
79 +
80 + ret = reftable_iterator_next_ref(&it, &ref);
81 + check_int(ret, ==, 1);
82 + }
83 +
84 + reftable_ref_record_release(&ref);
85 + reftable_iterator_destroy(&it);
86 + reftable_reader_decref(reader);
87 + strbuf_release(&buf);
88 + return 0;
89 +}
90 +
91 +int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
92 +{
93 + TEST(t_reader_seek_once(), "reader can seek once");
94 + TEST(t_reader_reseek(), "reader can reseek multiple times");
95 + return test_done();
96 +}