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 }