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 }