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 }