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