Factor code to prepare SQLite statements into a separate function.
[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 {
149     int rc;
150     sqlite3_stmt *stmt;
151
152     stmt = Prepare("insert into "
153                    "block_index(segmentid, object, checksum, size, timestamp) "
154                    "values (?, ?, ?, ?, julianday('now'))");
155
156     sqlite3_bind_int64(stmt, 1, SegmentToId(ref.get_segment()));
157     string obj = ref.get_sequence();
158     sqlite3_bind_text(stmt, 2, obj.c_str(), obj.size(), SQLITE_TRANSIENT);
159     sqlite3_bind_text(stmt, 3, checksum.c_str(), checksum.size(),
160                       SQLITE_TRANSIENT);
161     sqlite3_bind_int64(stmt, 4, size);
162
163     rc = sqlite3_step(stmt);
164     if (rc != SQLITE_DONE) {
165         fprintf(stderr, "Could not execute INSERT statement!\n");
166     }
167
168     sqlite3_finalize(stmt);
169 }
170
171 ObjectReference LocalDb::FindObject(const string &checksum, int64_t size)
172 {
173     int rc;
174     sqlite3_stmt *stmt;
175     ObjectReference ref;
176
177     stmt = Prepare("select segmentid, object from block_index "
178                    "where checksum = ? and size = ? and expired is null");
179     sqlite3_bind_text(stmt, 1, checksum.c_str(), checksum.size(),
180                       SQLITE_TRANSIENT);
181     sqlite3_bind_int64(stmt, 2, size);
182
183     rc = sqlite3_step(stmt);
184     if (rc == SQLITE_DONE) {
185     } else if (rc == SQLITE_ROW) {
186         ref = ObjectReference(IdToSegment(sqlite3_column_int64(stmt, 0)),
187                               (const char *)sqlite3_column_text(stmt, 1));
188     } else {
189         fprintf(stderr, "Could not execute SELECT statement!\n");
190     }
191
192     sqlite3_finalize(stmt);
193
194     return ref;
195 }
196
197 bool LocalDb::IsOldObject(const string &checksum, int64_t size)
198 {
199     int rc;
200     sqlite3_stmt *stmt;
201     bool found = false;
202
203     stmt = Prepare("select segmentid, object from block_index "
204                    "where checksum = ? and size = ?");
205     sqlite3_bind_text(stmt, 1, checksum.c_str(), checksum.size(),
206                       SQLITE_TRANSIENT);
207     sqlite3_bind_int64(stmt, 2, size);
208
209     rc = sqlite3_step(stmt);
210     if (rc == SQLITE_DONE) {
211         found = false;
212     } else if (rc == SQLITE_ROW) {
213         found = true;
214     } else {
215         fprintf(stderr, "Could not execute SELECT statement!\n");
216     }
217
218     sqlite3_finalize(stmt);
219
220     return found;
221 }
222
223 void LocalDb::UseObject(const ObjectReference& ref)
224 {
225     int rc;
226     sqlite3_stmt *stmt;
227
228     stmt = Prepare("insert or ignore into snapshot_contents "
229                    "select blockid, ? as snapshotid from block_index "
230                    "where segmentid = ? and object = ?");
231     sqlite3_bind_int64(stmt, 1, snapshotid);
232     sqlite3_bind_int64(stmt, 2, SegmentToId(ref.get_segment()));
233     string obj = ref.get_sequence();
234     sqlite3_bind_text(stmt, 3, obj.c_str(), obj.size(), SQLITE_TRANSIENT);
235
236     rc = sqlite3_step(stmt);
237     if (rc != SQLITE_DONE) {
238         fprintf(stderr, "Could not execute INSERT statement!\n");
239     }
240
241     sqlite3_finalize(stmt);
242 }