1
/* strings-table.c : operations on the `strings' table
3
* ====================================================================
4
* Copyright (c) 2000-2004 CollabNet. All rights reserved.
6
* This software is licensed as described in the file COPYING, which
7
* you should have received as part of this distribution. The terms
8
* are also available at http://subversion.tigris.org/license-1.html.
9
* If newer versions of this license are posted there, you may use a
10
* newer version instead, at your option.
12
* This software consists of voluntary contributions made by many
13
* individuals. For exact contribution history, see the revision
14
* history and logs, available at http://subversion.tigris.org/.
15
* ====================================================================
18
#include "bdb_compat.h"
20
#include "svn_pools.h"
25
#include "../key-gen.h"
26
#include "../../libsvn_fs/fs-loader.h"
28
#include "strings-table.h"
30
#include "svn_private_config.h"
33
/*** Creating and opening the strings table. ***/
36
svn_fs_bdb__open_strings_table (DB **strings_p,
40
const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
43
BDB_ERR (svn_fs_bdb__check_version());
44
BDB_ERR (db_create (&strings, env, 0));
46
/* Enable duplicate keys. This allows the data to be spread out across
47
multiple records. Note: this must occur before ->open(). */
48
BDB_ERR (strings->set_flags (strings, DB_DUP));
50
BDB_ERR (strings->open (SVN_BDB_OPEN_PARAMS(strings, NULL),
51
"strings", 0, DB_BTREE,
52
open_flags | SVN_BDB_AUTO_COMMIT,
59
/* Create the `next-key' table entry. */
62
svn_fs_base__str_to_dbt (&key, NEXT_KEY_KEY),
63
svn_fs_base__str_to_dbt (&value, "0"),
64
SVN_BDB_AUTO_COMMIT));
73
/*** Storing and retrieving strings. ***/
75
/* Allocate *CURSOR and advance it to first row in the set of rows
76
whose key is defined by QUERY. Set *LENGTH to the size of that
79
locate_key (apr_size_t *length,
86
base_fs_data_t *bfd = fs->fsap_data;
90
svn_fs_base__trail_debug (trail, "strings", "cursor");
91
SVN_ERR (BDB_WRAP (fs, _("creating cursor for reading a string"),
92
bfd->strings->cursor (bfd->strings, trail->db_txn,
95
/* Set up the DBT for reading the length of the record. */
96
svn_fs_base__clear_dbt (&result);
98
result.flags |= DB_DBT_USERMEM;
100
/* Advance the cursor to the key that we're looking for. */
101
db_err = (*cursor)->c_get (*cursor, query, &result, DB_SET);
103
/* We don't need to svn_fs_base__track_dbt() the result, because nothing
104
was allocated in it. */
106
/* If there's no such node, return an appropriately specific error. */
107
if (db_err == DB_NOTFOUND)
109
(*cursor)->c_close (*cursor);
110
return svn_error_createf
111
(SVN_ERR_FS_NO_SUCH_STRING, 0,
112
"No such string '%s'", (const char *)query->data);
118
if (db_err != SVN_BDB_DB_BUFFER_SMALL)
120
(*cursor)->c_close (*cursor);
121
return BDB_WRAP (fs, "moving cursor", db_err);
124
/* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a
125
zero length buf), so we need to re-run the operation to make
127
svn_fs_base__clear_dbt (&rerun);
128
rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL;
129
db_err = (*cursor)->c_get (*cursor, query, &rerun, DB_SET);
132
(*cursor)->c_close (*cursor);
133
return BDB_WRAP (fs, "rerunning cursor move", db_err);
137
/* ### this cast might not be safe? */
138
*length = (apr_size_t) result.size;
144
/* Advance CURSOR by a single row in the set of rows whose keys match
145
CURSOR's current location. Set *LENGTH to the size of that next
146
row. If any error occurs, CURSOR will be destroyed. */
148
get_next_length (apr_size_t *length, DBC *cursor, DBT *query)
153
/* Set up the DBT for reading the length of the record. */
154
svn_fs_base__clear_dbt (&result);
156
result.flags |= DB_DBT_USERMEM;
158
/* Note: this may change the QUERY DBT, but that's okay: we're going
159
to be sticking with the same key anyways. */
160
db_err = cursor->c_get (cursor, query, &result, DB_NEXT_DUP);
162
/* Note that we exit on DB_NOTFOUND. The caller uses that to end a loop. */
167
if (db_err != SVN_BDB_DB_BUFFER_SMALL)
169
cursor->c_close (cursor);
173
/* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a
174
zero length buf), so we need to re-run the operation to make
176
svn_fs_base__clear_dbt (&rerun);
177
rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL;
178
db_err = cursor->c_get (cursor, query, &rerun, DB_NEXT_DUP);
180
cursor->c_close (cursor);
183
/* ### this cast might not be safe? */
184
*length = (apr_size_t) result.size;
190
svn_fs_bdb__string_read (svn_fs_t *fs,
193
svn_filesize_t offset,
201
apr_size_t length, bytes_read = 0;
203
svn_fs_base__str_to_dbt (&query, key);
205
SVN_ERR (locate_key (&length, &cursor, &query, fs, trail, pool));
207
/* Seek through the records for this key, trying to find the record that
208
includes OFFSET. Note that we don't require reading from more than
209
one record since we're allowed to return partial reads. */
210
while (length <= offset)
214
/* Remember, if any error happens, our cursor has been closed
216
db_err = get_next_length (&length, cursor, &query);
218
/* No more records? They tried to read past the end. */
219
if (db_err == DB_NOTFOUND)
225
return BDB_WRAP (fs, "reading string", db_err);
228
/* The current record contains OFFSET. Fetch the contents now. Note that
229
OFFSET has been moved to be relative to this record. The length could
230
quite easily extend past this record, so we use DB_DBT_PARTIAL and
231
read successive records until we've filled the request. */
234
svn_fs_base__clear_dbt (&result);
235
result.data = buf + bytes_read;
236
result.ulen = *len - bytes_read;
237
result.doff = (u_int32_t)offset;
238
result.dlen = *len - bytes_read;
239
result.flags |= (DB_DBT_USERMEM | DB_DBT_PARTIAL);
240
db_err = cursor->c_get (cursor, &query, &result, DB_CURRENT);
243
cursor->c_close (cursor);
244
return BDB_WRAP (fs, "reading string", db_err);
247
bytes_read += result.size;
248
if (bytes_read == *len)
250
/* Done with the cursor. */
251
SVN_ERR (BDB_WRAP (fs, "closing string-reading cursor",
252
cursor->c_close (cursor)));
256
/* Remember, if any error happens, our cursor has been closed
258
db_err = get_next_length (&length, cursor, &query);
259
if (db_err == DB_NOTFOUND)
262
return BDB_WRAP (fs, "reading string", db_err);
264
/* We'll be reading from the beginning of the next record */
273
/* Get the current 'next-key' value and bump the record. */
275
get_key_and_bump (svn_fs_t *fs,
280
base_fs_data_t *bfd = fs->fsap_data;
282
char next_key[MAX_KEY_SIZE];
288
/* ### todo: see issue #409 for why bumping the key as part of this
289
trail is problematic. */
291
/* Open a cursor and move it to the 'next-key' value. We can then fetch
292
the contents and use the cursor to overwrite those contents. Since
293
this database allows duplicates, we can't do an arbitrary 'put' to
294
write the new value -- that would append, not overwrite. */
296
svn_fs_base__trail_debug (trail, "strings", "cursor");
297
SVN_ERR (BDB_WRAP (fs, "creating cursor for reading a string",
298
bfd->strings->cursor (bfd->strings, trail->db_txn,
301
/* Advance the cursor to 'next-key' and read it. */
303
db_err = cursor->c_get (cursor,
304
svn_fs_base__str_to_dbt (&query, NEXT_KEY_KEY),
305
svn_fs_base__result_dbt (&result),
309
cursor->c_close (cursor);
310
return BDB_WRAP (fs, "getting next-key value", db_err);
313
svn_fs_base__track_dbt (&result, pool);
314
*key = apr_pstrmemdup (pool, result.data, result.size);
316
/* Bump to future key. */
317
key_len = result.size;
318
svn_fs_base__next_key (result.data, &key_len, next_key);
320
/* Shove the new key back into the database, at the cursor position. */
321
db_err = cursor->c_put (cursor, &query,
322
svn_fs_base__str_to_dbt (&result, next_key),
326
cursor->c_close (cursor); /* ignore the error, the original is
328
return BDB_WRAP (fs, "bumping next string key", db_err);
331
return BDB_WRAP (fs, "closing string-reading cursor",
332
cursor->c_close (cursor));
336
svn_fs_bdb__string_append (svn_fs_t *fs,
343
base_fs_data_t *bfd = fs->fsap_data;
346
/* If the passed-in key is NULL, we graciously generate a new string
347
using the value of the `next-key' record in the strings table. */
350
SVN_ERR (get_key_and_bump (fs, key, trail, pool));
353
/* Store a new record into the database. */
354
svn_fs_base__trail_debug (trail, "strings", "put");
355
SVN_ERR (BDB_WRAP (fs, "appending string",
357
(bfd->strings, trail->db_txn,
358
svn_fs_base__str_to_dbt (&query, *key),
359
svn_fs_base__set_dbt (&result, buf, len),
367
svn_fs_bdb__string_clear (svn_fs_t *fs,
372
base_fs_data_t *bfd = fs->fsap_data;
376
svn_fs_base__str_to_dbt (&query, key);
378
/* Torch the prior contents */
379
svn_fs_base__trail_debug (trail, "strings", "del");
380
db_err = bfd->strings->del (bfd->strings, trail->db_txn, &query, 0);
382
/* If there's no such node, return an appropriately specific error. */
383
if (db_err == DB_NOTFOUND)
384
return svn_error_createf
385
(SVN_ERR_FS_NO_SUCH_STRING, 0,
386
"No such string '%s'", key);
388
/* Handle any other error conditions. */
389
SVN_ERR (BDB_WRAP (fs, "clearing string", db_err));
391
/* Shove empty data back in for this key. */
392
svn_fs_base__clear_dbt (&result);
395
result.flags |= DB_DBT_USERMEM;
397
svn_fs_base__trail_debug (trail, "strings", "put");
398
return BDB_WRAP (fs, "storing empty contents",
399
bfd->strings->put (bfd->strings, trail->db_txn,
400
&query, &result, 0));
405
svn_fs_bdb__string_size (svn_filesize_t *size,
415
svn_filesize_t total;
417
svn_fs_base__str_to_dbt (&query, key);
419
SVN_ERR (locate_key (&length, &cursor, &query, fs, trail, pool));
424
/* Remember, if any error happens, our cursor has been closed
426
db_err = get_next_length (&length, cursor, &query);
428
/* No more records? Then return the total length. */
429
if (db_err == DB_NOTFOUND)
435
return BDB_WRAP (fs, "fetching string length", db_err);
445
svn_fs_bdb__string_delete (svn_fs_t *fs,
450
base_fs_data_t *bfd = fs->fsap_data;
454
svn_fs_base__trail_debug (trail, "strings", "del");
455
db_err = bfd->strings->del (bfd->strings, trail->db_txn,
456
svn_fs_base__str_to_dbt (&query, key), 0);
458
/* If there's no such node, return an appropriately specific error. */
459
if (db_err == DB_NOTFOUND)
460
return svn_error_createf
461
(SVN_ERR_FS_NO_SUCH_STRING, 0,
462
"No such string '%s'", key);
464
/* Handle any other error conditions. */
465
SVN_ERR (BDB_WRAP (fs, "deleting string", db_err));
472
svn_fs_bdb__string_copy (svn_fs_t *fs,
473
const char **new_key,
478
base_fs_data_t *bfd = fs->fsap_data;
485
/* Copy off the old key in case the caller is sharing storage
486
between the old and new keys. */
487
const char *old_key = apr_pstrdup (pool, key);
489
SVN_ERR (get_key_and_bump (fs, new_key, trail, pool));
491
svn_fs_base__trail_debug (trail, "strings", "cursor");
492
SVN_ERR (BDB_WRAP (fs, "creating cursor for reading a string",
493
bfd->strings->cursor (bfd->strings, trail->db_txn,
496
svn_fs_base__str_to_dbt (&query, old_key);
497
svn_fs_base__str_to_dbt (©key, *new_key);
499
svn_fs_base__clear_dbt (&result);
501
/* Move to the first record and fetch its data (under BDB's mem mgmt). */
502
db_err = cursor->c_get (cursor, &query, &result, DB_SET);
505
cursor->c_close (cursor);
506
return BDB_WRAP (fs, "getting next-key value", db_err);
511
/* ### can we pass a BDB-provided buffer to another BDB function?
512
### they are supposed to have a duration up to certain points
513
### of calling back into BDB, but I'm not sure what the exact
514
### rules are. it is definitely nicer to use BDB buffers here
515
### to simplify things and reduce copies, but... hrm.
518
/* Write the data to the database */
519
svn_fs_base__trail_debug (trail, "strings", "put");
520
db_err = bfd->strings->put (bfd->strings, trail->db_txn,
521
©key, &result, 0);
524
cursor->c_close (cursor);
525
return BDB_WRAP (fs, "writing copied data", db_err);
528
/* Read the next chunk. Terminate loop if we're done. */
529
svn_fs_base__clear_dbt (&result);
530
db_err = cursor->c_get (cursor, &query, &result, DB_NEXT_DUP);
531
if (db_err == DB_NOTFOUND)
535
cursor->c_close (cursor);
536
return BDB_WRAP (fs, "fetching string data for a copy", db_err);
540
return BDB_WRAP (fs, "closing string-reading cursor",
541
cursor->c_close (cursor));