GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/history_sql.cc Lines: 135 151 89.4 %
Date: 2019-02-03 02:48:13 Branches: 182 386 47.2 %

Line Branch Exec Source
1
/**
2
 * This file is part of the CernVM File System.
3
 */
4
5
#include "history_sql.h"
6
7
#include <cassert>
8
9
#include "util/string.h"
10
11
namespace history {
12
13
const float    HistoryDatabase::kLatestSchema          = 1.0;
14
const float    HistoryDatabase::kLatestSupportedSchema = 1.0;
15
const unsigned HistoryDatabase::kLatestSchemaRevision  = 3;
16
17
/**
18
 * Database Schema ChangeLog:
19
 *
20
 * Schema Version 1.0
21
 *   -> Revision 3: deprecate (flush) table 'recycle_bin'
22
 *                  add table 'branches'
23
 *                  add column 'branch' to table tags
24
 *   -> Revision 2: add table 'recycle_bin'
25
 *   -> Revision 1: add field 'size'
26
 *
27
 */
28
29
15
const std::string HistoryDatabase::kFqrnKey = "fqrn";
30
31
32
/**
33
 * This method creates a new database file and initializes the database schema.
34
 */
35
83
bool HistoryDatabase::CreateEmptyDatabase() {
36
83
  assert(read_write());
37
38
83
  sqlite::Sql sql_foreign_keys(sqlite_db(), "PRAGMA foreign_keys = ON;");
39
83
  if (!sql_foreign_keys.Execute())
40
    return false;
41
42
  return CreateBranchesTable() &&
43
         CreateTagsTable() &&
44

83
         CreateRecycleBinTable();
45
}
46
47
48
83
bool HistoryDatabase::CreateTagsTable() {
49
83
  assert(read_write());
50
  return sqlite::Sql(sqlite_db(),
51
    "CREATE TABLE tags (name TEXT, hash TEXT, revision INTEGER, "
52
    "  timestamp INTEGER, channel INTEGER, description TEXT, size INTEGER, "
53
    "  branch TEXT, CONSTRAINT pk_tags PRIMARY KEY (name), "
54
83
    "  FOREIGN KEY (branch) REFERENCES branches (branch));").Execute();
55
}
56
57
58
85
bool HistoryDatabase::CreateRecycleBinTable() {
59
85
  assert(read_write());
60
  return sqlite::Sql(sqlite_db(),
61
    "CREATE TABLE recycle_bin (hash TEXT, flags INTEGER, "
62
85
    "  CONSTRAINT pk_hash PRIMARY KEY (hash))").Execute();
63
}
64
65
66
86
bool HistoryDatabase::CreateBranchesTable() {
67
86
  assert(read_write());
68
69
  sqlite::Sql sql_create(sqlite_db(),
70
    "CREATE TABLE branches (branch TEXT, parent TEXT, initial_revision INTEGER,"
71
    "  CONSTRAINT pk_branch PRIMARY KEY (branch), "
72
    "  FOREIGN KEY (parent) REFERENCES branches (branch), "
73
    "  CHECK ((branch <> '') OR (parent IS NULL)), "
74
86
    "  CHECK ((branch = '') OR (parent IS NOT NULL)));");
75
86
  bool retval = sql_create.Execute();
76
86
  if (!retval)
77
    return false;
78
79
  sqlite::Sql sql_init(sqlite_db(),
80
    "INSERT INTO branches (branch, parent, initial_revision) "
81
86
    "VALUES ('', NULL, 0);");
82
86
  retval = sql_init.Execute();
83
86
  return retval;
84
}
85
86
87
83
bool HistoryDatabase::InsertInitialValues(const std::string &repository_name) {
88
83
  assert(read_write());
89
83
  return this->SetProperty(kFqrnKey, repository_name);
90
}
91
92
93
130
bool HistoryDatabase::ContainsRecycleBin() const {
94

130
  return schema_version() >= 1.0 - kSchemaEpsilon && schema_revision() >= 2;
95
}
96
97
98
34
bool HistoryDatabase::CheckSchemaCompatibility() {
99
  return !((schema_version() < kLatestSupportedSchema - kSchemaEpsilon) ||
100

34
           (schema_version() > kLatestSchema          + kSchemaEpsilon));
101
}
102
103
104
8
bool HistoryDatabase::LiveSchemaUpgradeIfNecessary() {
105
8
  assert(read_write());
106
8
  assert(IsEqualSchema(schema_version(), 1.0));
107
108
8
  sqlite::Sql sql_foreign_keys(sqlite_db(), "PRAGMA foreign_keys = ON;");
109
8
  if (!sql_foreign_keys.Execute())
110
    return false;
111
8
  if (schema_revision() == kLatestSchemaRevision) {
112
5
    return true;
113
  }
114
115
  LogCvmfs(kLogHistory, kLogDebug, "upgrading history schema revision "
116
                                   "%.2f (Rev: %d) to %.2f (Rev: %d)",
117
           schema_version(), schema_revision(),
118
3
           kLatestSchema, kLatestSchemaRevision);
119
120
  const bool success = UpgradeSchemaRevision_10_1() &&
121
                       UpgradeSchemaRevision_10_2() &&
122

3
                       UpgradeSchemaRevision_10_3();
123
124

3
  return success && StoreSchemaRevision();
125
}
126
127
128
3
bool HistoryDatabase::UpgradeSchemaRevision_10_1() {
129
3
  if (schema_revision() > 0) {
130
2
    return true;
131
  }
132
133
1
  sqlite::Sql sql_upgrade(sqlite_db(), "ALTER TABLE tags ADD size INTEGER;");
134
1
  if (!sql_upgrade.Execute()) {
135
    LogCvmfs(kLogHistory, kLogStderr, "failed to upgrade tags table");
136
    return false;
137
  }
138
139
1
  set_schema_revision(1);
140
1
  return true;
141
}
142
143
144
3
bool HistoryDatabase::UpgradeSchemaRevision_10_2() {
145
3
  if (schema_revision() > 1) {
146
1
    return true;
147
  }
148
149
2
  if (!CreateRecycleBinTable()) {
150
    LogCvmfs(kLogHistory, kLogStderr, "failed to upgrade history database");
151
    return false;
152
  }
153
154
2
  set_schema_revision(2);
155
2
  return true;
156
}
157
158
159
3
bool HistoryDatabase::UpgradeSchemaRevision_10_3() {
160
3
  if (schema_revision() > 2) {
161
    return true;
162
  }
163
164
3
  if (!CreateBranchesTable()) {
165
    LogCvmfs(kLogHistory, kLogStderr, "failed to create branches table");
166
    return false;
167
  }
168
169
  sqlite::Sql sql_upgrade(sqlite_db(),
170
3
    "ALTER TABLE tags ADD branch TEXT REFERENCES branches (branch);");
171
3
  if (!sql_upgrade.Execute()) {
172
    LogCvmfs(kLogHistory, kLogStderr, "failed to upgrade tags table");
173
    return false;
174
  }
175
176
3
  sqlite::Sql sql_fill(sqlite_db(), "UPDATE tags SET branch = '';");
177
3
  if (!sql_fill.Execute()) {
178
    LogCvmfs(kLogHistory, kLogStderr, "failed to set branch default value");
179
    return false;
180
  }
181
182
  // We keep the table in the schema for backwards compatibility
183
3
  sqlite::Sql sql_flush(sqlite_db(), "DELETE FROM recycle_bin; VACUUM;");
184
3
  if (!sql_flush.Execute()) {
185
    LogCvmfs(kLogHistory, kLogStderr, "failed to flush recycle bin table");
186
    return false;
187
  }
188
189
3
  set_schema_revision(3);
190
3
  return true;
191
}
192
193
194
//------------------------------------------------------------------------------
195
196
#define DB_FIELDS_V1R0  "name, hash, revision, timestamp, channel, " \
197
                        "description, 0, ''"
198
#define DB_FIELDS_V1R1  "name, hash, revision, timestamp, channel, " \
199
                        "description, size, ''"
200
#define DB_FIELDS_V1R3  "name, hash, revision, timestamp, channel, " \
201
                        "description, size, branch"
202
#define DB_PLACEHOLDERS ":name, :hash, :revision, :timestamp, :channel, " \
203
                        ":description, :size, :branch"
204
#define ROLLBACK_COND   "(revision > :target_rev  OR " \
205
                        " name = :target_name) " \
206
                        "AND channel = :target_chan " \
207
                        "AND branch = ''"
208
209
#define MAKE_STATEMENT(STMT_TMPL, REV)       \
210
static const std::string REV =               \
211
  ReplaceAll(                                \
212
    ReplaceAll(                              \
213
      ReplaceAll(STMT_TMPL,                  \
214
        "@DB_FIELDS@", DB_FIELDS_ ## REV),   \
215
      "@DB_PLACEHOLDERS@", DB_PLACEHOLDERS), \
216
    "@ROLLBACK_COND@", ROLLBACK_COND)
217
218
#define MAKE_STATEMENTS(STMT_TMPL) \
219
  MAKE_STATEMENT(STMT_TMPL, V1R0); \
220
  MAKE_STATEMENT(STMT_TMPL, V1R1); \
221
  MAKE_STATEMENT(STMT_TMPL, V1R3)
222
223
#define DEFERRED_INIT(DB, REV) \
224
  DeferredInit((DB)->sqlite_db(), (REV).c_str())
225
226
#define DEFERRED_INITS(DB) \
227
  if ((DB)->IsEqualSchema((DB)->schema_version(), 1.0f) && \
228
      (DB)->schema_revision() == 0) {                      \
229
    DEFERRED_INIT((DB), V1R0);                             \
230
  } else if ((DB)->schema_revision() < 3) {                \
231
    DEFERRED_INIT((DB), V1R1);                             \
232
  } else {                                                 \
233
    DEFERRED_INIT((DB), V1R3);                             \
234
  }
235
236
91
SqlInsertTag::SqlInsertTag(const HistoryDatabase *database) {
237






91
  MAKE_STATEMENTS("INSERT INTO tags (@DB_FIELDS@) VALUES (@DB_PLACEHOLDERS@);");
238


91
  DEFERRED_INITS(database);
239
}
240
241
242
4268
bool SqlInsertTag::BindTag(const History::Tag &tag) {
243
  return
244
    BindText(1, tag.name) &&
245
    BindTextTransient(2, tag.root_hash.ToString()) &&  // temporary (ToString)
246
    BindInt64(3, tag.revision) &&
247
    BindInt64(4, tag.timestamp) &&
248
    BindInt64(5, tag.channel) &&
249
    BindText(6, tag.description) &&
250
    BindInt64(7, tag.size) &&
251





4268
    BindText(8, tag.branch);
252
}
253
254
255
//------------------------------------------------------------------------------
256
257
258
91
SqlRemoveTag::SqlRemoveTag(const HistoryDatabase *database) {
259
91
  DeferredInit(database->sqlite_db(), "DELETE FROM tags WHERE name = :name;");
260
}
261
262
12
bool SqlRemoveTag::BindName(const std::string &name) {
263
12
  return BindText(1, name);
264
}
265
266
267
//------------------------------------------------------------------------------
268
269
270
117
SqlFindTag::SqlFindTag(const HistoryDatabase *database) {
271






117
  MAKE_STATEMENTS("SELECT @DB_FIELDS@ FROM tags WHERE name = :name;");
272


117
  DEFERRED_INITS(database);
273
}
274
275
72
bool SqlFindTag::BindName(const std::string &name) {
276
72
  return BindText(1, name);
277
}
278
279
280
//------------------------------------------------------------------------------
281
282
283
117
SqlFindTagByDate::SqlFindTagByDate(const HistoryDatabase *database) {
284
  // figure out the tag that was HEAD to a given point in time
285
  //
286
  // conceptually goes back in the revision history  |  ORDER BY revision DESC
287
  // and picks the first tag                         |  LIMIT 1
288
  // that is older than the given timestamp          |  WHICH timestamp <= :ts
289
  MAKE_STATEMENTS("SELECT @DB_FIELDS@ FROM tags "
290
                  "WHERE (branch = '') AND (timestamp <= :timestamp) "
291






117
                  "ORDER BY revision DESC LIMIT 1;");
292


117
  DEFERRED_INITS(database);
293
}
294
295
8
bool SqlFindTagByDate::BindTimestamp(const time_t timestamp) {
296
8
  return BindInt64(1, timestamp);
297
}
298
299
300
//------------------------------------------------------------------------------
301
302
303
91
SqlFindBranchHead::SqlFindBranchHead(const HistoryDatabase *database) {
304
  // One of the tags with the highest revision on a given branch
305
  // Doesn't work on older database revisions
306
  MAKE_STATEMENTS("SELECT @DB_FIELDS@ FROM tags "
307
                  "WHERE (branch = :branch) "
308






91
                  "ORDER BY revision DESC LIMIT 1;");
309


91
  DEFERRED_INITS(database);
310
}
311
312
3
bool SqlFindBranchHead::BindBranchName(const std::string &branch_name) {
313
3
  return BindText(1, branch_name);
314
}
315
316
317
//------------------------------------------------------------------------------
318
319
320
117
SqlCountTags::SqlCountTags(const HistoryDatabase *database) {
321
117
  DeferredInit(database->sqlite_db(), "SELECT count(*) FROM tags;");
322
}
323
324
29
unsigned SqlCountTags::RetrieveCount() const {
325
29
  int64_t count = RetrieveInt64(0);
326
29
  assert(count >= 0);
327
29
  return static_cast<uint64_t>(count);
328
}
329
330
331
//------------------------------------------------------------------------------
332
333
334
117
SqlListTags::SqlListTags(const HistoryDatabase *database) {
335
  MAKE_STATEMENTS(
336






117
    "SELECT @DB_FIELDS@ FROM tags ORDER BY timestamp DESC, revision DESC;");
337


117
  DEFERRED_INITS(database);
338
}
339
340
341
//------------------------------------------------------------------------------
342
343
344
117
SqlGetChannelTips::SqlGetChannelTips(const HistoryDatabase *database) {
345
  MAKE_STATEMENTS("SELECT @DB_FIELDS@, MAX(revision) AS max_rev "
346
                  "FROM tags "
347
                  "WHERE branch = '' "
348






117
                  "GROUP BY channel;");
349


117
  DEFERRED_INITS(database);
350
}
351
352
117
SqlGetHashes::SqlGetHashes(const HistoryDatabase *database) {
353
  DeferredInit(database->sqlite_db(), "SELECT DISTINCT hash FROM tags "
354
117
                                      "ORDER BY timestamp, revision ASC");
355
}
356
357
2001
shash::Any SqlGetHashes::RetrieveHash() const {
358
  return shash::MkFromHexPtr(shash::HexPtr(RetrieveString(0)),
359
2001
                             shash::kSuffixCatalog);
360
}
361
362
363
//------------------------------------------------------------------------------
364
365
366
91
SqlRollbackTag::SqlRollbackTag(const HistoryDatabase *database) {
367






91
  MAKE_STATEMENTS("DELETE FROM tags WHERE @ROLLBACK_COND@;");
368


91
  DEFERRED_INITS(database);
369
}
370
371
372
//------------------------------------------------------------------------------
373
374
375
117
SqlListRollbackTags::SqlListRollbackTags(const HistoryDatabase *database) {
376
  MAKE_STATEMENTS("SELECT @DB_FIELDS@ FROM tags "
377
                  "WHERE @ROLLBACK_COND@ "
378






117
                  "ORDER BY revision DESC;");
379


117
  DEFERRED_INITS(database);
380
}
381
382
383
//------------------------------------------------------------------------------
384
385
386
117
SqlListBranches::SqlListBranches(const HistoryDatabase *database) {
387
117
  if (database->schema_revision() < 3)
388
3
    DeferredInit(database->sqlite_db(), "SELECT '', NULL, 0;");
389
  else
390
    DeferredInit(database->sqlite_db(),
391
114
      "SELECT branch, parent, initial_revision FROM branches;");
392
}
393
394
395
27
History::Branch SqlListBranches::RetrieveBranch() const {
396
27
  std::string branch = RetrieveString(0);
397
  std::string parent =
398

27
    (RetrieveType(1) == SQLITE_NULL) ? "" : RetrieveString(1);
399
27
  unsigned initial_revision = RetrieveInt64(2);
400
27
  return History::Branch(branch, parent, initial_revision);
401
}
402
403
404
//------------------------------------------------------------------------------
405
406
407
91
SqlInsertBranch::SqlInsertBranch(const HistoryDatabase *database) {
408
  DeferredInit(database->sqlite_db(),
409
    "INSERT INTO branches (branch, parent, initial_revision) "
410
91
    "VALUES (:branch, :parent, :initial_revision);");
411
}
412
413
414
18
bool SqlInsertBranch::BindBranch(const History::Branch &branch) {
415
  return
416
    BindText(1, branch.branch) &&
417
    BindText(2, branch.parent) &&
418

18
    BindInt64(3, branch.initial_revision);
419
}
420
421
422
//------------------------------------------------------------------------------
423
424
425
206
bool SqlRecycleBin::CheckSchema(const HistoryDatabase *database) const {
426
  return (database->IsEqualSchema(database->schema_version(), 1.0)) &&
427

206
         (database->schema_revision() >= 2);
428
}
429
430
431
//------------------------------------------------------------------------------
432
433
434
115
SqlRecycleBinList::SqlRecycleBinList(const HistoryDatabase *database) {
435
115
  assert(CheckSchema(database));
436
115
  DeferredInit(database->sqlite_db(), "SELECT hash, flags FROM recycle_bin;");
437
}
438
439
440
1
shash::Any SqlRecycleBinList::RetrieveHash() {
441
1
  const unsigned int flags = RetrieveInt64(1);
442
1
  shash::Suffix suffix = shash::kSuffixNone;
443
1
  if (flags & SqlRecycleBin::kFlagCatalog) {
444
1
    suffix = shash::kSuffixCatalog;
445
  }
446
447
1
  return shash::MkFromHexPtr(shash::HexPtr(RetrieveString(0)), suffix);
448
}
449
450
451
//------------------------------------------------------------------------------
452
453
454
91
SqlRecycleBinFlush::SqlRecycleBinFlush(const HistoryDatabase *database) {
455
91
  assert(CheckSchema(database));
456
91
  DeferredInit(database->sqlite_db(), "DELETE FROM recycle_bin;");
457
}
458
459
460

45
}; /* namespace history */