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