1 /++
2 	Implementation of the [arsd.database.Database] interface for
3 	accessing MySQL (and MariaDB) databases. Uses the official MySQL client
4 	library, and thus needs that installed to compile and run.
5 
6 	$(PITFALL
7 		If you're using MySQL client library v5.0 or less,
8 		you must pass this to dmd: `-version=Less_Than_MySQL_51`
9 		This is important - otherwise you will see bizarre segfaults!
10 	)
11 +/
12 module arsd.mysql;
13 
14 
15 //version(MySQL_51) {
16 	// we good
17 /*} else*/ version(Less_Than_MySQL_51) {
18 	// we good
19 } else {
20 	// default now is mysql 5.1 or up - it has been long
21 	// enough that surely most everyone uses it and we don't
22 	// need the pragma warning anymore. Of course, the old is
23 	// still available if you need to explicitly opt in.
24 	version = MySQL_51;
25 }
26 
27 version(Windows) {
28 	pragma(lib, "libmysql");
29 }
30 else {
31 	pragma(lib, "mysqlclient");
32 }
33 
34 public import arsd.database;
35 
36 import std.stdio;
37 import std.exception;
38 import std.string;
39 import std.conv;
40 import std.typecons;
41 import core.stdc.config;
42 
43 /++
44 	Represents a query result. You can loop over this with a
45 	`foreach` statement to access individual [Row|rows].
46 
47 	[Row]s expose both an index and associative array interface,
48 	so you can get `row[0]` for the first item, or `row["name"]`
49 	to get a column by name from the result set.
50 +/
51 class MySqlResult : ResultSet {
52 	private int[string] mapping;
53 	private MYSQL_RES* result;
54 
55 	private int itemsTotal;
56 	private int itemsUsed;
57 
58 	string sql;
59 
60 	this(MYSQL_RES* r, string sql) {
61 		result = r;
62 		itemsTotal = cast(int) length();
63 		itemsUsed = 0;
64 
65 		this.sql = sql;
66 
67 		// prime it
68 		if(itemsTotal)
69 			fetchNext();
70 	}
71 
72 	~this() {
73 		if(result !is null)
74 			mysql_free_result(result);
75 	}
76 
77 
78 	MYSQL_FIELD[] fields() @system {
79 		int numFields = mysql_num_fields(result);
80 		auto fields = mysql_fetch_fields(result);
81 
82 		MYSQL_FIELD[] ret;
83 		for(int i = 0; i < numFields; i++) {
84 			ret ~= fields[i];
85 		}
86 
87 		return ret;
88 	}
89 
90 
91 	/// The number of returned rows
92 	override size_t length() {
93 		if(result is null)
94 			return 0;
95 		return cast(int) mysql_num_rows(result);
96 	}
97 
98 	/// Range primitive used by `foreach`
99 	/// You may also use this to check if there was any result.
100 	override bool empty() {
101 		return itemsUsed == itemsTotal;
102 	}
103 
104 	/// Range primitive used by `foreach`
105 	override Row front() {
106 		return row;
107 	}
108 
109 	/// Range primitive used by `foreach`
110 	override void popFront() {
111 		itemsUsed++;
112 		if(itemsUsed < itemsTotal) {
113 			fetchNext();
114 		}
115 	}
116 
117 	override int getFieldIndex(string field) {
118 		if(mapping is null)
119 			makeFieldMapping();
120 		debug {
121 			if(field !in mapping)
122 				throw new Exception(field ~ " not in result");
123 		}
124 		return mapping[field];
125 	}
126 
127 	private void makeFieldMapping() @system {
128 		int numFields = mysql_num_fields(result);
129 		auto fields = mysql_fetch_fields(result);
130 
131 		if(fields is null)
132 			return;
133 
134 		for(int i = 0; i < numFields; i++) {
135 			if(fields[i].name !is null)
136 				mapping[fromCstring(fields[i].name, fields[i].name_length)] = i;
137 		}
138 	}
139 
140 	private void fetchNext() @system {
141 		assert(result);
142 		auto r = mysql_fetch_row(result);
143 		if(r is null)
144 			throw new Exception("there is no next row");
145 		uint numFields = mysql_num_fields(result);
146 		auto lengths = mysql_fetch_lengths(result);
147 		DatabaseDatum[] row;
148 		// potential FIXME: not really binary safe
149 
150 		columnIsNull.length = numFields;
151 		for(int a = 0; a < numFields; a++) {
152 			if(*(r+a) is null) {
153 				row ~= DatabaseDatum(null);
154 				columnIsNull[a] = true;
155 			} else {
156 				row ~= DatabaseDatum(fromCstring(*(r+a), *(lengths + a)));
157 				columnIsNull[a] = false;
158 			}
159 		}
160 
161 		this.row.row = row;
162 		this.row.resultSet = this;
163 	}
164 
165 
166 	override string[] fieldNames() @system {
167 		int numFields = mysql_num_fields(result);
168 		auto fields = mysql_fetch_fields(result);
169 
170 		string[] names;
171 		for(int i = 0; i < numFields; i++) {
172 			names ~= fromCstring(fields[i].name, fields[i].name_length);
173 		}
174 
175 		return names;
176 	}
177 
178 
179 
180 	bool[] columnIsNull;
181 	Row row;
182 }
183 
184 /++
185 	The main class for accessing the MySql database.
186 
187 	---
188 		// connect to database with the constructor
189 		auto db = new MySql("localhost", "my_user", "my_password", "my_database_name");
190 		// use the query function to execute sql...
191 		// use ? for data placeholders...
192 		db.query("INSERT INTO people (id, name) VALUES (?, ?)", 10, "My Name");
193 		// and use foreach to loop over result sets
194 		foreach(row; db.query("SELECT id, name FROM people ORDER BY name LIMIT 10"))
195 			writeln(row[0], " ", row["name"]); // index and name supported
196 	---
197 +/
198 class MySql : Database {
199 	this(string host, string user, string pass, string db, uint port = 0) {
200 		mysql = enforce!(DatabaseException)(
201 			mysql_init(null),
202 			"Couldn't init mysql");
203 		enforce!(DatabaseConnectionException)(
204 			mysql_real_connect(mysql, toCstring(host), toCstring(user), toCstring(pass), toCstring(db), port, null, 0),
205 			error());
206 
207 		dbname = db;
208 
209 		// we want UTF8 for everything
210 
211 		query("SET NAMES 'utf8mb4'");
212 		//query("SET CHARACTER SET utf8mb4");
213 	}
214 
215 	string dbname;
216 
217 	///
218 	override void startTransaction() {
219 		query("START TRANSACTION");
220 	}
221 
222 	override bool isAlive() {
223 		return true;
224 	}
225 
226 	string sysTimeToValue(SysTime s) {
227 		return "cast('" ~ escape(s.toISOExtString()) ~ "' as datetime)";
228 	}
229 
230 	string error() {
231 		return fromCstring(mysql_error(mysql));
232 	}
233 
234 	void close() {
235 		if(mysql) {
236 			mysql_close(mysql);
237 			mysql = null;
238 		}
239 	}
240 
241 	~this() {
242 		close();
243 	}
244 
245 	///
246 	int lastInsertId() {
247 		return cast(int) mysql_insert_id(mysql);
248 	}
249 
250 
251 
252 	/// Builds and executes an INERT INTO statement
253 	int insert(string table, MySqlResult result, string[string] columnsToModify, string[] columnsToSkip) {
254 		assert(!result.empty);
255 		string sql = "INSERT INTO `" ~ table ~ "` ";
256 
257 		string cols = "(";
258 		string vals = "(";
259 		bool outputted = false;
260 
261 		string[string] columns;
262 		auto cnames = result.fieldNames;
263 		foreach(i, col; result.front.toStringArray) {
264 			bool skipMe = false;
265 			foreach(skip; columnsToSkip) {
266 				if(cnames[i] == skip) {
267 					skipMe = true;
268 					break;
269 				}
270 			}
271 			if(skipMe)
272 				continue;
273 
274 			if(outputted) {
275 				cols ~= ",";
276 				vals ~= ",";
277 			} else
278 				outputted = true;
279 
280 			cols ~= cnames[i];
281 
282 			if(result.columnIsNull[i] && cnames[i] !in columnsToModify)
283 				vals ~= "NULL";
284 			else {
285 				string v = col;
286 				if(cnames[i] in columnsToModify)
287 					v = columnsToModify[cnames[i]];
288 
289 				vals ~= "'" ~ escape(v) ~ "'";
290 
291 			}
292 		}
293 
294 		cols ~= ")";
295 		vals ~= ")";
296 
297 		sql ~= cols ~ " VALUES " ~ vals;
298 
299 		query(sql);
300 
301 		result.popFront;
302 
303 		return lastInsertId;
304 	}
305 
306 	string escape(string str) {
307 		ubyte[] buffer = new ubyte[str.length * 2 + 1];
308 		buffer.length = mysql_real_escape_string(mysql, buffer.ptr, cast(cstring) str.ptr, cast(uint) str.length);
309 
310 		return cast(string) buffer;
311 	}
312 
313 	string escapeBinaryString(const(ubyte)[] data) {
314 		return tohexsql(data);
315 	}
316 
317 	string escaped(T...)(string sql, T t) {
318 		static if(t.length > 0) {
319 			string fixedup;
320 			int pos = 0;
321 
322 
323 			void escAndAdd(string str, int q) {
324 				ubyte[] buffer = new ubyte[str.length * 2 + 1];
325 				buffer.length = mysql_real_escape_string(mysql, buffer.ptr, cast(cstring) str.ptr, str.length);
326 
327 				fixedup ~= sql[pos..q] ~ '\'' ~ cast(string) buffer ~ '\'';
328 
329 			}
330 
331 			foreach(a; t) {
332 				int q = sql[pos..$].indexOf("?");
333 				if(q == -1)
334 					break;
335 				q += pos;
336 
337 				static if(__traits(compiles, t is null)) {
338 					if(t is null)
339 						fixedup  ~= sql[pos..q] ~ "NULL";
340 					else
341 						escAndAdd(to!string(*a), q);
342 				} else {
343 					string str = to!string(a);
344 					escAndAdd(str, q);
345 				}
346 
347 				pos = q+1;
348 			}
349 
350 			fixedup ~= sql[pos..$];
351 
352 			sql = fixedup;
353 
354 			//writefln("\n\nExecuting sql: %s", sql);
355 		}
356 
357 		return sql;
358 	}
359 
360 
361 	/// Gets a minimal ORM object from a query
362 	ResultByDataObject!R queryDataObject(R = DataObject, T...)(string sql, T t) {
363 		// modify sql for the best data object grabbing
364 		sql = fixupSqlForDataObjectUse(sql);
365 
366 		auto magic = query(sql, t);
367 		return ResultByDataObject!R(cast(MySqlResult) magic, this);
368 	}
369 
370 
371 	/// ditto
372 	ResultByDataObject!R queryDataObjectWithCustomKeys(R = DataObject, T...)(string[string] keyMapping, string sql, T t) {
373 		sql = fixupSqlForDataObjectUse(sql, keyMapping);
374 
375 		auto magic = query(sql, t);
376 		return ResultByDataObject!R(cast(MySqlResult) magic, this);
377 	}
378 
379 
380 
381 	///
382 	int affectedRows() {
383 		return cast(int) mysql_affected_rows(mysql);
384 	}
385 
386 	override ResultSet queryImpl(string sql, Variant[] args...) {
387 		sql = escapedVariants(this, sql, args);
388 
389 		enforce!(SqlException)(
390 			!mysql_query(mysql, toCstring(sql)),
391 		error() ~ " :::: " ~ sql);
392 
393 		return new MySqlResult(mysql_store_result(mysql), sql);
394 	}
395 /+
396 	Result queryOld(T...)(string sql, T t) {
397 		sql = escaped(sql, t);
398 
399 		if(sql.length == 0)
400 			throw new DatabaseException("empty query");
401 		/*
402 		static int queryCount = 0;
403 		queryCount++;
404 		if(sql.indexOf("INSERT") != -1)
405 			stderr.writefln("%d: %s", queryCount, sql.replace("\n", " ").replace("\t", ""));
406 		*/
407 
408 		version(dryRun) {
409 			pragma(msg, "This is a dry run compile, no queries will be run");
410 			writeln(sql);
411 			return Result(null, null);
412 		}
413 
414 		enforceEx!(DatabaseException)(
415 			!mysql_query(mysql, toCstring(sql)),
416 		error() ~ " :::: " ~ sql);
417 
418 		return Result(mysql_store_result(mysql), sql);
419 	}
420 +/
421 /+
422 	struct ResultByAssoc {
423 		this(Result* r) {
424 			result = r;
425 			fields = r.fieldNames();
426 		}
427 
428 		ulong length() { return result.length; }
429 		bool empty() { return result.empty; }
430 		void popFront() { result.popFront(); }
431 		string[string] front() {
432 			auto r = result.front;
433 			string[string] ret;
434 			foreach(i, a; r) {
435 				ret[fields[i]] = a;
436 			}
437 
438 			return ret;
439 		}
440 
441 		@disable this(this) { }
442 
443 		string[] fields;
444 		Result* result;
445 	}
446 
447 
448 	struct ResultByStruct(T) {
449 		this(Result* r) {
450 			result = r;
451 			fields = r.fieldNames();
452 		}
453 
454 		ulong length() { return result.length; }
455 		bool empty() { return result.empty; }
456 		void popFront() { result.popFront(); }
457 		T front() {
458 			auto r = result.front;
459 			string[string] ret;
460 			foreach(i, a; r) {
461 				ret[fields[i]] = a;
462 			}
463 
464 			T s;
465 			// FIXME: should use tupleOf
466 			foreach(member; s.tupleof) {
467 				if(member.stringof in ret)
468 					member = to!(typeof(member))(ret[member]);
469 			}
470 
471 			return s;
472 		}
473 
474 		@disable this(this) { }
475 
476 		string[] fields;
477 		Result* result;
478 	}
479 +/
480 
481 /+
482 
483 
484 	struct Result {
485 		private Result* heaped() {
486 			auto r = new Result(result, sql, false);
487 
488 			r.tupleof = this.tupleof;
489 
490 			this.itemsTotal = 0;
491 			this.result = null;
492 
493 			return r;
494 		}
495 
496 		this(MYSQL_RES* r, string sql, bool prime = true) {
497 			result = r;
498 			itemsTotal = length;
499 			itemsUsed = 0;
500 			this.sql = sql;
501 			// prime it here
502 			if(prime && itemsTotal)
503 				fetchNext();
504 		}
505 
506 		string sql;
507 
508 		~this() {
509 			if(result !is null)
510 			mysql_free_result(result);
511 		}
512 
513 		/+
514 		string[string][] fetchAssoc() {
515 
516 		}
517 		+/
518 
519 		ResultByAssoc byAssoc() {
520 			return ResultByAssoc(&this);
521 		}
522 
523 		ResultByStruct!(T) byStruct(T)() {
524 			return ResultByStruct!(T)(&this);
525 		}
526 
527 		string[] fieldNames() {
528 			int numFields = mysql_num_fields(result);
529 			auto fields = mysql_fetch_fields(result);
530 
531 			string[] names;
532 			for(int i = 0; i < numFields; i++) {
533 				names ~= fromCstring(fields[i].name);
534 			}
535 
536 			return names;
537 		}
538 
539 		MYSQL_FIELD[] fields() {
540 			int numFields = mysql_num_fields(result);
541 			auto fields = mysql_fetch_fields(result);
542 
543 			MYSQL_FIELD[] ret;
544 			for(int i = 0; i < numFields; i++) {
545 				ret ~= fields[i];
546 			}
547 
548 			return ret;
549 		}
550 
551 		ulong length() {
552 			if(result is null)
553 				return 0;
554 			return mysql_num_rows(result);
555 		}
556 
557 		bool empty() {
558 			return itemsUsed == itemsTotal;
559 		}
560 
561 		Row front() {
562 			return row;
563 		}
564 
565 		void popFront() {
566 			itemsUsed++;
567 			if(itemsUsed < itemsTotal) {
568 				fetchNext();
569 			}
570 		}
571 
572 		void fetchNext() {
573 			auto r = mysql_fetch_row(result);
574 			uint numFields = mysql_num_fields(result);
575 			uint* lengths = mysql_fetch_lengths(result);
576 			row.length = 0;
577 			// potential FIXME: not really binary safe
578 
579 			columnIsNull.length = numFields;
580 			for(int a = 0; a < numFields; a++) {
581 				if(*(r+a) is null) {
582 					row ~= null;
583 					columnIsNull[a] = true;
584 				} else {
585 					row ~= fromCstring(*(r+a), *(lengths + a));
586 					columnIsNull[a] = false;
587 				}
588 			}
589 		}
590 
591 		@disable this(this) {}
592 		private MYSQL_RES* result;
593 
594 		ulong itemsTotal;
595 		ulong itemsUsed;
596 
597 		alias string[] Row;
598 
599 		Row row;
600 		bool[] columnIsNull; // FIXME: should be part of the row
601 	}
602 +/
603 	MYSQL* getHandle() {
604 		return mysql;
605 	}
606 
607   private:
608 	MYSQL* mysql;
609 }
610 
611 struct ResultByDataObject(ObjType) if (is(ObjType : DataObject)) {
612 	this(MySqlResult r, MySql mysql) {
613 		result = r;
614 		auto fields = r.fields();
615 		this.mysql = mysql;
616 
617 		foreach(i, f; fields) {
618 			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);
619 			mappings[fromCstring(f.name)] = tuple(
620 					tbl,
621 					fromCstring(f.org_name is null ? f.name : f.org_name, f.org_name is null ? f.name_length : f.org_name_length));
622 		}
623 
624 
625 	}
626 
627 	Tuple!(string, string)[string] mappings;
628 
629 	ulong length() { return result.length; }
630 	bool empty() { return result.empty; }
631 	void popFront() { result.popFront(); }
632 	ObjType front() {
633 		return new ObjType(mysql, result.front.toAA, mappings);
634 	}
635 	// would it be good to add a new() method? would be valid even if empty
636 	// it'd just fill in the ID's at random and allow you to do the rest
637 
638 	@disable this(this) { }
639 
640 	MySqlResult result;
641 	MySql mysql;
642 }
643 
644 
645 // thanks to 0xEAB on discord for sending me initial prepared statement support
646 
647 struct Statement
648 {
649     ~this()
650     {
651         if (this.statement !is null)
652         {
653             this.statement.mysql_stmt_close();
654             this.statement = null;
655         }
656     }
657 
658     void reset()
659     {
660 	mysql_stmt_reset(statement);
661     }
662 
663 private:
664     MYSQL_STMT* statement;
665     MYSQL_BIND[] params;
666 }
667 
668 Statement* prepare(MySql m, string query) @trusted
669 {
670     MYSQL_STMT* s = m.getHandle.mysql_stmt_init();
671     immutable x = s.mysql_stmt_prepare(query.toStringz, cast(int) query.length);
672 
673     if (x != 0)
674     {
675         throw new Exception(m.getHandle.mysql_error.fromCstring);
676     }
677 
678     return new Statement(s);
679 }
680 
681 import std.traits : isNumeric;
682 
683 void bindParameter(T)(Statement* s, ref T value) if (isNumeric!T)
684 {
685     import std.traits : isUnsigned;
686 
687     MYSQL_BIND p = MYSQL_BIND();
688 
689     p.buffer = &value;
690     p.buffer_type = mySqlType!T;
691     p.is_unsigned = isUnsigned!T;
692 
693     s.params ~= p;
694     immutable x = s.statement.mysql_stmt_bind_param(&(s.params[$ - 1]));
695 
696     if (x != 0)
697     {
698         throw new Exception(s.statement.mysql_stmt_error.fromStringz.to!string);
699     }
700 }
701 
702 void bindParameterNull(Statement* s)
703 {
704     MYSQL_BIND p = MYSQL_BIND();
705 
706     p.buffer_type = enum_field_types.MYSQL_TYPE_NULL;
707 
708     s.params ~= p;
709     immutable x = s.statement.mysql_stmt_bind_param(null);
710 
711     if (x != 0)
712     {
713         throw new Exception(s.statement.mysql_stmt_error.fromStringz.to!string);
714     }
715 }
716 
717 void bindParameter(T)(Statement* s, T value) if (is(T == string))
718 {
719     import std.traits : isUnsigned;
720 
721     MYSQL_BIND p = MYSQL_BIND();
722 
723     p.buffer = cast(void*) value.toCstring();
724     p.buffer_type = mySqlType!string;
725     p.buffer_length = value.length;
726 
727     s.params ~= p;
728     immutable x = s.statement.mysql_stmt_bind_param(&s.params[$ - 1]);
729 
730     if (x != 0)
731     {
732         throw new Exception(s.statement.mysql_stmt_error.fromStringz.to!string);
733     }
734 }
735 
736 void execute(Statement* s) @trusted
737 {
738     immutable x = s.statement.mysql_stmt_execute();
739 
740     if (x != 0)
741     {
742         throw new Exception(s.statement.mysql_stmt_error.fromStringz.to!string);
743     }
744 }
745 
746 
747 extern(System) {
748 	/*
749 		from <my_alloc.h>
750 		original header actually contains members,
751 		but guess we don't need them here
752 	*/
753 	struct USED_MEM;
754 
755 	/*
756 		from <my_alloc.h>
757 	*/
758 	struct MEM_ROOT
759 	{
760 		USED_MEM* free; /* blocks with free memory in it */
761 		USED_MEM* used; /* blocks almost without free memory */
762 		USED_MEM* pre_alloc; /* preallocated block */
763 		/* if block have less memory it will be put in 'used' list */
764 		size_t min_malloc;
765 		size_t block_size; /* initial block size */
766 		uint block_num; /* allocated blocks counter */
767 		/*
768 		first free block in queue test counter (if it exceed
769 		MAX_BLOCK_USAGE_BEFORE_DROP block will be dropped in 'used' list)
770 		*/
771 		uint first_block_usage;
772 
773 		void function () error_handler;
774 	}
775 
776 	/*
777 		from <mysql_com.h>
778 
779 		original header actually contains members,
780 		but guess we don't need them here
781 	*/
782 	struct NET;
783 
784 	/* from <mysql_com.h> */
785 	enum MYSQL_ERRMSG_SIZE = 512;
786 
787 	/* from <mysql_com.h> */
788 	enum enum_field_types {
789 		MYSQL_TYPE_DECIMAL, MYSQL_TYPE_TINY,
790 		MYSQL_TYPE_SHORT,  MYSQL_TYPE_LONG,
791 		MYSQL_TYPE_FLOAT,  MYSQL_TYPE_DOUBLE,
792 		MYSQL_TYPE_NULL,   MYSQL_TYPE_TIMESTAMP,
793 		MYSQL_TYPE_LONGLONG,MYSQL_TYPE_INT24,
794 		MYSQL_TYPE_DATE,   MYSQL_TYPE_TIME,
795 		MYSQL_TYPE_DATETIME, MYSQL_TYPE_YEAR,
796 		MYSQL_TYPE_NEWDATE, MYSQL_TYPE_VARCHAR,
797 		MYSQL_TYPE_BIT,
798 
799 			/*
800 				mysql-5.6 compatibility temporal types.
801 				They're only used internally for reading RBR
802 				mysql-5.6 binary log events and mysql-5.6 frm files.
803 				They're never sent to the client.
804 			*/
805 			MYSQL_TYPE_TIMESTAMP2,
806 			MYSQL_TYPE_DATETIME2,
807 			MYSQL_TYPE_TIME2,
808 
809 			MYSQL_TYPE_NEWDECIMAL=246,
810 
811 		MYSQL_TYPE_ENUM=247,
812 		MYSQL_TYPE_SET=248,
813 		MYSQL_TYPE_TINY_BLOB=249,
814 		MYSQL_TYPE_MEDIUM_BLOB=250,
815 		MYSQL_TYPE_LONG_BLOB=251,
816 		MYSQL_TYPE_BLOB=252,
817 		MYSQL_TYPE_VAR_STRING=253,
818 		MYSQL_TYPE_STRING=254,
819 		MYSQL_TYPE_GEOMETRY=255
820 	}
821 
822 	/* from <my_list.h>*/
823 	struct LIST
824 	{
825 		LIST* prev;
826 		LIST* next;
827 		void* data;
828 	}
829 
830 	struct MYSQL;
831 	struct MYSQL_RES;
832 	/* typedef */ alias const(ubyte)* cstring;
833 
834 	alias my_bool = char;
835 	alias my_ulonglong = ulong;
836 
837 	struct MYSQL_FIELD {
838 		  cstring name;                 /* Name of column */
839 		  cstring org_name;             /* Original column name, if an alias */
840 		  cstring table;                /* Table of column if column was a field */
841 		  cstring org_table;            /* Org table name, if table was an alias */
842 		  cstring db;                   /* Database for table */
843 		  cstring catalog;	      /* Catalog for table */
844 		  cstring def;                  /* Default value (set by mysql_list_fields) */
845 		  c_ulong length;       /* Width of column (create length) */
846 		  c_ulong max_length;   /* Max width for selected set */
847 		  uint name_length;
848 		  uint org_name_length;
849 		  uint table_length;
850 		  uint org_table_length;
851 		  uint db_length;
852 		  uint catalog_length;
853 		  uint def_length;
854 		  uint flags;         /* Div flags */
855 		  uint decimals;      /* Number of decimals in field */
856 		  uint charsetnr;     /* Character set */
857 		  uint type; /* Type of field. See mysql_com.h for types */
858 		  // type is actually an enum btw
859 
860 		version(MySQL_51) {
861 			void* extension;
862 		}
863 	}
864 
865 	struct MYSQL_ROWS
866 	{
867 		MYSQL_ROWS* next; /* list of rows */
868 		MYSQL_ROW data;
869 		c_ulong length;
870 	}
871 
872 	alias MYSQL_ROW_OFFSET = MYSQL_ROWS*; /* offset to current row */
873 
874 	struct EMBEDDED_QUERY_RESULT;
875 
876 	struct MYSQL_DATA
877 	{
878 		MYSQL_ROWS* data;
879 		EMBEDDED_QUERY_RESULT* embedded_info;
880 		MEM_ROOT alloc;
881 		my_ulonglong rows;
882 		uint fields;
883 
884 		version(MySQL_51) {
885 			/* extra info for embedded library */
886 			void* extension;
887 		}
888 	}
889 
890 	/* statement state */
891 	enum enum_mysql_stmt_state
892 	{
893 		MYSQL_STMT_INIT_DONE = 1,
894 		MYSQL_STMT_PREPARE_DONE = 2,
895 		MYSQL_STMT_EXECUTE_DONE = 3,
896 		MYSQL_STMT_FETCH_DONE = 4
897 	}
898 
899 	enum enum_stmt_attr_type
900 	{
901 		/**
902 			When doing mysql_stmt_store_result calculate max_length attribute
903 			of statement metadata. This is to be consistent with the old API,
904 			where this was done automatically.
905 			In the new API we do that only by request because it slows down
906 			mysql_stmt_store_result sufficiently.
907 		*/
908 		STMT_ATTR_UPDATE_MAX_LENGTH = 0,
909 		/**
910 			unsigned long with combination of cursor flags (read only, for update, etc)
911 		*/
912 		STMT_ATTR_CURSOR_TYPE = 1,
913 		/**
914 			Amount of rows to retrieve from server per one fetch if using cursors.
915 			Accepts unsigned long attribute in the range 1 - ulong_max
916 		*/
917 		STMT_ATTR_PREFETCH_ROWS = 2
918 	}
919 
920 	struct MYSQL_BIND
921 	{
922 		c_ulong* length; /* output length pointer */
923 		my_bool* is_null; /* Pointer to null indicator */
924 		void* buffer; /* buffer to get/put data */
925 		/* set this if you want to track data truncations happened during fetch */
926 		my_bool* error;
927 		ubyte* row_ptr; /* for the current data position */
928 		void function (NET* net, MYSQL_BIND* param) store_param_func;
929 		void function (MYSQL_BIND*, MYSQL_FIELD*, ubyte** row) fetch_result;
930 		void function (MYSQL_BIND*, MYSQL_FIELD*, ubyte** row) skip_result;
931 		/* output buffer length, must be set when fetching str/binary */
932 		c_ulong buffer_length;
933 		c_ulong offset; /* offset position for char/binary fetch */
934 		c_ulong length_value; /* Used if length is 0 */
935 		uint param_number; /* For null count and error messages */
936 		uint pack_length; /* Internal length for packed data */
937 		enum_field_types buffer_type; /* buffer type */
938 		my_bool error_value; /* used if error is 0 */
939 		my_bool is_unsigned; /* set if integer type is unsigned */
940 		my_bool long_data_used; /* If used with mysql_send_long_data */
941 		my_bool is_null_value; /* Used if is_null is 0 */
942 		void* extension;
943 	}
944 
945 	struct st_mysql_stmt_extension;
946 
947 	/* statement handler */
948 	struct MYSQL_STMT
949 	{
950 		MEM_ROOT mem_root; /* root allocations */
951 		LIST list; /* list to keep track of all stmts */
952 		MYSQL* mysql; /* connection handle */
953 		MYSQL_BIND* params; /* input parameters */
954 		MYSQL_BIND* bind; /* output parameters */
955 		MYSQL_FIELD* fields; /* result set metadata */
956 		MYSQL_DATA result; /* cached result set */
957 		MYSQL_ROWS* data_cursor; /* current row in cached result */
958 		/*
959 		mysql_stmt_fetch() calls this function to fetch one row (it's different
960 		for buffered, unbuffered and cursor fetch).
961 		*/
962 		int function (MYSQL_STMT* stmt, ubyte** row) read_row_func;
963 		/* copy of mysql->affected_rows after statement execution */
964 		my_ulonglong affected_rows;
965 		my_ulonglong insert_id; /* copy of mysql->insert_id */
966 		c_ulong stmt_id; /* Id for prepared statement */
967 		c_ulong flags; /* i.e. type of cursor to open */
968 		c_ulong prefetch_rows; /* number of rows per one COM_FETCH */
969 		/*
970 		Copied from mysql->server_status after execute/fetch to know
971 		server-side cursor status for this statement.
972 		*/
973 		uint server_status;
974 		uint last_errno; /* error code */
975 		uint param_count; /* input parameter count */
976 		uint field_count; /* number of columns in result set */
977 		enum_mysql_stmt_state state; /* statement state */
978 		char[MYSQL_ERRMSG_SIZE] last_error; /* error message */
979 		char[6] sqlstate;
980 		/* Types of input parameters should be sent to server */
981 		my_bool send_types_to_server;
982 		my_bool bind_param_done; /* input buffers were supplied */
983 		ubyte bind_result_done; /* output buffers were supplied */
984 		/* mysql_stmt_close() had to cancel this result */
985 		my_bool unbuffered_fetch_cancelled;
986 		/*
987 		Is set to true if we need to calculate field->max_length for
988 		metadata fields when doing mysql_stmt_store_result.
989 		*/
990 		my_bool update_max_length;
991 		st_mysql_stmt_extension* extension;
992 	}
993 
994 	/* typedef */ alias cstring* MYSQL_ROW;
995 
996 	cstring mysql_get_client_info();
997 	MYSQL* mysql_init(MYSQL*);
998 	uint mysql_errno(MYSQL*);
999 	cstring mysql_error(MYSQL*);
1000 
1001 	MYSQL* mysql_real_connect(MYSQL*, cstring, cstring, cstring, cstring, uint, cstring, c_ulong);
1002 
1003 	int mysql_query(MYSQL*, cstring);
1004 
1005 	void mysql_close(MYSQL*);
1006 
1007 	ulong mysql_num_rows(MYSQL_RES*);
1008 	uint mysql_num_fields(MYSQL_RES*);
1009 	bool mysql_eof(MYSQL_RES*);
1010 
1011 	ulong mysql_affected_rows(MYSQL*);
1012 	ulong mysql_insert_id(MYSQL*);
1013 
1014 	MYSQL_RES* mysql_store_result(MYSQL*);
1015 	MYSQL_RES* mysql_use_result(MYSQL*);
1016 
1017 	MYSQL_ROW mysql_fetch_row(MYSQL_RES *);
1018 	c_ulong* mysql_fetch_lengths(MYSQL_RES*);
1019 	MYSQL_FIELD* mysql_fetch_field(MYSQL_RES*);
1020 	MYSQL_FIELD* mysql_fetch_fields(MYSQL_RES*);
1021 
1022 	uint mysql_real_escape_string(MYSQL*, ubyte* to, cstring from, c_ulong length);
1023 
1024 	void mysql_free_result(MYSQL_RES*);
1025 
1026 	MYSQL_STMT* mysql_stmt_init (MYSQL* mysql);
1027 	int mysql_stmt_prepare (MYSQL_STMT* stmt, const(char)* query, c_ulong length);
1028 	int mysql_stmt_execute (MYSQL_STMT* stmt);
1029 	my_bool mysql_stmt_bind_param (MYSQL_STMT* stmt, MYSQL_BIND* bnd);
1030 	my_bool mysql_stmt_close (MYSQL_STMT* stmt);
1031 	my_bool mysql_stmt_free_result (MYSQL_STMT* stmt);
1032 	my_bool mysql_stmt_reset (MYSQL_STMT* stmt);
1033 	uint mysql_stmt_errno (MYSQL_STMT* stmt);
1034 	const(char)* mysql_stmt_error (MYSQL_STMT* stmt);
1035 	const(char)* mysql_stmt_sqlstate (MYSQL_STMT* stmt);
1036 	my_ulonglong mysql_stmt_num_rows (MYSQL_STMT* stmt);
1037 	my_ulonglong mysql_stmt_affected_rows (MYSQL_STMT* stmt);
1038 	my_ulonglong mysql_stmt_insert_id (MYSQL_STMT* stmt);
1039 
1040 }
1041 
1042 import std.string;
1043 cstring toCstring(string c) {
1044 	return cast(cstring) toStringz(c);
1045 }
1046 
1047 import std.array;
1048 string fromCstring(cstring c, size_t len = size_t.max) @system {
1049 	string ret;
1050 	if(c is null)
1051 		return null;
1052 	if(len == 0)
1053 		return "";
1054 	if(len == size_t.max) {
1055 		auto iterator = c;
1056 		len = 0;
1057 		while(*iterator) {
1058 			iterator++;
1059 			len++;
1060 		}
1061 		assert(len >= 0);
1062 	}
1063 
1064 	ret = cast(string) (c[0 .. len].idup);
1065 
1066 	return ret;
1067 }
1068 
1069 enum_field_types getMySqlType(T)() {
1070 	static if (is(T == bool))
1071 		return enum_field_types.MYSQL_TYPE_TINY;
1072 
1073 	static if (is(T == char))
1074 		return enum_field_types.MYSQL_TYPE_TINY;
1075 
1076 		static if (is(T == byte) || is(T == ubyte))
1077 		return enum_field_types.MYSQL_TYPE_TINY;
1078 
1079 	else static if (is(T == short) || is(T == ushort))
1080 		return enum_field_types.MYSQL_TYPE_SHORT;
1081 
1082 	else static if (is(T == int) || is(T == uint))
1083 		return enum_field_types.MYSQL_TYPE_LONG;
1084 
1085 	else static if (is(T == long) || is(T == ulong))
1086 		return enum_field_types.MYSQL_TYPE_LONGLONG;
1087 
1088 	else static if (is(T == string))
1089 		return enum_field_types.MYSQL_TYPE_STRING;
1090 
1091 	else static if (is(T == float))
1092 		return enum_field_types.MYSQL_TYPE_FLOAT;
1093 
1094 	else static if (is(T == double))
1095 		return enum_field_types.MYSQL_TYPE_DOUBLE;
1096 
1097 	//else static if (is(T == byte[]))
1098 	//	return enum_field_types.MYSQL_TYPE_BLOB;
1099 
1100 	else
1101 		static assert("No MySQL equivalent known for " ~ T);
1102 }
1103 
1104 enum enum_field_types mySqlType(T) = getMySqlType!T;
1105 
1106 // FIXME: this should work generically with all database types and them moved to database.d
1107 ///
1108 Ret queryOneRow(Ret = Row, DB, string file = __FILE__, size_t line = __LINE__, T...)(DB db, string sql, T t) if(
1109 	(is(DB : Database))
1110 	// && (is(Ret == Row) || is(Ret : DataObject)))
1111 	)
1112 {
1113 	static if(is(Ret : DataObject) && is(DB == MySql)) {
1114 		auto res = db.queryDataObject!Ret(sql, t);
1115 		if(res.empty)
1116 			throw new EmptyResultException("result was empty", file, line);
1117 		return res.front;
1118 	} else static if(is(Ret == Row)) {
1119 		auto res = db.query(sql, t);
1120 		if(res.empty)
1121 			throw new EmptyResultException("result was empty", file, line);
1122 		return res.front;
1123 	} else static assert(0, "Unsupported single row query return value, " ~ Ret.stringof);
1124 }
1125 
1126 ///
1127 class EmptyResultException : Exception {
1128 	this(string message, string file = __FILE__, size_t line = __LINE__) {
1129 		super(message, file, line);
1130 	}
1131 }
1132 
1133 
1134 /*
1135 void main() {
1136 	auto mysql = new MySql("localhost", "uname", "password", "test");
1137 	scope(exit) delete mysql;
1138 
1139 	mysql.query("INSERT INTO users (id, password) VALUES (?, ?)", 10, "lol");
1140 
1141 	foreach(row; mysql.query("SELECT * FROM users")) {
1142 		writefln("%s %s %s %s", row["id"], row[0], row[1], row["username"]);
1143 	}
1144 }
1145 */
1146 
1147 /*
1148 struct ResultByStruct(T) {
1149 	this(MySql.Result* r) {
1150 		result = r;
1151 		fields = r.fieldNames();
1152 	}
1153 
1154 	ulong length() { return result.length; }
1155 	bool empty() { return result.empty; }
1156 	void popFront() { result.popFront(); }
1157 	T front() {
1158 		auto r = result.front;
1159 		T ret;
1160 		foreach(i, a; r) {
1161 			ret[fields[i]] = a;
1162 		}
1163 
1164 		return ret;
1165 	}
1166 
1167 	@disable this(this) { }
1168 
1169 	string[] fields;
1170 	MySql.Result* result;
1171 }
1172 */
1173 
1174 
1175 /+
1176 	mysql.linq.tablename.field[key] // select field from tablename where id = key
1177 
1178 	mysql.link["name"].table.field[key] // select field from table where name = key
1179 
1180 
1181 	auto q = mysql.prepQuery("select id from table where something");
1182 	q.sort("name");
1183 	q.limit(start, count);
1184 	q.page(3, pagelength = ?);
1185 
1186 	q.execute(params here); // returns the same Result range as query
1187 +/
1188 
1189 /*
1190 void main() {
1191 	auto db = new MySql("localhost", "uname", "password", "test");
1192 	foreach(item; db.queryDataObject("SELECT users.*, username
1193 		FROM users, password_manager_accounts
1194 		WHERE password_manager_accounts.user_id =  users.id LIMIT 5")) {
1195 		writefln("item: %s, %s", item.id, item.username);
1196 		item.first = "new";
1197 		item.last = "new2";
1198 		item.username = "kill";
1199 		//item.commitChanges();
1200 	}
1201 }
1202 */
1203 
1204 
1205 /*
1206 Copyright: Adam D. Ruppe, 2009 - 2024
1207 License:   GPL (erroneously started to be Boost in 2011)
1208 Authors: Adam D. Ruppe, with contributions from Nick Sabalausky
1209 */
1210