1 module utile.db.mysql;
2 import std.conv, std.meta, std.array, std..string, std.traits, std.typecons,
3 	std.exception, std.algorithm, utile.except, utile.db, utile.db.mysql.binding;
4 
5 version (Utile_Mysql):
6 
7 final class MySQL
8 {
9 	this(string host, string user, string pass, string db, uint port = 3306)
10 	{
11 		_db = mysql_init(null);
12 
13 		{
14 			bool opt = true;
15 			!mysql_options(_db, MYSQL_OPT_RECONNECT, &opt) || throwError(lastError);
16 		}
17 
18 		mysql_real_connect(_db, host.toStringz, user.toStringz, pass.toStringz,
19 				db.toStringz, port, null, 0) || throwError(lastError);
20 	}
21 
22 	~this()
23 	{
24 		_stmts.byValue.each!(a => remove(a));
25 		mysql_close(_db);
26 	}
27 
28 	mixin DbBase;
29 private:
30 	void process(MYSQL_STMT* stmt)
31 	{
32 		mysql_stmt_reset(stmt);
33 	}
34 
35 	auto process(A...)(MYSQL_STMT* stmt)
36 	{
37 		assert(mysql_stmt_field_count(stmt) == A.length, `incorrect number of fields to return`);
38 
39 		{
40 			bool attr = true;
41 			!mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &attr)
42 				|| throwError(lastError(stmt));
43 		}
44 
45 		auto self = this; // TODO: DMD BUG
46 
47 		struct S
48 		{
49 			this(this) @disable;
50 
51 			~this()
52 			{
53 				mysql_stmt_free_result(stmt);
54 				mysql_stmt_reset(stmt);
55 			}
56 
57 			bool empty() const
58 			{
59 				return !_hasRow;
60 			}
61 
62 			void popFront()
63 			{
64 				assert(_hasRow);
65 				_hasRow = self.fetch(stmt);
66 			}
67 
68 			auto array()
69 			{
70 				ReturnType!front[] res;
71 
72 				for (; _hasRow; popFront)
73 				{
74 					res ~= front;
75 				}
76 
77 				return res;
78 			}
79 
80 			auto front()
81 			{
82 				assert(_hasRow);
83 
84 				auto r = *_res;
85 
86 				foreach (i, T; A)
87 				{
88 					static if (isSomeString!T)
89 					{
90 						r[i] = r[i][0 .. *_lens[i]].idup;
91 					}
92 				}
93 
94 				static if (A.length > 1)
95 				{
96 					return r;
97 				}
98 				else
99 				{
100 					return r[0];
101 				}
102 			}
103 
104 		private:
105 			void initialize()
106 			{
107 				MYSQL_BIND[] arr;
108 				_res = new Tuple!A;
109 
110 				enforce(!mysql_stmt_store_result(stmt));
111 
112 				{
113 					auto info = mysql_stmt_result_metadata(stmt);
114 
115 					foreach (i, ref v; *_res)
116 					{
117 						c_ulong* len;
118 
119 						static if (isSomeString!(A[i]))
120 						{
121 							_lens[i] = len = new c_ulong;
122 							v.length = info.fields[i].max_length;
123 						}
124 
125 						arr ~= self.makeBind(&v, len);
126 					}
127 
128 					mysql_free_result(info);
129 				}
130 
131 				!mysql_stmt_bind_result(stmt, arr.ptr) || throwError(self.lastError(stmt));
132 				_hasRow = self.fetch(stmt);
133 			}
134 
135 			Tuple!A* _res;
136 			c_ulong*[uint] _lens;
137 			bool _hasRow;
138 		}
139 
140 		S s;
141 		s.initialize;
142 		return s;
143 	}
144 
145 	auto prepare(string sql)
146 	{
147 		auto stmt = _stmts.get(sql, null);
148 
149 		if (!stmt)
150 		{
151 			stmt = mysql_stmt_init(_db);
152 			!mysql_stmt_prepare(stmt, sql.ptr, cast(uint)sql.length) || throwError(lastError(stmt));
153 
154 			_stmts[sql] = stmt;
155 		}
156 
157 		return stmt;
158 	}
159 
160 	void bind(A...)(MYSQL_STMT* stmt, A args)
161 	{
162 		MYSQL_BIND[] ps;
163 		assert(mysql_stmt_param_count(stmt) == A.length, `incorrect number of bind parameters`);
164 
165 		foreach (ref v; args)
166 		{
167 			ps ~= makeBind(&v);
168 		}
169 
170 		!mysql_stmt_bind_param(stmt, ps.ptr) || throwError(lastError(stmt));
171 		execute(stmt);
172 	}
173 
174 	auto lastId(MYSQL_STMT* stmt)
175 	{
176 		return mysql_stmt_insert_id(stmt);
177 	}
178 
179 	auto affected(MYSQL_STMT* stmt)
180 	{
181 		return mysql_stmt_affected_rows(stmt);
182 	}
183 
184 	auto makeBind(T)(T* v, c_ulong* len = null)
185 	{
186 		MYSQL_BIND b;
187 
188 		static if (is(T == typeof(null)))
189 		{
190 			b.buffer_type = MYSQL_TYPE_NULL;
191 		}
192 		else static if (isFloatingPoint!T)
193 		{
194 			b.buffer = v;
195 			b.buffer_type = T.sizeof == 4 ? MYSQL_TYPE_FLOAT : MYSQL_TYPE_DOUBLE;
196 		}
197 		else static if (isIntegral!T)
198 		{
199 			/*static*/
200 			immutable aa = [
201 				1 : MYSQL_TYPE_TINY, 2 : MYSQL_TYPE_SHORT, 4 : MYSQL_TYPE_LONG,
202 				8 : MYSQL_TYPE_LONGLONG,
203 			];
204 
205 			b.is_unsigned = isUnsigned!T;
206 			b.buffer = v;
207 			b.buffer_type = aa[T.sizeof];
208 		}
209 		else static if (isSomeString!T)
210 		{
211 			b.length = len;
212 			b.buffer = cast(void*)v.ptr;
213 			b.buffer_length = cast(uint)v.length;
214 			b.buffer_type = MYSQL_TYPE_STRING;
215 		}
216 		else
217 		{
218 			static assert(false);
219 		}
220 
221 		return b;
222 	}
223 
224 	bool fetch(MYSQL_STMT* stmt)
225 	{
226 		auto r = mysql_stmt_fetch(stmt);
227 
228 		r != MYSQL_DATA_TRUNCATED || throwError(`data was truncated`);
229 		r == MYSQL_NO_DATA || !r || throwError(lastError(stmt));
230 
231 		return !r;
232 	}
233 
234 	void remove(MYSQL_STMT* stmt)
235 	{
236 		mysql_stmt_close(stmt);
237 	}
238 
239 	void execute(MYSQL_STMT* stmt)
240 	{
241 		!mysql_stmt_execute(stmt) || throwError(lastError(stmt));
242 	}
243 
244 	auto lastError()
245 	{
246 		return mysql_error(_db).fromStringz;
247 	}
248 
249 	auto lastError(MYSQL_STMT* stmt)
250 	{
251 		return mysql_stmt_error(stmt).fromStringz;
252 	}
253 
254 	MYSQL* _db;
255 	MYSQL_STMT*[string] _stmts;
256 }