reftable/table: introduce iterator for table blocks

Introduce a new iterator that allows the caller to iterate through all blocks contained in a table. This gives users more fine-grained control over how exactly those blocks are being read and exposes information to callers that was previously inaccessible. This iterator will be required by a future patch series that adds consistency checks for the reftable backend. In addition to that though we will also reimplement `reftable_table_print_blocks()` on top of this new iterator in a subsequent commit. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Apr 7, 2025 at 15:16 UTC da89659365181045b13df924de668764abf59444
3 files changed +173
reftable/reftable-table.h
+16
@@ -10,6 +10,7 @@
10 #define REFTABLE_TABLE_H
11
12 #include "reftable-iterator.h"
13 +#include "reftable-block.h"
14 #include "reftable-blocksource.h"
15
16 /*
@@ -99,4 +100,19 @@ uint64_t reftable_table_min_update_index(struct reftable_table *t);
100 /* print blocks onto stdout for debugging. */
101 int reftable_table_print_blocks(const char *tablename);
102
103 +/*
104 + * An iterator that iterates through the blocks contained in a given table.
105 + */
106 +struct reftable_table_iterator {
107 + void *iter_arg;
108 +};
109 +
110 +int reftable_table_iterator_init(struct reftable_table_iterator *it,
111 + struct reftable_table *t);
112 +
113 +void reftable_table_iterator_release(struct reftable_table_iterator *it);
114 +
115 +int reftable_table_iterator_next(struct reftable_table_iterator *it,
116 + const struct reftable_block **out);
117 +
118 #endif
reftable/table.c
+47
@@ -804,3 +804,50 @@ done:
804 table_iter_close(&ti);
805 return err;
806 }
807 +
808 +int reftable_table_iterator_init(struct reftable_table_iterator *it,
809 + struct reftable_table *t)
810 +{
811 + struct table_iter *ti;
812 + int err;
813 +
814 + REFTABLE_ALLOC_ARRAY(ti, 1);
815 + if (!ti)
816 + return REFTABLE_OUT_OF_MEMORY_ERROR;
817 +
818 + err = table_iter_init(ti, t);
819 + if (err < 0)
820 + goto out;
821 +
822 + it->iter_arg = ti;
823 + err = 0;
824 +
825 +out:
826 + if (err < 0)
827 + reftable_free(ti);
828 + return err;
829 +}
830 +
831 +void reftable_table_iterator_release(struct reftable_table_iterator *it)
832 +{
833 + if (!it->iter_arg)
834 + return;
835 + table_iter_close(it->iter_arg);
836 + reftable_free(it->iter_arg);
837 + it->iter_arg = NULL;
838 +}
839 +
840 +int reftable_table_iterator_next(struct reftable_table_iterator *it,
841 + const struct reftable_block **out)
842 +{
843 + struct table_iter *ti = it->iter_arg;
844 + int err;
845 +
846 + err = table_iter_next_block(ti);
847 + if (err)
848 + return err;
849 +
850 + *out = &ti->block;
851 +
852 + return 0;
853 +}
t/unit-tests/t-reftable-table.c
+110
@@ -1,7 +1,10 @@
1 #include "test-lib.h"
2 #include "lib-reftable.h"
3 #include "reftable/blocksource.h"
4 +#include "reftable/constants.h"
5 +#include "reftable/iter.h"
6 #include "reftable/table.h"
7 +#include "strbuf.h"
8
9 static int t_table_seek_once(void)
10 {
@@ -88,9 +91,116 @@ static int t_table_reseek(void)
91 return 0;
92 }
93
94 +static int t_table_block_iterator(void)
95 +{
96 + struct reftable_block_source source = { 0 };
97 + struct reftable_table_iterator it = { 0 };
98 + struct reftable_ref_record *records;
99 + const struct reftable_block *block;
100 + struct reftable_table *table;
101 + struct reftable_buf buf = REFTABLE_BUF_INIT;
102 + struct {
103 + uint8_t block_type;
104 + uint16_t header_off;
105 + uint16_t restart_count;
106 + uint16_t record_count;
107 + } expected_blocks[] = {
108 + {
109 + .block_type = BLOCK_TYPE_REF,
110 + .header_off = 24,
111 + .restart_count = 10,
112 + .record_count = 158,
113 + },
114 + {
115 + .block_type = BLOCK_TYPE_REF,
116 + .restart_count = 10,
117 + .record_count = 159,
118 + },
119 + {
120 + .block_type = BLOCK_TYPE_REF,
121 + .restart_count = 10,
122 + .record_count = 159,
123 + },
124 + {
125 + .block_type = BLOCK_TYPE_REF,
126 + .restart_count = 2,
127 + .record_count = 24,
128 + },
129 + {
130 + .block_type = BLOCK_TYPE_INDEX,
131 + .restart_count = 1,
132 + .record_count = 4,
133 + },
134 + {
135 + .block_type = BLOCK_TYPE_OBJ,
136 + .restart_count = 1,
137 + .record_count = 1,
138 + },
139 + };
140 + const size_t nrecords = 500;
141 + int ret;
142 +
143 + REFTABLE_CALLOC_ARRAY(records, nrecords);
144 + for (size_t i = 0; i < nrecords; i++) {
145 + records[i].value_type = REFTABLE_REF_VAL1;
146 + records[i].refname = xstrfmt("refs/heads/branch-%03"PRIuMAX,
147 + (uintmax_t) i);
148 + }
149 +
150 + t_reftable_write_to_buf(&buf, records, nrecords, NULL, 0, NULL);
151 + block_source_from_buf(&source, &buf);
152 +
153 + ret = reftable_table_new(&table, &source, "name");
154 + check(!ret);
155 +
156 + ret = reftable_table_iterator_init(&it, table);
157 + check(!ret);
158 +
159 + for (size_t i = 0; i < ARRAY_SIZE(expected_blocks); i++) {
160 + struct reftable_iterator record_it = { 0 };
161 + struct reftable_record record = {
162 + .type = expected_blocks[i].block_type,
163 + };
164 +
165 + ret = reftable_table_iterator_next(&it, &block);
166 + check(!ret);
167 +
168 + check_int(block->block_type, ==, expected_blocks[i].block_type);
169 + check_int(block->header_off, ==, expected_blocks[i].header_off);
170 + check_int(block->restart_count, ==, expected_blocks[i].restart_count);
171 +
172 + ret = reftable_block_init_iterator(block, &record_it);
173 + check(!ret);
174 +
175 + for (size_t j = 0; ; j++) {
176 + ret = iterator_next(&record_it, &record);
177 + if (ret > 0) {
178 + check_int(j, ==, expected_blocks[i].record_count);
179 + break;
180 + }
181 + check(!ret);
182 + }
183 +
184 + reftable_iterator_destroy(&record_it);
185 + reftable_record_release(&record);
186 + }
187 +
188 + ret = reftable_table_iterator_next(&it, &block);
189 + check_int(ret, ==, 1);
190 +
191 + for (size_t i = 0; i < nrecords; i++)
192 + reftable_free(records[i].refname);
193 + reftable_table_iterator_release(&it);
194 + reftable_table_decref(table);
195 + reftable_buf_release(&buf);
196 + reftable_free(records);
197 + return 0;
198 +}
199 +
200 int cmd_main(int argc UNUSED, const char *argv[] UNUSED)
201 {
202 TEST(t_table_seek_once(), "table can seek once");
203 TEST(t_table_reseek(), "table can reseek multiple times");
204 + TEST(t_table_block_iterator(), "table can iterate through blocks");
205 return test_done();
206 }