1 #include <ccan/tdb2/tdb.c>
2 #include <ccan/tdb2/open.c>
3 #include <ccan/tdb2/free.c>
4 #include <ccan/tdb2/lock.c>
5 #include <ccan/tdb2/io.c>
6 #include <ccan/tdb2/hash.c>
7 #include <ccan/tdb2/transaction.c>
8 #include <ccan/tdb2/check.c>
9 #include <ccan/tap/tap.h>
12 /* We rig the hash so adjacent-numbered records always clash. */
13 static uint64_t clash(const void *key, size_t len, uint64_t seed, void *priv)
15 return ((uint64_t)*(const unsigned int *)key)
16 << (64 - TDB_TOPLEVEL_HASH_BITS - 1);
19 int main(int argc, char *argv[])
22 struct tdb_context *tdb;
24 struct tdb_used_record rec;
25 struct tdb_data key = { (unsigned char *)&v, sizeof(v) };
26 struct tdb_data dbuf = { (unsigned char *)&v, sizeof(v) };
27 union tdb_attribute hattr = { .hash = { .base = { TDB_ATTRIBUTE_HASH },
29 int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP,
30 TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT,
31 TDB_NOMMAP|TDB_CONVERT,
34 hattr.base.next = &tap_log_attr;
36 plan_tests(sizeof(flags) / sizeof(flags[0])
37 * (91 + (2 * ((1 << TDB_HASH_GROUP_BITS) - 1))) + 1);
38 for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
40 tdb_off_t new_off, off, subhash;
42 tdb = tdb_open("run-04-basichash.tdb", flags[i],
43 O_RDWR|O_CREAT|O_TRUNC, 0600, &hattr);
49 /* Should not find it. */
50 ok1(find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL) == 0);
51 /* Should have created correct hash. */
52 ok1(h.h == tdb_hash(tdb, key.dptr, key.dsize));
53 /* Should have located space in group 0, bucket 0. */
54 ok1(h.group_start == offsetof(struct tdb_header, hashtable));
55 ok1(h.home_bucket == 0);
56 ok1(h.found_bucket == 0);
57 ok1(h.hash_used == TDB_TOPLEVEL_HASH_BITS);
59 /* Should have lock on bucket 0 */
60 ok1(h.hlock_start == 0);
62 1ULL << (64-(TDB_TOPLEVEL_HASH_BITS-TDB_HASH_GROUP_BITS)));
63 ok1((tdb->flags & TDB_NOLOCK) || tdb->file->num_lockrecs == 1);
64 ok1((tdb->flags & TDB_NOLOCK)
65 || tdb->file->lockrecs[0].off == TDB_HASH_LOCK_START);
66 /* FIXME: Check lock length */
68 /* Allocate a new record. */
69 new_off = alloc(tdb, key.dsize, dbuf.dsize, h.h,
70 TDB_USED_MAGIC, false);
71 ok1(!TDB_OFF_IS_ERR(new_off));
73 /* We should be able to add it now. */
74 ok1(add_to_hash(tdb, &h, new_off) == 0);
76 /* Make sure we fill it in for later finding. */
77 off = new_off + sizeof(struct tdb_used_record);
78 ok1(!tdb->methods->twrite(tdb, off, key.dptr, key.dsize));
80 ok1(!tdb->methods->twrite(tdb, off, dbuf.dptr, dbuf.dsize));
82 /* We should be able to unlock that OK. */
83 ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range,
86 /* Database should be consistent. */
87 ok1(tdb_check(tdb, NULL, NULL) == 0);
89 /* Now, this should give a successful lookup. */
90 ok1(find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL)
92 /* Should have created correct hash. */
93 ok1(h.h == tdb_hash(tdb, key.dptr, key.dsize));
94 /* Should have located space in group 0, bucket 0. */
95 ok1(h.group_start == offsetof(struct tdb_header, hashtable));
96 ok1(h.home_bucket == 0);
97 ok1(h.found_bucket == 0);
98 ok1(h.hash_used == TDB_TOPLEVEL_HASH_BITS);
100 /* Should have lock on bucket 0 */
101 ok1(h.hlock_start == 0);
103 1ULL << (64-(TDB_TOPLEVEL_HASH_BITS-TDB_HASH_GROUP_BITS)));
104 ok1((tdb->flags & TDB_NOLOCK) || tdb->file->num_lockrecs == 1);
105 ok1((tdb->flags & TDB_NOLOCK)
106 || tdb->file->lockrecs[0].off == TDB_HASH_LOCK_START);
107 /* FIXME: Check lock length */
109 ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range,
112 /* Database should be consistent. */
113 ok1(tdb_check(tdb, NULL, NULL) == 0);
115 /* Test expansion. */
117 ok1(find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL) == 0);
118 /* Should have created correct hash. */
119 ok1(h.h == tdb_hash(tdb, key.dptr, key.dsize));
120 /* Should have located space in group 0, bucket 1. */
121 ok1(h.group_start == offsetof(struct tdb_header, hashtable));
122 ok1(h.home_bucket == 0);
123 ok1(h.found_bucket == 1);
124 ok1(h.hash_used == TDB_TOPLEVEL_HASH_BITS);
126 /* Should have lock on bucket 0 */
127 ok1(h.hlock_start == 0);
129 1ULL << (64-(TDB_TOPLEVEL_HASH_BITS-TDB_HASH_GROUP_BITS)));
130 ok1((tdb->flags & TDB_NOLOCK) || tdb->file->num_lockrecs == 1);
131 ok1((tdb->flags & TDB_NOLOCK)
132 || tdb->file->lockrecs[0].off == TDB_HASH_LOCK_START);
133 /* FIXME: Check lock length */
135 /* Make it expand 0'th bucket. */
136 ok1(expand_group(tdb, &h) == 0);
137 /* First one should be subhash, next should be empty. */
138 ok1(is_subhash(h.group[0]));
139 subhash = (h.group[0] & TDB_OFF_MASK);
140 for (j = 1; j < (1 << TDB_HASH_GROUP_BITS); j++)
141 ok1(h.group[j] == 0);
143 ok1(tdb_write_convert(tdb, h.group_start,
144 h.group, sizeof(h.group)) == 0);
145 ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range,
148 /* Should be happy with expansion. */
149 ok1(tdb_check(tdb, NULL, NULL) == 0);
151 /* Should be able to find it. */
153 ok1(find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL)
155 /* Should have created correct hash. */
156 ok1(h.h == tdb_hash(tdb, key.dptr, key.dsize));
157 /* Should have located space in expanded group 0, bucket 0. */
158 ok1(h.group_start == subhash + sizeof(struct tdb_used_record));
159 ok1(h.home_bucket == 0);
160 ok1(h.found_bucket == 0);
161 ok1(h.hash_used == TDB_TOPLEVEL_HASH_BITS
162 + TDB_SUBLEVEL_HASH_BITS);
164 /* Should have lock on bucket 0 */
165 ok1(h.hlock_start == 0);
167 1ULL << (64-(TDB_TOPLEVEL_HASH_BITS-TDB_HASH_GROUP_BITS)));
168 ok1((tdb->flags & TDB_NOLOCK) || tdb->file->num_lockrecs == 1);
169 ok1((tdb->flags & TDB_NOLOCK)
170 || tdb->file->lockrecs[0].off == TDB_HASH_LOCK_START);
171 /* FIXME: Check lock length */
173 /* Simple delete should work. */
174 ok1(delete_from_hash(tdb, &h) == 0);
175 ok1(add_free_record(tdb, new_off,
176 sizeof(struct tdb_used_record)
177 + rec_key_length(&rec)
178 + rec_data_length(&rec)
179 + rec_extra_padding(&rec)) == 0);
180 ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range,
182 ok1(tdb_check(tdb, NULL, NULL) == 0);
184 /* Test second-level expansion: should expand 0th bucket. */
186 ok1(find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL) == 0);
187 /* Should have created correct hash. */
188 ok1(h.h == tdb_hash(tdb, key.dptr, key.dsize));
189 /* Should have located space in group 0, bucket 0. */
190 ok1(h.group_start == subhash + sizeof(struct tdb_used_record));
191 ok1(h.home_bucket == 0);
192 ok1(h.found_bucket == 0);
193 ok1(h.hash_used == TDB_TOPLEVEL_HASH_BITS+TDB_SUBLEVEL_HASH_BITS);
195 /* Should have lock on bucket 0 */
196 ok1(h.hlock_start == 0);
198 1ULL << (64-(TDB_TOPLEVEL_HASH_BITS-TDB_HASH_GROUP_BITS)));
199 ok1((tdb->flags & TDB_NOLOCK) || tdb->file->num_lockrecs == 1);
200 ok1((tdb->flags & TDB_NOLOCK)
201 || tdb->file->lockrecs[0].off == TDB_HASH_LOCK_START);
202 /* FIXME: Check lock length */
204 ok1(expand_group(tdb, &h) == 0);
205 /* First one should be subhash, next should be empty. */
206 ok1(is_subhash(h.group[0]));
207 subhash = (h.group[0] & TDB_OFF_MASK);
208 for (j = 1; j < (1 << TDB_HASH_GROUP_BITS); j++)
209 ok1(h.group[j] == 0);
210 ok1(tdb_write_convert(tdb, h.group_start,
211 h.group, sizeof(h.group)) == 0);
212 ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range,
215 /* Should be happy with expansion. */
216 ok1(tdb_check(tdb, NULL, NULL) == 0);
218 ok1(find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL) == 0);
219 /* Should have created correct hash. */
220 ok1(h.h == tdb_hash(tdb, key.dptr, key.dsize));
221 /* Should have located space in group 0, bucket 0. */
222 ok1(h.group_start == subhash + sizeof(struct tdb_used_record));
223 ok1(h.home_bucket == 0);
224 ok1(h.found_bucket == 0);
225 ok1(h.hash_used == TDB_TOPLEVEL_HASH_BITS
226 + TDB_SUBLEVEL_HASH_BITS * 2);
228 /* We should be able to add it now. */
229 /* Allocate a new record. */
230 new_off = alloc(tdb, key.dsize, dbuf.dsize, h.h,
231 TDB_USED_MAGIC, false);
232 ok1(!TDB_OFF_IS_ERR(new_off));
233 ok1(add_to_hash(tdb, &h, new_off) == 0);
235 /* Make sure we fill it in for later finding. */
236 off = new_off + sizeof(struct tdb_used_record);
237 ok1(!tdb->methods->twrite(tdb, off, key.dptr, key.dsize));
239 ok1(!tdb->methods->twrite(tdb, off, dbuf.dptr, dbuf.dsize));
241 /* We should be able to unlock that OK. */
242 ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range,
245 /* Database should be consistent. */
246 ok1(tdb_check(tdb, NULL, NULL) == 0);
248 /* Should be able to find it. */
250 ok1(find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL)
252 /* Should have created correct hash. */
253 ok1(h.h == tdb_hash(tdb, key.dptr, key.dsize));
254 /* Should have located space in expanded group 0, bucket 0. */
255 ok1(h.group_start == subhash + sizeof(struct tdb_used_record));
256 ok1(h.home_bucket == 0);
257 ok1(h.found_bucket == 0);
258 ok1(h.hash_used == TDB_TOPLEVEL_HASH_BITS
259 + TDB_SUBLEVEL_HASH_BITS * 2);
264 ok1(tap_log_messages == 0);
265 return exit_status();