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