Drop the obsolete snapshot_contents table from the local database.
[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 /* Helper function to prepare a statement for execution in the current
26  * database. */
27 sqlite3_stmt *LocalDb::Prepare(const char *sql)
28 {
29     sqlite3_stmt *stmt;
30     int rc;
31     const char *tail;
32
33     rc = sqlite3_prepare_v2(db, sql, strlen(sql), &stmt, &tail);
34     if (rc != SQLITE_OK) {
35         ReportError(rc);
36         throw IOException(string("Error preparing statement: ") + sql);
37     }
38
39     return stmt;
40 }
41
42 void LocalDb::ReportError(int rc)
43 {
44     fprintf(stderr, "Result code: %d\n", rc);
45     fprintf(stderr, "Error message: %s\n", sqlite3_errmsg(db));
46 }
47
48 void LocalDb::Open(const char *path, const char *snapshot_name,
49                    const char *snapshot_scheme)
50 {
51     int rc;
52
53     rc = sqlite3_open(path, &db);
54     if (rc) {
55         fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
56         sqlite3_close(db);
57         throw IOException("Error opening local database");
58     }
59
60     rc = sqlite3_exec(db, "begin", NULL, NULL, NULL);
61     if (rc) {
62         fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
63         sqlite3_close(db);
64         throw IOException("Error starting transaction");
65     }
66
67     sqlite3_extended_result_codes(db, 1);
68
69     /* Insert this snapshot into the database, and determine the integer key
70      * which will be used to identify it. */
71     sqlite3_stmt *stmt = Prepare("insert into "
72                                  "snapshots(name, scheme, timestamp) "
73                                  "values (?, ?, julianday('now'))");
74     sqlite3_bind_text(stmt, 1, snapshot_name, strlen(snapshot_name),
75                       SQLITE_TRANSIENT);
76     if (snapshot_scheme == NULL)
77         sqlite3_bind_null(stmt, 2);
78     else
79         sqlite3_bind_text(stmt, 2, snapshot_scheme, strlen(snapshot_scheme),
80                           SQLITE_TRANSIENT);
81
82     rc = sqlite3_step(stmt);
83     if (rc != SQLITE_DONE) {
84         ReportError(rc);
85         sqlite3_close(db);
86         throw IOException("Database execution error!");
87     }
88
89     snapshotid = sqlite3_last_insert_rowid(db);
90     sqlite3_finalize(stmt);
91     if (snapshotid == 0) {
92         ReportError(rc);
93         sqlite3_close(db);
94         throw IOException("Find snapshot id");
95     }
96
97     /* Create a temporary table which will be used to keep track of the objects
98      * used by this snapshot.  When the database is closed, we will summarize
99      * the results of this table into segments_used. */
100     rc = sqlite3_exec(db,
101                       "create temporary table snapshot_refs ("
102                       "    segmentid integer not null,"
103                       "    object text not null,"
104                       "    size integer not null"
105                       ")", NULL, NULL, NULL);
106     if (rc != SQLITE_OK) {
107         ReportError(rc);
108         sqlite3_close(db);
109         throw IOException("Database initialization");
110     }
111     rc = sqlite3_exec(db,
112                       "create unique index snapshot_refs_index "
113                       "on snapshot_refs(segmentid, object)",
114                       NULL, NULL, NULL);
115     if (rc != SQLITE_OK) {
116         ReportError(rc);
117         sqlite3_close(db);
118         throw IOException("Database initialization");
119     }
120 }
121
122 void LocalDb::Close()
123 {
124     int rc;
125
126     /* Summarize the snapshot_refs table into segments_used. */
127     sqlite3_stmt *stmt = Prepare("insert into segments_used "
128                                  "select ? as snapshotid, segmentid, "
129                                  "cast(used as real) / size as utilization "
130                                  "from "
131                                  "(select segmentid, sum(size) as used "
132                                  "from snapshot_refs group by segmentid) "
133                                  "join segments using (segmentid)");
134     sqlite3_bind_int64(stmt, 1, snapshotid);
135     rc = sqlite3_step(stmt);
136     if (rc != SQLITE_OK && rc != SQLITE_DONE) {
137         ReportError(rc);
138         sqlite3_close(db);
139         fprintf(stderr, "DATABASE ERROR: Unable to create segment summary!\n");
140     }
141     sqlite3_finalize(stmt);
142
143     /* Commit changes to the database and close. */
144     rc = sqlite3_exec(db, "commit", NULL, NULL, NULL);
145     if (rc != SQLITE_OK) {
146         fprintf(stderr, "DATABASE ERROR: Can't commit database!\n");
147         ReportError(rc);
148     }
149     sqlite3_close(db);
150 }
151
152 int64_t LocalDb::SegmentToId(const string &segment)
153 {
154     int rc;
155     sqlite3_stmt *stmt;
156     int64_t result;
157
158     stmt = Prepare("insert or ignore into segments(segment) values (?)");
159     sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
160                       SQLITE_TRANSIENT);
161     rc = sqlite3_step(stmt);
162     if (rc != SQLITE_DONE) {
163         throw IOException("Could not execute INSERT statement!");
164     }
165     sqlite3_finalize(stmt);
166
167     stmt = Prepare("select segmentid from segments where segment = ?");
168     sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
169                       SQLITE_TRANSIENT);
170
171     rc = sqlite3_step(stmt);
172     if (rc == SQLITE_DONE) {
173         throw IOException("No segment found by id");
174     } else if (rc == SQLITE_ROW) {
175         result = sqlite3_column_int64(stmt, 0);
176     } else {
177         throw IOException("Error executing find segment by id query");
178     }
179
180     sqlite3_finalize(stmt);
181
182     return result;
183 }
184
185 string LocalDb::IdToSegment(int64_t segmentid)
186 {
187     int rc;
188     sqlite3_stmt *stmt;
189     string result;
190
191     stmt = Prepare("select segment from segments where segmentid = ?");
192     sqlite3_bind_int64(stmt, 1, segmentid);
193
194     rc = sqlite3_step(stmt);
195     if (rc == SQLITE_DONE) {
196         throw IOException("No segment found by id");
197     } else if (rc == SQLITE_ROW) {
198         result = (const char *)sqlite3_column_text(stmt, 0);
199     } else {
200         throw IOException("Error executing find segment by id query");
201     }
202
203     sqlite3_finalize(stmt);
204
205     return result;
206 }
207
208 void LocalDb::StoreObject(const ObjectReference& ref,
209                           const string &checksum, int64_t size,
210                           double age)
211 {
212     int rc;
213     sqlite3_stmt *stmt;
214
215     if (age == 0.0) {
216         stmt = Prepare("insert into block_index("
217                        "segmentid, object, checksum, size, timestamp) "
218                        "values (?, ?, ?, ?, julianday('now'))");
219     } else {
220         stmt = Prepare("insert into block_index("
221                        "segmentid, object, checksum, size, timestamp) "
222                        "values (?, ?, ?, ?, ?)");
223     }
224
225     sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
226     string obj = ref.get_sequence();
227     sqlite3_bind_text(stmt, 2, obj.c_str(), obj.size(), SQLITE_TRANSIENT);
228     sqlite3_bind_text(stmt, 3, checksum.c_str(), checksum.size(),
229                       SQLITE_TRANSIENT);
230     sqlite3_bind_int64(stmt, 4, size);
231     if (age != 0.0)
232         sqlite3_bind_double(stmt, 5, age);
233
234     rc = sqlite3_step(stmt);
235     if (rc != SQLITE_DONE) {
236         fprintf(stderr, "Could not execute INSERT statement!\n");
237         ReportError(rc);
238     }
239
240     sqlite3_finalize(stmt);
241 }
242
243 ObjectReference LocalDb::FindObject(const string &checksum, int64_t size)
244 {
245     int rc;
246     sqlite3_stmt *stmt;
247     ObjectReference ref;
248
249     stmt = Prepare("select segmentid, object from block_index "
250                    "where checksum = ? and size = ? and expired is null");
251     sqlite3_bind_text(stmt, 1, checksum.c_str(), checksum.size(),
252                       SQLITE_TRANSIENT);
253     sqlite3_bind_int64(stmt, 2, size);
254
255     rc = sqlite3_step(stmt);
256     if (rc == SQLITE_DONE) {
257     } else if (rc == SQLITE_ROW) {
258         ref = ObjectReference(IdToSegment(sqlite3_column_int64(stmt, 0)),
259                               (const char *)sqlite3_column_text(stmt, 1));
260     } else {
261         fprintf(stderr, "Could not execute SELECT statement!\n");
262         ReportError(rc);
263     }
264
265     sqlite3_finalize(stmt);
266
267     return ref;
268 }
269
270 bool LocalDb::IsOldObject(const string &checksum, int64_t size, double *age,
271                           int *group)
272 {
273     int rc;
274     sqlite3_stmt *stmt;
275     bool found = false;
276
277     stmt = Prepare("select segmentid, object, timestamp, expired "
278                    "from block_index where checksum = ? and size = ?");
279     sqlite3_bind_text(stmt, 1, checksum.c_str(), checksum.size(),
280                       SQLITE_TRANSIENT);
281     sqlite3_bind_int64(stmt, 2, size);
282
283     rc = sqlite3_step(stmt);
284     if (rc == SQLITE_DONE) {
285         found = false;
286     } else if (rc == SQLITE_ROW) {
287         found = true;
288         *age = sqlite3_column_double(stmt, 2);
289         *group = sqlite3_column_int(stmt, 3);
290     } else {
291         fprintf(stderr, "Could not execute SELECT statement!\n");
292         ReportError(rc);
293     }
294
295     sqlite3_finalize(stmt);
296
297     return found;
298 }
299
300 /* Does this object still exist in the database (and not expired)? */
301 bool LocalDb::IsAvailable(const ObjectReference &ref)
302 {
303     int rc;
304     sqlite3_stmt *stmt;
305     bool found = false;
306
307     stmt = Prepare("select count(*) from block_index "
308                    "where segmentid = ? and object = ? and expired is null");
309     sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
310     sqlite3_bind_text(stmt, 2, ref.get_sequence().c_str(),
311                       ref.get_sequence().size(), SQLITE_TRANSIENT);
312
313     rc = sqlite3_step(stmt);
314     if (rc == SQLITE_DONE) {
315         found = false;
316     } else if (rc == SQLITE_ROW) {
317         if (sqlite3_column_int(stmt, 0) > 0)
318             found = true;
319     } else {
320         fprintf(stderr, "Could not execute SELECT statement!\n");
321         ReportError(rc);
322     }
323
324     sqlite3_finalize(stmt);
325
326     return found;
327 }
328
329 void LocalDb::UseObject(const ObjectReference& ref)
330 {
331     int rc;
332     sqlite3_stmt *stmt;
333
334     stmt = Prepare("insert or ignore into snapshot_refs "
335                    "select segmentid, object, size from block_index "
336                    "where segmentid = ? and object = ?");
337     sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
338     string obj = ref.get_sequence();
339     sqlite3_bind_text(stmt, 2, obj.c_str(), obj.size(), SQLITE_TRANSIENT);
340
341     rc = sqlite3_step(stmt);
342     if (rc != SQLITE_DONE) {
343         fprintf(stderr, "Could not execute INSERT statement!\n");
344         ReportError(rc);
345     }
346
347     sqlite3_finalize(stmt);
348 }
349
350 void LocalDb::SetSegmentChecksum(const std::string &segment,
351                                  const std::string &path,
352                                  const std::string &checksum)
353 {
354     int rc;
355     sqlite3_stmt *stmt;
356
357     stmt = Prepare("update segments set path = ?, checksum = ?, "
358                    "size = (select sum(size) from block_index "
359                    "        where segmentid = ?) "
360                    "where segmentid = ?");
361     sqlite3_bind_text(stmt, 1, path.c_str(), path.size(),
362                       SQLITE_TRANSIENT);
363     sqlite3_bind_text(stmt, 2, checksum.c_str(), checksum.size(),
364                       SQLITE_TRANSIENT);
365     sqlite3_bind_int64(stmt, 3, SegmentToId(segment));
366     sqlite3_bind_int64(stmt, 4, SegmentToId(segment));
367
368     rc = sqlite3_step(stmt);
369     if (rc != SQLITE_DONE) {
370         fprintf(stderr, "Could not update segment checksum in database!\n");
371         ReportError(rc);
372     }
373
374     sqlite3_finalize(stmt);
375 }
376
377 bool LocalDb::GetSegmentChecksum(const string &segment,
378                                  string *seg_path,
379                                  string *seg_checksum)
380 {
381     int rc;
382     sqlite3_stmt *stmt;
383     ObjectReference ref;
384     int found = false;
385
386     stmt = Prepare("select path, checksum from segments where segment = ?");
387     sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
388                       SQLITE_TRANSIENT);
389
390     rc = sqlite3_step(stmt);
391     if (rc == SQLITE_DONE) {
392     } else if (rc == SQLITE_ROW) {
393         found = true;
394         const char *val;
395
396         val = (const char *)sqlite3_column_text(stmt, 0);
397         if (val == NULL)
398             found = false;
399         else
400             *seg_path = val;
401
402         val = (const char *)sqlite3_column_text(stmt, 1);
403         if (val == NULL)
404             found = false;
405         else
406             *seg_checksum = val;
407     } else {
408         fprintf(stderr, "Could not execute SELECT statement!\n");
409         ReportError(rc);
410     }
411
412     sqlite3_finalize(stmt);
413
414     return found;
415 }