Rename --name to --scheme, and store the scheme name in 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(db, sql, strlen(sql), &stmt, &tail);
34     if (rc != SQLITE_OK) {
35         throw IOException(string("Error preparing statement: ") + sql);
36     }
37
38     return stmt;
39 }
40
41 void LocalDb::Open(const char *path, const char *snapshot_name,
42                    const char *snapshot_scheme)
43 {
44     int rc;
45
46     rc = sqlite3_open(path, &db);
47     if (rc) {
48         fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
49         sqlite3_close(db);
50         throw IOException("Error opening local database");
51     }
52
53     rc = sqlite3_exec(db, "begin", NULL, NULL, NULL);
54     if (rc) {
55         fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
56         sqlite3_close(db);
57         throw IOException("Error starting transaction");
58     }
59
60     /* Insert this snapshot into the database, and determine the integer key
61      * which will be used to identify it. */
62     sqlite3_stmt *stmt = Prepare("insert into "
63                                  "snapshots(name, scheme, timestamp) "
64                                  "values (?, ?, julianday('now'))");
65     sqlite3_bind_text(stmt, 1, snapshot_name, strlen(snapshot_name),
66                       SQLITE_TRANSIENT);
67     if (snapshot_scheme == NULL)
68         sqlite3_bind_null(stmt, 2);
69     else
70         sqlite3_bind_text(stmt, 2, snapshot_scheme, strlen(snapshot_scheme),
71                           SQLITE_TRANSIENT);
72
73     rc = sqlite3_step(stmt);
74     if (rc != SQLITE_DONE) {
75         sqlite3_close(db);
76         throw IOException("Database execution error!");
77     }
78
79     snapshotid = sqlite3_last_insert_rowid(db);
80     sqlite3_finalize(stmt);
81     if (snapshotid == 0) {
82         sqlite3_close(db);
83         throw IOException("Find snapshot id");
84     }
85 }
86
87 void LocalDb::Close()
88 {
89     int rc;
90     rc = sqlite3_exec(db, "commit", NULL, NULL, NULL);
91     if (rc != SQLITE_OK) {
92         fprintf(stderr, "Can't commit database!\n");
93     }
94     sqlite3_close(db);
95 }
96
97 int64_t LocalDb::SegmentToId(const string &segment)
98 {
99     int rc;
100     sqlite3_stmt *stmt;
101     int64_t result;
102
103     stmt = Prepare("insert or ignore into segments(segment) values (?)");
104     sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
105                       SQLITE_TRANSIENT);
106     rc = sqlite3_step(stmt);
107     if (rc != SQLITE_DONE) {
108         throw IOException("Could not execute INSERT statement!");
109     }
110     sqlite3_finalize(stmt);
111
112     stmt = Prepare("select segmentid from segments where segment = ?");
113     sqlite3_bind_text(stmt, 1, segment.c_str(), segment.size(),
114                       SQLITE_TRANSIENT);
115
116     rc = sqlite3_step(stmt);
117     if (rc == SQLITE_DONE) {
118         throw IOException("No segment found by id");
119     } else if (rc == SQLITE_ROW) {
120         result = sqlite3_column_int64(stmt, 0);
121     } else {
122         throw IOException("Error executing find segment by id query");
123     }
124
125     sqlite3_finalize(stmt);
126
127     return result;
128 }
129
130 string LocalDb::IdToSegment(int64_t segmentid)
131 {
132     int rc;
133     sqlite3_stmt *stmt;
134     string result;
135
136     stmt = Prepare("select segment from segments where segmentid = ?");
137     sqlite3_bind_int64(stmt, 1, segmentid);
138
139     rc = sqlite3_step(stmt);
140     if (rc == SQLITE_DONE) {
141         throw IOException("No segment found by id");
142     } else if (rc == SQLITE_ROW) {
143         result = (const char *)sqlite3_column_text(stmt, 0);
144     } else {
145         throw IOException("Error executing find segment by id query");
146     }
147
148     sqlite3_finalize(stmt);
149
150     return result;
151 }
152
153 void LocalDb::StoreObject(const ObjectReference& ref,
154                           const string &checksum, int64_t size,
155                           double age)
156 {
157     int rc;
158     sqlite3_stmt *stmt;
159
160     if (age == 0.0) {
161         stmt = Prepare("insert into block_index("
162                        "segmentid, object, checksum, size, timestamp) "
163                        "values (?, ?, ?, ?, julianday('now'))");
164     } else {
165         stmt = Prepare("insert into block_index("
166                        "segmentid, object, checksum, size, timestamp) "
167                        "values (?, ?, ?, ?, ?)");
168     }
169
170     sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
171     string obj = ref.get_sequence();
172     sqlite3_bind_text(stmt, 2, obj.c_str(), obj.size(), SQLITE_TRANSIENT);
173     sqlite3_bind_text(stmt, 3, checksum.c_str(), checksum.size(),
174                       SQLITE_TRANSIENT);
175     sqlite3_bind_int64(stmt, 4, size);
176     if (age != 0.0)
177         sqlite3_bind_double(stmt, 5, age);
178
179     rc = sqlite3_step(stmt);
180     if (rc != SQLITE_DONE) {
181         fprintf(stderr, "Could not execute INSERT statement!\n");
182     }
183
184     sqlite3_finalize(stmt);
185 }
186
187 ObjectReference LocalDb::FindObject(const string &checksum, int64_t size)
188 {
189     int rc;
190     sqlite3_stmt *stmt;
191     ObjectReference ref;
192
193     stmt = Prepare("select segmentid, object from block_index "
194                    "where checksum = ? and size = ? and expired is null");
195     sqlite3_bind_text(stmt, 1, checksum.c_str(), checksum.size(),
196                       SQLITE_TRANSIENT);
197     sqlite3_bind_int64(stmt, 2, size);
198
199     rc = sqlite3_step(stmt);
200     if (rc == SQLITE_DONE) {
201     } else if (rc == SQLITE_ROW) {
202         ref = ObjectReference(IdToSegment(sqlite3_column_int64(stmt, 0)),
203                               (const char *)sqlite3_column_text(stmt, 1));
204     } else {
205         fprintf(stderr, "Could not execute SELECT statement!\n");
206     }
207
208     sqlite3_finalize(stmt);
209
210     return ref;
211 }
212
213 bool LocalDb::IsOldObject(const string &checksum, int64_t size, double *age)
214 {
215     int rc;
216     sqlite3_stmt *stmt;
217     bool found = false;
218
219     stmt = Prepare("select segmentid, object, timestamp from block_index "
220                    "where checksum = ? and size = ?");
221     sqlite3_bind_text(stmt, 1, checksum.c_str(), checksum.size(),
222                       SQLITE_TRANSIENT);
223     sqlite3_bind_int64(stmt, 2, size);
224
225     rc = sqlite3_step(stmt);
226     if (rc == SQLITE_DONE) {
227         found = false;
228     } else if (rc == SQLITE_ROW) {
229         found = true;
230         *age = sqlite3_column_double(stmt, 2);
231     } else {
232         fprintf(stderr, "Could not execute SELECT statement!\n");
233     }
234
235     sqlite3_finalize(stmt);
236
237     return found;
238 }
239
240 /* Does this object still exist in the database (and not expired)? */
241 bool LocalDb::IsAvailable(const ObjectReference &ref)
242 {
243     int rc;
244     sqlite3_stmt *stmt;
245     bool found = false;
246
247     stmt = Prepare("select count(*) from block_index "
248                    "where segmentid = ? and object = ? and expired is null");
249     sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
250     sqlite3_bind_text(stmt, 2, ref.get_sequence().c_str(),
251                       ref.get_sequence().size(), SQLITE_TRANSIENT);
252
253     rc = sqlite3_step(stmt);
254     if (rc == SQLITE_DONE) {
255         found = false;
256     } else if (rc == SQLITE_ROW) {
257         if (sqlite3_column_int(stmt, 0) > 0)
258             found = true;
259     } else {
260         fprintf(stderr, "Could not execute SELECT statement!\n");
261     }
262
263     sqlite3_finalize(stmt);
264
265     return found;
266 }
267
268 void LocalDb::UseObject(const ObjectReference& ref)
269 {
270     int rc;
271     sqlite3_stmt *stmt;
272
273     stmt = Prepare("insert or ignore into snapshot_contents "
274                    "select blockid, ? as snapshotid from block_index "
275                    "where segmentid = ? and object = ?");
276     sqlite3_bind_int64(stmt, 1, snapshotid);
277     sqlite3_bind_int64(stmt, 2, SegmentToId(ref.get_segment()));
278     string obj = ref.get_sequence();
279     sqlite3_bind_text(stmt, 3, obj.c_str(), obj.size(), SQLITE_TRANSIENT);
280
281     rc = sqlite3_step(stmt);
282     if (rc != SQLITE_DONE) {
283         fprintf(stderr, "Could not execute INSERT statement!\n");
284     }
285
286     sqlite3_finalize(stmt);
287 }