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