1 /** 2 * Higher-level wrapper around ae.sys.sqlite3. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <vladimir@thecybershadow.net> 12 */ 13 14 module ae.sys.database; 15 16 import std.conv; 17 import std.exception; 18 19 import ae.sys.sqlite3; 20 public import ae.sys.sqlite3 : SQLiteException; 21 debug(DATABASE) import std.stdio : stderr; 22 23 struct Database 24 { 25 string dbFileName; 26 string[] schema; 27 28 this(string dbFileName, string[] schema = null) 29 { 30 this.dbFileName = dbFileName; 31 this.schema = schema; 32 } 33 34 SQLite.PreparedStatement stmt(string sql)() 35 { 36 debug(DATABASE) stderr.writeln(sql); 37 static SQLite.PreparedStatement statement = null; 38 if (!statement) 39 statement = db.prepare(sql).enforce("Statement compilation failed: " ~ sql); 40 return statement; 41 } 42 43 SQLite.PreparedStatement stmt(string sql) 44 { 45 debug(DATABASE) stderr.writeln(sql); 46 static SQLite.PreparedStatement[const(void)*] cache; 47 auto pstatement = sql.ptr in cache; 48 if (pstatement) 49 return *pstatement; 50 51 auto statement = db.prepare(sql); 52 enforce(statement, "Statement compilation failed: " ~ sql); 53 return cache[sql.ptr] = statement; 54 } 55 56 private SQLite instance; 57 58 @property SQLite db() 59 { 60 if (instance) 61 return instance; 62 63 instance = new SQLite(dbFileName); 64 scope(failure) instance = null; 65 66 // Protect against locked database due to queries from command 67 // line or cron 68 instance.exec("PRAGMA busy_timeout = 100;"); 69 70 if (schema !is null) 71 { 72 auto userVersion = stmt!"PRAGMA user_version".iterate().selectValue!int; 73 if (userVersion != schema.length) 74 { 75 enforce(userVersion <= schema.length, "Database schema version newer than latest supported by this program!"); 76 while (userVersion < schema.length) 77 { 78 auto upgradeInstruction = schema[userVersion]; 79 instance.exec("BEGIN TRANSACTION;"); 80 instance.exec(upgradeInstruction); 81 userVersion++; 82 instance.exec("PRAGMA user_version = " ~ text(userVersion)); 83 instance.exec("COMMIT TRANSACTION;"); 84 } 85 } 86 } 87 88 return instance; 89 } 90 } 91 92 T selectValue(T, Iter)(Iter iter) 93 { 94 foreach (T val; iter) 95 return val; 96 throw new Exception("No results for query"); 97 }