1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 // vim: ft=cpp:expandtab:ts=8:sw=4:softtabstop=4:
3 #ident "$Id$"
4 /*======
5 This file is part of PerconaFT.
6
7
8 Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved.
9
10 PerconaFT is free software: you can redistribute it and/or modify
11 it under the terms of the GNU General Public License, version 2,
12 as published by the Free Software Foundation.
13
14 PerconaFT is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with PerconaFT. If not, see <http://www.gnu.org/licenses/>.
21
22 ----------------------------------------
23
24 PerconaFT is free software: you can redistribute it and/or modify
25 it under the terms of the GNU Affero General Public License, version 3,
26 as published by the Free Software Foundation.
27
28 PerconaFT is distributed in the hope that it will be useful,
29 but WITHOUT ANY WARRANTY; without even the implied warranty of
30 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 GNU Affero General Public License for more details.
32
33 You should have received a copy of the GNU Affero General Public License
34 along with PerconaFT. If not, see <http://www.gnu.org/licenses/>.
35 ======= */
36
37 #ident "Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved."
38
39 #include "test.h"
40 #include <db.h>
41 #include <algorithm>
42
43 // Unit test for db->get_key_after_bytes.
44
45 static const int num_keys = 1<<10;
46
setup(DB_ENV ** envp,DB ** dbp,uint32_t nodesize,uint32_t basementnodesize)47 static void setup(DB_ENV **envp, DB **dbp, uint32_t nodesize, uint32_t basementnodesize) {
48 int r;
49 toku_os_recursive_delete(TOKU_TEST_FILENAME);
50 r = toku_os_mkdir(TOKU_TEST_FILENAME, S_IRWXU|S_IRWXG|S_IRWXO);
51 CKERR(r);
52 r = db_env_create(envp, 0);
53 CKERR(r);
54 DB_ENV *env = *envp;
55 r = env->set_default_bt_compare(env, int_dbt_cmp);
56 CKERR(r);
57 env->set_errfile(env, stderr);
58 r = env->open(env, TOKU_TEST_FILENAME, DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_MPOOL|DB_INIT_TXN|DB_CREATE|DB_PRIVATE, S_IRWXU|S_IRWXG|S_IRWXO);
59 CKERR(r);
60 r = db_create(dbp, env, 0);
61 CKERR(r);
62 DB *db = *dbp;
63 {
64 r = db->set_pagesize(db, nodesize);
65 CKERR(r);
66 r = db->set_readpagesize(db, basementnodesize);
67 CKERR(r);
68 DB_TXN *txn;
69 r = env->txn_begin(env, 0, &txn, 0);
70 CKERR(r);
71 r = db->open(db, txn, "foo.db", 0, DB_BTREE, DB_CREATE, S_IRWXU|S_IRWXG|S_IRWXO);
72 CKERR(r);
73 r = txn->commit(txn, 0);
74 CKERR(r);
75 }
76 }
77
fill(DB_ENV * env,DB * db)78 static void fill(DB_ENV *env, DB *db) {
79 int r;
80 DB_TXN *txn;
81 r = env->txn_begin(env, 0, &txn, 0);
82 CKERR(r);
83 int k, v;
84 DBT key, val;
85 dbt_init(&key, &k, sizeof k);
86 dbt_init(&val, &v, sizeof v);
87 for (int i = 0; i < num_keys; ++i) {
88 k = i;
89 v = i;
90 r = db->put(db, txn, &key, &val, 0);
91 CKERR(r);
92 }
93 r = txn->commit(txn, 0);
94 CKERR(r);
95 }
96
97 struct check_extra {
98 int start_key;
99 uint64_t skip_len;
100 bool filled;
101 bool exact;
102 };
103
check_callback(const DBT * end_key,uint64_t actually_skipped,void * extra)104 static void check_callback(const DBT *end_key, uint64_t actually_skipped, void *extra) {
105 struct check_extra *CAST_FROM_VOIDP(e, extra);
106
107 int real_start_key = std::min(std::max(e->start_key, 0), num_keys);
108 int expected_key = std::min(real_start_key + (e->skip_len / (2 * sizeof(int))), (uint64_t) num_keys);
109
110 if (e->exact) {
111 if (!e->filled || expected_key >= num_keys) {
112 expected_key = -1;
113 }
114 assert(actually_skipped <= e->skip_len);
115 if (expected_key == -1) {
116 assert(end_key == nullptr);
117 } else {
118 assert(e->skip_len - actually_skipped < 2 * (int) sizeof(int));
119 assert(end_key != nullptr);
120 assert(end_key->size == sizeof expected_key);
121 assert((*(int *) end_key->data) == expected_key);
122 }
123 } else {
124 // no sense in doing an inexact check if the table's empty
125 assert(e->filled);
126 int found;
127 if (end_key == nullptr) {
128 found = num_keys;
129 } else {
130 assert(end_key->size == sizeof found);
131 found = *(int *) end_key->data;
132 }
133 // These are just guesses. I don't have a good reason but they
134 // seem like alright bounds.
135 double skipped_portion = (double) e->skip_len / (num_keys * 2 * sizeof(int));
136 int key_slack = num_keys * std::max(std::min(skipped_portion, 0.25), 0.01);
137 int size_slack = key_slack * 2 * sizeof(int);
138 assert(found <= expected_key + key_slack);
139 assert(found >= expected_key - key_slack);
140 assert(actually_skipped <= e->skip_len + size_slack);
141 if (end_key != nullptr) {
142 // if we hit the end of the table, this definitely won't hold up
143 assert((int) actually_skipped >= (int) e->skip_len - size_slack);
144 }
145 }
146 }
147
check(DB_ENV * env,DB * db,int start_key,uint64_t skip_len,bool filled,bool exact)148 static void check(DB_ENV *env, DB *db, int start_key, uint64_t skip_len, bool filled, bool exact) {
149 int r;
150 DB_TXN *txn;
151 r = env->txn_begin(env, 0, &txn, 0);
152 CKERR(r);
153
154 DBT start_dbt, end_key;
155 dbt_init(&start_dbt, &start_key, sizeof start_key);
156 dbt_init(&end_key, nullptr, 0);
157
158 struct check_extra extra = {start_key, skip_len, filled, exact};
159 r = db->get_key_after_bytes(db, txn, (start_key == -2 ? nullptr : &start_dbt), skip_len, check_callback, &extra, 0);
160 CKERR(r);
161
162 r = txn->commit(txn, 0);
163 CKERR(r);
164 }
165
teardown(DB_ENV * env,DB * db)166 static void teardown(DB_ENV *env, DB *db) {
167 int r;
168 r = db->close(db, 0);
169 CKERR(r);
170 r = env->close(env, 0);
171 CKERR(r);
172 }
173
test_main(int argc,char * const argv[])174 int test_main(int argc, char * const argv[]) {
175 int r;
176 default_parse_args(argc, argv);
177
178 DB_ENV *env;
179 DB *db;
180
181 setup(&env, &db, 4<<20, 64<<10);
182
183 // if the table is empty, always say DB_NOTFOUND
184 for (int start_key = -2; start_key <= 1; ++start_key) {
185 for (int skip_len = 0; skip_len < 2; ++skip_len) {
186 check(env, db, start_key, skip_len, false, true);
187 }
188 }
189
190 fill(env, db);
191
192 // if start_key is bigger than any key, assert that we get DB_NOTFOUND
193 for (int extra_key = 0; extra_key < 10; extra_key += 5) {
194 for (int skip_len = 0; skip_len < 24; ++skip_len) {
195 check(env, db, num_keys + extra_key, skip_len, true, true);
196 }
197 }
198
199 // if start_key is nullptr or the first key or before the first key, we start at the beginning
200 for (int start_key = -2; start_key <= 0; ++start_key) {
201 for (int skip_len = 0; skip_len < 48; ++skip_len) {
202 check(env, db, start_key, skip_len, true, true);
203 }
204 }
205
206 // check a bunch of places in the middle too (use prime increments to get a good distribution of stuff)
207 for (int start_key = 0; start_key <= num_keys; start_key += 31) {
208 for (int skip_len = 0; skip_len < (num_keys + 1 - start_key) * (2 * (int) sizeof(int)); skip_len += 67) {
209 check(env, db, start_key, skip_len, true, true);
210 }
211 }
212
213 // TODO: test mvcc stuff (check that we only look at the latest val, which is the current behavior)
214
215 teardown(env, db);
216
217 // Try many bn and nodesizes
218 for (int basementnodesize = 1<<10; basementnodesize <= 64<<10; basementnodesize <<= 1) {
219 for (int nodesize = basementnodesize; nodesize <= 128<<10; nodesize <<= 2) {
220 setup(&env, &db, nodesize, basementnodesize);
221 fill(env, db);
222 // forces a rebalance of the root, to get multiple bns
223 r = env->txn_checkpoint(env, 0, 0, 0);
224 CKERR(r);
225 // near the beginning
226 for (int start_key = -2; start_key <= 1; ++start_key) {
227 for (int skip_len = 0; skip_len <= (num_keys + 1 - start_key) * (2 * (int) sizeof(int)); skip_len += 41) {
228 check(env, db, start_key, skip_len, true, false);
229 }
230 }
231 // near the end
232 for (int start_key = num_keys - 1; start_key <= num_keys + 1; ++start_key) {
233 for (int skip_len = 0; skip_len <= (num_keys + 1 - start_key) * (2 * (int) sizeof(int)); skip_len += 41) {
234 check(env, db, start_key, skip_len, true, false);
235 }
236 }
237 for (int start_key = 0; start_key <= num_keys; start_key += 17) {
238 for (int skip_len = 0; skip_len <= (num_keys + 1 - start_key) * (2 * (int) sizeof(int)); skip_len += 31) {
239 check(env, db, start_key, skip_len, true, false);
240 }
241 }
242 teardown(env, db);
243 }
244 }
245
246 return 0;
247 }
248