1 module mysql.maintests;
2 import mysql.test.common;
3 import mysql;
4 import mysql.protocol.constants;
5 
6 import std.exception;
7 import std.variant;
8 import std.typecons;
9 import std.array;
10 import std.algorithm;
11 
12 // mysql.commands
13 @("columnSpecial")
14 debug(MYSQLN_TESTS)
15 unittest
16 {
17 	import std.array;
18 	import std.range;
19 	import mysql.test.common;
20 	mixin(scopedCn);
21 
22 	// Setup
23 	cn.exec("DROP TABLE IF EXISTS `columnSpecial`");
24 	cn.exec("CREATE TABLE `columnSpecial` (
25 		`data` LONGBLOB
26 	) ENGINE=InnoDB DEFAULT CHARSET=utf8");
27 
28 	immutable totalSize = 1000; // Deliberately not a multiple of chunkSize below
29 	auto alph = cast(const(ubyte)[]) "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
30 	auto data = alph.cycle.take(totalSize).array;
31 	cn.exec("INSERT INTO `columnSpecial` VALUES (\""~(cast(string)data)~"\")");
32 
33 	// Common stuff
34 	int chunkSize;
35 	immutable selectSQL = "SELECT `data` FROM `columnSpecial`";
36 	ubyte[] received;
37 	bool lastValueOfFinished;
38 	void receiver(const(ubyte)[] chunk, bool finished)
39 	{
40 		assert(lastValueOfFinished == false);
41 
42 		if(finished)
43 			assert(chunk.length == chunkSize);
44 		else
45 			assert(chunk.length < chunkSize); // Not always true in general, but true in this unittest
46 
47 		received ~= chunk;
48 		lastValueOfFinished = finished;
49 	}
50 
51 	// Sanity check
52 	auto value = cn.queryValue(selectSQL);
53 	assert(!value.isNull);
54 	assert(value.get == data);
55 
56 	// Use ColumnSpecialization with sql string,
57 	// and totalSize as a multiple of chunkSize
58 	{
59 		chunkSize = 100;
60 		assert(cast(int)(totalSize / chunkSize) * chunkSize == totalSize);
61 		auto columnSpecial = ColumnSpecialization(0, 0xfc, chunkSize, &receiver);
62 
63 		received = null;
64 		lastValueOfFinished = false;
65 		value = cn.queryValue(selectSQL, [columnSpecial]);
66 		assert(!value.isNull);
67 		assert(value.get == data);
68 		//TODO: ColumnSpecialization is not yet implemented
69 		//assert(lastValueOfFinished == true);
70 		//assert(received == data);
71 	}
72 
73 	// Use ColumnSpecialization with sql string,
74 	// and totalSize as a non-multiple of chunkSize
75 	{
76 		chunkSize = 64;
77 		assert(cast(int)(totalSize / chunkSize) * chunkSize != totalSize);
78 		auto columnSpecial = ColumnSpecialization(0, 0xfc, chunkSize, &receiver);
79 
80 		received = null;
81 		lastValueOfFinished = false;
82 		value = cn.queryValue(selectSQL, [columnSpecial]);
83 		assert(!value.isNull);
84 		assert(value.get == data);
85 		//TODO: ColumnSpecialization is not yet implemented
86 		//assert(lastValueOfFinished == true);
87 		//assert(received == data);
88 	}
89 
90 	// Use ColumnSpecialization with prepared statement,
91 	// and totalSize as a multiple of chunkSize
92 	{
93 		chunkSize = 100;
94 		assert(cast(int)(totalSize / chunkSize) * chunkSize == totalSize);
95 		auto columnSpecial = ColumnSpecialization(0, 0xfc, chunkSize, &receiver);
96 
97 		received = null;
98 		lastValueOfFinished = false;
99 		auto prepared = cn.prepare(selectSQL);
100 		prepared.columnSpecials = [columnSpecial];
101 		value = cn.queryValue(prepared);
102 		assert(!value.isNull);
103 		assert(value.get == data);
104 		//TODO: ColumnSpecialization is not yet implemented
105 		//assert(lastValueOfFinished == true);
106 		//assert(received == data);
107 	}
108 }
109 
110 // Test what happens when queryRowTuple receives no rows
111 @("queryRowTuple_noRows")
112 debug(MYSQLN_TESTS)
113 unittest
114 {
115 	import mysql.test.common : scopedCn, createCn;
116 	mixin(scopedCn);
117 
118 	cn.exec("DROP TABLE IF EXISTS `queryRowTuple_noRows`");
119 	cn.exec("CREATE TABLE `queryRowTuple_noRows` (
120 		`val` INTEGER
121 	) ENGINE=InnoDB DEFAULT CHARSET=utf8");
122 
123 	immutable selectSQL = "SELECT * FROM `queryRowTuple_noRows`";
124 	int queryTupleResult;
125 	assertThrown!MYX(cn.queryRowTuple(selectSQL, queryTupleResult));
126 }
127 
128 @("execOverloads")
129 debug(MYSQLN_TESTS)
130 unittest
131 {
132 	import std.array;
133 	import mysql.connection;
134 	import mysql.test.common;
135 	mixin(scopedCn);
136 
137 	cn.exec("DROP TABLE IF EXISTS `execOverloads`");
138 	cn.exec("CREATE TABLE `execOverloads` (
139 		`i` INTEGER,
140 		`s` VARCHAR(50)
141 	) ENGINE=InnoDB DEFAULT CHARSET=utf8");
142 
143 	immutable prepareSQL = "INSERT INTO `execOverloads` VALUES (?, ?)";
144 
145 	// Do the inserts, using exec
146 
147 	// exec: const(char[]) sql
148 	assert(cn.exec("INSERT INTO `execOverloads` VALUES (1, \"aa\")") == 1);
149 	assert(cn.exec(prepareSQL, 2, "bb") == 1);
150 	assert(cn.exec(prepareSQL, [Variant(3), Variant("cc")]) == 1);
151 
152 	// exec: prepared sql
153 	auto prepared = cn.prepare(prepareSQL);
154 	prepared.setArgs(4, "dd");
155 	assert(cn.exec(prepared) == 1);
156 
157 	assert(cn.exec(prepared, 5, "ee") == 1);
158 	assert(prepared.getArg(0) == 5);
159 	assert(prepared.getArg(1) == "ee");
160 
161 	assert(cn.exec(prepared, [Variant(6), Variant("ff")]) == 1);
162 	assert(prepared.getArg(0) == 6);
163 	assert(prepared.getArg(1) == "ff");
164 
165 	// exec: bcPrepared sql
166 	auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL);
167 	bcPrepared.setArgs(7, "gg");
168 	assert(cn.exec(bcPrepared) == 1);
169 	assert(bcPrepared.getArg(0) == 7);
170 	assert(bcPrepared.getArg(1) == "gg");
171 
172 	// Check results
173 	auto rows = cn.query("SELECT * FROM `execOverloads`").array();
174 	assert(rows.length == 7);
175 
176 	assert(rows[0].length == 2);
177 	assert(rows[1].length == 2);
178 	assert(rows[2].length == 2);
179 	assert(rows[3].length == 2);
180 	assert(rows[4].length == 2);
181 	assert(rows[5].length == 2);
182 	assert(rows[6].length == 2);
183 
184 	assert(rows[0][0] == 1);
185 	assert(rows[0][1] == "aa");
186 	assert(rows[1][0] == 2);
187 	assert(rows[1][1] == "bb");
188 	assert(rows[2][0] == 3);
189 	assert(rows[2][1] == "cc");
190 	assert(rows[3][0] == 4);
191 	assert(rows[3][1] == "dd");
192 	assert(rows[4][0] == 5);
193 	assert(rows[4][1] == "ee");
194 	assert(rows[5][0] == 6);
195 	assert(rows[5][1] == "ff");
196 	assert(rows[6][0] == 7);
197 	assert(rows[6][1] == "gg");
198 }
199 
200 @("queryOverloads")
201 debug(MYSQLN_TESTS)
202 unittest
203 {
204 	import std.array;
205 	import mysql.connection;
206 	import mysql.test.common;
207 	mixin(scopedCn);
208 
209 	cn.exec("DROP TABLE IF EXISTS `queryOverloads`");
210 	cn.exec("CREATE TABLE `queryOverloads` (
211 		`i` INTEGER,
212 		`s` VARCHAR(50)
213 	) ENGINE=InnoDB DEFAULT CHARSET=utf8");
214 	cn.exec("INSERT INTO `queryOverloads` VALUES (1, \"aa\"), (2, \"bb\"), (3, \"cc\")");
215 
216 	immutable prepareSQL = "SELECT * FROM `queryOverloads` WHERE `i`=? AND `s`=?";
217 
218 	// Test query
219 	{
220 		Row[] rows;
221 
222 		// String sql
223 		rows = cn.query("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\"").array;
224 		assert(rows.length == 1);
225 		assert(rows[0].length == 2);
226 		assert(rows[0][0] == 1);
227 		assert(rows[0][1] == "aa");
228 
229 		rows = cn.query(prepareSQL, 2, "bb").array;
230 		assert(rows.length == 1);
231 		assert(rows[0].length == 2);
232 		assert(rows[0][0] == 2);
233 		assert(rows[0][1] == "bb");
234 
235 		rows = cn.query(prepareSQL, [Variant(3), Variant("cc")]).array;
236 		assert(rows.length == 1);
237 		assert(rows[0].length == 2);
238 		assert(rows[0][0] == 3);
239 		assert(rows[0][1] == "cc");
240 
241 		// Prepared sql
242 		auto prepared = cn.prepare(prepareSQL);
243 		prepared.setArgs(1, "aa");
244 		rows = cn.query(prepared).array;
245 		assert(rows.length == 1);
246 		assert(rows[0].length == 2);
247 		assert(rows[0][0] == 1);
248 		assert(rows[0][1] == "aa");
249 
250 		rows = cn.query(prepared, 2, "bb").array;
251 		assert(rows.length == 1);
252 		assert(rows[0].length == 2);
253 		assert(rows[0][0] == 2);
254 		assert(rows[0][1] == "bb");
255 
256 		rows = cn.query(prepared, [Variant(3), Variant("cc")]).array;
257 		assert(rows.length == 1);
258 		assert(rows[0].length == 2);
259 		assert(rows[0][0] == 3);
260 		assert(rows[0][1] == "cc");
261 
262 		// BCPrepared sql
263 		auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL);
264 		bcPrepared.setArgs(1, "aa");
265 		rows = cn.query(bcPrepared).array;
266 		assert(rows.length == 1);
267 		assert(rows[0].length == 2);
268 		assert(rows[0][0] == 1);
269 		assert(rows[0][1] == "aa");
270 	}
271 
272 	// Test queryRow
273 	{
274 		// Note, queryRow returns Nullable, but we always expect to get a row,
275 		// so we will let the `get` check in Nullable assert that it's not
276 		// null.
277 		Row row;
278 
279 		// String sql
280 		row = cn.queryRow("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\"").get;
281 		assert(row.length == 2);
282 		assert(row[0] == 1);
283 		assert(row[1] == "aa");
284 
285 		row = cn.queryRow(prepareSQL, 2, "bb").get;
286 		assert(row.length == 2);
287 		assert(row[0] == 2);
288 		assert(row[1] == "bb");
289 
290 		row = cn.queryRow(prepareSQL, [Variant(3), Variant("cc")]).get;
291 		assert(row.length == 2);
292 		assert(row[0] == 3);
293 		assert(row[1] == "cc");
294 
295 		// Prepared sql
296 		auto prepared = cn.prepare(prepareSQL);
297 		prepared.setArgs(1, "aa");
298 		row = cn.queryRow(prepared).get;
299 		assert(row.length == 2);
300 		assert(row[0] == 1);
301 		assert(row[1] == "aa");
302 
303 		row = cn.queryRow(prepared, 2, "bb").get;
304 		assert(row.length == 2);
305 		assert(row[0] == 2);
306 		assert(row[1] == "bb");
307 
308 		row = cn.queryRow(prepared, [Variant(3), Variant("cc")]).get;
309 		assert(row.length == 2);
310 		assert(row[0] == 3);
311 		assert(row[1] == "cc");
312 
313 		// BCPrepared sql
314 		auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL);
315 		bcPrepared.setArgs(1, "aa");
316 		row = cn.queryRow(bcPrepared).get;
317 		assert(row.length == 2);
318 		assert(row[0] == 1);
319 		assert(row[1] == "aa");
320 	}
321 
322 	// Test queryRowTuple
323 	{
324 		int i;
325 		string s;
326 
327 		// String sql
328 		cn.queryRowTuple("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\"", i, s);
329 		assert(i == 1);
330 		assert(s == "aa");
331 
332 		// Prepared sql
333 		auto prepared = cn.prepare(prepareSQL);
334 		prepared.setArgs(2, "bb");
335 		cn.queryRowTuple(prepared, i, s);
336 		assert(i == 2);
337 		assert(s == "bb");
338 
339 		// BCPrepared sql
340 		auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL);
341 		bcPrepared.setArgs(3, "cc");
342 		cn.queryRowTuple(bcPrepared, i, s);
343 		assert(i == 3);
344 		assert(s == "cc");
345 	}
346 
347 	// Test queryValue
348 	{
349 		Variant value;
350 
351 		// String sql
352 		value = cn.queryValue("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\"").get;
353 		assert(value.type != typeid(typeof(null)));
354 		assert(value == 1);
355 
356 		value = cn.queryValue(prepareSQL, 2, "bb").get;
357 		assert(value.type != typeid(typeof(null)));
358 		assert(value == 2);
359 
360 		value = cn.queryValue(prepareSQL, [Variant(3), Variant("cc")]).get;
361 		assert(value.type != typeid(typeof(null)));
362 		assert(value == 3);
363 
364 		// Prepared sql
365 		auto prepared = cn.prepare(prepareSQL);
366 		prepared.setArgs(1, "aa");
367 		value = cn.queryValue(prepared).get;
368 		assert(value.type != typeid(typeof(null)));
369 		assert(value == 1);
370 
371 		value = cn.queryValue(prepared, 2, "bb").get;
372 		assert(value.type != typeid(typeof(null)));
373 		assert(value == 2);
374 
375 		value = cn.queryValue(prepared, [Variant(3), Variant("cc")]).get;
376 		assert(value.type != typeid(typeof(null)));
377 		assert(value == 3);
378 
379 		// BCPrepared sql
380 		auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL);
381 		bcPrepared.setArgs(1, "aa");
382 		value = cn.queryValue(bcPrepared).get;
383 		assert(value.type != typeid(typeof(null)));
384 		assert(value == 1);
385 	}
386 }
387 
388 // mysql.connection
389 @("prepareFunction")
390 debug(MYSQLN_TESTS)
391 unittest
392 {
393 	import mysql.test.common;
394 	mixin(scopedCn);
395 
396 	exec(cn, `DROP FUNCTION IF EXISTS hello`);
397 	exec(cn, `
398 		CREATE FUNCTION hello (s CHAR(20))
399 		RETURNS CHAR(50) DETERMINISTIC
400 		RETURN CONCAT('Hello ',s,'!')
401 	`);
402 
403 	auto preparedHello = prepareFunction(cn, "hello", 1);
404 	preparedHello.setArgs("World");
405 	auto rs = cn.query(preparedHello).array;
406 	assert(rs.length == 1);
407 	assert(rs[0][0] == "Hello World!");
408 }
409 
410 @("prepareProcedure")
411 debug(MYSQLN_TESTS)
412 unittest
413 {
414 	import mysql.test.common;
415 	import mysql.test.integration;
416 	mixin(scopedCn);
417 	initBaseTestTables(cn);
418 
419 	exec(cn, `DROP PROCEDURE IF EXISTS insert2`);
420 	exec(cn, `
421 		CREATE PROCEDURE insert2 (IN p1 INT, IN p2 CHAR(50))
422 		BEGIN
423 			INSERT INTO basetest (intcol, stringcol) VALUES(p1, p2);
424 		END
425 	`);
426 
427 	auto preparedInsert2 = prepareProcedure(cn, "insert2", 2);
428 	preparedInsert2.setArgs(2001, "inserted string 1");
429 	cn.exec(preparedInsert2);
430 
431 	auto rs = query(cn, "SELECT stringcol FROM basetest WHERE intcol=2001").array;
432 	assert(rs.length == 1);
433 	assert(rs[0][0] == "inserted string 1");
434 }
435 
436 // This also serves as a regression test for #167:
437 // ResultRange doesn't get invalidated upon reconnect
438 @("reconnect")
439 debug(MYSQLN_TESTS)
440 unittest
441 {
442 	import std.variant;
443 	mixin(scopedCn);
444 	cn.exec("DROP TABLE IF EXISTS `reconnect`");
445 	cn.exec("CREATE TABLE `reconnect` (a INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8");
446 	cn.exec("INSERT INTO `reconnect` VALUES (1),(2),(3)");
447 
448 	enum sql = "SELECT a FROM `reconnect`";
449 
450 	// Sanity check
451 	auto rows = cn.query(sql).array;
452 	assert(rows[0][0] == 1);
453 	assert(rows[1][0] == 2);
454 	assert(rows[2][0] == 3);
455 
456 	// Ensure reconnect keeps the same connection when it's supposed to
457 	auto range = cn.query(sql);
458 	assert(range.front[0] == 1);
459 	cn.reconnect();
460 	assert(!cn.closed); // Is open?
461 	assert(range.isValid); // Still valid?
462 	range.popFront();
463 	assert(range.front[0] == 2);
464 
465 	// Ensure reconnect reconnects when it's supposed to
466 	range = cn.query(sql);
467 	assert(range.front[0] == 1);
468 	cn._clientCapabilities = ~cn._clientCapabilities; // Pretend that we're changing the clientCapabilities
469 	cn.reconnect(~cn._clientCapabilities);
470 	assert(!cn.closed); // Is open?
471 	assert(!range.isValid); // Was invalidated?
472 	cn.query(sql).array; // Connection still works?
473 
474 	// Try manually reconnecting
475 	range = cn.query(sql);
476 	assert(range.front[0] == 1);
477 	cn.connect(cn._clientCapabilities);
478 	assert(!cn.closed); // Is open?
479 	assert(!range.isValid); // Was invalidated?
480 	cn.query(sql).array; // Connection still works?
481 
482 	// Try manually closing and connecting
483 	range = cn.query(sql);
484 	assert(range.front[0] == 1);
485 	cn.close();
486 	assert(cn.closed); // Is closed?
487 	assert(!range.isValid); // Was invalidated?
488 	cn.connect(cn._clientCapabilities);
489 	assert(!cn.closed); // Is open?
490 	assert(!range.isValid); // Was invalidated?
491 	cn.query(sql).array; // Connection still works?
492 
493 	// Auto-reconnect upon a command
494 	cn.close();
495 	assert(cn.closed);
496 	range = cn.query(sql);
497 	assert(!cn.closed);
498 	assert(range.front[0] == 1);
499 }
500 
501 @("releaseAll")
502 debug(MYSQLN_TESTS)
503 unittest
504 {
505 	mixin(scopedCn);
506 
507 	cn.exec("DROP TABLE IF EXISTS `releaseAll`");
508 	cn.exec("CREATE TABLE `releaseAll` (a INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8");
509 
510 	auto preparedSelect = cn.prepare("SELECT * FROM `releaseAll`");
511 	auto preparedInsert = cn.prepare("INSERT INTO `releaseAll` (a) VALUES (1)");
512 	assert(cn.isRegistered(preparedSelect));
513 	assert(cn.isRegistered(preparedInsert));
514 
515 	cn.releaseAll();
516 	assert(!cn.isRegistered(preparedSelect));
517 	assert(!cn.isRegistered(preparedInsert));
518 	cn.exec("INSERT INTO `releaseAll` (a) VALUES (1)");
519 	assert(!cn.isRegistered(preparedSelect));
520 	assert(!cn.isRegistered(preparedInsert));
521 
522 	cn.exec(preparedInsert);
523 	cn.query(preparedSelect).array;
524 	assert(cn.isRegistered(preparedSelect));
525 	assert(cn.isRegistered(preparedInsert));
526 }
527 
528 // Test register, release, isRegistered, and auto-register for prepared statements
529 @("autoRegistration")
530 debug(MYSQLN_TESTS)
531 unittest
532 {
533 	import mysql.connection;
534 	import mysql.test.common;
535 
536 	Prepared preparedInsert;
537 	Prepared preparedSelect;
538 	immutable insertSQL = "INSERT INTO `autoRegistration` VALUES (1), (2)";
539 	immutable selectSQL = "SELECT `val` FROM `autoRegistration`";
540 	int queryTupleResult;
541 
542 	{
543 		mixin(scopedCn);
544 
545 		// Setup
546 		cn.exec("DROP TABLE IF EXISTS `autoRegistration`");
547 		cn.exec("CREATE TABLE `autoRegistration` (
548 			`val` INTEGER
549 		) ENGINE=InnoDB DEFAULT CHARSET=utf8");
550 
551 		// Initial register
552 		preparedInsert = cn.prepare(insertSQL);
553 		preparedSelect = cn.prepare(selectSQL);
554 
555 		// Test basic register, release, isRegistered
556 		assert(cn.isRegistered(preparedInsert));
557 		assert(cn.isRegistered(preparedSelect));
558 		cn.release(preparedInsert);
559 		cn.release(preparedSelect);
560 		assert(!cn.isRegistered(preparedInsert));
561 		assert(!cn.isRegistered(preparedSelect));
562 
563 		// Test manual re-register
564 		cn.register(preparedInsert);
565 		cn.register(preparedSelect);
566 		assert(cn.isRegistered(preparedInsert));
567 		assert(cn.isRegistered(preparedSelect));
568 
569 		// Test double register
570 		cn.register(preparedInsert);
571 		cn.register(preparedSelect);
572 		assert(cn.isRegistered(preparedInsert));
573 		assert(cn.isRegistered(preparedSelect));
574 
575 		// Test double release
576 		cn.release(preparedInsert);
577 		cn.release(preparedSelect);
578 		assert(!cn.isRegistered(preparedInsert));
579 		assert(!cn.isRegistered(preparedSelect));
580 		cn.release(preparedInsert);
581 		cn.release(preparedSelect);
582 		assert(!cn.isRegistered(preparedInsert));
583 		assert(!cn.isRegistered(preparedSelect));
584 	}
585 
586 	// Note that at this point, both prepared statements still exist,
587 	// but are no longer registered on any connection. In fact, there
588 	// are no open connections anymore.
589 
590 	// Test auto-register: exec
591 	{
592 		mixin(scopedCn);
593 
594 		assert(!cn.isRegistered(preparedInsert));
595 		cn.exec(preparedInsert);
596 		assert(cn.isRegistered(preparedInsert));
597 	}
598 
599 	// Test auto-register: query
600 	{
601 		mixin(scopedCn);
602 
603 		assert(!cn.isRegistered(preparedSelect));
604 		cn.query(preparedSelect).each();
605 		assert(cn.isRegistered(preparedSelect));
606 	}
607 
608 	// Test auto-register: queryRow
609 	{
610 		mixin(scopedCn);
611 
612 		assert(!cn.isRegistered(preparedSelect));
613 		cn.queryRow(preparedSelect);
614 		assert(cn.isRegistered(preparedSelect));
615 	}
616 
617 	// Test auto-register: queryRowTuple
618 	{
619 		mixin(scopedCn);
620 
621 		assert(!cn.isRegistered(preparedSelect));
622 		cn.queryRowTuple(preparedSelect, queryTupleResult);
623 		assert(cn.isRegistered(preparedSelect));
624 	}
625 
626 	// Test auto-register: queryValue
627 	{
628 		mixin(scopedCn);
629 
630 		assert(!cn.isRegistered(preparedSelect));
631 		cn.queryValue(preparedSelect);
632 		assert(cn.isRegistered(preparedSelect));
633 	}
634 }
635 
636 // An attempt to reproduce issue #81: Using mysql-native driver with no default database
637 // I'm unable to actually reproduce the error, though.
638 @("issue81")
639 debug(MYSQLN_TESTS)
640 unittest
641 {
642 	import mysql.escape;
643 	import std.conv;
644 	mixin(scopedCn);
645 
646 	cn.exec("DROP TABLE IF EXISTS `issue81`");
647 	cn.exec("CREATE TABLE `issue81` (a INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8");
648 	cn.exec("INSERT INTO `issue81` (a) VALUES (1)");
649 
650 	auto cn2 = new Connection(text("host=", cn._host, ";port=", cn._port, ";user=", cn._user, ";pwd=", cn._pwd));
651 	scope(exit) cn2.close();
652 
653 	cn2.query("SELECT * FROM `"~mysqlEscape(cn._db).text~"`.`issue81`");
654 }
655 
656 // Regression test for Issue #154:
657 // autoPurge can throw an exception if the socket was closed without purging
658 //
659 // This simulates a disconnect by closing the socket underneath the Connection
660 // object itself.
661 @("dropConnection")
662 debug(MYSQLN_TESTS)
663 unittest
664 {
665 	mixin(scopedCn);
666 
667 	cn.exec("DROP TABLE IF EXISTS `dropConnection`");
668 	cn.exec("CREATE TABLE `dropConnection` (
669 		`val` INTEGER
670 	) ENGINE=InnoDB DEFAULT CHARSET=utf8");
671 	cn.exec("INSERT INTO `dropConnection` VALUES (1), (2), (3)");
672 	import mysql.prepared;
673 	{
674 		auto prep = cn.prepare("SELECT * FROM `dropConnection`");
675 		cn.query(prep);
676 	}
677 	// close the socket forcibly
678 	cn._socket.close();
679 	// this should still work (it should reconnect).
680 	cn.exec("DROP TABLE `dropConnection`");
681 }
682 
683 /+
684 Test Prepared's ability to be safely refcount-released during a GC cycle
685 (ie, `Connection.release` must not allocate GC memory).
686 
687 Currently disabled because it's not guaranteed to always work
688 (and apparently, cannot be made to work?)
689 For relevant discussion, see issue #159:
690 https://github.com/mysql-d/mysql-native/issues/159
691 +/
692 version(none)
693 debug(MYSQLN_TESTS)
694 {
695 	/// Proof-of-concept ref-counted Prepared wrapper, just for testing,
696 	/// not really intended for actual use.
697 	private struct RCPreparedPayload
698 	{
699 		Prepared prepared;
700 		Connection conn; // Connection to be released from
701 
702 		alias prepared this;
703 
704 		@disable this(this); // not copyable
705 		~this()
706 		{
707 			// There are a couple calls to this dtor where `conn` happens to be null.
708 			if(conn is null)
709 				return;
710 
711 			assert(conn.isRegistered(prepared));
712 			conn.release(prepared);
713 		}
714 	}
715 	///ditto
716 	alias RCPrepared = RefCounted!(RCPreparedPayload, RefCountedAutoInitialize.no);
717 	///ditto
718 	private RCPrepared rcPrepare(Connection conn, const(char[]) sql)
719 	{
720 		import std.algorithm.mutation : move;
721 
722 		auto prepared = conn.prepare(sql);
723 		auto payload = RCPreparedPayload(prepared, conn);
724 		return refCounted(move(payload));
725 	}
726 
727 	@("rcPrepared")
728 	unittest
729 	{
730 		import core.memory;
731 		mixin(scopedCn);
732 
733 		cn.exec("DROP TABLE IF EXISTS `rcPrepared`");
734 		cn.exec("CREATE TABLE `rcPrepared` (
735 			`val` INTEGER
736 		) ENGINE=InnoDB DEFAULT CHARSET=utf8");
737 		cn.exec("INSERT INTO `rcPrepared` VALUES (1), (2), (3)");
738 
739 		// Define this in outer scope to guarantee data is left pending when
740 		// RCPrepared's payload is collected. This will guarantee
741 		// that Connection will need to queue the release.
742 		ResultRange rows;
743 
744 		void bar()
745 		{
746 			class Foo { RCPrepared p; }
747 			auto foo = new Foo();
748 
749 			auto rcStmt = cn.rcPrepare("SELECT * FROM `rcPrepared`");
750 			foo.p = rcStmt;
751 			rows = cn.query(rcStmt);
752 
753 			/+
754 			At this point, there are two references to the prepared statement:
755 			One in a `Foo` object (currently bound to `foo`), and one on the stack.
756 
757 			Returning from this function will destroy the one on the stack,
758 			and deterministically reduce the refcount to 1.
759 
760 			So, right here we set `foo` to null to *keep* the Foo object's
761 			reference to the prepared statement, but set adrift the Foo object
762 			itself, ready to be destroyed (along with the only remaining
763 			prepared statement reference it contains) by the next GC cycle.
764 
765 			Thus, `RCPreparedPayload.~this` and `Connection.release(Prepared)`
766 			will be executed during a GC cycle...and had better not perform
767 			any allocations, or else...boom!
768 			+/
769 			foo = null;
770 		}
771 
772 		bar();
773 		assert(cn.hasPending); // Ensure Connection is forced to queue the release.
774 		GC.collect(); // `Connection.release(Prepared)` better not be allocating, or boom!
775 	}
776 }
777 
778 // mysql.exceptions
779 @("wrongFunctionException")
780 debug(MYSQLN_TESTS)
781 unittest
782 {
783 	import std.exception;
784 	import mysql.commands;
785 	import mysql.connection;
786 	import mysql.prepared;
787 	import mysql.test.common : scopedCn, createCn;
788 	mixin(scopedCn);
789 
790 	cn.exec("DROP TABLE IF EXISTS `wrongFunctionException`");
791 	cn.exec("CREATE TABLE `wrongFunctionException` (
792 		`val` INTEGER
793 	) ENGINE=InnoDB DEFAULT CHARSET=utf8");
794 
795 	immutable insertSQL = "INSERT INTO `wrongFunctionException` VALUES (1), (2)";
796 	immutable selectSQL = "SELECT * FROM `wrongFunctionException`";
797 	Prepared preparedInsert;
798 	Prepared preparedSelect;
799 	int queryTupleResult;
800 	assertNotThrown!MYXWrongFunction(cn.exec(insertSQL));
801 	assertNotThrown!MYXWrongFunction(cn.query(selectSQL).each());
802 	assertNotThrown!MYXWrongFunction(cn.queryRowTuple(selectSQL, queryTupleResult));
803 	assertNotThrown!MYXWrongFunction(preparedInsert = cn.prepare(insertSQL));
804 	assertNotThrown!MYXWrongFunction(preparedSelect = cn.prepare(selectSQL));
805 	assertNotThrown!MYXWrongFunction(cn.exec(preparedInsert));
806 	assertNotThrown!MYXWrongFunction(cn.query(preparedSelect).each());
807 	assertNotThrown!MYXWrongFunction(cn.queryRowTuple(preparedSelect, queryTupleResult));
808 
809 	assertThrown!MYXResultRecieved(cn.exec(selectSQL));
810 	assertThrown!MYXNoResultRecieved(cn.query(insertSQL).each());
811 	assertThrown!MYXNoResultRecieved(cn.queryRowTuple(insertSQL, queryTupleResult));
812 	assertThrown!MYXResultRecieved(cn.exec(preparedSelect));
813 	assertThrown!MYXNoResultRecieved(cn.query(preparedInsert).each());
814 	assertThrown!MYXNoResultRecieved(cn.queryRowTuple(preparedInsert, queryTupleResult));
815 }
816 
817 // mysql.pool
818 version(Have_vibe_core)
819 {
820 	@("onNewConnection")
821 	debug(MYSQLN_TESTS)
822 	unittest
823 	{
824 		auto count = 0;
825 		void callback(Connection conn)
826 		{
827 			count++;
828 		}
829 
830 		// Test getting/setting
831 		auto poolA = new MySQLPool(testConnectionStr, &callback);
832 		auto poolB = new MySQLPool(testConnectionStr);
833 		auto poolNoCallback = new MySQLPool(testConnectionStr);
834 
835 		assert(poolA.onNewConnection == &callback);
836 		assert(poolB.onNewConnection is null);
837 		assert(poolNoCallback.onNewConnection is null);
838 
839 		poolB.onNewConnection = &callback;
840 		assert(poolB.onNewConnection == &callback);
841 		assert(count == 0);
842 
843 		// Ensure callback is called
844 		{
845 			auto connA = poolA.lockConnection();
846 			assert(!connA.closed);
847 			assert(count == 1);
848 
849 			auto connB = poolB.lockConnection();
850 			assert(!connB.closed);
851 			assert(count == 2);
852 		}
853 
854 		// Ensure works with no callback
855 		{
856 			auto oldCount = count;
857 			auto poolC = new MySQLPool(testConnectionStr);
858 			auto connC = poolC.lockConnection();
859 			assert(!connC.closed);
860 			assert(count == oldCount);
861 		}
862 	}
863 
864 	@("registration")
865 	debug(MYSQLN_TESTS)
866 	unittest
867 	{
868 		import mysql.commands;
869 		auto pool = new MySQLPool(testConnectionStr);
870 
871 		// Setup
872 		auto cn = pool.lockConnection();
873 		cn.exec("DROP TABLE IF EXISTS `poolRegistration`");
874 		cn.exec("CREATE TABLE `poolRegistration` (
875 			`data` LONGBLOB
876 		) ENGINE=InnoDB DEFAULT CHARSET=utf8");
877 		immutable sql = "SELECT * from `poolRegistration`";
878 		auto cn2 = pool.lockConnection();
879 		pool.applyAuto(cn2);
880 		assert(cn !is cn2);
881 
882 		// Tests:
883 		// Initial
884 		assert(pool.isAutoCleared(sql));
885 		assert(pool.isAutoRegistered(sql));
886 		assert(pool.isAutoReleased(sql));
887 		assert(!cn.isRegistered(sql));
888 		assert(!cn2.isRegistered(sql));
889 
890 		// Register on connection #1
891 		auto prepared = cn.prepare(sql);
892 		{
893 			assert(pool.isAutoCleared(sql));
894 			assert(pool.isAutoRegistered(sql));
895 			assert(pool.isAutoReleased(sql));
896 			assert(cn.isRegistered(sql));
897 			assert(!cn2.isRegistered(sql));
898 
899 			auto cn3 = pool.lockConnection();
900 			pool.applyAuto(cn3);
901 			assert(!cn3.isRegistered(sql));
902 		}
903 
904 		// autoRegister
905 		pool.autoRegister(prepared);
906 		{
907 			assert(!pool.isAutoCleared(sql));
908 			assert(pool.isAutoRegistered(sql));
909 			assert(!pool.isAutoReleased(sql));
910 			assert(cn.isRegistered(sql));
911 			assert(!cn2.isRegistered(sql));
912 
913 			auto cn3 = pool.lockConnection();
914 			pool.applyAuto(cn3);
915 			assert(cn3.isRegistered(sql));
916 		}
917 
918 		// autoRelease
919 		pool.autoRelease(prepared);
920 		{
921 			assert(!pool.isAutoCleared(sql));
922 			assert(!pool.isAutoRegistered(sql));
923 			assert(pool.isAutoReleased(sql));
924 			assert(cn.isRegistered(sql));
925 			assert(!cn2.isRegistered(sql));
926 
927 			auto cn3 = pool.lockConnection();
928 			pool.applyAuto(cn3);
929 			assert(!cn3.isRegistered(sql));
930 		}
931 
932 		// clearAuto
933 		pool.clearAuto(prepared);
934 		{
935 			assert(pool.isAutoCleared(sql));
936 			assert(pool.isAutoRegistered(sql));
937 			assert(pool.isAutoReleased(sql));
938 			assert(cn.isRegistered(sql));
939 			assert(!cn2.isRegistered(sql));
940 
941 			auto cn3 = pool.lockConnection();
942 			pool.applyAuto(cn3);
943 			assert(!cn3.isRegistered(sql));
944 		}
945 	}
946 
947 	@("closedConnection") // "cct"
948 	debug(MYSQLN_TESTS)
949 	{
950 		import mysql.pool;
951 		MySQLPool cctPool;
952 		int cctCount=0;
953 
954 		void cctStart()
955 		{
956 			import std.array;
957 			import mysql.commands;
958 
959 			cctPool = new MySQLPool(testConnectionStr);
960 			cctPool.onNewConnection = (Connection conn) { cctCount++; };
961 			assert(cctCount == 0);
962 
963 			auto cn = cctPool.lockConnection();
964 			assert(!cn.closed);
965 			cn.close();
966 			assert(cn.closed);
967 			assert(cctCount == 1);
968 		}
969 
970 		unittest
971 		{
972 			cctStart();
973 			assert(cctCount == 1);
974 
975 			auto cn = cctPool.lockConnection();
976 			assert(cctCount == 1);
977 			assert(!cn.closed);
978 		}
979 	}
980 }
981 
982 // mysql.prepared
983 @("paramSpecial")
984 debug(MYSQLN_TESTS)
985 unittest
986 {
987 	import std.array;
988 	import std.range;
989 	import mysql.connection;
990 	import mysql.test.common;
991 	mixin(scopedCn);
992 
993 	// Setup
994 	cn.exec("DROP TABLE IF EXISTS `paramSpecial`");
995 	cn.exec("CREATE TABLE `paramSpecial` (
996 		`data` LONGBLOB
997 	) ENGINE=InnoDB DEFAULT CHARSET=utf8");
998 
999 	immutable totalSize = 1000; // Deliberately not a multiple of chunkSize below
1000 	auto alph = cast(const(ubyte)[]) "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
1001 	auto data = alph.cycle.take(totalSize).array;
1002 
1003 	int chunkSize;
1004 	const(ubyte)[] dataToSend;
1005 	bool finished;
1006 	uint sender(ubyte[] chunk)
1007 	{
1008 		assert(!finished);
1009 		assert(chunk.length == chunkSize);
1010 
1011 		if(dataToSend.length < chunkSize)
1012 		{
1013 			auto actualSize = cast(uint) dataToSend.length;
1014 			chunk[0..actualSize] = dataToSend[];
1015 			finished = true;
1016 			dataToSend.length = 0;
1017 			return actualSize;
1018 		}
1019 		else
1020 		{
1021 			chunk[] = dataToSend[0..chunkSize];
1022 			dataToSend = dataToSend[chunkSize..$];
1023 			return chunkSize;
1024 		}
1025 	}
1026 
1027 	immutable selectSQL = "SELECT `data` FROM `paramSpecial`";
1028 
1029 	// Sanity check
1030 	cn.exec("INSERT INTO `paramSpecial` VALUES (\""~(cast(string)data)~"\")");
1031 	auto value = cn.queryValue(selectSQL);
1032 	assert(!value.isNull);
1033 	assert(value.get == data);
1034 
1035 	{
1036 		// Clear table
1037 		cn.exec("DELETE FROM `paramSpecial`");
1038 		value = cn.queryValue(selectSQL); // Ensure deleted
1039 		assert(value.isNull);
1040 
1041 		// Test: totalSize as a multiple of chunkSize
1042 		chunkSize = 100;
1043 		assert(cast(int)(totalSize / chunkSize) * chunkSize == totalSize);
1044 		auto paramSpecial = ParameterSpecialization(0, SQLType.INFER_FROM_D_TYPE, chunkSize, &sender);
1045 
1046 		finished = false;
1047 		dataToSend = data;
1048 		auto prepared = cn.prepare("INSERT INTO `paramSpecial` VALUES (?)");
1049 		prepared.setArg(0, cast(ubyte[])[], paramSpecial);
1050 		assert(cn.exec(prepared) == 1);
1051 		value = cn.queryValue(selectSQL);
1052 		assert(!value.isNull);
1053 		assert(value.get == data);
1054 	}
1055 
1056 	{
1057 		// Clear table
1058 		cn.exec("DELETE FROM `paramSpecial`");
1059 		value = cn.queryValue(selectSQL); // Ensure deleted
1060 		assert(value.isNull);
1061 
1062 		// Test: totalSize as a non-multiple of chunkSize
1063 		chunkSize = 64;
1064 		assert(cast(int)(totalSize / chunkSize) * chunkSize != totalSize);
1065 		auto paramSpecial = ParameterSpecialization(0, SQLType.INFER_FROM_D_TYPE, chunkSize, &sender);
1066 
1067 		finished = false;
1068 		dataToSend = data;
1069 		auto prepared = cn.prepare("INSERT INTO `paramSpecial` VALUES (?)");
1070 		prepared.setArg(0, cast(ubyte[])[], paramSpecial);
1071 		assert(cn.exec(prepared) == 1);
1072 		value = cn.queryValue(selectSQL);
1073 		assert(!value.isNull);
1074 		assert(value.get == data);
1075 	}
1076 }
1077 
1078 @("setArg-typeMods")
1079 debug(MYSQLN_TESTS)
1080 unittest
1081 {
1082 	import mysql.test.common;
1083 	mixin(scopedCn);
1084 
1085 	// Setup
1086 	cn.exec("DROP TABLE IF EXISTS `setArg-typeMods`");
1087 	cn.exec("CREATE TABLE `setArg-typeMods` (
1088 		`i` INTEGER
1089 	) ENGINE=InnoDB DEFAULT CHARSET=utf8");
1090 
1091 	auto insertSQL = "INSERT INTO `setArg-typeMods` VALUES (?)";
1092 
1093 	// Sanity check
1094 	{
1095 		int i = 111;
1096 		assert(cn.exec(insertSQL, i) == 1);
1097 		auto value = cn.queryValue("SELECT `i` FROM `setArg-typeMods`");
1098 		assert(!value.isNull);
1099 		assert(value.get == i);
1100 	}
1101 
1102 	// Test const(int)
1103 	{
1104 		const(int) i = 112;
1105 		assert(cn.exec(insertSQL, i) == 1);
1106 	}
1107 
1108 	// Test immutable(int)
1109 	{
1110 		immutable(int) i = 113;
1111 		assert(cn.exec(insertSQL, i) == 1);
1112 	}
1113 
1114 	// Note: Variant doesn't seem to support
1115 	// `shared(T)` or `shared(const(T)`. Only `shared(immutable(T))`.
1116 
1117 	// Test shared immutable(int)
1118 	{
1119 		shared immutable(int) i = 113;
1120 		assert(cn.exec(insertSQL, i) == 1);
1121 	}
1122 }
1123 
1124 @("setNullArg")
1125 debug(MYSQLN_TESTS)
1126 unittest
1127 {
1128 	import mysql.connection;
1129 	import mysql.test.common;
1130 	mixin(scopedCn);
1131 
1132 	cn.exec("DROP TABLE IF EXISTS `setNullArg`");
1133 	cn.exec("CREATE TABLE `setNullArg` (
1134 		`val` INTEGER
1135 	) ENGINE=InnoDB DEFAULT CHARSET=utf8");
1136 
1137 	immutable insertSQL = "INSERT INTO `setNullArg` VALUES (?)";
1138 	immutable selectSQL = "SELECT * FROM `setNullArg`";
1139 	auto preparedInsert = cn.prepare(insertSQL);
1140 	assert(preparedInsert.sql == insertSQL);
1141 	Row[] rs;
1142 
1143 	{
1144 		Nullable!int nullableInt;
1145 		nullableInt.nullify();
1146 		preparedInsert.setArg(0, nullableInt);
1147 		assert(preparedInsert.getArg(0).type == typeid(typeof(null)));
1148 		nullableInt = 7;
1149 		preparedInsert.setArg(0, nullableInt);
1150 		assert(preparedInsert.getArg(0) == 7);
1151 
1152 		nullableInt.nullify();
1153 		preparedInsert.setArgs(nullableInt);
1154 		assert(preparedInsert.getArg(0).type == typeid(typeof(null)));
1155 		nullableInt = 7;
1156 		preparedInsert.setArgs(nullableInt);
1157 		assert(preparedInsert.getArg(0) == 7);
1158 	}
1159 
1160 	preparedInsert.setArg(0, 5);
1161 	cn.exec(preparedInsert);
1162 	rs = cn.query(selectSQL).array;
1163 	assert(rs.length == 1);
1164 	assert(rs[0][0] == 5);
1165 
1166 	preparedInsert.setArg(0, null);
1167 	cn.exec(preparedInsert);
1168 	rs = cn.query(selectSQL).array;
1169 	assert(rs.length == 2);
1170 	assert(rs[0][0] == 5);
1171 	assert(rs[1].isNull(0));
1172 	assert(rs[1][0].type == typeid(typeof(null)));
1173 
1174 	preparedInsert.setArg(0, Variant(null));
1175 	cn.exec(preparedInsert);
1176 	rs = cn.query(selectSQL).array;
1177 	assert(rs.length == 3);
1178 	assert(rs[0][0] == 5);
1179 	assert(rs[1].isNull(0));
1180 	assert(rs[2].isNull(0));
1181 	assert(rs[1][0].type == typeid(typeof(null)));
1182 	assert(rs[2][0].type == typeid(typeof(null)));
1183 }
1184 
1185 @("lastInsertID")
1186 debug(MYSQLN_TESTS)
1187 unittest
1188 {
1189 	import mysql.connection;
1190 	mixin(scopedCn);
1191 	cn.exec("DROP TABLE IF EXISTS `testPreparedLastInsertID`");
1192 	cn.exec("CREATE TABLE `testPreparedLastInsertID` (
1193             `a` INTEGER NOT NULL AUTO_INCREMENT,
1194             PRIMARY KEY (a)
1195 	) ENGINE=InnoDB DEFAULT CHARSET=utf8");
1196 
1197 	auto stmt = cn.prepare("INSERT INTO `testPreparedLastInsertID` VALUES()");
1198 	cn.exec(stmt);
1199 	assert(stmt.lastInsertID == 1);
1200 	cn.exec(stmt);
1201 	assert(stmt.lastInsertID == 2);
1202 	cn.exec(stmt);
1203 	assert(stmt.lastInsertID == 3);
1204 }
1205 
1206 // Test PreparedRegistrations
1207 debug(MYSQLN_TESTS)
1208 {
1209 	PreparedRegistrations!TestPreparedRegistrationsGood1 testPreparedRegistrationsGood1;
1210 	PreparedRegistrations!TestPreparedRegistrationsGood2 testPreparedRegistrationsGood2;
1211 
1212 	@("PreparedRegistrations")
1213 	unittest
1214 	{
1215 		// Test init
1216 		PreparedRegistrations!TestPreparedRegistrationsGood2 pr;
1217 		assert(pr.directLookup.keys.length == 0);
1218 
1219 		void resetData(bool isQueued1, bool isQueued2, bool isQueued3)
1220 		{
1221 			pr.directLookup["1"] = TestPreparedRegistrationsGood2(isQueued1, "1");
1222 			pr.directLookup["2"] = TestPreparedRegistrationsGood2(isQueued2, "2");
1223 			pr.directLookup["3"] = TestPreparedRegistrationsGood2(isQueued3, "3");
1224 			assert(pr.directLookup.keys.length == 3);
1225 		}
1226 
1227 		// Test resetData (sanity check)
1228 		resetData(false, true, false);
1229 		assert(pr.directLookup["1"] == TestPreparedRegistrationsGood2(false, "1"));
1230 		assert(pr.directLookup["2"] == TestPreparedRegistrationsGood2(true,  "2"));
1231 		assert(pr.directLookup["3"] == TestPreparedRegistrationsGood2(false, "3"));
1232 
1233 		// Test opIndex
1234 		resetData(false, true, false);
1235 		pr.directLookup["1"] = TestPreparedRegistrationsGood2(false, "1");
1236 		pr.directLookup["2"] = TestPreparedRegistrationsGood2(true,  "2");
1237 		pr.directLookup["3"] = TestPreparedRegistrationsGood2(false, "3");
1238 		assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1"));
1239 		assert(pr["2"] == TestPreparedRegistrationsGood2(true,  "2"));
1240 		assert(pr["3"] == TestPreparedRegistrationsGood2(false, "3"));
1241 		assert(pr["4"].isNull);
1242 
1243 		// Test queueForRelease
1244 		resetData(false, true, false);
1245 		pr.queueForRelease("2");
1246 		assert(pr.directLookup.keys.length == 3);
1247 		assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1"));
1248 		assert(pr["2"] == TestPreparedRegistrationsGood2(true,  "2"));
1249 		assert(pr["3"] == TestPreparedRegistrationsGood2(false, "3"));
1250 
1251 		pr.queueForRelease("3");
1252 		assert(pr.directLookup.keys.length == 3);
1253 		assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1"));
1254 		assert(pr["2"] == TestPreparedRegistrationsGood2(true,  "2"));
1255 		assert(pr["3"] == TestPreparedRegistrationsGood2(true,  "3"));
1256 
1257 		pr.queueForRelease("4");
1258 		assert(pr.directLookup.keys.length == 3);
1259 		assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1"));
1260 		assert(pr["2"] == TestPreparedRegistrationsGood2(true,  "2"));
1261 		assert(pr["3"] == TestPreparedRegistrationsGood2(true,  "3"));
1262 
1263 		// Test unqueueForRelease
1264 		resetData(false, true, false);
1265 		pr.unqueueForRelease("1");
1266 		assert(pr.directLookup.keys.length == 3);
1267 		assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1"));
1268 		assert(pr["2"] == TestPreparedRegistrationsGood2(true,  "2"));
1269 		assert(pr["3"] == TestPreparedRegistrationsGood2(false, "3"));
1270 
1271 		pr.unqueueForRelease("2");
1272 		assert(pr.directLookup.keys.length == 3);
1273 		assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1"));
1274 		assert(pr["2"] == TestPreparedRegistrationsGood2(false, "2"));
1275 		assert(pr["3"] == TestPreparedRegistrationsGood2(false, "3"));
1276 
1277 		pr.unqueueForRelease("4");
1278 		assert(pr.directLookup.keys.length == 3);
1279 		assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1"));
1280 		assert(pr["2"] == TestPreparedRegistrationsGood2(false, "2"));
1281 		assert(pr["3"] == TestPreparedRegistrationsGood2(false, "3"));
1282 
1283 		// Test queueAllForRelease
1284 		resetData(false, true, false);
1285 		pr.queueAllForRelease();
1286 		assert(pr["1"] == TestPreparedRegistrationsGood2(true,  "1"));
1287 		assert(pr["2"] == TestPreparedRegistrationsGood2(true,  "2"));
1288 		assert(pr["3"] == TestPreparedRegistrationsGood2(true,  "3"));
1289 		assert(pr["4"].isNull);
1290 
1291 		// Test clear
1292 		resetData(false, true, false);
1293 		pr.clear();
1294 		assert(pr.directLookup.keys.length == 0);
1295 
1296 		// Test registerIfNeeded
1297 		auto doRegister(const(char[]) sql) { return TestPreparedRegistrationsGood2(false, sql); }
1298 		pr.registerIfNeeded("1", &doRegister);
1299 		assert(pr.directLookup.keys.length == 1);
1300 		assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1"));
1301 
1302 		pr.registerIfNeeded("1", &doRegister);
1303 		assert(pr.directLookup.keys.length == 1);
1304 		assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1"));
1305 
1306 		pr.registerIfNeeded("2", &doRegister);
1307 		assert(pr.directLookup.keys.length == 2);
1308 		assert(pr["1"] == TestPreparedRegistrationsGood2(false, "1"));
1309 		assert(pr["2"] == TestPreparedRegistrationsGood2(false, "2"));
1310 	}
1311 }
1312 
1313 // mysql.result
1314 @("getName")
1315 debug(MYSQLN_TESTS)
1316 unittest
1317 {
1318 	import mysql.test.common;
1319 	import mysql.commands;
1320 	mixin(scopedCn);
1321 	cn.exec("DROP TABLE IF EXISTS `row_getName`");
1322 	cn.exec("CREATE TABLE `row_getName` (someValue INTEGER, another INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8");
1323 	cn.exec("INSERT INTO `row_getName` VALUES (1, 2), (3, 4)");
1324 
1325 	enum sql = "SELECT another, someValue FROM `row_getName`";
1326 
1327 	auto rows = cn.query(sql).array;
1328 	assert(rows.length == 2);
1329 	assert(rows[0][0] == 2);
1330 	assert(rows[0][1] == 1);
1331 	assert(rows[0].getName(0) == "another");
1332 	assert(rows[0].getName(1) == "someValue");
1333 	assert(rows[1][0] == 4);
1334 	assert(rows[1][1] == 3);
1335 	assert(rows[1].getName(0) == "another");
1336 	assert(rows[1].getName(1) == "someValue");
1337 }
1338 
1339 // issue 222, set column names when data is null.
1340 @("colNamesForBinary")
1341 debug(MYSQLN_TESTS)
1342 unittest
1343 {
1344 	import mysql.test.common;
1345 	import mysql.commands;
1346 	mixin(scopedCn);
1347 	// binary mode happens with prepared statements
1348 	auto row = cn.queryRow("SELECT `colname` FROM (SELECT 1 AS `id`, NULL AS `colname`) as `tbl` WHERE `id` = ?", 1);
1349 	assert(row.get[0] == null);
1350 	assert(row.get.getName(0) == "colname");
1351 }