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