GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
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 |
Generated by: GCOVR (Version 4.1) |