Differentiate between never-before-seen objects and seen-but-expired.
[cumulus.git] / localdb.cc
1 /* LBS: An LFS-inspired filesystem backup system
2  * Copyright (C) 2007  Michael Vrable
3  *
4  * When creating backup snapshots, maintain a local database of data blocks and
5  * checksums, in addition to the data contents (which may be stored remotely).
6  * This database is consulted when attempting to build incremental snapshots,
7  * as it says which objects can be reused.
8  *
9  * The database is implemented as an SQLite3 database, but this implementation
10  * detail is kept internal to this file, so that the storage format may be
11  * changed later. */
12
13 #include <assert.h>
14 #include <stdio.h>
15 #include <string.h>
16 #include <sqlite3.h>
17
18 #include <string>
19
20 #include "localdb.h"
21 #include "store.h"
22
23 using std::string;
24
25 void LocalDb::Open(const char *path, const char *snapshot_name)
26 {
27     int rc;
28
29     rc = sqlite3_open(path, &db);
30     if (rc) {
31         fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
32         sqlite3_close(db);
33         throw IOException("Error opening local database");
34     }
35
36     rc = sqlite3_exec(db, "begin", NULL, NULL, NULL);
37     if (rc) {
38         fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
39         sqlite3_close(db);
40         throw IOException("Error starting transaction");
41     }
42
43     /* Insert this snapshot into the database, and determine the integer key
44      * which will be used to identify it. */
45     sqlite3_stmt *stmt;
46     static const char s[] =
47         "insert into snapshots(name, timestamp) "
48         "values (?, julianday('now'))";
49     const char *tail;
50
51     rc = sqlite3_prepare_v2(db, s, strlen(s), &stmt, &tail);
52     if (rc != SQLITE_OK) {
53         sqlite3_close(db);
54         throw IOException("Error adding snapshot");
55     }
56
57     sqlite3_bind_text(stmt, 1, snapshot_name, strlen(snapshot_name),
58                       SQLITE_TRANSIENT);
59
60     rc = sqlite3_step(stmt);
61     if (rc != SQLITE_DONE) {
62         sqlite3_close(db);
63         throw IOException("Database execution error!");
64     }
65
66     snapshotid = sqlite3_last_insert_rowid(db);
67     sqlite3_finalize(stmt);
68     if (snapshotid == 0) {
69         sqlite3_close(db);
70         throw IOException("Find snapshot id");
71     }
72 }
73
74 void LocalDb::Close()
75 {
76     int rc;
77     rc = sqlite3_exec(db, "commit", NULL, NULL, NULL);
78     if (rc != SQLITE_OK) {
79         fprintf(stderr, "Can't commit database!\n");
80     }
81     sqlite3_close(db);
82 }
83
84 int64_t LocalDb::SegmentToId(const string &segment)
85 {
86     int rc;
87     sqlite3_stmt *stmt;
88     static const char s1[] =
89         "insert or ignore into segments(segment) values (?);";
90     static const char s2[] =
91         "select segmentid from segments where segment = ?";
92     const char *tail;
93     int64_t result;
94
95     rc = sqlite3_prepare_v2(db, s1, strlen(s1), &stmt, &tail);
96     if (rc != SQLITE_OK) {
97         throw IOException("Find id by segment name");
98     }
99     sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
100                       SQLITE_TRANSIENT);
101     rc = sqlite3_step(stmt);
102     if (rc != SQLITE_DONE) {
103         throw IOException("Could not execute INSERT statement!");
104     }
105     sqlite3_finalize(stmt);
106
107     rc = sqlite3_prepare_v2(db, s2, strlen(s2), &stmt, &tail);
108     if (rc != SQLITE_OK) {
109         throw IOException("Find id by segment name");
110     }
111
112     sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
113                       SQLITE_TRANSIENT);
114
115     rc = sqlite3_step(stmt);
116     if (rc == SQLITE_DONE) {
117         throw IOException("No segment found by id");
118     } else if (rc == SQLITE_ROW) {
119         result = sqlite3_column_int64(stmt, 0);
120     } else {
121         throw IOException("Error executing find segment by id query");
122     }
123
124     sqlite3_finalize(stmt);
125
126     return result;
127 }
128
129 string LocalDb::IdToSegment(int64_t segmentid)
130 {
131     int rc;
132     sqlite3_stmt *stmt;
133     static const char s[] =
134         "select segment from segments where segmentid = ?";
135     const char *tail;
136     string result;
137
138     rc = sqlite3_prepare_v2(db, s, strlen(s), &stmt, &tail);
139     if (rc != SQLITE_OK) {
140         throw IOException("Find segment by id");
141     }
142
143     sqlite3_bind_int64(stmt, 1, segmentid);
144
145     rc = sqlite3_step(stmt);
146     if (rc == SQLITE_DONE) {
147         throw IOException("No segment found by id");
148     } else if (rc == SQLITE_ROW) {
149         result = (const char *)sqlite3_column_text(stmt, 0);
150     } else {
151         throw IOException("Error executing find segment by id query");
152     }
153
154     sqlite3_finalize(stmt);
155
156     return result;
157 }
158
159 void LocalDb::StoreObject(const ObjectReference& ref,
160                           const string &checksum, int64_t size)
161 {
162     int rc;
163     sqlite3_stmt *stmt;
164     static const char s[] =
165         "insert into "
166         "block_index(segmentid, object, checksum, size, timestamp) "
167         "values (?, ?, ?, ?, julianday('now'))";
168     const char *tail;
169
170     rc = sqlite3_prepare_v2(db, s, strlen(s), &stmt, &tail);
171     if (rc != SQLITE_OK) {
172         return;
173     }
174
175     sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
176     string obj = ref.get_sequence();
177     sqlite3_bind_text(stmt, 2, obj.c_str(), obj.size(), SQLITE_TRANSIENT);
178     sqlite3_bind_text(stmt, 3, checksum.c_str(), checksum.size(),
179                       SQLITE_TRANSIENT);
180     sqlite3_bind_int64(stmt, 4, size);
181
182     rc = sqlite3_step(stmt);
183     if (rc != SQLITE_DONE) {
184         fprintf(stderr, "Could not execute INSERT statement!\n");
185     }
186
187     sqlite3_finalize(stmt);
188 }
189
190 ObjectReference LocalDb::FindObject(const string &checksum, int64_t size)
191 {
192     int rc;
193     sqlite3_stmt *stmt;
194     static const char s[] =
195         "select segmentid, object from block_index "
196         "where checksum = ? and size = ? and expired is null";
197     const char *tail;
198
199     ObjectReference ref;
200
201     rc = sqlite3_prepare_v2(db, s, strlen(s), &stmt, &tail);
202     if (rc != SQLITE_OK) {
203         return ref;
204     }
205
206     sqlite3_bind_text(stmt, 1, checksum.c_str(), checksum.size(),
207                       SQLITE_TRANSIENT);
208     sqlite3_bind_int64(stmt, 2, size);
209
210     rc = sqlite3_step(stmt);
211     if (rc == SQLITE_DONE) {
212     } else if (rc == SQLITE_ROW) {
213         ref = ObjectReference(IdToSegment(sqlite3_column_int64(stmt, 0)),
214                               (const char *)sqlite3_column_text(stmt, 1));
215     } else {
216         fprintf(stderr, "Could not execute SELECT statement!\n");
217     }
218
219     sqlite3_finalize(stmt);
220
221     return ref;
222 }
223
224 bool LocalDb::IsOldObject(const string &checksum, int64_t size)
225 {
226     int rc;
227     sqlite3_stmt *stmt;
228     static const char s[] =
229         "select segmentid, object from block_index "
230         "where checksum = ? and size = ?";
231     const char *tail;
232
233     bool found = false;
234
235     rc = sqlite3_prepare_v2(db, s, strlen(s), &stmt, &tail);
236     if (rc != SQLITE_OK) {
237         return false;
238     }
239
240     sqlite3_bind_text(stmt, 1, checksum.c_str(), checksum.size(),
241                       SQLITE_TRANSIENT);
242     sqlite3_bind_int64(stmt, 2, size);
243
244     rc = sqlite3_step(stmt);
245     if (rc == SQLITE_DONE) {
246         found = false;
247     } else if (rc == SQLITE_ROW) {
248         found = true;
249     } else {
250         fprintf(stderr, "Could not execute SELECT statement!\n");
251     }
252
253     sqlite3_finalize(stmt);
254
255     return found;
256 }
257
258 void LocalDb::UseObject(const ObjectReference& ref)
259 {
260     int rc;
261     sqlite3_stmt *stmt;
262     static const char s[] =
263         "insert or ignore into snapshot_contents "
264         "select blockid, ? as snapshotid from block_index "
265         "where segmentid = ? and object = ?";
266     const char *tail;
267
268     rc = sqlite3_prepare_v2(db, s, strlen(s), &stmt, &tail);
269     if (rc != SQLITE_OK) {
270         return;
271     }
272
273     sqlite3_bind_int64(stmt, 1, snapshotid);
274     sqlite3_bind_int64(stmt, 2, SegmentToId(ref.get_segment()));
275     string obj = ref.get_sequence();
276     sqlite3_bind_text(stmt, 3, obj.c_str(), obj.size(), SQLITE_TRANSIENT);
277
278     rc = sqlite3_step(stmt);
279     if (rc != SQLITE_DONE) {
280         fprintf(stderr, "Could not execute INSERT statement!\n");
281     }
282
283     sqlite3_finalize(stmt);
284 }