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