comparison Orthanc/SQLite/Connection.cpp @ 0:02f7a0400a91

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 25 Feb 2015 13:45:35 +0100
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:02f7a0400a91
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 <sqlite3.h>
48 #include <string.h>
49
50 #if ORTHANC_SQLITE_STANDALONE != 1
51 #include <glog/logging.h>
52 #endif
53
54
55 namespace Orthanc
56 {
57 namespace SQLite
58 {
59 Connection::Connection() :
60 db_(NULL),
61 transactionNesting_(0),
62 needsRollback_(false)
63 {
64 }
65
66
67 Connection::~Connection()
68 {
69 Close();
70 }
71
72
73 void Connection::CheckIsOpen() const
74 {
75 if (!db_)
76 {
77 throw OrthancSQLiteException("SQLite: The database is not opened");
78 }
79 }
80
81 void Connection::Open(const std::string& path)
82 {
83 if (db_)
84 {
85 throw OrthancSQLiteException("SQLite: Connection is already open");
86 }
87
88 int err = sqlite3_open(path.c_str(), &db_);
89 if (err != SQLITE_OK)
90 {
91 Close();
92 db_ = NULL;
93 throw OrthancSQLiteException("SQLite: Unable to open the database");
94 }
95
96 // Execute PRAGMAs at this point
97 // http://www.sqlite.org/pragma.html
98 Execute("PRAGMA FOREIGN_KEYS=ON;");
99 Execute("PRAGMA RECURSIVE_TRIGGERS=ON;");
100 }
101
102 void Connection::OpenInMemory()
103 {
104 Open(":memory:");
105 }
106
107 void Connection::Close()
108 {
109 ClearCache();
110
111 if (db_)
112 {
113 sqlite3_close(db_);
114 db_ = NULL;
115 }
116 }
117
118 void Connection::ClearCache()
119 {
120 for (CachedStatements::iterator
121 it = cachedStatements_.begin();
122 it != cachedStatements_.end(); ++it)
123 {
124 delete it->second;
125 }
126
127 cachedStatements_.clear();
128 }
129
130
131 StatementReference& Connection::GetCachedStatement(const StatementId& id,
132 const char* sql)
133 {
134 CachedStatements::iterator i = cachedStatements_.find(id);
135 if (i != cachedStatements_.end())
136 {
137 if (i->second->GetReferenceCount() >= 1)
138 {
139 throw OrthancSQLiteException("SQLite: This cached statement is already being referred to");
140 }
141
142 return *i->second;
143 }
144 else
145 {
146 StatementReference* statement = new StatementReference(db_, sql);
147 cachedStatements_[id] = statement;
148 return *statement;
149 }
150 }
151
152
153 bool Connection::Execute(const char* sql)
154 {
155 #if ORTHANC_SQLITE_STANDALONE != 1
156 VLOG(1) << "SQLite::Connection::Execute " << sql;
157 #endif
158
159 CheckIsOpen();
160
161 int error = sqlite3_exec(db_, sql, NULL, NULL, NULL);
162 if (error == SQLITE_ERROR)
163 {
164 throw OrthancSQLiteException("SQLite Execute error: " + std::string(sqlite3_errmsg(db_)));
165 }
166 else
167 {
168 return error == SQLITE_OK;
169 }
170 }
171
172 int Connection::ExecuteAndReturnErrorCode(const char* sql)
173 {
174 CheckIsOpen();
175 return sqlite3_exec(db_, sql, NULL, NULL, NULL);
176 }
177
178 // Info querying -------------------------------------------------------------
179
180 bool Connection::IsSQLValid(const char* sql)
181 {
182 sqlite3_stmt* stmt = NULL;
183 if (sqlite3_prepare_v2(db_, sql, -1, &stmt, NULL) != SQLITE_OK)
184 return false;
185
186 sqlite3_finalize(stmt);
187 return true;
188 }
189
190 bool Connection::DoesTableOrIndexExist(const char* name,
191 const char* type) const
192 {
193 // Our SQL is non-mutating, so this cast is OK.
194 Statement statement(const_cast<Connection&>(*this),
195 "SELECT name FROM sqlite_master WHERE type=? AND name=?");
196 statement.BindString(0, type);
197 statement.BindString(1, name);
198 return statement.Step(); // Table exists if any row was returned.
199 }
200
201 bool Connection::DoesTableExist(const char* table_name) const
202 {
203 return DoesTableOrIndexExist(table_name, "table");
204 }
205
206 bool Connection::DoesIndexExist(const char* index_name) const
207 {
208 return DoesTableOrIndexExist(index_name, "index");
209 }
210
211 bool Connection::DoesColumnExist(const char* table_name, const char* column_name) const
212 {
213 std::string sql("PRAGMA TABLE_INFO(");
214 sql.append(table_name);
215 sql.append(")");
216
217 // Our SQL is non-mutating, so this cast is OK.
218 Statement statement(const_cast<Connection&>(*this), sql.c_str());
219
220 while (statement.Step()) {
221 if (!statement.ColumnString(1).compare(column_name))
222 return true;
223 }
224 return false;
225 }
226
227 int64_t Connection::GetLastInsertRowId() const
228 {
229 return sqlite3_last_insert_rowid(db_);
230 }
231
232 int Connection::GetLastChangeCount() const
233 {
234 return sqlite3_changes(db_);
235 }
236
237 int Connection::GetErrorCode() const
238 {
239 return sqlite3_errcode(db_);
240 }
241
242 int Connection::GetLastErrno() const
243 {
244 int err = 0;
245 if (SQLITE_OK != sqlite3_file_control(db_, NULL, SQLITE_LAST_ERRNO, &err))
246 return -2;
247
248 return err;
249 }
250
251 const char* Connection::GetErrorMessage() const
252 {
253 return sqlite3_errmsg(db_);
254 }
255
256
257 bool Connection::BeginTransaction()
258 {
259 if (needsRollback_)
260 {
261 assert(transactionNesting_ > 0);
262
263 // When we're going to rollback, fail on this begin and don't actually
264 // mark us as entering the nested transaction.
265 return false;
266 }
267
268 bool success = true;
269 if (!transactionNesting_)
270 {
271 needsRollback_ = false;
272
273 Statement begin(*this, SQLITE_FROM_HERE, "BEGIN TRANSACTION");
274 if (!begin.Run())
275 return false;
276 }
277 transactionNesting_++;
278 return success;
279 }
280
281 void Connection::RollbackTransaction()
282 {
283 if (!transactionNesting_)
284 {
285 throw OrthancSQLiteException("Rolling back a nonexistent transaction");
286 }
287
288 transactionNesting_--;
289
290 if (transactionNesting_ > 0)
291 {
292 // Mark the outermost transaction as needing rollback.
293 needsRollback_ = true;
294 return;
295 }
296
297 DoRollback();
298 }
299
300 bool Connection::CommitTransaction()
301 {
302 if (!transactionNesting_)
303 {
304 throw OrthancSQLiteException("Committing a nonexistent transaction");
305 }
306 transactionNesting_--;
307
308 if (transactionNesting_ > 0)
309 {
310 // Mark any nested transactions as failing after we've already got one.
311 return !needsRollback_;
312 }
313
314 if (needsRollback_)
315 {
316 DoRollback();
317 return false;
318 }
319
320 Statement commit(*this, SQLITE_FROM_HERE, "COMMIT");
321 return commit.Run();
322 }
323
324 void Connection::DoRollback()
325 {
326 Statement rollback(*this, SQLITE_FROM_HERE, "ROLLBACK");
327 rollback.Run();
328 needsRollback_ = false;
329 }
330
331
332
333
334
335
336 static void ScalarFunctionCaller(sqlite3_context* rawContext,
337 int argc,
338 sqlite3_value** argv)
339 {
340 FunctionContext context(rawContext, argc, argv);
341
342 void* payload = sqlite3_user_data(rawContext);
343 assert(payload != NULL);
344
345 IScalarFunction& func = *reinterpret_cast<IScalarFunction*>(payload);
346 func.Compute(context);
347 }
348
349
350 static void ScalarFunctionDestroyer(void* payload)
351 {
352 assert(payload != NULL);
353 delete reinterpret_cast<IScalarFunction*>(payload);
354 }
355
356
357 IScalarFunction* Connection::Register(IScalarFunction* func)
358 {
359 int err = sqlite3_create_function_v2(db_,
360 func->GetName(),
361 func->GetCardinality(),
362 SQLITE_UTF8,
363 func,
364 ScalarFunctionCaller,
365 NULL,
366 NULL,
367 ScalarFunctionDestroyer);
368
369 if (err != SQLITE_OK)
370 {
371 delete func;
372 throw OrthancSQLiteException("SQLite: Unable to register a function");
373 }
374
375 return func;
376 }
377
378
379 void Connection::FlushToDisk()
380 {
381 #if ORTHANC_SQLITE_STANDALONE != 1
382 VLOG(1) << "SQLite::Connection::FlushToDisk";
383 #endif
384
385 int err = sqlite3_wal_checkpoint(db_, NULL);
386
387 if (err != SQLITE_OK)
388 {
389 throw OrthancSQLiteException("SQLite: Unable to flush the database");
390 }
391 }
392 }
393 }