Fix compiler warning due to missing function prototype.
[svn.git] / subversion / libsvn_fs_base / bdb / strings-table.c
bloba1866a4a5f7766c0f28922614879652f06354faa
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"
19 #include "svn_fs.h"
20 #include "svn_pools.h"
21 #include "../fs.h"
22 #include "../err.h"
23 #include "dbt.h"
24 #include "../trail.h"
25 #include "../key-gen.h"
26 #include "../../libsvn_fs/fs-loader.h"
27 #include "bdb-err.h"
28 #include "strings-table.h"
30 #include "svn_private_config.h"
33 /*** Creating and opening the strings table. ***/
35 int
36 svn_fs_bdb__open_strings_table(DB **strings_p,
37 DB_ENV *env,
38 svn_boolean_t create)
40 const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
41 DB *strings;
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, 0666));
54 if (create)
56 DBT key, value;
58 /* Create the `next-key' table entry. */
59 BDB_ERR(strings->put
60 (strings, 0,
61 svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY),
62 svn_fs_base__str_to_dbt(&value, "0"), 0));
65 *strings_p = strings;
66 return 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
75 first row. */
76 static svn_error_t *
77 locate_key(apr_size_t *length,
78 DBC **cursor,
79 DBT *query,
80 svn_fs_t *fs,
81 trail_t *trail,
82 apr_pool_t *pool)
84 base_fs_data_t *bfd = fs->fsap_data;
85 int db_err;
86 DBT result;
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,
91 cursor, 0)));
93 /* Set up the DBT for reading the length of the record. */
94 svn_fs_base__clear_dbt(&result);
95 result.ulen = 0;
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);
112 if (db_err)
114 DBT rerun;
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
124 it happen. */
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);
128 if (db_err)
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;
138 return SVN_NO_ERROR;
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. */
145 static int
146 get_next_length(apr_size_t *length, DBC *cursor, DBT *query)
148 DBT result;
149 int db_err;
151 /* Set up the DBT for reading the length of the record. */
152 svn_fs_base__clear_dbt(&result);
153 result.ulen = 0;
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. */
161 if (db_err)
163 DBT rerun;
165 if (db_err != SVN_BDB_DB_BUFFER_SMALL)
167 svn_bdb_dbc_close(cursor);
168 return db_err;
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
173 it happen. */
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);
177 if (db_err)
178 svn_bdb_dbc_close(cursor);
181 /* ### this cast might not be safe? */
182 *length = (apr_size_t) result.size;
183 return db_err;
187 svn_error_t *
188 svn_fs_bdb__string_read(svn_fs_t *fs,
189 const char *key,
190 char *buf,
191 svn_filesize_t offset,
192 apr_size_t *len,
193 trail_t *trail,
194 apr_pool_t *pool)
196 int db_err;
197 DBT query, result;
198 DBC *cursor;
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)
210 offset -= length;
212 /* Remember, if any error happens, our cursor has been closed
213 for us. */
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)
219 *len = 0;
220 return SVN_NO_ERROR;
222 if (db_err)
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. */
230 while (1)
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);
239 if (db_err)
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)));
251 break;
254 /* Remember, if any error happens, our cursor has been closed
255 for us. */
256 db_err = get_next_length(&length, cursor, &query);
257 if (db_err == DB_NOTFOUND)
258 break;
259 if (db_err)
260 return BDB_WRAP(fs, "reading string", db_err);
262 /* We'll be reading from the beginning of the next record */
263 offset = 0;
266 *len = bytes_read;
267 return SVN_NO_ERROR;
271 /* Get the current 'next-key' value and bump the record. */
272 static svn_error_t *
273 get_key_and_bump(svn_fs_t *fs,
274 const char **key,
275 trail_t *trail,
276 apr_pool_t *pool)
278 base_fs_data_t *bfd = fs->fsap_data;
279 DBC *cursor;
280 char next_key[MAX_KEY_SIZE];
281 apr_size_t key_len;
282 int db_err;
283 DBT query;
284 DBT result;
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,
297 &cursor, 0)));
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),
304 DB_SET);
305 if (db_err)
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),
321 DB_CURRENT);
322 if (db_err)
324 svn_bdb_dbc_close(cursor); /* ignore the error, the original is
325 more important. */
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));
333 svn_error_t *
334 svn_fs_bdb__string_append(svn_fs_t *fs,
335 const char **key,
336 apr_size_t len,
337 const char *buf,
338 trail_t *trail,
339 apr_pool_t *pool)
341 base_fs_data_t *bfd = fs->fsap_data;
342 DBT query, result;
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. */
346 if (*key == NULL)
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",
354 bfd->strings->put
355 (bfd->strings, trail->db_txn,
356 svn_fs_base__str_to_dbt(&query, *key),
357 svn_fs_base__set_dbt(&result, buf, len),
358 0)));
360 return SVN_NO_ERROR;
364 svn_error_t *
365 svn_fs_bdb__string_clear(svn_fs_t *fs,
366 const char *key,
367 trail_t *trail,
368 apr_pool_t *pool)
370 base_fs_data_t *bfd = fs->fsap_data;
371 int db_err;
372 DBT query, result;
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);
391 result.data = 0;
392 result.size = 0;
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));
402 svn_error_t *
403 svn_fs_bdb__string_size(svn_filesize_t *size,
404 svn_fs_t *fs,
405 const char *key,
406 trail_t *trail,
407 apr_pool_t *pool)
409 int db_err;
410 DBT query;
411 DBC *cursor;
412 apr_size_t length;
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));
419 total = length;
420 while (1)
422 /* Remember, if any error happens, our cursor has been closed
423 for us. */
424 db_err = get_next_length(&length, cursor, &query);
426 /* No more records? Then return the total length. */
427 if (db_err == DB_NOTFOUND)
429 *size = total;
430 return SVN_NO_ERROR;
432 if (db_err)
433 return BDB_WRAP(fs, "fetching string length", db_err);
435 total += length;
438 /* NOTREACHED */
442 svn_error_t *
443 svn_fs_bdb__string_delete(svn_fs_t *fs,
444 const char *key,
445 trail_t *trail,
446 apr_pool_t *pool)
448 base_fs_data_t *bfd = fs->fsap_data;
449 int db_err;
450 DBT query;
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));
465 return SVN_NO_ERROR;
469 svn_error_t *
470 svn_fs_bdb__string_copy(svn_fs_t *fs,
471 const char **new_key,
472 const char *key,
473 trail_t *trail,
474 apr_pool_t *pool)
476 base_fs_data_t *bfd = fs->fsap_data;
477 DBT query;
478 DBT result;
479 DBT copykey;
480 DBC *cursor;
481 int db_err;
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,
492 &cursor, 0)));
494 svn_fs_base__str_to_dbt(&query, old_key);
495 svn_fs_base__str_to_dbt(&copykey, *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);
501 if (db_err)
503 svn_bdb_dbc_close(cursor);
504 return BDB_WRAP(fs, "getting next-key value", db_err);
507 while (1)
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 &copykey, &result, 0);
520 if (db_err)
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)
530 break;
531 if (db_err)
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));