comparison Orthanc/Core/SQLite/Connection.cpp @ 78:d6da56f86e5a

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