1 module mysql.mysql;
2 
3 import mysql.binding;
4 
5 public import mysql.mysql_result;
6 public import mysql.mysql_row;
7 import mysql.query_interface;
8 
9 import std.stdio;
10 import std.exception;
11 import std.typecons;
12 
13 class MysqlDatabaseException : Exception {
14     this(string msg, string file = __FILE__, size_t line = __LINE__) {
15         super(msg, file, line);
16     }
17 }
18 
19 class Mysql {
20     private string _dbname;
21     private MYSQL* mysql;
22     private string lastErrorMsg;
23 
24     this(string host, string user, string pass, string db) {
25         initMysql();
26         connect(host, 0, user, pass, db, null);
27     }
28 
29     this(string host, uint port, string user, string pass, string db, string charset = null) {
30         initMysql();
31         connect(host, port, user, pass, db, null, charset);
32     }
33 
34     this(string host, string user, string pass) {
35         initMysql();
36         connect(host, user, pass);
37     }
38 
39     this() {
40         initMysql();
41     }
42 
43     private void initMysql () {
44         mysql = enforceEx!(MysqlDatabaseException)(mysql_init(null), "Couldn't init mysql");
45         setReconnect(true);
46     }
47 
48     void connect(string host, uint port, string user, string pass, string db, string unixSocket, string charset = null) {
49         enforceEx!(MysqlDatabaseException)(
50             mysql_real_connect(mysql,
51                 toCstring(host),
52                 toCstring(user),
53                 toCstring(pass),
54                 toCstring(db),
55                 port,
56                 unixSocket ? toCstring(unixSocket) : null,
57                 0),
58             error()
59         );
60 
61         _dbname = db;
62 
63         if (charset != null) setCharset(charset);
64     }
65 
66     void connect(string host, uint port, string user, string pass, string db, string charset="utf8") {
67         connect(host, port, user, pass, db, null, charset);
68     }
69 
70     void connect(string host, string user, string pass, string db) {
71         connect(host, 0, user, pass, db, null);
72     }
73 
74     void connect(string host, string user, string pass) {
75         connect(host, 0, user, pass, null, null);
76     }
77 
78     int selectDb(string newDbName) {
79         auto res = mysql_select_db(mysql, toCstring(newDbName));
80         _dbname = newDbName;
81         return res;
82     }
83 
84     string dbname() {
85         return _dbname;
86     }
87 
88     int setOption(mysql_option option, const void* value) {
89         return mysql_options(mysql, option, &value);
90     }
91 
92     int setReconnect(bool value) {
93         return setOption(mysql_option.MYSQL_OPT_RECONNECT, &value);
94     }
95 
96     int setConnectTimeout(int value) {
97         return setOption(mysql_option.MYSQL_OPT_CONNECT_TIMEOUT, cast(const(char*))value);
98     }
99 
100     int setCharset(string charset) {
101         return mysql_set_character_set(mysql, toCstring(charset));
102     }
103 
104     string charset() {
105         return fromCstring(mysql_character_set_name(mysql));
106     }
107 
108     static ulong clientVersion() {
109         return mysql_get_client_version();
110     }
111 
112     static string clientVersionString() {
113         return fromCstring(mysql_get_client_info());
114     }
115 
116     void startTransaction() {
117         query("START TRANSACTION");
118     }
119 
120     string error() {
121         return fromCstring(mysql_error(mysql));
122     }
123 
124     void close() {
125         if (mysql) {
126             mysql_close(mysql);
127             mysql = null;
128         }
129     }
130 
131     ~this() {
132         close();
133     }
134 
135     // MYSQL API call
136     int lastInsertId() {
137         return cast(int) mysql_insert_id(mysql);
138     }
139 
140     // MYSQL API call
141     int affectedRows() {
142         return cast(int) mysql_affected_rows(mysql);
143     }
144 
145     // MYSQL API call
146     string escape(string str) {
147         ubyte[] buffer = new ubyte[str.length * 2 + 1];
148         buffer.length = mysql_real_escape_string(mysql, buffer.ptr, cast(cstring) str.ptr, cast(uint) str.length);
149 
150         return cast(string) buffer;
151     }
152 
153     // MYSQL API call
154     MysqlResult queryImpl(string sql) {
155         enforceEx!(MysqlDatabaseException)(
156             !mysql_query(mysql, toCstring(sql)),
157         error() ~ " :::: " ~ sql);
158 
159         return new MysqlResult(mysql_store_result(mysql), sql);
160     }
161 
162     // To be used with commands that do not return a result (INSERT, UPDATE, etc...)
163     bool execImpl(string sql) {
164         bool success = false;
165 
166         if (mysql_query(mysql, toCstring(sql)) == 0) {
167             success = true;
168             this.lastErrorMsg = "";
169         } else {
170             this.lastErrorMsg = error() ~ " :::: " ~ sql;
171         }
172 
173         return success;
174     }
175 
176     // MYSQL API call
177     int ping() {
178         return mysql_ping(mysql);
179     }
180 
181     // MYSQL API call
182     string stat() {
183         return fromCstring(mysql_stat(mysql));
184     }
185 
186     // ====== helpers ======
187 
188     // Smart interface thing.
189     // accept multiple attributes and make replacement of '?' in sql
190     // like this:
191     // auto row = mysql.query("select * from table where id = ?", 10);
192     MysqlResult query(T...)(string sql, T t) {
193         return queryImpl(QueryInterface.makeQuery(this, sql, t));
194     }
195 
196     bool exec(T...)(string sql, T t) {
197         return execImpl(QueryInterface.makeQuery(this, sql, t));
198     }
199 
200     string dbErrorMsg() {
201         return this.lastErrorMsg;
202     }
203 
204     // simply make mysq.query().front
205     // and if no rows then raise an exception
206     Nullable!MysqlRow queryOneRow(string file = __FILE__, size_t line = __LINE__, T...)(string sql, T t) {
207         auto res = query(sql, t);
208         if (res.empty) {
209             return Nullable!MysqlRow.init;
210         }
211         auto row = res.front;
212 
213         return Nullable!MysqlRow(row);
214     }
215 
216 /*
217     ResultByDataObject!R queryDataObject(R = DataObject, T...)(string sql, T t) {
218         // modify sql for the best data object grabbing
219         sql = fixupSqlForDataObjectUse(sql);
220 
221         auto magic = query(sql, t);
222         return ResultByDataObject!R(cast(MysqlResult) magic, this);
223     }
224 
225 
226     ResultByDataObject!R queryDataObjectWithCustomKeys(R = DataObject, T...)(string[string] keyMapping, string sql, T t) {
227         sql = fixupSqlForDataObjectUse(sql, keyMapping);
228 
229         auto magic = query(sql, t);
230         return ResultByDataObject!R(cast(MysqlResult) magic, this);
231     }
232 */
233 }
234 
235 /*
236 struct ResultByDataObject(ObjType) if (is(ObjType : DataObject)) {
237     MysqlResult result;
238     Mysql mysql;
239 
240     this(MysqlResult r, Mysql mysql) {
241         result = r;
242         auto fields = r.fields();
243         this.mysql = mysql;
244 
245         foreach(i, f; fields) {
246             string tbl = fromCstring(f.org_table is null ? f.table : f.org_table, f.org_table is null ? f.table_length : f.org_table_length);
247             mappings[fromCstring(f.name)] = tuple(
248                     tbl,
249                     fromCstring(f.org_name is null ? f.name : f.org_name, f.org_name is null ? f.name_length : f.org_name_length));
250         }
251 
252 
253     }
254 
255     Tuple!(string, string)[string] mappings;
256 
257     ulong length() { return result.length; }
258     bool empty() { return result.empty; }
259     void popFront() { result.popFront(); }
260     ObjType front() {
261         return new ObjType(mysql, result.front.toAA, mappings);
262     }
263     // would it be good to add a new() method? would be valid even if empty
264     // it'd just fill in the ID's at random and allow you to do the rest
265 
266     @disable this(this) { }
267 }
268 */
269 
270 
271 class EmptyResultException : Exception {
272     this(string message, string file = __FILE__, size_t line = __LINE__) {
273         super(message, file, line);
274     }
275 }
276 
277 
278 /*
279 void main() {
280     auto mysql = new Mysql("localhost", "uname", "password", "test");
281     scope(exit) delete mysql;
282 
283     mysql.query("INSERT INTO users (id, password) VALUES (?, ?)", 10, "lol");
284 
285     foreach(row; mysql.query("SELECT * FROM users")) {
286         writefln("%s %s %s %s", row["id"], row[0], row[1], row["username"]);
287     }
288 }
289 */
290 
291 /+
292     mysql.linq.tablename.field[key] // select field from tablename where id = key
293 
294     mysql.link["name"].table.field[key] // select field from table where name = key
295 
296 
297     auto q = mysql.prepQuery("select id from table where something");
298     q.sort("name");
299     q.limit(start, count);
300     q.page(3, pagelength = ?);
301 
302     q.execute(params here); // returns the same Result range as query
303 +/
304 
305 /*
306 void main() {
307     auto db = new Mysql("localhost", "uname", "password", "test");
308     foreach(item; db.queryDataObject("SELECT users.*, username
309         FROM users, password_manager_accounts
310         WHERE password_manager_accounts.user_id =  users.id LIMIT 5")) {
311         writefln("item: %s, %s", item.id, item.username);
312         item.first = "new";
313         item.last = "new2";
314         item.username = "kill";
315         //item.commitChanges();
316     }
317 }
318 */
319 
320 
321 /*
322 Copyright: Adam D. Ruppe, 2009 - 2011
323 License:   <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
324 Authors: Adam D. Ruppe, with contributions from Nick Sabalausky
325 
326         Copyright Adam D. Ruppe 2009 - 2011.
327 Distributed under the Boost Software License, Version 1.0.
328    (See accompanying file LICENSE_1_0.txt or copy at
329         http://www.boost.org/LICENSE_1_0.txt)
330 */
331