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