1 /* strings-table.c : operations on the `strings' table
3 * ====================================================================
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
20 * ====================================================================
23 #include "bdb_compat.h"
25 #include "svn_pools.h"
30 #include "../key-gen.h"
31 #include "../../libsvn_fs/fs-loader.h"
33 #include "strings-table.h"
35 #include "svn_private_config.h"
38 /*** Creating and opening the strings table. ***/
41 svn_fs_bdb__open_strings_table(DB **strings_p,
45 const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
48 BDB_ERR(svn_fs_bdb__check_version());
49 BDB_ERR(db_create(&strings, env, 0));
51 /* Enable duplicate keys. This allows the data to be spread out across
52 multiple records. Note: this must occur before ->open(). */
53 BDB_ERR(strings->set_flags(strings, DB_DUP));
55 BDB_ERR((strings->open)(SVN_BDB_OPEN_PARAMS(strings, NULL),
56 "strings", 0, DB_BTREE,
63 /* Create the `next-key' table entry. */
66 svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY),
67 svn_fs_base__str_to_dbt(&value, "0"), 0));
76 /*** Storing and retrieving strings. ***/
78 /* Allocate *CURSOR and advance it to first row in the set of rows
79 whose key is defined by QUERY. Set *LENGTH to the size of that
82 locate_key(apr_size_t *length,
89 base_fs_data_t *bfd = fs->fsap_data;
93 svn_fs_base__trail_debug(trail, "strings", "cursor");
94 SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"),
95 bfd->strings->cursor(bfd->strings, trail->db_txn,
98 /* Set up the DBT for reading the length of the record. */
99 svn_fs_base__clear_dbt(&result);
101 result.flags |= DB_DBT_USERMEM;
103 /* Advance the cursor to the key that we're looking for. */
104 db_err = svn_bdb_dbc_get(*cursor, query, &result, DB_SET);
106 /* We don't need to svn_fs_base__track_dbt() the result, because nothing
107 was allocated in it. */
109 /* If there's no such node, return an appropriately specific error. */
110 if (db_err == DB_NOTFOUND)
112 svn_bdb_dbc_close(*cursor);
113 return svn_error_createf
114 (SVN_ERR_FS_NO_SUCH_STRING, 0,
115 "No such string '%s'", (const char *)query->data);
121 if (db_err != SVN_BDB_DB_BUFFER_SMALL)
123 svn_bdb_dbc_close(*cursor);
124 return BDB_WRAP(fs, N_("moving cursor"), db_err);
127 /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a
128 zero length buf), so we need to re-run the operation to make
130 svn_fs_base__clear_dbt(&rerun);
131 rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL;
132 db_err = svn_bdb_dbc_get(*cursor, query, &rerun, DB_SET);
135 svn_bdb_dbc_close(*cursor);
136 return BDB_WRAP(fs, N_("rerunning cursor move"), db_err);
140 /* ### this cast might not be safe? */
141 *length = (apr_size_t) result.size;
147 /* Advance CURSOR by a single row in the set of rows whose keys match
148 CURSOR's current location. Set *LENGTH to the size of that next
149 row. If any error occurs, CURSOR will be destroyed. */
151 get_next_length(apr_size_t *length, DBC *cursor, DBT *query)
156 /* Set up the DBT for reading the length of the record. */
157 svn_fs_base__clear_dbt(&result);
159 result.flags |= DB_DBT_USERMEM;
161 /* Note: this may change the QUERY DBT, but that's okay: we're going
162 to be sticking with the same key anyways. */
163 db_err = svn_bdb_dbc_get(cursor, query, &result, DB_NEXT_DUP);
165 /* Note that we exit on DB_NOTFOUND. The caller uses that to end a loop. */
170 if (db_err != SVN_BDB_DB_BUFFER_SMALL)
172 svn_bdb_dbc_close(cursor);
176 /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a
177 zero length buf), so we need to re-run the operation to make
179 svn_fs_base__clear_dbt(&rerun);
180 rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL;
181 db_err = svn_bdb_dbc_get(cursor, query, &rerun, DB_NEXT_DUP);
183 svn_bdb_dbc_close(cursor);
186 /* ### this cast might not be safe? */
187 *length = (apr_size_t) result.size;
193 svn_fs_bdb__string_read(svn_fs_t *fs,
196 svn_filesize_t offset,
204 apr_size_t length, bytes_read = 0;
206 svn_fs_base__str_to_dbt(&query, key);
208 SVN_ERR(locate_key(&length, &cursor, &query, fs, trail, pool));
210 /* Seek through the records for this key, trying to find the record that
211 includes OFFSET. Note that we don't require reading from more than
212 one record since we're allowed to return partial reads. */
213 while (length <= offset)
217 /* Remember, if any error happens, our cursor has been closed
219 db_err = get_next_length(&length, cursor, &query);
221 /* No more records? They tried to read past the end. */
222 if (db_err == DB_NOTFOUND)
228 return BDB_WRAP(fs, N_("reading string"), db_err);
231 /* The current record contains OFFSET. Fetch the contents now. Note that
232 OFFSET has been moved to be relative to this record. The length could
233 quite easily extend past this record, so we use DB_DBT_PARTIAL and
234 read successive records until we've filled the request. */
237 svn_fs_base__clear_dbt(&result);
238 result.data = buf + bytes_read;
239 result.ulen = *len - bytes_read;
240 result.doff = (u_int32_t)offset;
241 result.dlen = *len - bytes_read;
242 result.flags |= (DB_DBT_USERMEM | DB_DBT_PARTIAL);
243 db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_CURRENT);
246 svn_bdb_dbc_close(cursor);
247 return BDB_WRAP(fs, N_("reading string"), db_err);
250 bytes_read += result.size;
251 if (bytes_read == *len)
253 /* Done with the cursor. */
254 SVN_ERR(BDB_WRAP(fs, N_("closing string-reading cursor"),
255 svn_bdb_dbc_close(cursor)));
259 /* Remember, if any error happens, our cursor has been closed
261 db_err = get_next_length(&length, cursor, &query);
262 if (db_err == DB_NOTFOUND)
265 return BDB_WRAP(fs, N_("reading string"), db_err);
267 /* We'll be reading from the beginning of the next record */
276 /* Get the current 'next-key' value and bump the record. */
278 get_key_and_bump(svn_fs_t *fs,
283 base_fs_data_t *bfd = fs->fsap_data;
285 char next_key[MAX_KEY_SIZE];
291 /* ### todo: see issue #409 for why bumping the key as part of this
292 trail is problematic. */
294 /* Open a cursor and move it to the 'next-key' value. We can then fetch
295 the contents and use the cursor to overwrite those contents. Since
296 this database allows duplicates, we can't do an arbitrary 'put' to
297 write the new value -- that would append, not overwrite. */
299 svn_fs_base__trail_debug(trail, "strings", "cursor");
300 SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"),
301 bfd->strings->cursor(bfd->strings, trail->db_txn,
304 /* Advance the cursor to 'next-key' and read it. */
306 db_err = svn_bdb_dbc_get(cursor,
307 svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY),
308 svn_fs_base__result_dbt(&result),
312 svn_bdb_dbc_close(cursor);
313 return BDB_WRAP(fs, N_("getting next-key value"), db_err);
316 svn_fs_base__track_dbt(&result, pool);
317 *key = apr_pstrmemdup(pool, result.data, result.size);
319 /* Bump to future key. */
320 key_len = result.size;
321 svn_fs_base__next_key(result.data, &key_len, next_key);
323 /* Shove the new key back into the database, at the cursor position. */
324 db_err = svn_bdb_dbc_put(cursor, &query,
325 svn_fs_base__str_to_dbt(&result, next_key),
329 svn_bdb_dbc_close(cursor); /* ignore the error, the original is
331 return BDB_WRAP(fs, N_("bumping next string key"), db_err);
334 return BDB_WRAP(fs, N_("closing string-reading cursor"),
335 svn_bdb_dbc_close(cursor));
339 svn_fs_bdb__string_append(svn_fs_t *fs,
346 base_fs_data_t *bfd = fs->fsap_data;
349 /* If the passed-in key is NULL, we graciously generate a new string
350 using the value of the `next-key' record in the strings table. */
353 SVN_ERR(get_key_and_bump(fs, key, trail, pool));
356 /* Store a new record into the database. */
357 svn_fs_base__trail_debug(trail, "strings", "put");
358 return BDB_WRAP(fs, N_("appending string"),
360 (bfd->strings, trail->db_txn,
361 svn_fs_base__str_to_dbt(&query, *key),
362 svn_fs_base__set_dbt(&result, buf, len),
368 svn_fs_bdb__string_clear(svn_fs_t *fs,
373 base_fs_data_t *bfd = fs->fsap_data;
377 svn_fs_base__str_to_dbt(&query, key);
379 /* Torch the prior contents */
380 svn_fs_base__trail_debug(trail, "strings", "del");
381 db_err = bfd->strings->del(bfd->strings, trail->db_txn, &query, 0);
383 /* If there's no such node, return an appropriately specific error. */
384 if (db_err == DB_NOTFOUND)
385 return svn_error_createf
386 (SVN_ERR_FS_NO_SUCH_STRING, 0,
387 "No such string '%s'", key);
389 /* Handle any other error conditions. */
390 SVN_ERR(BDB_WRAP(fs, N_("clearing string"), db_err));
392 /* Shove empty data back in for this key. */
393 svn_fs_base__clear_dbt(&result);
396 result.flags |= DB_DBT_USERMEM;
398 svn_fs_base__trail_debug(trail, "strings", "put");
399 return BDB_WRAP(fs, N_("storing empty contents"),
400 bfd->strings->put(bfd->strings, trail->db_txn,
401 &query, &result, 0));
406 svn_fs_bdb__string_size(svn_filesize_t *size,
416 svn_filesize_t total;
418 svn_fs_base__str_to_dbt(&query, key);
420 SVN_ERR(locate_key(&length, &cursor, &query, fs, trail, pool));
425 /* Remember, if any error happens, our cursor has been closed
427 db_err = get_next_length(&length, cursor, &query);
429 /* No more records? Then return the total length. */
430 if (db_err == DB_NOTFOUND)
436 return BDB_WRAP(fs, N_("fetching string length"), db_err);
446 svn_fs_bdb__string_delete(svn_fs_t *fs,
451 base_fs_data_t *bfd = fs->fsap_data;
455 svn_fs_base__trail_debug(trail, "strings", "del");
456 db_err = bfd->strings->del(bfd->strings, trail->db_txn,
457 svn_fs_base__str_to_dbt(&query, key), 0);
459 /* If there's no such node, return an appropriately specific error. */
460 if (db_err == DB_NOTFOUND)
461 return svn_error_createf
462 (SVN_ERR_FS_NO_SUCH_STRING, 0,
463 "No such string '%s'", key);
465 /* Handle any other error conditions. */
466 return BDB_WRAP(fs, N_("deleting string"), db_err);
471 svn_fs_bdb__string_copy(svn_fs_t *fs,
472 const char **new_key,
477 base_fs_data_t *bfd = fs->fsap_data;
484 /* Copy off the old key in case the caller is sharing storage
485 between the old and new keys. */
486 const char *old_key = apr_pstrdup(pool, key);
488 SVN_ERR(get_key_and_bump(fs, new_key, trail, pool));
490 svn_fs_base__trail_debug(trail, "strings", "cursor");
491 SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"),
492 bfd->strings->cursor(bfd->strings, trail->db_txn,
495 svn_fs_base__str_to_dbt(&query, old_key);
496 svn_fs_base__str_to_dbt(©key, *new_key);
498 svn_fs_base__clear_dbt(&result);
500 /* Move to the first record and fetch its data (under BDB's mem mgmt). */
501 db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET);
504 svn_bdb_dbc_close(cursor);
505 return BDB_WRAP(fs, N_("getting next-key value"), db_err);
510 /* ### can we pass a BDB-provided buffer to another BDB function?
511 ### they are supposed to have a duration up to certain points
512 ### of calling back into BDB, but I'm not sure what the exact
513 ### rules are. it is definitely nicer to use BDB buffers here
514 ### to simplify things and reduce copies, but... hrm.
517 /* Write the data to the database */
518 svn_fs_base__trail_debug(trail, "strings", "put");
519 db_err = bfd->strings->put(bfd->strings, trail->db_txn,
520 ©key, &result, 0);
523 svn_bdb_dbc_close(cursor);
524 return BDB_WRAP(fs, N_("writing copied data"), db_err);
527 /* Read the next chunk. Terminate loop if we're done. */
528 svn_fs_base__clear_dbt(&result);
529 db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP);
530 if (db_err == DB_NOTFOUND)
534 svn_bdb_dbc_close(cursor);
535 return BDB_WRAP(fs, N_("fetching string data for a copy"), db_err);
539 return BDB_WRAP(fs, N_("closing string-reading cursor"),
540 svn_bdb_dbc_close(cursor));