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