1 module utils.db.sqlite;
2 
3 import
4 		std.conv,
5 		std.meta,
6 		std.array,
7 		std..string,
8 		std.traits,
9 		std.typecons,
10 		std.exception,
11 		std.algorithm,
12 
13 		etc.c.sqlite3,
14 		utils.except;
15 
16 
17 final class SQLite
18 {
19 	this(string name)
20 	{
21 		sqlite3_open(name.toStringz, &_db) == SQLITE_OK || throwError(lastError);
22 	}
23 
24 	~this()
25 	{
26 		_stmts.byValue.each!(a => remove(a));
27 		sqlite3_close(_db);
28 	}
29 
30 	void backup(SQLite dest)
31 	{
32 		auto bk = sqlite3_backup_init(dest._db, `main`, _db, `main`);
33 		bk || throwError(dest.lastError);
34 
35 		scope(exit)
36 		{
37 			sqlite3_backup_finish(bk);
38 		}
39 
40 		auto rc = sqlite3_backup_step(bk, -1);
41 		rc == SQLITE_DONE || throwError!`error backuping db: %s`(rc);
42 	}
43 
44 package:
45 	void process(sqlite3_stmt* stmt)
46 	{
47 		execute(stmt);
48 		sqlite3_reset(stmt);
49 	}
50 
51 	auto process(A...)(sqlite3_stmt* stmt)
52 	{
53 		auto self = this; // TODO: DMD BUG
54 
55 		struct S
56 		{
57 			this(this) @disable;
58 
59 			~this()
60 			{
61 				sqlite3_reset(stmt);
62 			}
63 
64 			const empty()
65 			{
66 				return !_hasRow;
67 			}
68 
69 			void popFront()
70 			{
71 				assert(!empty);
72 				_hasRow = self.execute(stmt);
73 			}
74 
75 			auto array()
76 			{
77 				ReturnType!front[] res;
78 
79 				for(; _hasRow; popFront)
80 				{
81 					res ~= front;
82 				}
83 
84 				return res;
85 			}
86 
87 			auto front()
88 			{
89 				assert(_hasRow);
90 
91 				Tuple!A r;
92 
93 				foreach(i, ref v; r)
94 				{
95 					alias T = A[i];
96 
97 					v = sqlite3_column_text(stmt, i).fromStringz.to!T;
98 				}
99 
100 				static if(A.length > 1)
101 				{
102 					return r;
103 				}
104 				else
105 				{
106 					return r[0];
107 				}
108 			}
109 
110 		private:
111 			bool _hasRow;
112 		}
113 
114 		return S(execute(stmt));
115 	}
116 
117 	auto prepare(string sql)
118 	{
119 		auto stmt = _stmts.get(sql, null);
120 
121 		if(!stmt)
122 		{
123 			sqlite3_prepare_v2(_db, sql.toStringz, cast(int)sql.length, &stmt, null) == SQLITE_OK || throwError(lastError);
124 			_stmts[sql] = stmt;
125 		}
126 
127 		return stmt;
128 	}
129 
130 	void bind(A...)(sqlite3_stmt* stmt, A args)
131 	{
132 		foreach(uint i, v; args)
133 		{
134 			int res;
135 
136 			alias T = typeof(v);
137 			auto idx = i + 1;
138 
139 			static if(is(T == typeof(null)))
140 			{
141 				res = sqlite3_bind_null(stmt, idx);
142 			}
143 			else static if(isFloatingPoint!T)
144 			{
145 				res = sqlite3_bind_double(stmt, idx, v);
146 			}
147 			else static if(isIntegral!T)
148 			{
149 				res = sqlite3_bind_int64(stmt, idx, v);
150 			}
151 			else static if(isSomeString!T)
152 			{
153 				res = sqlite3_bind_text(stmt, idx, v.toStringz, cast(int)v.length, SQLITE_TRANSIENT);
154 			}
155 			else
156 			{
157 				static assert(false);
158 			}
159 
160 			res == SQLITE_OK || throwError(lastError);
161 		}
162 	}
163 
164 	auto lastId(sqlite3_stmt*)
165 	{
166 		return sqlite3_last_insert_rowid(_db);
167 	}
168 
169 	auto affected(sqlite3_stmt*)
170 	{
171 		return sqlite3_changes(_db);
172 	}
173 
174 private:
175 	void remove(sqlite3_stmt* stmt)
176 	{
177 		sqlite3_finalize(stmt);
178 	}
179 
180 	bool execute(sqlite3_stmt* stmt)
181 	{
182 		auto res = sqlite3_step(stmt);
183 		res == SQLITE_ROW || res == SQLITE_DONE || throwError(lastError);
184 		return res == SQLITE_ROW;
185 	}
186 
187 	auto lastError()
188 	{
189 		return sqlite3_errmsg(_db).fromStringz;
190 	}
191 
192 	sqlite3* _db;
193 	sqlite3_stmt*[string] _stmts;
194 }