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 }