tdb2: allow multiple chain locks.
[ccan] / ccan / tdb2 / test / run-04-basichash.c
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>
10 #include "logging.h"
11
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)
14 {
15         return ((uint64_t)*(const unsigned int *)key)
16                 << (64 - TDB_TOPLEVEL_HASH_BITS - 1);
17 }
18
19 int main(int argc, char *argv[])
20 {
21         unsigned int i, j;
22         struct tdb_context *tdb;
23         unsigned int v;
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 },
28                                                 .fn = clash } };
29         int flags[] = { TDB_INTERNAL, TDB_DEFAULT, TDB_NOMMAP,
30                         TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT,
31                         TDB_NOMMAP|TDB_CONVERT,
32         };
33
34         hattr.base.next = &tap_log_attr;
35
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++) {
39                 struct hash_info h;
40                 tdb_off_t new_off, off, subhash;
41
42                 tdb = tdb_open("run-04-basichash.tdb", flags[i],
43                                O_RDWR|O_CREAT|O_TRUNC, 0600, &hattr);
44                 ok1(tdb);
45                 if (!tdb)
46                         continue;
47
48                 v = 0;
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);
58
59                 /* Should have lock on bucket 0 */
60                 ok1(h.hlock_start == 0);
61                 ok1(h.hlock_range == 
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 */
67
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));
72
73                 /* We should be able to add it now. */
74                 ok1(add_to_hash(tdb, &h, new_off) == 0);
75
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));
79                 off += key.dsize;
80                 ok1(!tdb->methods->twrite(tdb, off, dbuf.dptr, dbuf.dsize));
81
82                 /* We should be able to unlock that OK. */
83                 ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range,
84                                       F_WRLCK) == 0);
85
86                 /* Database should be consistent. */
87                 ok1(tdb_check(tdb, NULL, NULL) == 0);
88
89                 /* Now, this should give a successful lookup. */
90                 ok1(find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL)
91                     == new_off);
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);
99
100                 /* Should have lock on bucket 0 */
101                 ok1(h.hlock_start == 0);
102                 ok1(h.hlock_range == 
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 */
108
109                 ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range,
110                                       F_WRLCK) == 0);
111                 
112                 /* Database should be consistent. */
113                 ok1(tdb_check(tdb, NULL, NULL) == 0);
114
115                 /* Test expansion. */
116                 v = 1;
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);
125
126                 /* Should have lock on bucket 0 */
127                 ok1(h.hlock_start == 0);
128                 ok1(h.hlock_range == 
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 */
134
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);
142
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,
146                                       F_WRLCK) == 0);
147
148                 /* Should be happy with expansion. */
149                 ok1(tdb_check(tdb, NULL, NULL) == 0);
150
151                 /* Should be able to find it. */
152                 v = 0;
153                 ok1(find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL)
154                     == new_off);
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);
163
164                 /* Should have lock on bucket 0 */
165                 ok1(h.hlock_start == 0);
166                 ok1(h.hlock_range == 
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 */
172
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,
181                                       F_WRLCK) == 0);
182                 ok1(tdb_check(tdb, NULL, NULL) == 0);
183
184                 /* Test second-level expansion: should expand 0th bucket. */
185                 v = 0;
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);
194
195                 /* Should have lock on bucket 0 */
196                 ok1(h.hlock_start == 0);
197                 ok1(h.hlock_range == 
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 */
203
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,
213                                       F_WRLCK) == 0);
214
215                 /* Should be happy with expansion. */
216                 ok1(tdb_check(tdb, NULL, NULL) == 0);
217
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);
227
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);
234
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));
238                 off += key.dsize;
239                 ok1(!tdb->methods->twrite(tdb, off, dbuf.dptr, dbuf.dsize));
240
241                 /* We should be able to unlock that OK. */
242                 ok1(tdb_unlock_hashes(tdb, h.hlock_start, h.hlock_range,
243                                       F_WRLCK) == 0);
244
245                 /* Database should be consistent. */
246                 ok1(tdb_check(tdb, NULL, NULL) == 0);
247
248                 /* Should be able to find it. */
249                 v = 0;
250                 ok1(find_and_lock(tdb, key, F_WRLCK, &h, &rec, NULL)
251                     == new_off);
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);
260
261                 tdb_close(tdb);
262         }
263
264         ok1(tap_log_messages == 0);
265         return exit_status();
266 }