GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/history_sqlite.cc Lines: 216 239 90.4 %
Date: 2019-02-03 02:48:13 Branches: 139 234 59.4 %

Line Branch Exec Source
1
/**
2
 * This file is part of the CernVM File System.
3
 */
4
5
#include "history_sqlite.h"
6
7
using namespace std;  // NOLINT
8
9
namespace history {
10
11
15
const std::string SqliteHistory::kPreviousRevisionKey = "previous_revision";
12
13
14
26
SqliteHistory* SqliteHistory::Open(const std::string &file_name) {
15
26
  const bool read_write = false;
16
26
  return Open(file_name, read_write);
17
}
18
19
20
8
SqliteHistory* SqliteHistory::OpenWritable(const std::string &file_name) {
21
8
  const bool read_write = true;
22
8
  return Open(file_name, read_write);
23
}
24
25
26
34
SqliteHistory* SqliteHistory::Open(const std::string &file_name,
27
                                   const bool read_write) {
28
34
  SqliteHistory *history = new SqliteHistory();
29

34
  if (NULL == history || !history->OpenDatabase(file_name, read_write)) {
30
    delete history;
31
    return NULL;
32
  }
33
34
  LogCvmfs(kLogHistory, kLogDebug,
35
           "opened history database '%s' for repository '%s' %s",
36
           file_name.c_str(), history->fqrn().c_str(),
37
34
           ((history->IsWritable()) ? "(writable)" : ""));
38
39
34
  return history;
40
}
41
42
43
83
SqliteHistory* SqliteHistory::Create(const std::string &file_name,
44
                                     const std::string &fqrn) {
45
83
  SqliteHistory *history = new SqliteHistory();
46

83
  if (NULL == history || !history->CreateDatabase(file_name, fqrn)) {
47
    delete history;
48
    return NULL;
49
  }
50
51
  LogCvmfs(kLogHistory, kLogDebug, "created empty history database '%s' for"
52
                                   "repository '%s'",
53
83
           file_name.c_str(), fqrn.c_str());
54
83
  return history;
55
}
56
57
58
34
bool SqliteHistory::OpenDatabase(
59
  const std::string &file_name,
60
  const bool read_write
61
) {
62
34
  assert(!database_);
63
  const HistoryDatabase::OpenMode mode = (read_write)
64
                                           ? HistoryDatabase::kOpenReadWrite
65
34
                                           : HistoryDatabase::kOpenReadOnly;
66
34
  database_ = HistoryDatabase::Open(file_name, mode);
67
34
  if (!database_.IsValid()) {
68
    return false;
69
  }
70
71
34
  if (!database_->HasProperty(HistoryDatabase::kFqrnKey)) {
72
    LogCvmfs(kLogHistory, kLogDebug, "opened history database does not provide "
73
                                     "an FQRN under '%s'",
74
             HistoryDatabase::kFqrnKey.c_str());
75
    return false;
76
  }
77
78
34
  set_fqrn(database_->GetProperty<std::string>(HistoryDatabase::kFqrnKey));
79
34
  PrepareQueries();
80
34
  return true;
81
}
82
83
84
83
bool SqliteHistory::CreateDatabase(const std::string &file_name,
85
                                   const std::string &repo_name) {
86
83
  assert(!database_);
87
83
  assert(fqrn().empty());
88
83
  set_fqrn(repo_name);
89
83
  database_ = HistoryDatabase::Create(file_name);
90

83
  if (!database_ || !database_->InsertInitialValues(repo_name)) {
91
    LogCvmfs(kLogHistory, kLogDebug,
92
             "failed to initialize empty database '%s', for repository '%s'",
93
             file_name.c_str(), repo_name.c_str());
94
    return false;
95
  }
96
97
83
  PrepareQueries();
98
83
  return true;
99
}
100
101
102
117
void SqliteHistory::PrepareQueries() {
103
117
  assert(database_);
104
105
117
  find_tag_           = new SqlFindTag          (database_.weak_ref());
106
117
  find_tag_by_date_   = new SqlFindTagByDate    (database_.weak_ref());
107
117
  count_tags_         = new SqlCountTags        (database_.weak_ref());
108
117
  list_tags_          = new SqlListTags         (database_.weak_ref());
109
117
  channel_tips_       = new SqlGetChannelTips   (database_.weak_ref());
110
117
  get_hashes_         = new SqlGetHashes        (database_.weak_ref());
111
117
  list_rollback_tags_ = new SqlListRollbackTags (database_.weak_ref());
112
117
  list_branches_      = new SqlListBranches     (database_.weak_ref());
113
114
117
  if (database_->ContainsRecycleBin()) {
115
115
    recycle_list_ = new SqlRecycleBinList(database_.weak_ref());
116
  }
117
118
117
  if (IsWritable()) {
119
91
    insert_tag_         = new SqlInsertTag          (database_.weak_ref());
120
91
    remove_tag_         = new SqlRemoveTag          (database_.weak_ref());
121
91
    rollback_tag_       = new SqlRollbackTag        (database_.weak_ref());
122
91
    recycle_empty_      = new SqlRecycleBinFlush    (database_.weak_ref());
123
91
    insert_branch_      = new SqlInsertBranch       (database_.weak_ref());
124
91
    find_branch_head_   = new SqlFindBranchHead     (database_.weak_ref());
125
  }
126
117
}
127
128
129
24
bool SqliteHistory::BeginTransaction()  const {
130
24
  return database_->BeginTransaction();
131
}
132
133
134
20
bool SqliteHistory::CommitTransaction() const {
135
20
  return database_->CommitTransaction();
136
}
137
138
139
40
bool SqliteHistory::SetPreviousRevision(const shash::Any &history_hash) {
140
40
  assert(database_);
141
40
  assert(IsWritable());
142
40
  return database_->SetProperty(kPreviousRevisionKey, history_hash.ToString());
143
}
144
145
146
6
shash::Any SqliteHistory::previous_revision() const {
147
6
  assert(database_);
148
  const std::string hash_str =
149
6
    database_->GetProperty<std::string>(kPreviousRevisionKey);
150
6
  return shash::MkFromHexPtr(shash::HexPtr(hash_str), shash::kSuffixHistory);
151
}
152
153
154
198
bool SqliteHistory::IsWritable() const {
155
198
  assert(database_);
156
198
  return database_->read_write();
157
}
158
159
29
unsigned SqliteHistory::GetNumberOfTags() const {
160
29
  assert(database_);
161
29
  assert(count_tags_.IsValid());
162
29
  bool retval = count_tags_->FetchRow();
163
29
  assert(retval);
164
29
  const unsigned count = count_tags_->RetrieveCount();
165
29
  retval = count_tags_->Reset();
166
29
  assert(retval);
167
29
  return count;
168
}
169
170
171
4268
bool SqliteHistory::Insert(const History::Tag &tag) {
172
4268
  assert(database_);
173
4268
  assert(insert_tag_.IsValid());
174
175
  return insert_tag_->BindTag(tag) &&
176
         insert_tag_->Execute()    &&
177

4268
         insert_tag_->Reset();
178
}
179
180
181
13
bool SqliteHistory::Remove(const std::string &name) {
182
13
  assert(database_);
183
13
  assert(remove_tag_.IsValid());
184
185
13
  Tag condemned_tag;
186
13
  if (!GetByName(name, &condemned_tag)) {
187
1
    return true;
188
  }
189
190
  return remove_tag_->BindName(name)      &&
191
         remove_tag_->Execute()           &&
192

12
         remove_tag_->Reset();
193
}
194
195
196
31
bool SqliteHistory::Exists(const std::string &name) const {
197
31
  Tag existing_tag;
198
31
  return GetByName(name, &existing_tag);
199
}
200
201
202
72
bool SqliteHistory::GetByName(const std::string &name, Tag *tag) const {
203
72
  assert(database_);
204
72
  assert(find_tag_.IsValid());
205
72
  assert(NULL != tag);
206
207

72
  if (!find_tag_->BindName(name) || !find_tag_->FetchRow()) {
208
14
    find_tag_->Reset();
209
14
    return false;
210
  }
211
212
58
  *tag = find_tag_->RetrieveTag();
213
58
  return find_tag_->Reset();
214
}
215
216
217
8
bool SqliteHistory::GetByDate(const time_t timestamp, Tag *tag) const {
218
8
  assert(database_);
219
8
  assert(find_tag_by_date_.IsValid());
220
8
  assert(NULL != tag);
221
222

8
  if (!find_tag_by_date_->BindTimestamp(timestamp) ||
223
      !find_tag_by_date_->FetchRow())
224
  {
225
3
    find_tag_by_date_->Reset();
226
3
    return false;
227
  }
228
229
5
  *tag = find_tag_by_date_->RetrieveTag();
230
5
  return find_tag_by_date_->Reset();
231
}
232
233
234
8
bool SqliteHistory::List(std::vector<Tag> *tags) const {
235
8
  assert(list_tags_.IsValid());
236
8
  return RunListing(tags, list_tags_.weak_ref());
237
}
238
239
3
bool SqliteHistory::Tips(std::vector<Tag> *channel_tips) const {
240
3
  assert(channel_tips_.IsValid());
241
3
  return RunListing(channel_tips, channel_tips_.weak_ref());
242
}
243
244
template <class SqlListingT>
245
5
bool SqliteHistory::RunListing(std::vector<Tag> *list, SqlListingT *sql) const {
246

16
  assert(database_);
247

16
  assert(NULL != list);
248
249

1215
  while (sql->FetchRow()) {
250
1183
    list->push_back(sql->RetrieveTag());
251
  }
252
253
16
  return sql->Reset();
254
}
255
256
257
3
bool SqliteHistory::GetBranchHead(const string &branch_name, Tag *tag) const {
258
3
  assert(database_);
259
3
  assert(find_branch_head_.IsValid());
260
3
  assert(tag != NULL);
261
262

3
  if (!find_branch_head_->BindBranchName(branch_name) ||
263
      !find_branch_head_->FetchRow())
264
  {
265
1
    find_branch_head_->Reset();
266
1
    return false;
267
  }
268
269
2
  *tag = find_branch_head_->RetrieveTag();
270
2
  return find_branch_head_->Reset();
271
}
272
273
274
3
bool SqliteHistory::ExistsBranch(const string &branch_name) const {
275
3
  vector<Branch> branches;
276
3
  if (!ListBranches(&branches))
277
    return false;
278
8
  for (unsigned i = 0; i < branches.size(); ++i) {
279
7
    if (branches[i].branch == branch_name)
280
2
      return true;
281
  }
282
1
  return false;
283
}
284
285
286
18
bool SqliteHistory::InsertBranch(const Branch &branch) {
287
18
  assert(database_);
288
18
  assert(insert_branch_.IsValid());
289
290
  return insert_branch_->BindBranch(branch) &&
291
         insert_branch_->Execute()    &&
292

18
         insert_branch_->Reset();
293
}
294
295
296
1
bool SqliteHistory::PruneBranches() {
297
  // Parent pointers might point to abandoned branches.  Redirect them to the
298
  // parent of the abandoned branch.  This has to be repeated until the fix
299
  // point is reached.  It always works because we never delete the root branch
300
  sqlite::Sql sql_fix_parent_pointers(database_->sqlite_db(),
301
    "INSERT OR REPLACE INTO branches (branch, parent, initial_revision) "
302
    "SELECT branches.branch, abandoned_parent, branches.initial_revision "
303
    "  FROM branches "
304
    "  INNER JOIN (SELECT DISTINCT branches.branch AS abandoned_branch, "
305
    "              branches.parent AS abandoned_parent FROM branches "
306
    "              LEFT OUTER JOIN tags ON (branches.branch=tags.branch)"
307
    "              WHERE tags.branch IS NULL) "
308
1
    "  ON (branches.parent=abandoned_branch);");
309
  // Detect if fix point is reached
310
  sqlite::Sql sql_remaining_rows(database_->sqlite_db(),
311
    "SELECT count(*) FROM branches "
312
    "INNER JOIN "
313
    "  (SELECT DISTINCT branches.branch AS abandoned_branch FROM branches "
314
    "   LEFT OUTER JOIN tags ON (branches.branch=tags.branch) "
315
    "   WHERE tags.branch IS NULL) "
316
1
    "ON (branches.parent=abandoned_branch);");
317
318
  bool retval;
319
2
  do {
320
3
    retval = sql_remaining_rows.FetchRow();
321
3
    if (!retval)
322
      return false;
323
3
    int64_t count = sql_remaining_rows.RetrieveInt64(0);
324
3
    assert(count >= 0);
325
3
    if (count == 0)
326
1
      break;
327
2
    retval = sql_remaining_rows.Reset();
328
2
    assert(retval);
329
330
2
    retval = sql_fix_parent_pointers.Execute();
331
2
    if (!retval)
332
      return false;
333
2
    retval = sql_fix_parent_pointers.Reset();
334
2
    assert(retval);
335
  } while (true);
336
337
  sqlite::Sql sql_remove_branches(database_->sqlite_db(),
338
    "DELETE FROM branches "
339
1
    "WHERE branch NOT IN (SELECT DISTINCT branch FROM tags);");
340
1
  retval = sql_remove_branches.Execute();
341
1
  return retval;
342
}
343
344
345
10
bool SqliteHistory::ListBranches(vector<Branch> *branches) const {
346
47
  while (list_branches_->FetchRow()) {
347
27
    branches->push_back(list_branches_->RetrieveBranch());
348
  }
349
350
10
  return list_branches_->Reset();
351
}
352
353
354
13
bool SqliteHistory::ListRecycleBin(std::vector<shash::Any> *hashes) const {
355
13
  assert(database_);
356
357
13
  if (!database_->ContainsRecycleBin()) {
358
2
    return false;
359
  }
360
361
11
  assert(NULL != hashes);
362
11
  hashes->clear();
363
23
  while (recycle_list_->FetchRow()) {
364
1
    hashes->push_back(recycle_list_->RetrieveHash());
365
  }
366
367
11
  return recycle_list_->Reset();
368
}
369
370
371
4
bool SqliteHistory::EmptyRecycleBin() {
372
4
  assert(database_);
373
4
  assert(IsWritable());
374
4
  assert(recycle_empty_.IsValid());
375
  return recycle_empty_->Execute() &&
376

4
         recycle_empty_->Reset();
377
}
378
379
380
3
bool SqliteHistory::Rollback(const Tag &updated_target_tag) {
381
3
  assert(database_);
382
3
  assert(IsWritable());
383
3
  assert(rollback_tag_.IsValid());
384
385
3
  Tag old_target_tag;
386
3
  bool success = false;
387
388
  // open a transaction (if non open yet)
389
3
  const bool need_to_commit = BeginTransaction();
390
391
  // retrieve the old version of the target tag from the history
392
3
  success = GetByName(updated_target_tag.name, &old_target_tag);
393
3
  if (!success) {
394
    LogCvmfs(kLogHistory, kLogDebug, "failed to retrieve old target tag '%s'",
395
1
                                     updated_target_tag.name.c_str());
396
1
    return false;
397
  }
398
399
  // sanity checks
400
2
  assert(old_target_tag.channel     == updated_target_tag.channel);
401
2
  assert(old_target_tag.description == updated_target_tag.description);
402
403
  // rollback the history to the target tag
404
  // (essentially removing all intermediate tags + the old target tag)
405
  success = rollback_tag_->BindTargetTag(old_target_tag) &&
406
            rollback_tag_->Execute()                     &&
407

2
            rollback_tag_->Reset();
408

2
  if (!success || Exists(old_target_tag.name)) {
409
    LogCvmfs(kLogHistory, kLogDebug, "failed to remove intermediate tags in "
410
                                     "channel '%d' until '%s' - '%d'",
411
                                     old_target_tag.channel,
412
                                     old_target_tag.name.c_str(),
413
                                     old_target_tag.revision);
414
    return false;
415
  }
416
417
  // insert the provided updated target tag into the history concluding the
418
  // rollback operation
419
2
  success = Insert(updated_target_tag);
420
2
  if (!success) {
421
    LogCvmfs(kLogHistory, kLogDebug, "failed to insert updated target tag '%s'",
422
                                     updated_target_tag.name.c_str());
423
    return false;
424
  }
425
426
2
  if (need_to_commit) {
427
    success = CommitTransaction();
428
    assert(success);
429
  }
430
431
2
  return true;
432
}
433
434
435
6
bool SqliteHistory::ListTagsAffectedByRollback(
436
                                            const std::string  &target_tag_name,
437
                                            std::vector<Tag>   *tags) const {
438
  // retrieve the old version of the target tag from the history
439
6
  Tag target_tag;
440
6
  if (!GetByName(target_tag_name, &target_tag)) {
441
    LogCvmfs(kLogHistory, kLogDebug, "failed to retrieve target tag '%s'",
442
1
                                     target_tag_name.c_str());
443
1
    return false;
444
  }
445
446
  // prepage listing command to find affected tags for a potential rollback
447
5
  if (!list_rollback_tags_->BindTargetTag(target_tag)) {
448
    LogCvmfs(kLogHistory, kLogDebug,
449
             "failed to prepare rollback listing query");
450
    return false;
451
  }
452
453
  // run the listing and return the results
454
5
  return RunListing(tags, list_rollback_tags_.weak_ref());
455
}
456
457
458
2
bool SqliteHistory::GetHashes(std::vector<shash::Any> *hashes) const {
459
2
  assert(database_);
460
2
  assert(NULL != hashes);
461
462
2005
  while (get_hashes_->FetchRow()) {
463
2001
    hashes->push_back(get_hashes_->RetrieveHash());
464
  }
465
466
2
  return get_hashes_->Reset();
467
}
468
469
470
10
void SqliteHistory::TakeDatabaseFileOwnership() {
471
10
  assert(database_);
472
10
  database_->TakeFileOwnership();
473
10
}
474
475
476
void SqliteHistory::DropDatabaseFileOwnership() {
477
  assert(database_);
478
  database_->DropFileOwnership();
479
}
480
481

45
}  // namespace history