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