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
,
58 /* Create the `next-key' table entry. */
61 svn_fs_base__str_to_dbt(&key
, NEXT_KEY_KEY
),
62 svn_fs_base__str_to_dbt(&value
, "0"), 0));
71 /*** Storing and retrieving strings. ***/
73 /* Allocate *CURSOR and advance it to first row in the set of rows
74 whose key is defined by QUERY. Set *LENGTH to the size of that
77 locate_key(apr_size_t
*length
,
84 base_fs_data_t
*bfd
= fs
->fsap_data
;
88 svn_fs_base__trail_debug(trail
, "strings", "cursor");
89 SVN_ERR(BDB_WRAP(fs
, _("creating cursor for reading a string"),
90 bfd
->strings
->cursor(bfd
->strings
, trail
->db_txn
,
93 /* Set up the DBT for reading the length of the record. */
94 svn_fs_base__clear_dbt(&result
);
96 result
.flags
|= DB_DBT_USERMEM
;
98 /* Advance the cursor to the key that we're looking for. */
99 db_err
= svn_bdb_dbc_get(*cursor
, query
, &result
, DB_SET
);
101 /* We don't need to svn_fs_base__track_dbt() the result, because nothing
102 was allocated in it. */
104 /* If there's no such node, return an appropriately specific error. */
105 if (db_err
== DB_NOTFOUND
)
107 svn_bdb_dbc_close(*cursor
);
108 return svn_error_createf
109 (SVN_ERR_FS_NO_SUCH_STRING
, 0,
110 "No such string '%s'", (const char *)query
->data
);
116 if (db_err
!= SVN_BDB_DB_BUFFER_SMALL
)
118 svn_bdb_dbc_close(*cursor
);
119 return BDB_WRAP(fs
, "moving cursor", db_err
);
122 /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a
123 zero length buf), so we need to re-run the operation to make
125 svn_fs_base__clear_dbt(&rerun
);
126 rerun
.flags
|= DB_DBT_USERMEM
| DB_DBT_PARTIAL
;
127 db_err
= svn_bdb_dbc_get(*cursor
, query
, &rerun
, DB_SET
);
130 svn_bdb_dbc_close(*cursor
);
131 return BDB_WRAP(fs
, "rerunning cursor move", db_err
);
135 /* ### this cast might not be safe? */
136 *length
= (apr_size_t
) result
.size
;
142 /* Advance CURSOR by a single row in the set of rows whose keys match
143 CURSOR's current location. Set *LENGTH to the size of that next
144 row. If any error occurs, CURSOR will be destroyed. */
146 get_next_length(apr_size_t
*length
, DBC
*cursor
, DBT
*query
)
151 /* Set up the DBT for reading the length of the record. */
152 svn_fs_base__clear_dbt(&result
);
154 result
.flags
|= DB_DBT_USERMEM
;
156 /* Note: this may change the QUERY DBT, but that's okay: we're going
157 to be sticking with the same key anyways. */
158 db_err
= svn_bdb_dbc_get(cursor
, query
, &result
, DB_NEXT_DUP
);
160 /* Note that we exit on DB_NOTFOUND. The caller uses that to end a loop. */
165 if (db_err
!= SVN_BDB_DB_BUFFER_SMALL
)
167 svn_bdb_dbc_close(cursor
);
171 /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a
172 zero length buf), so we need to re-run the operation to make
174 svn_fs_base__clear_dbt(&rerun
);
175 rerun
.flags
|= DB_DBT_USERMEM
| DB_DBT_PARTIAL
;
176 db_err
= svn_bdb_dbc_get(cursor
, query
, &rerun
, DB_NEXT_DUP
);
178 svn_bdb_dbc_close(cursor
);
181 /* ### this cast might not be safe? */
182 *length
= (apr_size_t
) result
.size
;
188 svn_fs_bdb__string_read(svn_fs_t
*fs
,
191 svn_filesize_t offset
,
199 apr_size_t length
, bytes_read
= 0;
201 svn_fs_base__str_to_dbt(&query
, key
);
203 SVN_ERR(locate_key(&length
, &cursor
, &query
, fs
, trail
, pool
));
205 /* Seek through the records for this key, trying to find the record that
206 includes OFFSET. Note that we don't require reading from more than
207 one record since we're allowed to return partial reads. */
208 while (length
<= offset
)
212 /* Remember, if any error happens, our cursor has been closed
214 db_err
= get_next_length(&length
, cursor
, &query
);
216 /* No more records? They tried to read past the end. */
217 if (db_err
== DB_NOTFOUND
)
223 return BDB_WRAP(fs
, "reading string", db_err
);
226 /* The current record contains OFFSET. Fetch the contents now. Note that
227 OFFSET has been moved to be relative to this record. The length could
228 quite easily extend past this record, so we use DB_DBT_PARTIAL and
229 read successive records until we've filled the request. */
232 svn_fs_base__clear_dbt(&result
);
233 result
.data
= buf
+ bytes_read
;
234 result
.ulen
= *len
- bytes_read
;
235 result
.doff
= (u_int32_t
)offset
;
236 result
.dlen
= *len
- bytes_read
;
237 result
.flags
|= (DB_DBT_USERMEM
| DB_DBT_PARTIAL
);
238 db_err
= svn_bdb_dbc_get(cursor
, &query
, &result
, DB_CURRENT
);
241 svn_bdb_dbc_close(cursor
);
242 return BDB_WRAP(fs
, "reading string", db_err
);
245 bytes_read
+= result
.size
;
246 if (bytes_read
== *len
)
248 /* Done with the cursor. */
249 SVN_ERR(BDB_WRAP(fs
, "closing string-reading cursor",
250 svn_bdb_dbc_close(cursor
)));
254 /* Remember, if any error happens, our cursor has been closed
256 db_err
= get_next_length(&length
, cursor
, &query
);
257 if (db_err
== DB_NOTFOUND
)
260 return BDB_WRAP(fs
, "reading string", db_err
);
262 /* We'll be reading from the beginning of the next record */
271 /* Get the current 'next-key' value and bump the record. */
273 get_key_and_bump(svn_fs_t
*fs
,
278 base_fs_data_t
*bfd
= fs
->fsap_data
;
280 char next_key
[MAX_KEY_SIZE
];
286 /* ### todo: see issue #409 for why bumping the key as part of this
287 trail is problematic. */
289 /* Open a cursor and move it to the 'next-key' value. We can then fetch
290 the contents and use the cursor to overwrite those contents. Since
291 this database allows duplicates, we can't do an arbitrary 'put' to
292 write the new value -- that would append, not overwrite. */
294 svn_fs_base__trail_debug(trail
, "strings", "cursor");
295 SVN_ERR(BDB_WRAP(fs
, "creating cursor for reading a string",
296 bfd
->strings
->cursor(bfd
->strings
, trail
->db_txn
,
299 /* Advance the cursor to 'next-key' and read it. */
301 db_err
= svn_bdb_dbc_get(cursor
,
302 svn_fs_base__str_to_dbt(&query
, NEXT_KEY_KEY
),
303 svn_fs_base__result_dbt(&result
),
307 svn_bdb_dbc_close(cursor
);
308 return BDB_WRAP(fs
, "getting next-key value", db_err
);
311 svn_fs_base__track_dbt(&result
, pool
);
312 *key
= apr_pstrmemdup(pool
, result
.data
, result
.size
);
314 /* Bump to future key. */
315 key_len
= result
.size
;
316 svn_fs_base__next_key(result
.data
, &key_len
, next_key
);
318 /* Shove the new key back into the database, at the cursor position. */
319 db_err
= svn_bdb_dbc_put(cursor
, &query
,
320 svn_fs_base__str_to_dbt(&result
, next_key
),
324 svn_bdb_dbc_close(cursor
); /* ignore the error, the original is
326 return BDB_WRAP(fs
, "bumping next string key", db_err
);
329 return BDB_WRAP(fs
, "closing string-reading cursor",
330 svn_bdb_dbc_close(cursor
));
334 svn_fs_bdb__string_append(svn_fs_t
*fs
,
341 base_fs_data_t
*bfd
= fs
->fsap_data
;
344 /* If the passed-in key is NULL, we graciously generate a new string
345 using the value of the `next-key' record in the strings table. */
348 SVN_ERR(get_key_and_bump(fs
, key
, trail
, pool
));
351 /* Store a new record into the database. */
352 svn_fs_base__trail_debug(trail
, "strings", "put");
353 SVN_ERR(BDB_WRAP(fs
, "appending string",
355 (bfd
->strings
, trail
->db_txn
,
356 svn_fs_base__str_to_dbt(&query
, *key
),
357 svn_fs_base__set_dbt(&result
, buf
, len
),
365 svn_fs_bdb__string_clear(svn_fs_t
*fs
,
370 base_fs_data_t
*bfd
= fs
->fsap_data
;
374 svn_fs_base__str_to_dbt(&query
, key
);
376 /* Torch the prior contents */
377 svn_fs_base__trail_debug(trail
, "strings", "del");
378 db_err
= bfd
->strings
->del(bfd
->strings
, trail
->db_txn
, &query
, 0);
380 /* If there's no such node, return an appropriately specific error. */
381 if (db_err
== DB_NOTFOUND
)
382 return svn_error_createf
383 (SVN_ERR_FS_NO_SUCH_STRING
, 0,
384 "No such string '%s'", key
);
386 /* Handle any other error conditions. */
387 SVN_ERR(BDB_WRAP(fs
, "clearing string", db_err
));
389 /* Shove empty data back in for this key. */
390 svn_fs_base__clear_dbt(&result
);
393 result
.flags
|= DB_DBT_USERMEM
;
395 svn_fs_base__trail_debug(trail
, "strings", "put");
396 return BDB_WRAP(fs
, "storing empty contents",
397 bfd
->strings
->put(bfd
->strings
, trail
->db_txn
,
398 &query
, &result
, 0));
403 svn_fs_bdb__string_size(svn_filesize_t
*size
,
413 svn_filesize_t total
;
415 svn_fs_base__str_to_dbt(&query
, key
);
417 SVN_ERR(locate_key(&length
, &cursor
, &query
, fs
, trail
, pool
));
422 /* Remember, if any error happens, our cursor has been closed
424 db_err
= get_next_length(&length
, cursor
, &query
);
426 /* No more records? Then return the total length. */
427 if (db_err
== DB_NOTFOUND
)
433 return BDB_WRAP(fs
, "fetching string length", db_err
);
443 svn_fs_bdb__string_delete(svn_fs_t
*fs
,
448 base_fs_data_t
*bfd
= fs
->fsap_data
;
452 svn_fs_base__trail_debug(trail
, "strings", "del");
453 db_err
= bfd
->strings
->del(bfd
->strings
, trail
->db_txn
,
454 svn_fs_base__str_to_dbt(&query
, key
), 0);
456 /* If there's no such node, return an appropriately specific error. */
457 if (db_err
== DB_NOTFOUND
)
458 return svn_error_createf
459 (SVN_ERR_FS_NO_SUCH_STRING
, 0,
460 "No such string '%s'", key
);
462 /* Handle any other error conditions. */
463 SVN_ERR(BDB_WRAP(fs
, "deleting string", db_err
));
470 svn_fs_bdb__string_copy(svn_fs_t
*fs
,
471 const char **new_key
,
476 base_fs_data_t
*bfd
= fs
->fsap_data
;
483 /* Copy off the old key in case the caller is sharing storage
484 between the old and new keys. */
485 const char *old_key
= apr_pstrdup(pool
, key
);
487 SVN_ERR(get_key_and_bump(fs
, new_key
, trail
, pool
));
489 svn_fs_base__trail_debug(trail
, "strings", "cursor");
490 SVN_ERR(BDB_WRAP(fs
, "creating cursor for reading a string",
491 bfd
->strings
->cursor(bfd
->strings
, trail
->db_txn
,
494 svn_fs_base__str_to_dbt(&query
, old_key
);
495 svn_fs_base__str_to_dbt(©key
, *new_key
);
497 svn_fs_base__clear_dbt(&result
);
499 /* Move to the first record and fetch its data (under BDB's mem mgmt). */
500 db_err
= svn_bdb_dbc_get(cursor
, &query
, &result
, DB_SET
);
503 svn_bdb_dbc_close(cursor
);
504 return BDB_WRAP(fs
, "getting next-key value", db_err
);
509 /* ### can we pass a BDB-provided buffer to another BDB function?
510 ### they are supposed to have a duration up to certain points
511 ### of calling back into BDB, but I'm not sure what the exact
512 ### rules are. it is definitely nicer to use BDB buffers here
513 ### to simplify things and reduce copies, but... hrm.
516 /* Write the data to the database */
517 svn_fs_base__trail_debug(trail
, "strings", "put");
518 db_err
= bfd
->strings
->put(bfd
->strings
, trail
->db_txn
,
519 ©key
, &result
, 0);
522 svn_bdb_dbc_close(cursor
);
523 return BDB_WRAP(fs
, "writing copied data", db_err
);
526 /* Read the next chunk. Terminate loop if we're done. */
527 svn_fs_base__clear_dbt(&result
);
528 db_err
= svn_bdb_dbc_get(cursor
, &query
, &result
, DB_NEXT_DUP
);
529 if (db_err
== DB_NOTFOUND
)
533 svn_bdb_dbc_close(cursor
);
534 return BDB_WRAP(fs
, "fetching string data for a copy", db_err
);
538 return BDB_WRAP(fs
, "closing string-reading cursor",
539 svn_bdb_dbc_close(cursor
));