1251881Speter/* changes-table.c : operations on the `changes' table
2251881Speter *
3251881Speter * ====================================================================
4251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
5251881Speter *    or more contributor license agreements.  See the NOTICE file
6251881Speter *    distributed with this work for additional information
7251881Speter *    regarding copyright ownership.  The ASF licenses this file
8251881Speter *    to you under the Apache License, Version 2.0 (the
9251881Speter *    "License"); you may not use this file except in compliance
10251881Speter *    with the License.  You may obtain a copy of the License at
11251881Speter *
12251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
13251881Speter *
14251881Speter *    Unless required by applicable law or agreed to in writing,
15251881Speter *    software distributed under the License is distributed on an
16251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17251881Speter *    KIND, either express or implied.  See the License for the
18251881Speter *    specific language governing permissions and limitations
19251881Speter *    under the License.
20251881Speter * ====================================================================
21251881Speter */
22251881Speter
23251881Speter#include "bdb_compat.h"
24251881Speter
25251881Speter#include <apr_hash.h>
26251881Speter#include <apr_tables.h>
27251881Speter
28251881Speter#include "svn_hash.h"
29251881Speter#include "svn_fs.h"
30251881Speter#include "svn_pools.h"
31251881Speter#include "svn_path.h"
32251881Speter#include "../fs.h"
33251881Speter#include "../err.h"
34251881Speter#include "../trail.h"
35251881Speter#include "../id.h"
36251881Speter#include "../util/fs_skels.h"
37251881Speter#include "../../libsvn_fs/fs-loader.h"
38251881Speter#include "bdb-err.h"
39251881Speter#include "dbt.h"
40251881Speter#include "changes-table.h"
41251881Speter
42251881Speter#include "private/svn_fs_util.h"
43251881Speter#include "private/svn_fspath.h"
44251881Speter#include "svn_private_config.h"
45251881Speter
46251881Speter
47251881Speter/*** Creating and opening the changes table. ***/
48251881Speter
49251881Speterint
50251881Spetersvn_fs_bdb__open_changes_table(DB **changes_p,
51251881Speter                               DB_ENV *env,
52251881Speter                               svn_boolean_t create)
53251881Speter{
54251881Speter  const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
55251881Speter  DB *changes;
56251881Speter
57251881Speter  BDB_ERR(svn_fs_bdb__check_version());
58251881Speter  BDB_ERR(db_create(&changes, env, 0));
59251881Speter
60251881Speter  /* Enable duplicate keys. This allows us to store the changes
61251881Speter     one-per-row.  Note: this must occur before ->open().  */
62251881Speter  BDB_ERR(changes->set_flags(changes, DB_DUP));
63251881Speter
64251881Speter  BDB_ERR((changes->open)(SVN_BDB_OPEN_PARAMS(changes, NULL),
65251881Speter                          "changes", 0, DB_BTREE,
66251881Speter                          open_flags, 0666));
67251881Speter
68251881Speter  *changes_p = changes;
69251881Speter  return 0;
70251881Speter}
71251881Speter
72251881Speter
73251881Speter
74251881Speter/*** Storing and retrieving changes.  ***/
75251881Speter
76251881Spetersvn_error_t *
77251881Spetersvn_fs_bdb__changes_add(svn_fs_t *fs,
78251881Speter                        const char *key,
79251881Speter                        change_t *change,
80251881Speter                        trail_t *trail,
81251881Speter                        apr_pool_t *pool)
82251881Speter{
83251881Speter  base_fs_data_t *bfd = fs->fsap_data;
84251881Speter  DBT query, value;
85251881Speter  svn_skel_t *skel;
86251881Speter
87251881Speter  /* Convert native type to skel. */
88251881Speter  SVN_ERR(svn_fs_base__unparse_change_skel(&skel, change, pool));
89251881Speter
90251881Speter  /* Store a new record into the database. */
91251881Speter  svn_fs_base__str_to_dbt(&query, key);
92251881Speter  svn_fs_base__skel_to_dbt(&value, skel, pool);
93251881Speter  svn_fs_base__trail_debug(trail, "changes", "put");
94251881Speter  return BDB_WRAP(fs, N_("creating change"),
95251881Speter                  bfd->changes->put(bfd->changes, trail->db_txn,
96251881Speter                                    &query, &value, 0));
97251881Speter}
98251881Speter
99251881Speter
100251881Spetersvn_error_t *
101251881Spetersvn_fs_bdb__changes_delete(svn_fs_t *fs,
102251881Speter                           const char *key,
103251881Speter                           trail_t *trail,
104251881Speter                           apr_pool_t *pool)
105251881Speter{
106251881Speter  int db_err;
107251881Speter  DBT query;
108251881Speter  base_fs_data_t *bfd = fs->fsap_data;
109251881Speter
110251881Speter  svn_fs_base__trail_debug(trail, "changes", "del");
111251881Speter  db_err = bfd->changes->del(bfd->changes, trail->db_txn,
112251881Speter                             svn_fs_base__str_to_dbt(&query, key), 0);
113251881Speter
114251881Speter  /* If there're no changes for KEY, that is acceptable.  Any other
115251881Speter     error should be propagated to the caller, though.  */
116251881Speter  if ((db_err) && (db_err != DB_NOTFOUND))
117251881Speter    {
118251881Speter      SVN_ERR(BDB_WRAP(fs, N_("deleting changes"), db_err));
119251881Speter    }
120251881Speter
121251881Speter  return SVN_NO_ERROR;
122251881Speter}
123251881Speter
124251881Speter
125251881Speter/* Merge the internal-use-only CHANGE into a hash of public-FS
126251881Speter   svn_fs_path_change2_t CHANGES, collapsing multiple changes into a
127251881Speter   single succinct change per path. */
128251881Speterstatic svn_error_t *
129251881Speterfold_change(apr_hash_t *changes,
130251881Speter            const change_t *change)
131251881Speter{
132251881Speter  apr_pool_t *pool = apr_hash_pool_get(changes);
133251881Speter  svn_fs_path_change2_t *old_change, *new_change;
134251881Speter  const char *path;
135251881Speter
136251881Speter  if ((old_change = svn_hash_gets(changes, change->path)))
137251881Speter    {
138251881Speter      /* This path already exists in the hash, so we have to merge
139251881Speter         this change into the already existing one. */
140251881Speter
141251881Speter      /* Since the path already exists in the hash, we don't have to
142251881Speter         dup the allocation for the path itself. */
143251881Speter      path = change->path;
144251881Speter
145251881Speter      /* Sanity check:  only allow NULL node revision ID in the
146251881Speter         `reset' case. */
147251881Speter      if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset))
148251881Speter        return svn_error_create
149251881Speter          (SVN_ERR_FS_CORRUPT, NULL,
150251881Speter           _("Missing required node revision ID"));
151251881Speter
152251881Speter      /* Sanity check:  we should be talking about the same node
153251881Speter         revision ID as our last change except where the last change
154251881Speter         was a deletion. */
155251881Speter      if (change->noderev_id
156251881Speter          && (! svn_fs_base__id_eq(old_change->node_rev_id,
157251881Speter                                   change->noderev_id))
158251881Speter          && (old_change->change_kind != svn_fs_path_change_delete))
159251881Speter        return svn_error_create
160251881Speter          (SVN_ERR_FS_CORRUPT, NULL,
161251881Speter           _("Invalid change ordering: new node revision ID without delete"));
162251881Speter
163251881Speter      /* Sanity check: an add, replacement, or reset must be the first
164251881Speter         thing to follow a deletion. */
165251881Speter      if ((old_change->change_kind == svn_fs_path_change_delete)
166251881Speter          && (! ((change->kind == svn_fs_path_change_replace)
167251881Speter                 || (change->kind == svn_fs_path_change_reset)
168251881Speter                 || (change->kind == svn_fs_path_change_add))))
169251881Speter        return svn_error_create
170251881Speter          (SVN_ERR_FS_CORRUPT, NULL,
171251881Speter           _("Invalid change ordering: non-add change on deleted path"));
172251881Speter
173251881Speter      /* Sanity check: an add can't follow anything except
174251881Speter         a delete or reset.  */
175251881Speter      if ((change->kind == svn_fs_path_change_add)
176251881Speter          && (old_change->change_kind != svn_fs_path_change_delete)
177251881Speter          && (old_change->change_kind != svn_fs_path_change_reset))
178251881Speter        return svn_error_create
179251881Speter          (SVN_ERR_FS_CORRUPT, NULL,
180251881Speter           _("Invalid change ordering: add change on preexisting path"));
181251881Speter
182251881Speter      /* Now, merge that change in. */
183251881Speter      switch (change->kind)
184251881Speter        {
185251881Speter        case svn_fs_path_change_reset:
186251881Speter          /* A reset here will simply remove the path change from the
187251881Speter             hash. */
188251881Speter          old_change = NULL;
189251881Speter          break;
190251881Speter
191251881Speter        case svn_fs_path_change_delete:
192251881Speter          if (old_change->change_kind == svn_fs_path_change_add)
193251881Speter            {
194251881Speter              /* If the path was introduced in this transaction via an
195251881Speter                 add, and we are deleting it, just remove the path
196251881Speter                 altogether. */
197251881Speter              old_change = NULL;
198251881Speter            }
199251881Speter          else
200251881Speter            {
201251881Speter              /* A deletion overrules all previous changes. */
202251881Speter              old_change->change_kind = svn_fs_path_change_delete;
203251881Speter              old_change->text_mod = change->text_mod;
204251881Speter              old_change->prop_mod = change->prop_mod;
205251881Speter            }
206251881Speter          break;
207251881Speter
208251881Speter        case svn_fs_path_change_add:
209251881Speter        case svn_fs_path_change_replace:
210251881Speter          /* An add at this point must be following a previous delete,
211251881Speter             so treat it just like a replace. */
212251881Speter          old_change->change_kind = svn_fs_path_change_replace;
213251881Speter          old_change->node_rev_id = svn_fs_base__id_copy(change->noderev_id,
214251881Speter                                                         pool);
215251881Speter          old_change->text_mod = change->text_mod;
216251881Speter          old_change->prop_mod = change->prop_mod;
217251881Speter          break;
218251881Speter
219251881Speter        case svn_fs_path_change_modify:
220251881Speter        default:
221251881Speter          if (change->text_mod)
222251881Speter            old_change->text_mod = TRUE;
223251881Speter          if (change->prop_mod)
224251881Speter            old_change->prop_mod = TRUE;
225251881Speter          break;
226251881Speter        }
227251881Speter
228251881Speter      /* Point our new_change to our (possibly modified) old_change. */
229251881Speter      new_change = old_change;
230251881Speter    }
231251881Speter  else
232251881Speter    {
233251881Speter      /* This change is new to the hash, so make a new public change
234251881Speter         structure from the internal one (in the hash's pool), and dup
235251881Speter         the path into the hash's pool, too. */
236251881Speter      new_change = svn_fs__path_change_create_internal(
237251881Speter                       svn_fs_base__id_copy(change->noderev_id, pool),
238251881Speter                       change->kind,
239251881Speter                       pool);
240251881Speter      new_change->text_mod = change->text_mod;
241251881Speter      new_change->prop_mod = change->prop_mod;
242251881Speter      new_change->node_kind = svn_node_unknown;
243251881Speter      new_change->copyfrom_known = FALSE;
244251881Speter      path = apr_pstrdup(pool, change->path);
245251881Speter    }
246251881Speter
247251881Speter  /* Add (or update) this path. */
248251881Speter  svn_hash_sets(changes, path, new_change);
249251881Speter
250251881Speter  return SVN_NO_ERROR;
251251881Speter}
252251881Speter
253251881Speter
254251881Spetersvn_error_t *
255251881Spetersvn_fs_bdb__changes_fetch(apr_hash_t **changes_p,
256251881Speter                          svn_fs_t *fs,
257251881Speter                          const char *key,
258251881Speter                          trail_t *trail,
259251881Speter                          apr_pool_t *pool)
260251881Speter{
261251881Speter  base_fs_data_t *bfd = fs->fsap_data;
262251881Speter  DBC *cursor;
263251881Speter  DBT query, result;
264251881Speter  int db_err = 0, db_c_err = 0;
265251881Speter  svn_error_t *err = SVN_NO_ERROR;
266251881Speter  apr_hash_t *changes = apr_hash_make(pool);
267251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
268251881Speter
269251881Speter  /* Get a cursor on the first record matching KEY, and then loop over
270251881Speter     the records, adding them to the return array. */
271251881Speter  svn_fs_base__trail_debug(trail, "changes", "cursor");
272251881Speter  SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading changes"),
273251881Speter                   bfd->changes->cursor(bfd->changes, trail->db_txn,
274251881Speter                                        &cursor, 0)));
275251881Speter
276251881Speter  /* Advance the cursor to the key that we're looking for. */
277251881Speter  svn_fs_base__str_to_dbt(&query, key);
278251881Speter  svn_fs_base__result_dbt(&result);
279251881Speter  db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET);
280251881Speter  if (! db_err)
281251881Speter    svn_fs_base__track_dbt(&result, pool);
282251881Speter
283251881Speter  while (! db_err)
284251881Speter    {
285251881Speter      change_t *change;
286251881Speter      svn_skel_t *result_skel;
287251881Speter
288251881Speter      /* Clear the per-iteration subpool. */
289251881Speter      svn_pool_clear(subpool);
290251881Speter
291251881Speter      /* RESULT now contains a change record associated with KEY.  We
292251881Speter         need to parse that skel into an change_t structure ...  */
293251881Speter      result_skel = svn_skel__parse(result.data, result.size, subpool);
294251881Speter      if (! result_skel)
295251881Speter        {
296251881Speter          err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
297251881Speter                                  _("Error reading changes for key '%s'"),
298251881Speter                                  key);
299251881Speter          goto cleanup;
300251881Speter        }
301251881Speter      err = svn_fs_base__parse_change_skel(&change, result_skel, subpool);
302251881Speter      if (err)
303251881Speter        goto cleanup;
304251881Speter
305251881Speter      /* ... and merge it with our return hash.  */
306251881Speter      err = fold_change(changes, change);
307251881Speter      if (err)
308251881Speter        goto cleanup;
309251881Speter
310251881Speter      /* Now, if our change was a deletion or replacement, we have to
311251881Speter         blow away any changes thus far on paths that are (or, were)
312251881Speter         children of this path.
313251881Speter         ### i won't bother with another iteration pool here -- at
314251881Speter             most we talking about a few extra dups of paths into what
315251881Speter             is already a temporary subpool.
316251881Speter      */
317251881Speter      if ((change->kind == svn_fs_path_change_delete)
318251881Speter          || (change->kind == svn_fs_path_change_replace))
319251881Speter        {
320251881Speter          apr_hash_index_t *hi;
321251881Speter
322251881Speter          for (hi = apr_hash_first(subpool, changes);
323251881Speter               hi;
324251881Speter               hi = apr_hash_next(hi))
325251881Speter            {
326251881Speter              /* KEY is the path. */
327251881Speter              const void *hashkey;
328251881Speter              apr_ssize_t klen;
329251881Speter              const char *child_relpath;
330251881Speter
331251881Speter              apr_hash_this(hi, &hashkey, &klen, NULL);
332251881Speter
333251881Speter              /* If we come across our own path, ignore it.
334251881Speter                 If we come across a child of our path, remove it. */
335251881Speter              child_relpath = svn_fspath__skip_ancestor(change->path, hashkey);
336251881Speter              if (child_relpath && *child_relpath)
337251881Speter                apr_hash_set(changes, hashkey, klen, NULL);
338251881Speter            }
339251881Speter        }
340251881Speter
341251881Speter      /* Advance the cursor to the next record with this same KEY, and
342251881Speter         fetch that record. */
343251881Speter      svn_fs_base__result_dbt(&result);
344251881Speter      db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP);
345251881Speter      if (! db_err)
346251881Speter        svn_fs_base__track_dbt(&result, pool);
347251881Speter    }
348251881Speter
349251881Speter  /* Destroy the per-iteration subpool. */
350251881Speter  svn_pool_destroy(subpool);
351251881Speter
352251881Speter  /* If there are no (more) change records for this KEY, we're
353251881Speter     finished.  Just return the (possibly empty) array.  Any other
354251881Speter     error, however, needs to get handled appropriately.  */
355251881Speter  if (db_err && (db_err != DB_NOTFOUND))
356251881Speter    err = BDB_WRAP(fs, N_("fetching changes"), db_err);
357251881Speter
358251881Speter cleanup:
359251881Speter  /* Close the cursor. */
360251881Speter  db_c_err = svn_bdb_dbc_close(cursor);
361251881Speter
362251881Speter  /* If we had an error prior to closing the cursor, return the error. */
363251881Speter  if (err)
364251881Speter    return svn_error_trace(err);
365251881Speter
366251881Speter  /* If our only error thus far was when we closed the cursor, return
367251881Speter     that error. */
368251881Speter  if (db_c_err)
369251881Speter    SVN_ERR(BDB_WRAP(fs, N_("closing changes cursor"), db_c_err));
370251881Speter
371251881Speter  /* Finally, set our return variable and get outta here. */
372251881Speter  *changes_p = changes;
373251881Speter  return SVN_NO_ERROR;
374251881Speter}
375251881Speter
376251881Speter
377251881Spetersvn_error_t *
378251881Spetersvn_fs_bdb__changes_fetch_raw(apr_array_header_t **changes_p,
379251881Speter                              svn_fs_t *fs,
380251881Speter                              const char *key,
381251881Speter                              trail_t *trail,
382251881Speter                              apr_pool_t *pool)
383251881Speter{
384251881Speter  base_fs_data_t *bfd = fs->fsap_data;
385251881Speter  DBC *cursor;
386251881Speter  DBT query, result;
387251881Speter  int db_err = 0, db_c_err = 0;
388251881Speter  svn_error_t *err = SVN_NO_ERROR;
389251881Speter  change_t *change;
390251881Speter  apr_array_header_t *changes = apr_array_make(pool, 4, sizeof(change));
391251881Speter
392251881Speter  /* Get a cursor on the first record matching KEY, and then loop over
393251881Speter     the records, adding them to the return array. */
394251881Speter  svn_fs_base__trail_debug(trail, "changes", "cursor");
395251881Speter  SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading changes"),
396251881Speter                   bfd->changes->cursor(bfd->changes, trail->db_txn,
397251881Speter                                        &cursor, 0)));
398251881Speter
399251881Speter  /* Advance the cursor to the key that we're looking for. */
400251881Speter  svn_fs_base__str_to_dbt(&query, key);
401251881Speter  svn_fs_base__result_dbt(&result);
402251881Speter  db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET);
403251881Speter  if (! db_err)
404251881Speter    svn_fs_base__track_dbt(&result, pool);
405251881Speter
406251881Speter  while (! db_err)
407251881Speter    {
408251881Speter      svn_skel_t *result_skel;
409251881Speter
410251881Speter      /* RESULT now contains a change record associated with KEY.  We
411251881Speter         need to parse that skel into an change_t structure ...  */
412251881Speter      result_skel = svn_skel__parse(result.data, result.size, pool);
413251881Speter      if (! result_skel)
414251881Speter        {
415251881Speter          err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
416251881Speter                                  _("Error reading changes for key '%s'"),
417251881Speter                                  key);
418251881Speter          goto cleanup;
419251881Speter        }
420251881Speter      err = svn_fs_base__parse_change_skel(&change, result_skel, pool);
421251881Speter      if (err)
422251881Speter        goto cleanup;
423251881Speter
424251881Speter      /* ... and add it to our return array.  */
425251881Speter      APR_ARRAY_PUSH(changes, change_t *) = change;
426251881Speter
427251881Speter      /* Advance the cursor to the next record with this same KEY, and
428251881Speter         fetch that record. */
429251881Speter      svn_fs_base__result_dbt(&result);
430251881Speter      db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP);
431251881Speter      if (! db_err)
432251881Speter        svn_fs_base__track_dbt(&result, pool);
433251881Speter    }
434251881Speter
435251881Speter  /* If there are no (more) change records for this KEY, we're
436251881Speter     finished.  Just return the (possibly empty) array.  Any other
437251881Speter     error, however, needs to get handled appropriately.  */
438251881Speter  if (db_err && (db_err != DB_NOTFOUND))
439251881Speter    err = BDB_WRAP(fs, N_("fetching changes"), db_err);
440251881Speter
441251881Speter cleanup:
442251881Speter  /* Close the cursor. */
443251881Speter  db_c_err = svn_bdb_dbc_close(cursor);
444251881Speter
445251881Speter  /* If we had an error prior to closing the cursor, return the error. */
446251881Speter  if (err)
447251881Speter    return svn_error_trace(err);
448251881Speter
449251881Speter  /* If our only error thus far was when we closed the cursor, return
450251881Speter     that error. */
451251881Speter  if (db_c_err)
452251881Speter    SVN_ERR(BDB_WRAP(fs, N_("closing changes cursor"), db_c_err));
453251881Speter
454251881Speter  /* Finally, set our return variable and get outta here. */
455251881Speter  *changes_p = changes;
456251881Speter  return SVN_NO_ERROR;
457251881Speter}
458