1 /**
2  * Higher-level wrapper over etc.c.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.sqlite3;
15 
16 pragma(lib, "sqlite3");
17 
18 import etc.c.sqlite3;
19 import std.string : toStringz;
20 import std.conv : to;
21 
22 final class SQLite
23 {
24 	private sqlite3* db;
25 
26 	this(string fn)
27 	{
28 		sqenforce(sqlite3_open(toStringz(fn), &db));
29 	}
30 
31 	~this()
32 	{
33 		sqlite3_close(db);
34 	}
35 
36 	auto query(string sql)
37 	{
38 		struct Iterator
39 		{
40 			alias int delegate(ref const(char)[][] args, ref const(char)[][] columns) F;
41 
42 			string sql;
43 			sqlite3* db;
44 			F dg;
45 			int fres;
46 			Throwable throwable = null;
47 
48 			int opApply(F dg)
49 			{
50 				this.dg = dg;
51 				auto res = sqlite3_exec(db, toStringz(sql), &callback, &this, null);
52 				if (res == SQLITE_ABORT)
53 				{
54 					if (throwable)
55 						throw throwable;
56 					else
57 						return fres;
58 				}
59 				else
60 				if (res != SQLITE_OK)
61 					throw new SQLiteException(db, res);
62 				return 0;
63 			}
64 
65 			static /*nothrow*/ extern(C) int callback(void* ctx, int argc, char** argv, char** colv)
66 			{
67 				auto i = cast(Iterator*)ctx;
68 				static const(char)[][] args, cols;
69 				args.length = cols.length = argc;
70 				foreach (n; 0..argc)
71 					args[n] = to!(const(char)[])(argv[n]),
72 					cols[n] = to!(const(char)[])(colv[n]);
73 				try
74 					return i.fres = i.dg(args, cols);
75 				catch (Exception e)
76 				{
77 					i.throwable = e;
78 					return 1;
79 				}
80 			}
81 		}
82 
83 		return Iterator(sql, db);
84 	}
85 
86 	void exec(string sql)
87 	{
88 		foreach (cells, columns; query(sql))
89 			break;
90 	}
91 
92 	@property long lastInsertRowID()
93 	{
94 		return sqlite3_last_insert_rowid(db);
95 	}
96 
97 	final class PreparedStatement
98 	{
99 		sqlite3_stmt* stmt;
100 
101 		void bind(int idx, int v)
102 		{
103 			sqlite3_bind_int(stmt, idx, v);
104 		}
105 
106 		void bind(int idx, long v)
107 		{
108 			sqlite3_bind_int64(stmt, idx, v);
109 		}
110 
111 		void bind(int idx, double v)
112 		{
113 			sqlite3_bind_double(stmt, idx, v);
114 		}
115 
116 		void bind(int idx, in char[] v)
117 		{
118 			sqlite3_bind_text(stmt, idx, v.ptr, to!int(v.length), SQLITE_TRANSIENT);
119 		}
120 
121 		void bind(int idx, in wchar[] v)
122 		{
123 			sqlite3_bind_text16(stmt, idx, v.ptr, to!int(v.length*2), SQLITE_TRANSIENT);
124 		}
125 
126 		void bind(int idx, void* n)
127 		{
128 			assert(n is null);
129 			sqlite3_bind_null(stmt, idx);
130 		}
131 
132 		void bind(int idx, in ubyte[] v)
133 		{
134 			sqlite3_bind_blob(stmt, idx, v.ptr, to!int(v.length), SQLITE_TRANSIENT);
135 		}
136 
137 		void bindAll(T...)(T args)
138 		{
139 			foreach (int n, arg; args)
140 				bind(n+1, arg);
141 		}
142 
143 		/// Return "true" if a row is available, "false" if done.
144 		bool step()
145 		{
146 			auto res = sqlite3_step(stmt);
147 			if (res == SQLITE_DONE)
148 			{
149 				reset();
150 				return false;
151 			}
152 			else
153 			if (res == SQLITE_ROW)
154 				return true;
155 			else
156 			{
157 				sqlite3_reset(stmt);
158 				sqenforce(res);
159 				return false; // only on SQLITE_OK, which shouldn't happen
160 			}
161 		}
162 
163 		void reset()
164 		{
165 			sqenforce(sqlite3_reset(stmt));
166 		}
167 
168 		void exec(T...)(T args)
169 		{
170 			static if (T.length)
171 				bindAll!T(args);
172 			while (step()) {}
173 		}
174 
175 		static struct Iterator
176 		{
177 			PreparedStatement stmt;
178 
179 			@trusted int opApply(U...)(int delegate(ref U args) @system dg)
180 			{
181 				int res = 0;
182 				while (stmt.step())
183 				{
184 					scope(failure) stmt.reset();
185 					static if (U.length == 1 && is(U[0] V : V[string]))
186 					{
187 						U[0] result;
188 						foreach (c; 0..stmt.columnCount())
189 							result[stmt.columnName(c)] = stmt.column!V(c);
190 						res = dg(result);
191 					}
192 					else
193 					{
194 						U columns;
195 						stmt.columns(columns);
196 						res = dg(columns);
197 					}
198 					if (res)
199 					{
200 						stmt.reset();
201 						break;
202 					}
203 				}
204 				return res;
205 			}
206 		}
207 
208 		Iterator iterate(T...)(T args)
209 		{
210 			static if (T.length)
211 				bindAll!T(args);
212 			return Iterator(this);
213 		}
214 
215 		T column(T)(int idx)
216 		{
217 			static if (is(T == string))
218 				return (cast(char*)sqlite3_column_blob(stmt, idx))[0..sqlite3_column_bytes(stmt, idx)].idup;
219 			else
220 			static if (is(T == int))
221 				return sqlite3_column_int(stmt, idx);
222 			else
223 			static if (is(T == long))
224 				return sqlite3_column_int64(stmt, idx);
225 			else
226 			static if (is(T == bool))
227 				return sqlite3_column_int(stmt, idx) != 0;
228 			else
229 			static if (is(T == double))
230 				return sqlite3_column_double(stmt, idx);
231 			else
232 				static assert(0, "Can't get column with type " ~ T.stringof);
233 		}
234 
235 		void columns(T...)(ref T args)
236 		{
237 			foreach (i, arg; args)
238 				args[i] = column!(typeof(arg))(i);
239 		}
240 
241 		T[] getArray(T=string)()
242 		{
243 			T[] result = new T[dataCount()];
244 			foreach (i, ref value; result)
245 				value = column!T(i);
246 			return result;
247 		}
248 
249 		T[string] getAssoc(T=string)()
250 		{
251 			T[string] result;
252 			foreach (i; 0..dataCount())
253 				result[columnName(i)] = column!T(i);
254 			return result;
255 		}
256 
257 		int columnCount()
258 		{
259 			return sqlite3_column_count(stmt);
260 		}
261 
262 		int dataCount()
263 		{
264 			return sqlite3_data_count(stmt);
265 		}
266 
267 		string columnName(int idx)
268 		{
269 			return to!string(sqlite3_column_name(stmt, idx));
270 		}
271 
272 		~this()
273 		{
274 			sqlite3_finalize(stmt);
275 		}
276 	}
277 
278 	PreparedStatement prepare(string sql)
279 	{
280 		auto s = new PreparedStatement;
281 		sqenforce(sqlite3_prepare_v2(db, toStringz(sql), -1, &s.stmt, null));
282 		return s;
283 	}
284 
285 	private void sqenforce(int res)
286 	{
287 		if (res != SQLITE_OK)
288 			throw new SQLiteException(db, res);
289 	}
290 }
291 
292 class SQLiteException : Exception
293 {
294 	int code;
295 
296 	this(sqlite3* db, int code)
297 	{
298 		this.code = code;
299 		super(to!string(sqlite3_errmsg(db)));
300 	}
301 }
302