Mercurial > hg > orthanc
annotate Core/SQLite/Connection.cpp @ 633:e207b33216db
merge
author | jodogne |
---|---|
date | Tue, 29 Oct 2013 09:04:25 +0100 |
parents | bdd72233b105 |
children | 08eca5d86aad |
rev | line source |
---|---|
0 | 1 /** |
59 | 2 * Orthanc - A Lightweight, RESTful DICOM Store |
398 | 3 * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, |
0 | 4 * Belgium |
5 * | |
17 | 6 * Copyright (c) 2012 The Chromium Authors. All rights reserved. |
7 * | |
8 * Redistribution and use in source and binary forms, with or without | |
9 * modification, are permitted provided that the following conditions are | |
10 * met: | |
0 | 11 * |
17 | 12 * * Redistributions of source code must retain the above copyright |
13 * notice, this list of conditions and the following disclaimer. | |
14 * * Redistributions in binary form must reproduce the above | |
15 * copyright notice, this list of conditions and the following disclaimer | |
16 * in the documentation and/or other materials provided with the | |
17 * distribution. | |
18 * * Neither the name of Google Inc., the name of the CHU of Liege, | |
19 * nor the names of its contributors may be used to endorse or promote | |
20 * products derived from this software without specific prior written | |
21 * permission. | |
22 * | |
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
27 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
28 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
29 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
30 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
31 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
33 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
0 | 34 **/ |
35 | |
36 | |
37 #include "Connection.h" | |
38 | |
39 #include <memory> | |
40 #include <cassert> | |
41 #include <sqlite3.h> | |
42 #include <string.h> | |
43 | |
137
0e97abc7b950
fix of a bug in older versions of sqlite
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
59
diff
changeset
|
44 #include <glog/logging.h> |
0 | 45 |
46 | |
59 | 47 namespace Orthanc |
0 | 48 { |
49 namespace SQLite | |
50 { | |
51 Connection::Connection() : | |
52 db_(NULL), | |
53 transactionNesting_(0), | |
54 needsRollback_(false) | |
55 { | |
56 } | |
57 | |
58 | |
59 Connection::~Connection() | |
60 { | |
61 Close(); | |
62 } | |
63 | |
64 | |
65 void Connection::CheckIsOpen() const | |
66 { | |
67 if (!db_) | |
68 { | |
59 | 69 throw OrthancException("SQLite: The database is not opened"); |
0 | 70 } |
71 } | |
72 | |
73 void Connection::Open(const std::string& path) | |
74 { | |
75 if (db_) | |
76 { | |
59 | 77 throw OrthancException("SQLite: Connection is already open"); |
0 | 78 } |
79 | |
80 int err = sqlite3_open(path.c_str(), &db_); | |
81 if (err != SQLITE_OK) | |
82 { | |
83 Close(); | |
84 db_ = NULL; | |
59 | 85 throw OrthancException("SQLite: Unable to open the database"); |
0 | 86 } |
87 | |
88 // Execute PRAGMAs at this point | |
89 // http://www.sqlite.org/pragma.html | |
90 Execute("PRAGMA FOREIGN_KEYS=ON;"); | |
181
2dece1526c06
simplifying db schema
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
137
diff
changeset
|
91 Execute("PRAGMA RECURSIVE_TRIGGERS=ON;"); |
0 | 92 } |
93 | |
94 void Connection::OpenInMemory() | |
95 { | |
96 Open(":memory:"); | |
97 } | |
98 | |
99 void Connection::Close() | |
100 { | |
101 ClearCache(); | |
102 | |
103 if (db_) | |
104 { | |
105 sqlite3_close(db_); | |
106 db_ = NULL; | |
107 } | |
108 } | |
109 | |
110 void Connection::ClearCache() | |
111 { | |
112 for (CachedStatements::iterator | |
113 it = cachedStatements_.begin(); | |
114 it != cachedStatements_.end(); it++) | |
115 { | |
116 delete it->second; | |
117 } | |
118 | |
119 cachedStatements_.clear(); | |
120 } | |
121 | |
122 | |
123 StatementReference& Connection::GetCachedStatement(const StatementId& id, | |
124 const char* sql) | |
125 { | |
126 CachedStatements::iterator i = cachedStatements_.find(id); | |
127 if (i != cachedStatements_.end()) | |
128 { | |
129 if (i->second->GetReferenceCount() >= 1) | |
130 { | |
59 | 131 throw OrthancException("SQLite: This cached statement is already being referred to"); |
0 | 132 } |
133 | |
134 return *i->second; | |
135 } | |
136 else | |
137 { | |
138 StatementReference* statement = new StatementReference(db_, sql); | |
139 cachedStatements_[id] = statement; | |
140 return *statement; | |
141 } | |
142 } | |
143 | |
144 | |
145 bool Connection::Execute(const char* sql) | |
146 { | |
137
0e97abc7b950
fix of a bug in older versions of sqlite
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
59
diff
changeset
|
147 VLOG(1) << "SQLite::Connection::Execute " << sql; |
0 | 148 CheckIsOpen(); |
149 | |
150 int error = sqlite3_exec(db_, sql, NULL, NULL, NULL); | |
151 if (error == SQLITE_ERROR) | |
152 { | |
59 | 153 throw OrthancException("SQLite Execute error: " + std::string(sqlite3_errmsg(db_))); |
0 | 154 } |
155 else | |
156 { | |
157 return error == SQLITE_OK; | |
158 } | |
159 } | |
160 | |
161 int Connection::ExecuteAndReturnErrorCode(const char* sql) | |
162 { | |
163 CheckIsOpen(); | |
164 return sqlite3_exec(db_, sql, NULL, NULL, NULL); | |
165 } | |
166 | |
167 // Info querying ------------------------------------------------------------- | |
168 | |
169 bool Connection::IsSQLValid(const char* sql) | |
170 { | |
171 sqlite3_stmt* stmt = NULL; | |
172 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, NULL) != SQLITE_OK) | |
173 return false; | |
174 | |
175 sqlite3_finalize(stmt); | |
176 return true; | |
177 } | |
178 | |
179 bool Connection::DoesTableOrIndexExist(const char* name, | |
180 const char* type) const | |
181 { | |
182 // Our SQL is non-mutating, so this cast is OK. | |
183 Statement statement(const_cast<Connection&>(*this), | |
184 "SELECT name FROM sqlite_master WHERE type=? AND name=?"); | |
185 statement.BindString(0, type); | |
186 statement.BindString(1, name); | |
187 return statement.Step(); // Table exists if any row was returned. | |
188 } | |
189 | |
190 bool Connection::DoesTableExist(const char* table_name) const | |
191 { | |
192 return DoesTableOrIndexExist(table_name, "table"); | |
193 } | |
194 | |
195 bool Connection::DoesIndexExist(const char* index_name) const | |
196 { | |
197 return DoesTableOrIndexExist(index_name, "index"); | |
198 } | |
199 | |
200 bool Connection::DoesColumnExist(const char* table_name, const char* column_name) const | |
201 { | |
202 std::string sql("PRAGMA TABLE_INFO("); | |
203 sql.append(table_name); | |
204 sql.append(")"); | |
205 | |
206 // Our SQL is non-mutating, so this cast is OK. | |
207 Statement statement(const_cast<Connection&>(*this), sql.c_str()); | |
208 | |
209 while (statement.Step()) { | |
210 if (!statement.ColumnString(1).compare(column_name)) | |
211 return true; | |
212 } | |
213 return false; | |
214 } | |
215 | |
216 int64_t Connection::GetLastInsertRowId() const | |
217 { | |
218 return sqlite3_last_insert_rowid(db_); | |
219 } | |
220 | |
221 int Connection::GetLastChangeCount() const | |
222 { | |
223 return sqlite3_changes(db_); | |
224 } | |
225 | |
226 int Connection::GetErrorCode() const | |
227 { | |
228 return sqlite3_errcode(db_); | |
229 } | |
230 | |
231 int Connection::GetLastErrno() const | |
232 { | |
233 int err = 0; | |
234 if (SQLITE_OK != sqlite3_file_control(db_, NULL, SQLITE_LAST_ERRNO, &err)) | |
235 return -2; | |
236 | |
237 return err; | |
238 } | |
239 | |
240 const char* Connection::GetErrorMessage() const | |
241 { | |
242 return sqlite3_errmsg(db_); | |
243 } | |
244 | |
245 | |
246 bool Connection::BeginTransaction() | |
247 { | |
248 if (needsRollback_) | |
249 { | |
250 assert(transactionNesting_ > 0); | |
251 | |
252 // When we're going to rollback, fail on this begin and don't actually | |
253 // mark us as entering the nested transaction. | |
254 return false; | |
255 } | |
256 | |
257 bool success = true; | |
258 if (!transactionNesting_) | |
259 { | |
260 needsRollback_ = false; | |
261 | |
262 Statement begin(*this, SQLITE_FROM_HERE, "BEGIN TRANSACTION"); | |
263 if (!begin.Run()) | |
264 return false; | |
265 } | |
266 transactionNesting_++; | |
267 return success; | |
268 } | |
269 | |
270 void Connection::RollbackTransaction() | |
271 { | |
272 if (!transactionNesting_) | |
273 { | |
59 | 274 throw OrthancException("Rolling back a nonexistent transaction"); |
0 | 275 } |
276 | |
277 transactionNesting_--; | |
278 | |
279 if (transactionNesting_ > 0) | |
280 { | |
281 // Mark the outermost transaction as needing rollback. | |
282 needsRollback_ = true; | |
283 return; | |
284 } | |
285 | |
286 DoRollback(); | |
287 } | |
288 | |
289 bool Connection::CommitTransaction() | |
290 { | |
291 if (!transactionNesting_) | |
292 { | |
59 | 293 throw OrthancException("Committing a nonexistent transaction"); |
0 | 294 } |
295 transactionNesting_--; | |
296 | |
297 if (transactionNesting_ > 0) | |
298 { | |
299 // Mark any nested transactions as failing after we've already got one. | |
300 return !needsRollback_; | |
301 } | |
302 | |
303 if (needsRollback_) | |
304 { | |
305 DoRollback(); | |
306 return false; | |
307 } | |
308 | |
309 Statement commit(*this, SQLITE_FROM_HERE, "COMMIT"); | |
310 return commit.Run(); | |
311 } | |
312 | |
313 void Connection::DoRollback() | |
314 { | |
315 Statement rollback(*this, SQLITE_FROM_HERE, "ROLLBACK"); | |
316 rollback.Run(); | |
317 needsRollback_ = false; | |
318 } | |
319 | |
320 | |
321 | |
322 | |
323 | |
324 | |
325 static void ScalarFunctionCaller(sqlite3_context* rawContext, | |
326 int argc, | |
327 sqlite3_value** argv) | |
328 { | |
329 FunctionContext context(rawContext, argc, argv); | |
330 | |
331 void* payload = sqlite3_user_data(rawContext); | |
332 assert(payload != NULL); | |
333 | |
334 IScalarFunction& func = *(IScalarFunction*) payload; | |
335 func.Compute(context); | |
336 } | |
337 | |
338 | |
339 static void ScalarFunctionDestroyer(void* payload) | |
340 { | |
341 assert(payload != NULL); | |
342 delete (IScalarFunction*) payload; | |
343 } | |
344 | |
345 | |
346 IScalarFunction* Connection::Register(IScalarFunction* func) | |
347 { | |
348 int err = sqlite3_create_function_v2(db_, | |
349 func->GetName(), | |
350 func->GetCardinality(), | |
351 SQLITE_UTF8, | |
352 func, | |
353 ScalarFunctionCaller, | |
354 NULL, | |
355 NULL, | |
356 ScalarFunctionDestroyer); | |
357 | |
358 if (err != SQLITE_OK) | |
359 { | |
360 delete func; | |
59 | 361 throw OrthancException("SQLite: Unable to register a function"); |
0 | 362 } |
363 | |
364 return func; | |
365 } | |
366 | |
206
4453a010d0db
flush to disk thread
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
181
diff
changeset
|
367 |
4453a010d0db
flush to disk thread
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
181
diff
changeset
|
368 void Connection::FlushToDisk() |
4453a010d0db
flush to disk thread
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
181
diff
changeset
|
369 { |
4453a010d0db
flush to disk thread
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
181
diff
changeset
|
370 VLOG(1) << "SQLite::Connection::FlushToDisk"; |
4453a010d0db
flush to disk thread
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
181
diff
changeset
|
371 int err = sqlite3_wal_checkpoint(db_, NULL); |
4453a010d0db
flush to disk thread
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
181
diff
changeset
|
372 |
4453a010d0db
flush to disk thread
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
181
diff
changeset
|
373 if (err != SQLITE_OK) |
4453a010d0db
flush to disk thread
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
181
diff
changeset
|
374 { |
4453a010d0db
flush to disk thread
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
181
diff
changeset
|
375 throw OrthancException("SQLite: Unable to flush the database"); |
4453a010d0db
flush to disk thread
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
181
diff
changeset
|
376 } |
4453a010d0db
flush to disk thread
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
181
diff
changeset
|
377 } |
0 | 378 } |
379 } |