1 module utile.db.sqlite;
2 import std, core.sync.mutex, core.sync.rwmutex, etc.c.sqlite3, utile.except, utile.db, utile.misc;
3 
4 final class SQLite
5 {
6 	this(string name)
7 	{
8 		const(char)* p;
9 		auto flags = SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
10 
11 		if (name.empty)
12 		{
13 			flags |= SQLITE_OPEN_MEMORY;
14 		}
15 		else
16 			p = name.toStringz;
17 
18 		auto code = sqlite3_open_v2(p, &_db, flags, null);
19 		code == SQLITE_OK || error(code);
20 
21 		exec(`pragma temp_store = MEMORY;`);
22 		exec(`pragma synchronous = NORMAL;`);
23 
24 		_mutex = new ReadWriteMutex(ReadWriteMutex.Policy.PREFER_READERS);
25 	}
26 
27 	~this()
28 	{
29 		_cache.byValue.each!(a => remove(a));
30 		sqlite3_close(_db);
31 	}
32 
33 	void backup(SQLite dest)
34 	{
35 		auto bk = sqlite3_backup_init(dest._db, MainDb, _db, MainDb);
36 		bk || throwError(`cannot init backup`);
37 
38 		scope (exit)
39 		{
40 			sqlite3_backup_finish(bk);
41 		}
42 
43 		auto code = sqlite3_backup_step(bk, -1);
44 		code == SQLITE_DONE || error(code);
45 	}
46 
47 	static Blob blobNull() => ( & _null)[0 .. 0];
48 	static string textNull() => cast(string)blobNull;
49 
50 	void begin() => exec(`begin;`);
51 	void commit() => exec(`commit;`);
52 	void rollback() => exec(`rollback;`);
53 
54 	mixin DbBase;
55 private:
56 	enum immutable(char)[4] MainDb = `main`;
57 
58 	void exec(const(char)* sql)
59 	{
60 		char* msg;
61 		sqlite3_exec(_db, sql, null, null, &msg);
62 
63 		if (msg)
64 		{
65 			auto s = msg.fromStringz.idup;
66 			sqlite3_free(msg);
67 
68 			throwError!`error executing query: %s`(s);
69 		}
70 	}
71 
72 	void process(S* s)
73 	{
74 		execute(s.stmt);
75 		reset(s);
76 	}
77 
78 	auto process(A...)(S* s)
79 	{
80 		auto self = this; // TODO: DMD BUG
81 		auto stmt = s.stmt;
82 
83 		struct S
84 		{
85 			this(this) @disable;
86 
87 			~this() => self.reset(s);
88 
89 			const empty() => !_hasRow;
90 
91 			void popFront()
92 			in
93 			{
94 				assert(_hasRow);
95 			}
96 			do
97 			{
98 				_hasRow = self.execute(stmt);
99 			}
100 
101 			auto array()
102 			{
103 				ReturnType!front[] res;
104 
105 				for (; _hasRow; popFront)
106 				{
107 					res ~= front;
108 				}
109 
110 				return res;
111 			}
112 
113 			auto front()
114 			in
115 			{
116 				assert(_hasRow);
117 			}
118 			do
119 			{
120 				Tuple!A r;
121 
122 				debug
123 				{
124 					auto N = r.Types.length;
125 					auto cnt = sqlite3_column_count(stmt);
126 
127 					cnt == N || throwError!`expected %u columns, but query returned %u`(N, cnt);
128 				}
129 
130 				foreach (i, ref v; r)
131 				{
132 					alias T = r.Types[i];
133 
134 					static if (isFloatingPoint!T)
135 					{
136 						v = cast(T)sqlite3_column_double(stmt, i);
137 					}
138 					else static if (isIntegral!T)
139 					{
140 						v = cast(T)sqlite3_column_int64(stmt, i);
141 					}
142 					else static if (is(T == string))
143 					{
144 						v = sqlite3_column_text(stmt, i)[0 .. dataLen(i)].idup;
145 					}
146 					else static if (is(T == Blob))
147 					{
148 						v = cast(Blob)sqlite3_column_blob(stmt, i)[0 .. dataLen(i)].dup;
149 					}
150 					else
151 						static assert(false);
152 				}
153 
154 				static if (A.length > 1)
155 					return r;
156 				else
157 					return r[0];
158 			}
159 
160 		private:
161 			auto dataLen(uint col) => sqlite3_column_bytes(stmt, col);
162 
163 			bool _hasRow;
164 		}
165 
166 		return S(execute(stmt));
167 	}
168 
169 	auto prepare(string sql)
170 	{
171 		S* s;
172 
173 		synchronized (_mutex.reader)
174 		{
175 			s = _cache.get(sql, null);
176 		}
177 
178 		if (s)
179 		{
180 			s.mutex.lock;
181 			return s;
182 		}
183 
184 		synchronized (_mutex.writer)
185 		{
186 			s = _cache.get(sql, null);
187 
188 			if (s is null)
189 			{
190 				sqlite3_stmt* stmt;
191 
192 				auto code = sqlite3_prepare_v2(_db, sql.toStringz, cast(uint)sql.length, &stmt, null); 
193 				code == SQLITE_OK || error(code);
194 
195 				s = _cache[sql] = new S(new Mutex, stmt);
196 			}
197 
198 			s.mutex.lock;
199 			return s;
200 		}
201 	}
202 
203 	void bind(A...)(S* s, A args)
204 	{
205 		auto stmt = s.stmt;
206 
207 		debug
208 		{
209 			auto cnt = sqlite3_bind_parameter_count(stmt);
210 			A.length == cnt || throwError!`expected %u parameters to bind, but %u provided`(cnt, A.length);
211 		}
212 
213 		foreach (uint i, v; args)
214 		{
215 			alias T = Unqual!(typeof(v));
216 
217 			uint code;
218 			uint idx = i + 1;
219 
220 			static if (is(T == typeof(null)))
221 			{
222 				code = sqlite3_bind_null(stmt, idx);
223 			}
224 			else static if (isFloatingPoint!T)
225 			{
226 				code = sqlite3_bind_double(stmt, idx, v);
227 			}
228 			else static if (isIntegral!T)
229 			{
230 				code = sqlite3_bind_int64(stmt, idx, v);
231 			}
232 			else static if (is(T == string))
233 			{
234 				const(char)* p;
235 
236 				if (cast(void*)v.ptr is &_null)
237 				{
238 					p = null;
239 				}
240 				else
241 					p = v.length ? v.ptr : cast(const(char)*)&_null;
242 
243 				code = sqlite3_bind_text64(stmt, idx, p, v.length, SQLITE_TRANSIENT, SQLITE_UTF8);
244 			}
245 			else static if (is(T == Blob))
246 			{
247 				const(ubyte)* p;
248 
249 				if (v.ptr is &_null)
250 				{
251 					p = null;
252 				}
253 				else
254 					p = v.length ? v.ptr : &_null;
255 
256 				code = sqlite3_bind_blob64(stmt, idx, p, v.length, SQLITE_TRANSIENT);
257 			}
258 			else
259 				static assert(false);
260 
261 			code == SQLITE_OK || error(code);
262 		}
263 	}
264 
265 	auto lastId(S * ) => sqlite3_last_insert_rowid(_db);
266 	auto affected(S * ) => sqlite3_changes(_db);
267 private:
268 	void reset(S* s)
269 	{
270 		sqlite3_reset(s.stmt);
271 		s.mutex.unlock;
272 	}
273 
274 	void remove(S* s)
275 	{
276 		s.mutex.destroy;
277 		sqlite3_finalize(s.stmt);
278 	}
279 
280 	bool execute(sqlite3_stmt* stmt)
281 	{
282 		auto res = sqlite3_step(stmt);
283 		res == SQLITE_ROW || res == SQLITE_DONE || error(res);
284 		return res == SQLITE_ROW;
285 	}
286 
287 	bool error(uint code)
288 	{
289 		return throwError(sqlite3_errstr(code).fromStringz.assumeUnique);
290 	}
291 
292 	struct S
293 	{
294 		Mutex mutex;
295 		sqlite3_stmt* stmt;
296 	}
297 
298 	sqlite3* _db;
299 
300 	S*[string] _cache;
301 	ReadWriteMutex _mutex;
302 
303 	immutable __gshared ubyte _null;
304 }