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 <ae@cy.md>
12  */
13 
14 module ae.sys.sqlite3;
15 
16 pragma(lib, "sqlite3");
17 
18 import etc.c.sqlite3;
19 import std.exception;
20 import std.string : toStringz;
21 import std.conv : to;
22 import std.traits;
23 import std.typecons : Nullable;
24 
25 /// `sqlite3*` wrapper.
26 final class SQLite
27 {
28 	/// C `sqlite3*` object.
29 	sqlite3* db;
30 
31 	this(string fn, bool readOnly = false)
32 	{
33 		sqenforce(sqlite3_open_v2(toStringz(fn), &db, readOnly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, null));
34 	} ///
35 
36 	~this() /*@nogc*/
37 	{
38 		sqlite3_close(db);
39 	}
40 
41 	/// Run a simple query, provided as an SQL string.
42 	auto query(string sql)
43 	{
44 		struct Iterator
45 		{
46 			alias int delegate(ref const(char)[][] args, ref const(char)[][] columns) F;
47 
48 			string sql;
49 			sqlite3* db;
50 			F dg;
51 			int fres;
52 			Throwable throwable = null;
53 
54 			int opApply(F dg)
55 			{
56 				this.dg = dg;
57 				auto res = sqlite3_exec(db, toStringz(sql), &callback, &this, null);
58 				if (res == SQLITE_ABORT)
59 				{
60 					if (throwable)
61 						throw throwable;
62 					else
63 						return fres;
64 				}
65 				else
66 				if (res != SQLITE_OK)
67 					throw new SQLiteException(db, res);
68 				return 0;
69 			}
70 
71 			static /*nothrow*/ extern(C) int callback(void* ctx, int argc, char** argv, char** colv)
72 			{
73 				auto i = cast(Iterator*)ctx;
74 				static const(char)[][] args, cols;
75 				args.length = cols.length = argc;
76 				foreach (n; 0..argc)
77 					args[n] = to!(const(char)[])(argv[n]),
78 					cols[n] = to!(const(char)[])(colv[n]);
79 				try
80 					return i.fres = i.dg(args, cols);
81 				catch (Exception e)
82 				{
83 					i.throwable = e;
84 					return 1;
85 				}
86 			}
87 		}
88 
89 		return Iterator(sql, db);
90 	}
91 
92 	/// Run a simple query, discarding the result.
93 	void exec(string sql)
94 	{
95 		foreach (cells, columns; query(sql))
96 			break;
97 	}
98 
99 	/// Return the ID of the last inserted row.
100 	/// (`sqlite3_last_insert_rowid`)
101 	@property long lastInsertRowID()
102 	{
103 		return sqlite3_last_insert_rowid(db);
104 	}
105 
106 	/// Return the number of changed rows.
107 	/// (`sqlite3_changes`)
108 	@property int changes()
109 	{
110 		return sqlite3_changes(db);
111 	}
112 
113 	/// `sqlite3_stmt*` wrapper.
114 	final class PreparedStatement
115 	{
116 		private sqlite3_stmt* stmt;
117 
118 		/// `sqlite3_bind_XXX` wrapper.
119 		void bind(int idx, int v)
120 		{
121 			sqlite3_bind_int(stmt, idx, v);
122 		}
123 
124 		void bind(int idx, long v)
125 		{
126 			sqlite3_bind_int64(stmt, idx, v);
127 		} /// ditto
128 
129 		void bind(int idx, double v)
130 		{
131 			sqlite3_bind_double(stmt, idx, v);
132 		} /// ditto
133 
134 		void bind(int idx, in char[] v)
135 		{
136 			sqlite3_bind_text(stmt, idx, v.ptr, to!int(v.length), SQLITE_TRANSIENT);
137 		} /// ditto
138 
139 		void bind(int idx, in wchar[] v)
140 		{
141 			sqlite3_bind_text16(stmt, idx, v.ptr, to!int(v.length*2), SQLITE_TRANSIENT);
142 		} /// ditto
143 
144 		void bind(int idx, void* n)
145 		{
146 			assert(n is null);
147 			sqlite3_bind_null(stmt, idx);
148 		} /// ditto
149 
150 		void bind(int idx, const(void)[] v)
151 		{
152 			sqlite3_bind_blob(stmt, idx, v.ptr, to!int(v.length), SQLITE_TRANSIENT);
153 		} /// ditto
154 
155 		void bind(T)(int idx, Nullable!T v)
156 		if (is(typeof(bind(idx, v.get()))))
157 		{
158 			if (v.isNull)
159 				sqlite3_bind_null(stmt, idx);
160 			else
161 				bind(v.get());
162 		} /// ditto
163 
164 		/// Bind all arguments according to their type, in order.
165 		void bindAll(T...)(T args)
166 		{
167 			foreach (int n, arg; args)
168 				bind(n+1, arg);
169 		}
170 
171 		/// Return "true" if a row is available, "false" if done.
172 		bool step()
173 		{
174 			auto res = sqlite3_step(stmt);
175 			if (res == SQLITE_DONE)
176 			{
177 				reset();
178 				return false;
179 			}
180 			else
181 			if (res == SQLITE_ROW)
182 				return true;
183 			else
184 			{
185 				scope(exit) sqlite3_reset(stmt);
186 				sqenforce(res);
187 				return false; // only on SQLITE_OK, which shouldn't happen
188 			}
189 		}
190 
191 		/// Calls `sqlite3_reset`.
192 		void reset()
193 		{
194 			sqenforce(sqlite3_reset(stmt));
195 		}
196 
197 		/// Binds the given arguments and executes the prepared statement, discarding the result.
198 		void exec(T...)(T args)
199 		{
200 			static if (T.length)
201 				bindAll!T(args);
202 			while (step()) {}
203 		}
204 
205 		/// Binds the given arguments and executes the prepared statement, returning the results as an iterator.
206 		static struct Iterator
207 		{
208 			PreparedStatement stmt; ///
209 
210 			@trusted int opApply(U...)(int delegate(ref U args) @system dg)
211 			{
212 				int res = 0;
213 				while (stmt.step())
214 				{
215 					scope(failure) stmt.reset();
216 					static if (U.length == 1 && is(U[0] V : V[]) &&
217 						!is(U[0] : string) && !is(Unqual!V == void) && !is(V == ubyte))
218 					{
219 						U[0] result;
220 						result.length = stmt.columnCount();
221 						foreach (int c, ref r; result)
222 							r = stmt.column!V(c);
223 						res = dg(result);
224 					}
225 					else
226 					static if (U.length == 1 && is(U[0] V : V[string]))
227 					{
228 						U[0] result;
229 						foreach (c; 0..stmt.columnCount())
230 							result[stmt.columnName(c)] = stmt.column!V(c);
231 						res = dg(result);
232 					}
233 					else
234 					{
235 						U columns;
236 						stmt.columns(columns);
237 						res = dg(columns);
238 					}
239 					if (res)
240 					{
241 						stmt.reset();
242 						break;
243 					}
244 				}
245 				return res;
246 			} ///
247 		}
248 
249 		/// ditto
250 		Iterator iterate(T...)(T args)
251 		{
252 			static if (T.length)
253 				bindAll!T(args);
254 			return Iterator(this);
255 		}
256 
257 		/// Returns the value of a column by its index, as the given D type.
258 		T column(T)(int idx)
259 		{
260 			static if (is(T == Nullable!U, U))
261 				return sqlite3_column_type(stmt, idx) == SQLITE_NULL
262 					? T.init
263 					: T(column!U(idx));
264 			else
265 			static if (is(T == string))
266 				return (cast(char*)sqlite3_column_blob(stmt, idx))[0..sqlite3_column_bytes(stmt, idx)].idup;
267 			else
268 			static if (is(T V : V[]) && (is(Unqual!V == void) || is(V == ubyte)))
269 			{
270 				auto arr = (cast(V*)sqlite3_column_blob(stmt, idx))[0..sqlite3_column_bytes(stmt, idx)];
271 				static if (isStaticArray!T)
272 				{
273 					enforce(arr.length == T.length, "Wrong size for static array column");
274 					return arr[0..T.length];
275 				}
276 				else
277 					return arr.dup;
278 			}
279 			else
280 			static if (is(T == int))
281 				return sqlite3_column_int(stmt, idx);
282 			else
283 			static if (is(T == long))
284 				return sqlite3_column_int64(stmt, idx);
285 			else
286 			static if (is(T == bool))
287 				return sqlite3_column_int(stmt, idx) != 0;
288 			else
289 			static if (is(T == double))
290 				return sqlite3_column_double(stmt, idx);
291 			else
292 				static assert(0, "Can't get column with type " ~ T.stringof);
293 		}
294 
295 		unittest
296 		{
297 			PreparedStatement s;
298 			if (false)
299 			{
300 				s.column!(void[])(0);
301 				s.column!(ubyte[])(0);
302 				s.column!(void[16])(0);
303 				s.column!(ubyte[16])(0);
304 				s.column!(Nullable!(ubyte[16]))(0);
305 			}
306 		}
307 
308 		/// Returns the value of all columns, as the given D types.
309 		void columns(T...)(ref T args)
310 		{
311 			foreach (i, arg; args)
312 				args[i] = column!(typeof(arg))(i);
313 		}
314 
315 		/// Returns the value of all columns, as an array of the given D type (`string` by default).
316 		T[] getArray(T=string)()
317 		{
318 			T[] result = new T[dataCount()];
319 			foreach (i, ref value; result)
320 				value = column!T(i);
321 			return result;
322 		}
323 
324 		/// Returns the value of all columns as an associative array,
325 		/// with the column names as the key,
326 		/// and the values with given D type (`string` by default).
327 		T[string] getAssoc(T=string)()
328 		{
329 			T[string] result;
330 			foreach (i; 0..dataCount())
331 				result[columnName(i)] = column!T(i);
332 			return result;
333 		}
334 
335 		/// `sqlite3_column_count` wrapper.
336 		int columnCount()
337 		{
338 			return sqlite3_column_count(stmt);
339 		}
340 
341 		/// `sqlite3_data_count` wrapper.
342 		int dataCount()
343 		{
344 			return sqlite3_data_count(stmt);
345 		}
346 
347 		/// Returns the column name by its index, as a D string.
348 		string columnName(int idx)
349 		{
350 			return to!string(sqlite3_column_name(stmt, idx));
351 		}
352 
353 		~this() /*@nogc*/
354 		{
355 			sqlite3_finalize(stmt);
356 		}
357 	}
358 
359 	/// Construct a prepared statement.
360 	PreparedStatement prepare(string sql)
361 	{
362 		auto s = new PreparedStatement;
363 		const(char)* tail;
364 		auto sqlz = toStringz(sql);
365 		sqenforce(sqlite3_prepare_v2(db, sqlz, -1, &s.stmt, &tail));
366 		assert(tail == sqlz + sql.length, "Trailing SQL not compiled: " ~ sql[tail - sqlz .. $]);
367 		return s;
368 	}
369 
370 	private void sqenforce(int res)
371 	{
372 		if (res != SQLITE_OK)
373 			throw new SQLiteException(db, res);
374 	}
375 }
376 
377 /// Exception class thrown on SQLite errors.
378 class SQLiteException : Exception
379 {
380 	int code; ///
381 
382 	this(sqlite3* db, int code)
383 	{
384 		this.code = code;
385 		super(to!string(sqlite3_errmsg(db)) ~ " (" ~ to!string(code) ~ ")");
386 	} ///
387 }