reftable: add merged table view

This adds an abstract, read-only interface to the ref database. This primitive is used to construct the read view of the ref database (the read view is constructed by merging several *.ref files). It also provides the mechanism to provide a unified view of the refs in the main repository and the per-worktree refs. Signed-off-by: Han-Wen Nienhuys <hanwen@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Han-Wen Nienhuys committed Oct 7, 2021 at 20:25 UTC 1ae2b8cda84c4c662d8f60898e034d9643f097b6
6 files changed +943
Makefile
+2
@@ -2462,6 +2462,7 @@ REFTABLE_OBJS += reftable/block.o
2462 REFTABLE_OBJS += reftable/blocksource.o
2463 REFTABLE_OBJS += reftable/iter.o
2464 REFTABLE_OBJS += reftable/publicbasics.o
2465 +REFTABLE_OBJS += reftable/merged.o
2466 REFTABLE_OBJS += reftable/pq.o
2467 REFTABLE_OBJS += reftable/reader.o
2468 REFTABLE_OBJS += reftable/record.o
@@ -2473,6 +2474,7 @@ REFTABLE_OBJS += reftable/writer.o
2474
2475 REFTABLE_TEST_OBJS += reftable/basics_test.o
2476 REFTABLE_TEST_OBJS += reftable/block_test.o
2477 +REFTABLE_TEST_OBJS += reftable/merged_test.o
2478 REFTABLE_TEST_OBJS += reftable/pq_test.o
2479 REFTABLE_TEST_OBJS += reftable/record_test.o
2480 REFTABLE_TEST_OBJS += reftable/readwrite_test.o
reftable/merged.c new
+362
@@ -0,0 +1,362 @@
1 +/*
2 +Copyright 2020 Google LLC
3 +
4 +Use of this source code is governed by a BSD-style
5 +license that can be found in the LICENSE file or at
6 +https://developers.google.com/open-source/licenses/bsd
7 +*/
8 +
9 +#include "merged.h"
10 +
11 +#include "constants.h"
12 +#include "iter.h"
13 +#include "pq.h"
14 +#include "reader.h"
15 +#include "record.h"
16 +#include "generic.h"
17 +#include "reftable-merged.h"
18 +#include "reftable-error.h"
19 +#include "system.h"
20 +
21 +static int merged_iter_init(struct merged_iter *mi)
22 +{
23 + int i = 0;
24 + for (i = 0; i < mi->stack_len; i++) {
25 + struct reftable_record rec = reftable_new_record(mi->typ);
26 + int err = iterator_next(&mi->stack[i], &rec);
27 + if (err < 0) {
28 + return err;
29 + }
30 +
31 + if (err > 0) {
32 + reftable_iterator_destroy(&mi->stack[i]);
33 + reftable_record_destroy(&rec);
34 + } else {
35 + struct pq_entry e = {
36 + .rec = rec,
37 + .index = i,
38 + };
39 + merged_iter_pqueue_add(&mi->pq, e);
40 + }
41 + }
42 +
43 + return 0;
44 +}
45 +
46 +static void merged_iter_close(void *p)
47 +{
48 + struct merged_iter *mi = p;
49 + int i = 0;
50 + merged_iter_pqueue_release(&mi->pq);
51 + for (i = 0; i < mi->stack_len; i++) {
52 + reftable_iterator_destroy(&mi->stack[i]);
53 + }
54 + reftable_free(mi->stack);
55 +}
56 +
57 +static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi,
58 + size_t idx)
59 +{
60 + struct reftable_record rec = reftable_new_record(mi->typ);
61 + struct pq_entry e = {
62 + .rec = rec,
63 + .index = idx,
64 + };
65 + int err = iterator_next(&mi->stack[idx], &rec);
66 + if (err < 0)
67 + return err;
68 +
69 + if (err > 0) {
70 + reftable_iterator_destroy(&mi->stack[idx]);
71 + reftable_record_destroy(&rec);
72 + return 0;
73 + }
74 +
75 + merged_iter_pqueue_add(&mi->pq, e);
76 + return 0;
77 +}
78 +
79 +static int merged_iter_advance_subiter(struct merged_iter *mi, size_t idx)
80 +{
81 + if (iterator_is_null(&mi->stack[idx]))
82 + return 0;
83 + return merged_iter_advance_nonnull_subiter(mi, idx);
84 +}
85 +
86 +static int merged_iter_next_entry(struct merged_iter *mi,
87 + struct reftable_record *rec)
88 +{
89 + struct strbuf entry_key = STRBUF_INIT;
90 + struct pq_entry entry = { 0 };
91 + int err = 0;
92 +
93 + if (merged_iter_pqueue_is_empty(mi->pq))
94 + return 1;
95 +
96 + entry = merged_iter_pqueue_remove(&mi->pq);
97 + err = merged_iter_advance_subiter(mi, entry.index);
98 + if (err < 0)
99 + return err;
100 +
101 + /*
102 + One can also use reftable as datacenter-local storage, where the ref
103 + database is maintained in globally consistent database (eg.
104 + CockroachDB or Spanner). In this scenario, replication delays together
105 + with compaction may cause newer tables to contain older entries. In
106 + such a deployment, the loop below must be changed to collect all
107 + entries for the same key, and return new the newest one.
108 + */
109 + reftable_record_key(&entry.rec, &entry_key);
110 + while (!merged_iter_pqueue_is_empty(mi->pq)) {
111 + struct pq_entry top = merged_iter_pqueue_top(mi->pq);
112 + struct strbuf k = STRBUF_INIT;
113 + int err = 0, cmp = 0;
114 +
115 + reftable_record_key(&top.rec, &k);
116 +
117 + cmp = strbuf_cmp(&k, &entry_key);
118 + strbuf_release(&k);
119 +
120 + if (cmp > 0) {
121 + break;
122 + }
123 +
124 + merged_iter_pqueue_remove(&mi->pq);
125 + err = merged_iter_advance_subiter(mi, top.index);
126 + if (err < 0) {
127 + return err;
128 + }
129 + reftable_record_destroy(&top.rec);
130 + }
131 +
132 + reftable_record_copy_from(rec, &entry.rec, hash_size(mi->hash_id));
133 + reftable_record_destroy(&entry.rec);
134 + strbuf_release(&entry_key);
135 + return 0;
136 +}
137 +
138 +static int merged_iter_next(struct merged_iter *mi, struct reftable_record *rec)
139 +{
140 + while (1) {
141 + int err = merged_iter_next_entry(mi, rec);
142 + if (err == 0 && mi->suppress_deletions &&
143 + reftable_record_is_deletion(rec)) {
144 + continue;
145 + }
146 +
147 + return err;
148 + }
149 +}
150 +
151 +static int merged_iter_next_void(void *p, struct reftable_record *rec)
152 +{
153 + struct merged_iter *mi = p;
154 + if (merged_iter_pqueue_is_empty(mi->pq))
155 + return 1;
156 +
157 + return merged_iter_next(mi, rec);
158 +}
159 +
160 +static struct reftable_iterator_vtable merged_iter_vtable = {
161 + .next = &merged_iter_next_void,
162 + .close = &merged_iter_close,
163 +};
164 +
165 +static void iterator_from_merged_iter(struct reftable_iterator *it,
166 + struct merged_iter *mi)
167 +{
168 + assert(!it->ops);
169 + it->iter_arg = mi;
170 + it->ops = &merged_iter_vtable;
171 +}
172 +
173 +int reftable_new_merged_table(struct reftable_merged_table **dest,
174 + struct reftable_table *stack, int n,
175 + uint32_t hash_id)
176 +{
177 + struct reftable_merged_table *m = NULL;
178 + uint64_t last_max = 0;
179 + uint64_t first_min = 0;
180 + int i = 0;
181 + for (i = 0; i < n; i++) {
182 + uint64_t min = reftable_table_min_update_index(&stack[i]);
183 + uint64_t max = reftable_table_max_update_index(&stack[i]);
184 +
185 + if (reftable_table_hash_id(&stack[i]) != hash_id) {
186 + return REFTABLE_FORMAT_ERROR;
187 + }
188 + if (i == 0 || min < first_min) {
189 + first_min = min;
190 + }
191 + if (i == 0 || max > last_max) {
192 + last_max = max;
193 + }
194 + }
195 +
196 + m = reftable_calloc(sizeof(struct reftable_merged_table));
197 + m->stack = stack;
198 + m->stack_len = n;
199 + m->min = first_min;
200 + m->max = last_max;
201 + m->hash_id = hash_id;
202 + *dest = m;
203 + return 0;
204 +}
205 +
206 +/* clears the list of subtable, without affecting the readers themselves. */
207 +void merged_table_release(struct reftable_merged_table *mt)
208 +{
209 + FREE_AND_NULL(mt->stack);
210 + mt->stack_len = 0;
211 +}
212 +
213 +void reftable_merged_table_free(struct reftable_merged_table *mt)
214 +{
215 + if (!mt) {
216 + return;
217 + }
218 + merged_table_release(mt);
219 + reftable_free(mt);
220 +}
221 +
222 +uint64_t
223 +reftable_merged_table_max_update_index(struct reftable_merged_table *mt)
224 +{
225 + return mt->max;
226 +}
227 +
228 +uint64_t
229 +reftable_merged_table_min_update_index(struct reftable_merged_table *mt)
230 +{
231 + return mt->min;
232 +}
233 +
234 +static int reftable_table_seek_record(struct reftable_table *tab,
235 + struct reftable_iterator *it,
236 + struct reftable_record *rec)
237 +{
238 + return tab->ops->seek_record(tab->table_arg, it, rec);
239 +}
240 +
241 +static int merged_table_seek_record(struct reftable_merged_table *mt,
242 + struct reftable_iterator *it,
243 + struct reftable_record *rec)
244 +{
245 + struct reftable_iterator *iters = reftable_calloc(
246 + sizeof(struct reftable_iterator) * mt->stack_len);
247 + struct merged_iter merged = {
248 + .stack = iters,
249 + .typ = reftable_record_type(rec),
250 + .hash_id = mt->hash_id,
251 + .suppress_deletions = mt->suppress_deletions,
252 + };
253 + int n = 0;
254 + int err = 0;
255 + int i = 0;
256 + for (i = 0; i < mt->stack_len && err == 0; i++) {
257 + int e = reftable_table_seek_record(&mt->stack[i], &iters[n],
258 + rec);
259 + if (e < 0) {
260 + err = e;
261 + }
262 + if (e == 0) {
263 + n++;
264 + }
265 + }
266 + if (err < 0) {
267 + int i = 0;
268 + for (i = 0; i < n; i++) {
269 + reftable_iterator_destroy(&iters[i]);
270 + }
271 + reftable_free(iters);
272 + return err;
273 + }
274 +
275 + merged.stack_len = n;
276 + err = merged_iter_init(&merged);
277 + if (err < 0) {
278 + merged_iter_close(&merged);
279 + return err;
280 + } else {
281 + struct merged_iter *p =
282 + reftable_malloc(sizeof(struct merged_iter));
283 + *p = merged;
284 + iterator_from_merged_iter(it, p);
285 + }
286 + return 0;
287 +}
288 +
289 +int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,
290 + struct reftable_iterator *it,
291 + const char *name)
292 +{
293 + struct reftable_ref_record ref = {
294 + .refname = (char *)name,
295 + };
296 + struct reftable_record rec = { NULL };
297 + reftable_record_from_ref(&rec, &ref);
298 + return merged_table_seek_record(mt, it, &rec);
299 +}
300 +
301 +int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,
302 + struct reftable_iterator *it,
303 + const char *name, uint64_t update_index)
304 +{
305 + struct reftable_log_record log = {
306 + .refname = (char *)name,
307 + .update_index = update_index,
308 + };
309 + struct reftable_record rec = { NULL };
310 + reftable_record_from_log(&rec, &log);
311 + return merged_table_seek_record(mt, it, &rec);
312 +}
313 +
314 +int reftable_merged_table_seek_log(struct reftable_merged_table *mt,
315 + struct reftable_iterator *it,
316 + const char *name)
317 +{
318 + uint64_t max = ~((uint64_t)0);
319 + return reftable_merged_table_seek_log_at(mt, it, name, max);
320 +}
321 +
322 +uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *mt)
323 +{
324 + return mt->hash_id;
325 +}
326 +
327 +static int reftable_merged_table_seek_void(void *tab,
328 + struct reftable_iterator *it,
329 + struct reftable_record *rec)
330 +{
331 + return merged_table_seek_record(tab, it, rec);
332 +}
333 +
334 +static uint32_t reftable_merged_table_hash_id_void(void *tab)
335 +{
336 + return reftable_merged_table_hash_id(tab);
337 +}
338 +
339 +static uint64_t reftable_merged_table_min_update_index_void(void *tab)
340 +{
341 + return reftable_merged_table_min_update_index(tab);
342 +}
343 +
344 +static uint64_t reftable_merged_table_max_update_index_void(void *tab)
345 +{
346 + return reftable_merged_table_max_update_index(tab);
347 +}
348 +
349 +static struct reftable_table_vtable merged_table_vtable = {
350 + .seek_record = reftable_merged_table_seek_void,
351 + .hash_id = reftable_merged_table_hash_id_void,
352 + .min_update_index = reftable_merged_table_min_update_index_void,
353 + .max_update_index = reftable_merged_table_max_update_index_void,
354 +};
355 +
356 +void reftable_table_from_merged_table(struct reftable_table *tab,
357 + struct reftable_merged_table *merged)
358 +{
359 + assert(!tab->ops);
360 + tab->ops = &merged_table_vtable;
361 + tab->table_arg = merged;
362 +}
reftable/merged.h new
+38
@@ -0,0 +1,38 @@
1 +/*
2 +Copyright 2020 Google LLC
3 +
4 +Use of this source code is governed by a BSD-style
5 +license that can be found in the LICENSE file or at
6 +https://developers.google.com/open-source/licenses/bsd
7 +*/
8 +
9 +#ifndef MERGED_H
10 +#define MERGED_H
11 +
12 +#include "pq.h"
13 +
14 +struct reftable_merged_table {
15 + struct reftable_table *stack;
16 + size_t stack_len;
17 + uint32_t hash_id;
18 +
19 + /* If unset, produce deletions. This is useful for compaction. For the
20 + * full stack, deletions should be produced. */
21 + int suppress_deletions;
22 +
23 + uint64_t min;
24 + uint64_t max;
25 +};
26 +
27 +struct merged_iter {
28 + struct reftable_iterator *stack;
29 + uint32_t hash_id;
30 + size_t stack_len;
31 + uint8_t typ;
32 + int suppress_deletions;
33 + struct merged_iter_pqueue pq;
34 +};
35 +
36 +void merged_table_release(struct reftable_merged_table *mt);
37 +
38 +#endif
reftable/merged_test.c new
+468
@@ -0,0 +1,468 @@
1 +/*
2 +Copyright 2020 Google LLC
3 +
4 +Use of this source code is governed by a BSD-style
5 +license that can be found in the LICENSE file or at
6 +https://developers.google.com/open-source/licenses/bsd
7 +*/
8 +
9 +#include "merged.h"
10 +
11 +#include "system.h"
12 +
13 +#include "basics.h"
14 +#include "blocksource.h"
15 +#include "constants.h"
16 +#include "reader.h"
17 +#include "record.h"
18 +#include "test_framework.h"
19 +#include "reftable-merged.h"
20 +#include "reftable-tests.h"
21 +#include "reftable-generic.h"
22 +#include "reftable-writer.h"
23 +
24 +static void write_test_table(struct strbuf *buf,
25 + struct reftable_ref_record refs[], int n)
26 +{
27 + int min = 0xffffffff;
28 + int max = 0;
29 + int i = 0;
30 + int err;
31 +
32 + struct reftable_write_options opts = {
33 + .block_size = 256,
34 + };
35 + struct reftable_writer *w = NULL;
36 + for (i = 0; i < n; i++) {
37 + uint64_t ui = refs[i].update_index;
38 + if (ui > max) {
39 + max = ui;
40 + }
41 + if (ui < min) {
42 + min = ui;
43 + }
44 + }
45 +
46 + w = reftable_new_writer(&strbuf_add_void, buf, &opts);
47 + reftable_writer_set_limits(w, min, max);
48 +
49 + for (i = 0; i < n; i++) {
50 + uint64_t before = refs[i].update_index;
51 + int n = reftable_writer_add_ref(w, &refs[i]);
52 + EXPECT(n == 0);
53 + EXPECT(before == refs[i].update_index);
54 + }
55 +
56 + err = reftable_writer_close(w);
57 + EXPECT_ERR(err);
58 +
59 + reftable_writer_free(w);
60 +}
61 +
62 +static void write_test_log_table(struct strbuf *buf,
63 + struct reftable_log_record logs[], int n,
64 + uint64_t update_index)
65 +{
66 + int i = 0;
67 + int err;
68 +
69 + struct reftable_write_options opts = {
70 + .block_size = 256,
71 + .exact_log_message = 1,
72 + };
73 + struct reftable_writer *w = NULL;
74 + w = reftable_new_writer(&strbuf_add_void, buf, &opts);
75 + reftable_writer_set_limits(w, update_index, update_index);
76 +
77 + for (i = 0; i < n; i++) {
78 + int err = reftable_writer_add_log(w, &logs[i]);
79 + EXPECT_ERR(err);
80 + }
81 +
82 + err = reftable_writer_close(w);
83 + EXPECT_ERR(err);
84 +
85 + reftable_writer_free(w);
86 +}
87 +
88 +static struct reftable_merged_table *
89 +merged_table_from_records(struct reftable_ref_record **refs,
90 + struct reftable_block_source **source,
91 + struct reftable_reader ***readers, int *sizes,
92 + struct strbuf *buf, int n)
93 +{
94 + int i = 0;
95 + struct reftable_merged_table *mt = NULL;
96 + int err;
97 + struct reftable_table *tabs =
98 + reftable_calloc(n * sizeof(struct reftable_table));
99 + *readers = reftable_calloc(n * sizeof(struct reftable_reader *));
100 + *source = reftable_calloc(n * sizeof(**source));
101 + for (i = 0; i < n; i++) {
102 + write_test_table(&buf[i], refs[i], sizes[i]);
103 + block_source_from_strbuf(&(*source)[i], &buf[i]);
104 +
105 + err = reftable_new_reader(&(*readers)[i], &(*source)[i],
106 + "name");
107 + EXPECT_ERR(err);
108 + reftable_table_from_reader(&tabs[i], (*readers)[i]);
109 + }
110 +
111 + err = reftable_new_merged_table(&mt, tabs, n, GIT_SHA1_FORMAT_ID);
112 + EXPECT_ERR(err);
113 + return mt;
114 +}
115 +
116 +static void readers_destroy(struct reftable_reader **readers, size_t n)
117 +{
118 + int i = 0;
119 + for (; i < n; i++)
120 + reftable_reader_free(readers[i]);
121 + reftable_free(readers);
122 +}
123 +
124 +static void test_merged_between(void)
125 +{
126 + uint8_t hash1[GIT_SHA1_RAWSZ] = { 1, 2, 3, 0 };
127 +
128 + struct reftable_ref_record r1[] = { {
129 + .refname = "b",
130 + .update_index = 1,
131 + .value_type = REFTABLE_REF_VAL1,
132 + .value.val1 = hash1,
133 + } };
134 + struct reftable_ref_record r2[] = { {
135 + .refname = "a",
136 + .update_index = 2,
137 + .value_type = REFTABLE_REF_DELETION,
138 + } };
139 +
140 + struct reftable_ref_record *refs[] = { r1, r2 };
141 + int sizes[] = { 1, 1 };
142 + struct strbuf bufs[2] = { STRBUF_INIT, STRBUF_INIT };
143 + struct reftable_block_source *bs = NULL;
144 + struct reftable_reader **readers = NULL;
145 + struct reftable_merged_table *mt =
146 + merged_table_from_records(refs, &bs, &readers, sizes, bufs, 2);
147 + int i;
148 + struct reftable_ref_record ref = { NULL };
149 + struct reftable_iterator it = { NULL };
150 + int err = reftable_merged_table_seek_ref(mt, &it, "a");
151 + EXPECT_ERR(err);
152 +
153 + err = reftable_iterator_next_ref(&it, &ref);
154 + EXPECT_ERR(err);
155 + EXPECT(ref.update_index == 2);
156 + reftable_ref_record_release(&ref);
157 + reftable_iterator_destroy(&it);
158 + readers_destroy(readers, 2);
159 + reftable_merged_table_free(mt);
160 + for (i = 0; i < ARRAY_SIZE(bufs); i++) {
161 + strbuf_release(&bufs[i]);
162 + }
163 + reftable_free(bs);
164 +}
165 +
166 +static void test_merged(void)
167 +{
168 + uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };
169 + uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };
170 + struct reftable_ref_record r1[] = {
171 + {
172 + .refname = "a",
173 + .update_index = 1,
174 + .value_type = REFTABLE_REF_VAL1,
175 + .value.val1 = hash1,
176 + },
177 + {
178 + .refname = "b",
179 + .update_index = 1,
180 + .value_type = REFTABLE_REF_VAL1,
181 + .value.val1 = hash1,
182 + },
183 + {
184 + .refname = "c",
185 + .update_index = 1,
186 + .value_type = REFTABLE_REF_VAL1,
187 + .value.val1 = hash1,
188 + }
189 + };
190 + struct reftable_ref_record r2[] = { {
191 + .refname = "a",
192 + .update_index = 2,
193 + .value_type = REFTABLE_REF_DELETION,
194 + } };
195 + struct reftable_ref_record r3[] = {
196 + {
197 + .refname = "c",
198 + .update_index = 3,
199 + .value_type = REFTABLE_REF_VAL1,
200 + .value.val1 = hash2,
201 + },
202 + {
203 + .refname = "d",
204 + .update_index = 3,
205 + .value_type = REFTABLE_REF_VAL1,
206 + .value.val1 = hash1,
207 + },
208 + };
209 +
210 + struct reftable_ref_record want[] = {
211 + r2[0],
212 + r1[1],
213 + r3[0],
214 + r3[1],
215 + };
216 +
217 + struct reftable_ref_record *refs[] = { r1, r2, r3 };
218 + int sizes[3] = { 3, 1, 2 };
219 + struct strbuf bufs[3] = { STRBUF_INIT, STRBUF_INIT, STRBUF_INIT };
220 + struct reftable_block_source *bs = NULL;
221 + struct reftable_reader **readers = NULL;
222 + struct reftable_merged_table *mt =
223 + merged_table_from_records(refs, &bs, &readers, sizes, bufs, 3);
224 +
225 + struct reftable_iterator it = { NULL };
226 + int err = reftable_merged_table_seek_ref(mt, &it, "a");
227 + struct reftable_ref_record *out = NULL;
228 + size_t len = 0;
229 + size_t cap = 0;
230 + int i = 0;
231 +
232 + EXPECT_ERR(err);
233 + EXPECT(reftable_merged_table_hash_id(mt) == GIT_SHA1_FORMAT_ID);
234 + EXPECT(reftable_merged_table_min_update_index(mt) == 1);
235 +
236 + while (len < 100) { /* cap loops/recursion. */
237 + struct reftable_ref_record ref = { NULL };
238 + int err = reftable_iterator_next_ref(&it, &ref);
239 + if (err > 0) {
240 + break;
241 + }
242 + if (len == cap) {
243 + cap = 2 * cap + 1;
244 + out = reftable_realloc(
245 + out, sizeof(struct reftable_ref_record) * cap);
246 + }
247 + out[len++] = ref;
248 + }
249 + reftable_iterator_destroy(&it);
250 +
251 + EXPECT(ARRAY_SIZE(want) == len);
252 + for (i = 0; i < len; i++) {
253 + EXPECT(reftable_ref_record_equal(&want[i], &out[i],
254 + GIT_SHA1_RAWSZ));
255 + }
256 + for (i = 0; i < len; i++) {
257 + reftable_ref_record_release(&out[i]);
258 + }
259 + reftable_free(out);
260 +
261 + for (i = 0; i < 3; i++) {
262 + strbuf_release(&bufs[i]);
263 + }
264 + readers_destroy(readers, 3);
265 + reftable_merged_table_free(mt);
266 + reftable_free(bs);
267 +}
268 +
269 +static struct reftable_merged_table *
270 +merged_table_from_log_records(struct reftable_log_record **logs,
271 + struct reftable_block_source **source,
272 + struct reftable_reader ***readers, int *sizes,
273 + struct strbuf *buf, int n)
274 +{
275 + int i = 0;
276 + struct reftable_merged_table *mt = NULL;
277 + int err;
278 + struct reftable_table *tabs =
279 + reftable_calloc(n * sizeof(struct reftable_table));
280 + *readers = reftable_calloc(n * sizeof(struct reftable_reader *));
281 + *source = reftable_calloc(n * sizeof(**source));
282 + for (i = 0; i < n; i++) {
283 + write_test_log_table(&buf[i], logs[i], sizes[i], i + 1);
284 + block_source_from_strbuf(&(*source)[i], &buf[i]);
285 +
286 + err = reftable_new_reader(&(*readers)[i], &(*source)[i],
287 + "name");
288 + EXPECT_ERR(err);
289 + reftable_table_from_reader(&tabs[i], (*readers)[i]);
290 + }
291 +
292 + err = reftable_new_merged_table(&mt, tabs, n, GIT_SHA1_FORMAT_ID);
293 + EXPECT_ERR(err);
294 + return mt;
295 +}
296 +
297 +static void test_merged_logs(void)
298 +{
299 + uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };
300 + uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };
301 + uint8_t hash3[GIT_SHA1_RAWSZ] = { 3 };
302 + struct reftable_log_record r1[] = {
303 + {
304 + .refname = "a",
305 + .update_index = 2,
306 + .value_type = REFTABLE_LOG_UPDATE,
307 + .value.update = {
308 + .old_hash = hash2,
309 + /* deletion */
310 + .name = "jane doe",
311 + .email = "jane@invalid",
312 + .message = "message2",
313 + }
314 + },
315 + {
316 + .refname = "a",
317 + .update_index = 1,
318 + .value_type = REFTABLE_LOG_UPDATE,
319 + .value.update = {
320 + .old_hash = hash1,
321 + .new_hash = hash2,
322 + .name = "jane doe",
323 + .email = "jane@invalid",
324 + .message = "message1",
325 + }
326 + },
327 + };
328 + struct reftable_log_record r2[] = {
329 + {
330 + .refname = "a",
331 + .update_index = 3,
332 + .value_type = REFTABLE_LOG_UPDATE,
333 + .value.update = {
334 + .new_hash = hash3,
335 + .name = "jane doe",
336 + .email = "jane@invalid",
337 + .message = "message3",
338 + }
339 + },
340 + };
341 + struct reftable_log_record r3[] = {
342 + {
343 + .refname = "a",
344 + .update_index = 2,
345 + .value_type = REFTABLE_LOG_DELETION,
346 + },
347 + };
348 + struct reftable_log_record want[] = {
349 + r2[0],
350 + r3[0],
351 + r1[1],
352 + };
353 +
354 + struct reftable_log_record *logs[] = { r1, r2, r3 };
355 + int sizes[3] = { 2, 1, 1 };
356 + struct strbuf bufs[3] = { STRBUF_INIT, STRBUF_INIT, STRBUF_INIT };
357 + struct reftable_block_source *bs = NULL;
358 + struct reftable_reader **readers = NULL;
359 + struct reftable_merged_table *mt = merged_table_from_log_records(
360 + logs, &bs, &readers, sizes, bufs, 3);
361 +
362 + struct reftable_iterator it = { NULL };
363 + int err = reftable_merged_table_seek_log(mt, &it, "a");
364 + struct reftable_log_record *out = NULL;
365 + size_t len = 0;
366 + size_t cap = 0;
367 + int i = 0;
368 +
369 + EXPECT_ERR(err);
370 + EXPECT(reftable_merged_table_hash_id(mt) == GIT_SHA1_FORMAT_ID);
371 + EXPECT(reftable_merged_table_min_update_index(mt) == 1);
372 +
373 + while (len < 100) { /* cap loops/recursion. */
374 + struct reftable_log_record log = { NULL };
375 + int err = reftable_iterator_next_log(&it, &log);
376 + if (err > 0) {
377 + break;
378 + }
379 + if (len == cap) {
380 + cap = 2 * cap + 1;
381 + out = reftable_realloc(
382 + out, sizeof(struct reftable_log_record) * cap);
383 + }
384 + out[len++] = log;
385 + }
386 + reftable_iterator_destroy(&it);
387 +
388 + EXPECT(ARRAY_SIZE(want) == len);
389 + for (i = 0; i < len; i++) {
390 + EXPECT(reftable_log_record_equal(&want[i], &out[i],
391 + GIT_SHA1_RAWSZ));
392 + }
393 +
394 + err = reftable_merged_table_seek_log_at(mt, &it, "a", 2);
395 + EXPECT_ERR(err);
396 + reftable_log_record_release(&out[0]);
397 + err = reftable_iterator_next_log(&it, &out[0]);
398 + EXPECT_ERR(err);
399 + EXPECT(reftable_log_record_equal(&out[0], &r3[0], GIT_SHA1_RAWSZ));
400 + reftable_iterator_destroy(&it);
401 +
402 + for (i = 0; i < len; i++) {
403 + reftable_log_record_release(&out[i]);
404 + }
405 + reftable_free(out);
406 +
407 + for (i = 0; i < 3; i++) {
408 + strbuf_release(&bufs[i]);
409 + }
410 + readers_destroy(readers, 3);
411 + reftable_merged_table_free(mt);
412 + reftable_free(bs);
413 +}
414 +
415 +static void test_default_write_opts(void)
416 +{
417 + struct reftable_write_options opts = { 0 };
418 + struct strbuf buf = STRBUF_INIT;
419 + struct reftable_writer *w =
420 + reftable_new_writer(&strbuf_add_void, &buf, &opts);
421 +
422 + struct reftable_ref_record rec = {
423 + .refname = "master",
424 + .update_index = 1,
425 + };
426 + int err;
427 + struct reftable_block_source source = { NULL };
428 + struct reftable_table *tab = reftable_calloc(sizeof(*tab) * 1);
429 + uint32_t hash_id;
430 + struct reftable_reader *rd = NULL;
431 + struct reftable_merged_table *merged = NULL;
432 +
433 + reftable_writer_set_limits(w, 1, 1);
434 +
435 + err = reftable_writer_add_ref(w, &rec);
436 + EXPECT_ERR(err);
437 +
438 + err = reftable_writer_close(w);
439 + EXPECT_ERR(err);
440 + reftable_writer_free(w);
441 +
442 + block_source_from_strbuf(&source, &buf);
443 +
444 + err = reftable_new_reader(&rd, &source, "filename");
445 + EXPECT_ERR(err);
446 +
447 + hash_id = reftable_reader_hash_id(rd);
448 + EXPECT(hash_id == GIT_SHA1_FORMAT_ID);
449 +
450 + reftable_table_from_reader(&tab[0], rd);
451 + err = reftable_new_merged_table(&merged, tab, 1, GIT_SHA1_FORMAT_ID);
452 + EXPECT_ERR(err);
453 +
454 + reftable_reader_free(rd);
455 + reftable_merged_table_free(merged);
456 + strbuf_release(&buf);
457 +}
458 +
459 +/* XXX test refs_for(oid) */
460 +
461 +int merged_test_main(int argc, const char *argv[])
462 +{
463 + RUN_TEST(test_merged_logs);
464 + RUN_TEST(test_merged_between);
465 + RUN_TEST(test_merged);
466 + RUN_TEST(test_default_write_opts);
467 + return 0;
468 +}
reftable/reftable-merged.h new
+72
@@ -0,0 +1,72 @@
1 +/*
2 +Copyright 2020 Google LLC
3 +
4 +Use of this source code is governed by a BSD-style
5 +license that can be found in the LICENSE file or at
6 +https://developers.google.com/open-source/licenses/bsd
7 +*/
8 +
9 +#ifndef REFTABLE_MERGED_H
10 +#define REFTABLE_MERGED_H
11 +
12 +#include "reftable-iterator.h"
13 +
14 +/*
15 + * Merged tables
16 + *
17 + * A ref database kept in a sequence of table files. The merged_table presents a
18 + * unified view to reading (seeking, iterating) a sequence of immutable tables.
19 + *
20 + * The merged tables are on purpose kept disconnected from their actual storage
21 + * (eg. files on disk), because it is useful to merge tables aren't files. For
22 + * example, the per-workspace and global ref namespace can be implemented as a
23 + * merged table of two stacks of file-backed reftables.
24 + */
25 +
26 +/* A merged table is implements seeking/iterating over a stack of tables. */
27 +struct reftable_merged_table;
28 +
29 +/* A generic reftable; see below. */
30 +struct reftable_table;
31 +
32 +/* reftable_new_merged_table creates a new merged table. It takes ownership of
33 + the stack array.
34 +*/
35 +int reftable_new_merged_table(struct reftable_merged_table **dest,
36 + struct reftable_table *stack, int n,
37 + uint32_t hash_id);
38 +
39 +/* returns an iterator positioned just before 'name' */
40 +int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,
41 + struct reftable_iterator *it,
42 + const char *name);
43 +
44 +/* returns an iterator for log entry, at given update_index */
45 +int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,
46 + struct reftable_iterator *it,
47 + const char *name, uint64_t update_index);
48 +
49 +/* like reftable_merged_table_seek_log_at but look for the newest entry. */
50 +int reftable_merged_table_seek_log(struct reftable_merged_table *mt,
51 + struct reftable_iterator *it,
52 + const char *name);
53 +
54 +/* returns the max update_index covered by this merged table. */
55 +uint64_t
56 +reftable_merged_table_max_update_index(struct reftable_merged_table *mt);
57 +
58 +/* returns the min update_index covered by this merged table. */
59 +uint64_t
60 +reftable_merged_table_min_update_index(struct reftable_merged_table *mt);
61 +
62 +/* releases memory for the merged_table */
63 +void reftable_merged_table_free(struct reftable_merged_table *m);
64 +
65 +/* return the hash ID of the merged table. */
66 +uint32_t reftable_merged_table_hash_id(struct reftable_merged_table *m);
67 +
68 +/* create a generic table from reftable_merged_table */
69 +void reftable_table_from_merged_table(struct reftable_table *tab,
70 + struct reftable_merged_table *table);
71 +
72 +#endif
t/helper/test-reftable.c
+1
@@ -5,6 +5,7 @@ int cmd__reftable(int argc, const char **argv)
5 {
6 basics_test_main(argc, argv);
7 block_test_main(argc, argv);
8 + merged_test_main(argc, argv);
9 pq_test_main(argc, argv);
10 record_test_main(argc, argv);
11 readwrite_test_main(argc, argv);