1 /++ 2 Use a DB via plain SQL statements. 3 4 Commands that are expected to return a result set - queries - have distinctive 5 methods that are enforced. That is it will be an error to call such a method 6 with an SQL command that does not produce a result set. So for commands like 7 SELECT, use the `query` functions. For other commands, like 8 INSERT/UPDATE/CREATE/etc, use `exec`. 9 +/ 10 11 module mysql.commands; 12 13 import std.conv; 14 import std.exception; 15 import std.range; 16 import std.typecons; 17 import std.variant; 18 19 import mysql.connection; 20 import mysql.exceptions; 21 import mysql.prepared; 22 import mysql.protocol.constants; 23 import mysql.protocol.extra_types; 24 import mysql.protocol.packets; 25 import mysql.result; 26 27 /// This feature is not yet implemented. It currently has no effect. 28 /+ 29 A struct to represent specializations of returned statement columns. 30 31 If you are executing a query that will include result columns that are large objects, 32 it may be expedient to deal with the data as it is received rather than first buffering 33 it to some sort of byte array. These two variables allow for this. If both are provided 34 then the corresponding column will be fed to the stipulated delegate in chunks of 35 `chunkSize`, with the possible exception of the last chunk, which may be smaller. 36 The bool argument `finished` will be set to true when the last chunk is set. 37 38 Be aware when specifying types for column specializations that for some reason the 39 field descriptions returned for a resultset have all of the types TINYTEXT, MEDIUMTEXT, 40 TEXT, LONGTEXT, TINYBLOB, MEDIUMBLOB, BLOB, and LONGBLOB lumped as type 0xfc 41 contrary to what it says in the protocol documentation. 42 +/ 43 struct ColumnSpecialization 44 { 45 size_t cIndex; // parameter number 0 - number of params-1 46 ushort type; 47 uint chunkSize; /// In bytes 48 void delegate(const(ubyte)[] chunk, bool finished) chunkDelegate; 49 } 50 ///ditto 51 alias CSN = ColumnSpecialization; 52 53 @("columnSpecial") 54 debug(MYSQLN_TESTS) 55 unittest 56 { 57 import std.array; 58 import std.range; 59 import mysql.test.common; 60 mixin(scopedCn); 61 62 // Setup 63 cn.exec("DROP TABLE IF EXISTS `columnSpecial`"); 64 cn.exec("CREATE TABLE `columnSpecial` ( 65 `data` LONGBLOB 66 ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); 67 68 immutable totalSize = 1000; // Deliberately not a multiple of chunkSize below 69 auto alph = cast(const(ubyte)[]) "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 70 auto data = alph.cycle.take(totalSize).array; 71 cn.exec("INSERT INTO `columnSpecial` VALUES (\""~(cast(string)data)~"\")"); 72 73 // Common stuff 74 int chunkSize; 75 immutable selectSQL = "SELECT `data` FROM `columnSpecial`"; 76 ubyte[] received; 77 bool lastValueOfFinished; 78 void receiver(const(ubyte)[] chunk, bool finished) 79 { 80 assert(lastValueOfFinished == false); 81 82 if(finished) 83 assert(chunk.length == chunkSize); 84 else 85 assert(chunk.length < chunkSize); // Not always true in general, but true in this unittest 86 87 received ~= chunk; 88 lastValueOfFinished = finished; 89 } 90 91 // Sanity check 92 auto value = cn.queryValue(selectSQL); 93 assert(!value.isNull); 94 assert(value.get == data); 95 96 // Use ColumnSpecialization with sql string, 97 // and totalSize as a multiple of chunkSize 98 { 99 chunkSize = 100; 100 assert(cast(int)(totalSize / chunkSize) * chunkSize == totalSize); 101 auto columnSpecial = ColumnSpecialization(0, 0xfc, chunkSize, &receiver); 102 103 received = null; 104 lastValueOfFinished = false; 105 value = cn.queryValue(selectSQL, [columnSpecial]); 106 assert(!value.isNull); 107 assert(value.get == data); 108 //TODO: ColumnSpecialization is not yet implemented 109 //assert(lastValueOfFinished == true); 110 //assert(received == data); 111 } 112 113 // Use ColumnSpecialization with sql string, 114 // and totalSize as a non-multiple of chunkSize 115 { 116 chunkSize = 64; 117 assert(cast(int)(totalSize / chunkSize) * chunkSize != totalSize); 118 auto columnSpecial = ColumnSpecialization(0, 0xfc, chunkSize, &receiver); 119 120 received = null; 121 lastValueOfFinished = false; 122 value = cn.queryValue(selectSQL, [columnSpecial]); 123 assert(!value.isNull); 124 assert(value.get == data); 125 //TODO: ColumnSpecialization is not yet implemented 126 //assert(lastValueOfFinished == true); 127 //assert(received == data); 128 } 129 130 // Use ColumnSpecialization with prepared statement, 131 // and totalSize as a multiple of chunkSize 132 { 133 chunkSize = 100; 134 assert(cast(int)(totalSize / chunkSize) * chunkSize == totalSize); 135 auto columnSpecial = ColumnSpecialization(0, 0xfc, chunkSize, &receiver); 136 137 received = null; 138 lastValueOfFinished = false; 139 auto prepared = cn.prepare(selectSQL); 140 prepared.columnSpecials = [columnSpecial]; 141 value = cn.queryValue(prepared); 142 assert(!value.isNull); 143 assert(value.get == data); 144 //TODO: ColumnSpecialization is not yet implemented 145 //assert(lastValueOfFinished == true); 146 //assert(received == data); 147 } 148 } 149 150 package struct ExecQueryImplInfo 151 { 152 bool isPrepared; 153 154 // For non-prepared statements: 155 string sql; 156 157 // For prepared statements: 158 uint hStmt; 159 PreparedStmtHeaders psh; 160 Variant[] inParams; 161 ParameterSpecialization[] psa; 162 } 163 164 /++ 165 Internal implementation for the exec and query functions. 166 167 Execute a one-off SQL command. 168 169 Any result set can be accessed via Connection.getNextRow(), but you should really be 170 using the query function for such queries. 171 172 Params: ra = An out parameter to receive the number of rows affected. 173 Returns: true if there was a (possibly empty) result set. 174 +/ 175 //TODO: All low-level commms should be moved into the mysql.protocol package. 176 package bool execQueryImpl(Connection conn, ExecQueryImplInfo info, out ulong ra) 177 { 178 scope(failure) conn.kill(); 179 180 // Send data 181 if(info.isPrepared) 182 ProtocolPrepared.sendCommand(conn, info.hStmt, info.psh, info.inParams, info.psa); 183 else 184 { 185 conn.sendCmd(CommandType.QUERY, info.sql); 186 conn._fieldCount = 0; 187 } 188 189 // Handle response 190 ubyte[] packet = conn.getPacket(); 191 bool rv; 192 if (packet.front == ResultPacketMarker.ok || packet.front == ResultPacketMarker.error) 193 { 194 conn.resetPacket(); 195 auto okp = OKErrorPacket(packet); 196 enforcePacketOK(okp); 197 ra = okp.affected; 198 conn._serverStatus = okp.serverStatus; 199 conn._insertID = okp.insertID; 200 rv = false; 201 } 202 else 203 { 204 // There was presumably a result set 205 assert(packet.front >= 1 && packet.front <= 250); // Result set packet header should have this value 206 conn._headersPending = conn._rowsPending = true; 207 conn._binaryPending = info.isPrepared; 208 auto lcb = packet.consumeIfComplete!LCB(); 209 assert(!lcb.isNull); 210 assert(!lcb.isIncomplete); 211 conn._fieldCount = cast(ushort)lcb.value; 212 assert(conn._fieldCount == lcb.value); 213 rv = true; 214 ra = 0; 215 } 216 return rv; 217 } 218 219 ///ditto 220 package bool execQueryImpl(Connection conn, ExecQueryImplInfo info) 221 { 222 ulong rowsAffected; 223 return execQueryImpl(conn, info, rowsAffected); 224 } 225 226 /++ 227 Execute an SQL command or prepared statement, such as INSERT/UPDATE/CREATE/etc. 228 229 This method is intended for commands such as which do not produce a result set 230 (otherwise, use one of the `query` functions instead.) If the SQL command does 231 produces a result set (such as SELECT), `mysql.exceptions.MYXResultRecieved` 232 will be thrown. 233 234 If `args` is supplied, the sql string will automatically be used as a prepared 235 statement. Prepared statements are automatically cached by mysql-native, 236 so there's no performance penalty for using this multiple times for the 237 same statement instead of manually preparing a statement. 238 239 If `args` and `prepared` are both provided, `args` will be used, 240 and any arguments that are already set in the prepared statement 241 will automatically be replaced with `args` (note, just like calling 242 `mysql.prepared.Prepared.setArgs`, this will also remove all 243 `mysql.prepared.ParameterSpecialization` that may have been applied). 244 245 Only use the `string sql` overload that doesn't take `args` 246 when you are not going to be using the same 247 command repeatedly and you are CERTAIN all the data you're sending is properly 248 escaped. Otherwise, consider using overload that takes a `Prepared`. 249 250 If you need to use any `mysql.prepared.ParameterSpecialization`, use 251 `mysql.connection.prepare` to manually create a `mysql.prepared.Prepared`, 252 and set your parameter specializations using `mysql.prepared.Prepared.setArg` 253 or `mysql.prepared.Prepared.setArgs`. 254 255 Type_Mappings: $(TYPE_MAPPINGS) 256 257 Params: 258 conn = An open `mysql.connection.Connection` to the database. 259 sql = The SQL command to be run. 260 prepared = The prepared statement to be run. 261 262 Returns: The number of rows affected. 263 264 Example: 265 --- 266 auto myInt = 7; 267 auto rowsAffected = myConnection.exec("INSERT INTO `myTable` (`a`) VALUES (?)", myInt); 268 --- 269 +/ 270 ulong exec(Connection conn, string sql) 271 { 272 return execImpl(conn, ExecQueryImplInfo(false, sql)); 273 } 274 ///ditto 275 ulong exec(T...)(Connection conn, string sql, T args) 276 if(T.length > 0 && !is(T[0] == Variant[])) 277 { 278 auto prepared = conn.prepare(sql); 279 prepared.setArgs(args); 280 return exec(conn, prepared); 281 } 282 ///ditto 283 ulong exec(Connection conn, string sql, Variant[] args) 284 { 285 auto prepared = conn.prepare(sql); 286 prepared.setArgs(args); 287 return exec(conn, prepared); 288 } 289 290 ///ditto 291 ulong exec(Connection conn, ref Prepared prepared) 292 { 293 auto preparedInfo = conn.registerIfNeeded(prepared.sql); 294 auto ra = execImpl(conn, prepared.getExecQueryImplInfo(preparedInfo.statementId)); 295 prepared._lastInsertID = conn.lastInsertID; 296 return ra; 297 } 298 ///ditto 299 ulong exec(T...)(Connection conn, ref Prepared prepared, T args) 300 if(T.length > 0 && !is(T[0] == Variant[])) 301 { 302 prepared.setArgs(args); 303 return exec(conn, prepared); 304 } 305 ///ditto 306 ulong exec(Connection conn, ref Prepared prepared, Variant[] args) 307 { 308 prepared.setArgs(args); 309 return exec(conn, prepared); 310 } 311 312 ///ditto 313 ulong exec(Connection conn, ref BackwardCompatPrepared prepared) 314 { 315 auto p = prepared.prepared; 316 auto result = exec(conn, p); 317 prepared._prepared = p; 318 return result; 319 } 320 321 /// Common implementation for `exec` overloads 322 package ulong execImpl(Connection conn, ExecQueryImplInfo info) 323 { 324 ulong rowsAffected; 325 bool receivedResultSet = execQueryImpl(conn, info, rowsAffected); 326 if(receivedResultSet) 327 { 328 conn.purgeResult(); 329 throw new MYXResultRecieved(); 330 } 331 332 return rowsAffected; 333 } 334 335 /++ 336 Execute an SQL SELECT command or prepared statement. 337 338 This returns an input range of `mysql.result.Row`, so if you need random access 339 to the `mysql.result.Row` elements, simply call 340 $(LINK2 https://dlang.org/phobos/std_array.html#array, `std.array.array()`) 341 on the result. 342 343 If the SQL command does not produce a result set (such as INSERT/CREATE/etc), 344 then `mysql.exceptions.MYXNoResultRecieved` will be thrown. Use 345 `exec` instead for such commands. 346 347 If `args` is supplied, the sql string will automatically be used as a prepared 348 statement. Prepared statements are automatically cached by mysql-native, 349 so there's no performance penalty for using this multiple times for the 350 same statement instead of manually preparing a statement. 351 352 If `args` and `prepared` are both provided, `args` will be used, 353 and any arguments that are already set in the prepared statement 354 will automatically be replaced with `args` (note, just like calling 355 `mysql.prepared.Prepared.setArgs`, this will also remove all 356 `mysql.prepared.ParameterSpecialization` that may have been applied). 357 358 Only use the `string sql` overload that doesn't take `args` 359 when you are not going to be using the same 360 command repeatedly and you are CERTAIN all the data you're sending is properly 361 escaped. Otherwise, consider using overload that takes a `Prepared`. 362 363 If you need to use any `mysql.prepared.ParameterSpecialization`, use 364 `mysql.connection.prepare` to manually create a `mysql.prepared.Prepared`, 365 and set your parameter specializations using `mysql.prepared.Prepared.setArg` 366 or `mysql.prepared.Prepared.setArgs`. 367 368 Type_Mappings: $(TYPE_MAPPINGS) 369 370 Params: 371 conn = An open `mysql.connection.Connection` to the database. 372 sql = The SQL command to be run. 373 prepared = The prepared statement to be run. 374 csa = Not yet implemented. 375 376 Returns: A (possibly empty) `mysql.result.ResultRange`. 377 378 Example: 379 --- 380 ResultRange oneAtATime = myConnection.query("SELECT * from `myTable`"); 381 Row[] allAtOnce = myConnection.query("SELECT * from `myTable`").array; 382 383 auto myInt = 7; 384 ResultRange rows = myConnection.query("SELECT * FROM `myTable` WHERE `a` = ?", myInt); 385 --- 386 +/ 387 /+ 388 Future text: 389 If there are long data items among the expected result columns you can use 390 the `csa` param to specify that they are to be subject to chunked transfer via a 391 delegate. 392 393 csa = An optional array of `ColumnSpecialization` structs. If you need to 394 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`. 395 +/ 396 ResultRange query(Connection conn, string sql, ColumnSpecialization[] csa = null) 397 { 398 return queryImpl(csa, conn, ExecQueryImplInfo(false, sql)); 399 } 400 ///ditto 401 ResultRange query(T...)(Connection conn, string sql, T args) 402 if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[])) 403 { 404 auto prepared = conn.prepare(sql); 405 prepared.setArgs(args); 406 return query(conn, prepared); 407 } 408 ///ditto 409 ResultRange query(Connection conn, string sql, Variant[] args) 410 { 411 auto prepared = conn.prepare(sql); 412 prepared.setArgs(args); 413 return query(conn, prepared); 414 } 415 416 ///ditto 417 ResultRange query(Connection conn, ref Prepared prepared) 418 { 419 auto preparedInfo = conn.registerIfNeeded(prepared.sql); 420 auto result = queryImpl(prepared.columnSpecials, conn, prepared.getExecQueryImplInfo(preparedInfo.statementId)); 421 prepared._lastInsertID = conn.lastInsertID; // Conceivably, this might be needed when multi-statements are enabled. 422 return result; 423 } 424 ///ditto 425 ResultRange query(T...)(Connection conn, ref Prepared prepared, T args) 426 if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[])) 427 { 428 prepared.setArgs(args); 429 return query(conn, prepared); 430 } 431 ///ditto 432 ResultRange query(Connection conn, ref Prepared prepared, Variant[] args) 433 { 434 prepared.setArgs(args); 435 return query(conn, prepared); 436 } 437 438 ///ditto 439 ResultRange query(Connection conn, ref BackwardCompatPrepared prepared) 440 { 441 auto p = prepared.prepared; 442 auto result = query(conn, p); 443 prepared._prepared = p; 444 return result; 445 } 446 447 /// Common implementation for `query` overloads 448 package ResultRange queryImpl(ColumnSpecialization[] csa, 449 Connection conn, ExecQueryImplInfo info) 450 { 451 ulong ra; 452 enforceEx!MYXNoResultRecieved(execQueryImpl(conn, info, ra)); 453 454 conn._rsh = ResultSetHeaders(conn, conn._fieldCount); 455 if(csa !is null) 456 conn._rsh.addSpecializations(csa); 457 458 conn._headersPending = false; 459 return ResultRange(conn, conn._rsh, conn._rsh.fieldNames); 460 } 461 462 /++ 463 Execute an SQL SELECT command or prepared statement where you only want the 464 first `mysql.result.Row`, if any. 465 466 If the SQL command does not produce a result set (such as INSERT/CREATE/etc), 467 then `mysql.exceptions.MYXNoResultRecieved` will be thrown. Use 468 `exec` instead for such commands. 469 470 If `args` is supplied, the sql string will automatically be used as a prepared 471 statement. Prepared statements are automatically cached by mysql-native, 472 so there's no performance penalty for using this multiple times for the 473 same statement instead of manually preparing a statement. 474 475 If `args` and `prepared` are both provided, `args` will be used, 476 and any arguments that are already set in the prepared statement 477 will automatically be replaced with `args` (note, just like calling 478 `mysql.prepared.Prepared.setArgs`, this will also remove all 479 `mysql.prepared.ParameterSpecialization` that may have been applied). 480 481 Only use the `string sql` overload that doesn't take `args` 482 when you are not going to be using the same 483 command repeatedly and you are CERTAIN all the data you're sending is properly 484 escaped. Otherwise, consider using overload that takes a `Prepared`. 485 486 If you need to use any `mysql.prepared.ParameterSpecialization`, use 487 `mysql.connection.prepare` to manually create a `mysql.prepared.Prepared`, 488 and set your parameter specializations using `mysql.prepared.Prepared.setArg` 489 or `mysql.prepared.Prepared.setArgs`. 490 491 Type_Mappings: $(TYPE_MAPPINGS) 492 493 Params: 494 conn = An open `mysql.connection.Connection` to the database. 495 sql = The SQL command to be run. 496 prepared = The prepared statement to be run. 497 csa = Not yet implemented. 498 499 Returns: `Nullable!(mysql.result.Row)`: This will be null (check via `Nullable.isNull`) if the 500 query resulted in an empty result set. 501 502 Example: 503 --- 504 auto myInt = 7; 505 Nullable!Row row = myConnection.queryRow("SELECT * FROM `myTable` WHERE `a` = ?", myInt); 506 --- 507 +/ 508 /+ 509 Future text: 510 If there are long data items among the expected result columns you can use 511 the `csa` param to specify that they are to be subject to chunked transfer via a 512 delegate. 513 514 csa = An optional array of `ColumnSpecialization` structs. If you need to 515 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`. 516 +/ 517 /+ 518 Future text: 519 If there are long data items among the expected result columns you can use 520 the `csa` param to specify that they are to be subject to chunked transfer via a 521 delegate. 522 523 csa = An optional array of `ColumnSpecialization` structs. If you need to 524 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`. 525 +/ 526 Nullable!Row queryRow(Connection conn, string sql, ColumnSpecialization[] csa = null) 527 { 528 return queryRowImpl(csa, conn, ExecQueryImplInfo(false, sql)); 529 } 530 ///ditto 531 Nullable!Row queryRow(T...)(Connection conn, string sql, T args) 532 if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[])) 533 { 534 auto prepared = conn.prepare(sql); 535 prepared.setArgs(args); 536 return queryRow(conn, prepared); 537 } 538 ///ditto 539 Nullable!Row queryRow(Connection conn, string sql, Variant[] args) 540 { 541 auto prepared = conn.prepare(sql); 542 prepared.setArgs(args); 543 return queryRow(conn, prepared); 544 } 545 546 ///ditto 547 Nullable!Row queryRow(Connection conn, ref Prepared prepared) 548 { 549 auto preparedInfo = conn.registerIfNeeded(prepared.sql); 550 auto result = queryRowImpl(prepared.columnSpecials, conn, prepared.getExecQueryImplInfo(preparedInfo.statementId)); 551 prepared._lastInsertID = conn.lastInsertID; // Conceivably, this might be needed when multi-statements are enabled. 552 return result; 553 } 554 ///ditto 555 Nullable!Row queryRow(T...)(Connection conn, ref Prepared prepared, T args) 556 if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[])) 557 { 558 prepared.setArgs(args); 559 return queryRow(conn, prepared); 560 } 561 ///ditto 562 Nullable!Row queryRow(Connection conn, ref Prepared prepared, Variant[] args) 563 { 564 prepared.setArgs(args); 565 return queryRow(conn, prepared); 566 } 567 568 ///ditto 569 Nullable!Row queryRow(Connection conn, ref BackwardCompatPrepared prepared) 570 { 571 auto p = prepared.prepared; 572 auto result = queryRow(conn, p); 573 prepared._prepared = p; 574 return result; 575 } 576 577 /// Common implementation for `querySet` overloads. 578 package Nullable!Row queryRowImpl(ColumnSpecialization[] csa, Connection conn, 579 ExecQueryImplInfo info) 580 { 581 auto results = queryImpl(csa, conn, info); 582 if(results.empty) 583 return Nullable!Row(); 584 else 585 { 586 auto row = results.front; 587 results.close(); 588 return Nullable!Row(row); 589 } 590 } 591 592 /++ 593 Execute an SQL SELECT command or prepared statement where you only want the 594 first `mysql.result.Row`, and place result values into a set of D variables. 595 596 This method will throw if any column type is incompatible with the corresponding D variable. 597 598 Unlike the other query functions, queryRowTuple will throw 599 `mysql.exceptions.MYX` if the result set is empty 600 (and thus the reference variables passed in cannot be filled). 601 602 If the SQL command does not produce a result set (such as INSERT/CREATE/etc), 603 then `mysql.exceptions.MYXNoResultRecieved` will be thrown. Use 604 `exec` instead for such commands. 605 606 Only use the `string sql` overload when you are not going to be using the same 607 command repeatedly and you are CERTAIN all the data you're sending is properly 608 escaped. Otherwise, consider using overload that takes a `Prepared`. 609 610 Type_Mappings: $(TYPE_MAPPINGS) 611 612 Params: 613 conn = An open `mysql.connection.Connection` to the database. 614 sql = The SQL command to be run. 615 prepared = The prepared statement to be run. 616 args = The variables, taken by reference, to receive the values. 617 +/ 618 void queryRowTuple(T...)(Connection conn, string sql, ref T args) 619 { 620 return queryRowTupleImpl(conn, ExecQueryImplInfo(false, sql), args); 621 } 622 623 ///ditto 624 void queryRowTuple(T...)(Connection conn, ref Prepared prepared, ref T args) 625 { 626 auto preparedInfo = conn.registerIfNeeded(prepared.sql); 627 queryRowTupleImpl(conn, prepared.getExecQueryImplInfo(preparedInfo.statementId), args); 628 prepared._lastInsertID = conn.lastInsertID; // Conceivably, this might be needed when multi-statements are enabled. 629 } 630 631 ///ditto 632 void queryRowTuple(T...)(Connection conn, ref BackwardCompatPrepared prepared, ref T args) 633 { 634 auto p = prepared.prepared; 635 queryRowTuple(conn, p, args); 636 prepared._prepared = p; 637 } 638 639 /// Common implementation for `queryRowTuple` overloads. 640 package void queryRowTupleImpl(T...)(Connection conn, ExecQueryImplInfo info, ref T args) 641 { 642 ulong ra; 643 enforceEx!MYXNoResultRecieved(execQueryImpl(conn, info, ra)); 644 645 Row rr = conn.getNextRow(); 646 /+if (!rr._valid) // The result set was empty - not a crime. 647 return;+/ 648 enforceEx!MYX(rr._values.length == args.length, "Result column count does not match the target tuple."); 649 foreach (size_t i, dummy; args) 650 { 651 enforceEx!MYX(typeid(args[i]).toString() == rr._values[i].type.toString(), 652 "Tuple "~to!string(i)~" type and column type are not compatible."); 653 args[i] = rr._values[i].get!(typeof(args[i])); 654 } 655 // If there were more rows, flush them away 656 // Question: Should I check in purgeResult and throw if there were - it's very inefficient to 657 // allow sloppy SQL that does not ensure just one row! 658 conn.purgeResult(); 659 } 660 661 // Test what happends when queryRowTuple receives no rows 662 @("queryRowTuple_noRows") 663 debug(MYSQLN_TESTS) 664 unittest 665 { 666 import mysql.test.common : scopedCn, createCn; 667 mixin(scopedCn); 668 669 cn.exec("DROP TABLE IF EXISTS `queryRowTuple_noRows`"); 670 cn.exec("CREATE TABLE `queryRowTuple_noRows` ( 671 `val` INTEGER 672 ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); 673 674 immutable selectSQL = "SELECT * FROM `queryRowTuple_noRows`"; 675 int queryTupleResult; 676 assertThrown!MYX(cn.queryRowTuple(selectSQL, queryTupleResult)); 677 } 678 679 /++ 680 Execute an SQL SELECT command or prepared statement and return a single value: 681 the first column of the first row received. 682 683 If the query did not produce any rows, or the rows it produced have zero columns, 684 this will return `Nullable!Variant()`, ie, null. Test for this with `result.isNull`. 685 686 If the query DID produce a result, but the value actually received is NULL, 687 then `result.isNull` will be FALSE, and `result.get` will produce a Variant 688 which CONTAINS null. Check for this with `result.get.type == typeid(typeof(null))`. 689 690 If the SQL command does not produce a result set (such as INSERT/CREATE/etc), 691 then `mysql.exceptions.MYXNoResultRecieved` will be thrown. Use 692 `exec` instead for such commands. 693 694 If `args` is supplied, the sql string will automatically be used as a prepared 695 statement. Prepared statements are automatically cached by mysql-native, 696 so there's no performance penalty for using this multiple times for the 697 same statement instead of manually preparing a statement. 698 699 If `args` and `prepared` are both provided, `args` will be used, 700 and any arguments that are already set in the prepared statement 701 will automatically be replaced with `args` (note, just like calling 702 `mysql.prepared.Prepared.setArgs`, this will also remove all 703 `mysql.prepared.ParameterSpecialization` that may have been applied). 704 705 Only use the `string sql` overload that doesn't take `args` 706 when you are not going to be using the same 707 command repeatedly and you are CERTAIN all the data you're sending is properly 708 escaped. Otherwise, consider using overload that takes a `Prepared`. 709 710 If you need to use any `mysql.prepared.ParameterSpecialization`, use 711 `mysql.connection.prepare` to manually create a `mysql.prepared.Prepared`, 712 and set your parameter specializations using `mysql.prepared.Prepared.setArg` 713 or `mysql.prepared.Prepared.setArgs`. 714 715 Type_Mappings: $(TYPE_MAPPINGS) 716 717 Params: 718 conn = An open `mysql.connection.Connection` to the database. 719 sql = The SQL command to be run. 720 prepared = The prepared statement to be run. 721 csa = Not yet implemented. 722 723 Returns: `Nullable!Variant`: This will be null (check via `Nullable.isNull`) if the 724 query resulted in an empty result set. 725 726 Example: 727 --- 728 auto myInt = 7; 729 Nullable!Variant value = myConnection.queryRow("SELECT * FROM `myTable` WHERE `a` = ?", myInt); 730 --- 731 +/ 732 /+ 733 Future text: 734 If there are long data items among the expected result columns you can use 735 the `csa` param to specify that they are to be subject to chunked transfer via a 736 delegate. 737 738 csa = An optional array of `ColumnSpecialization` structs. If you need to 739 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`. 740 +/ 741 /+ 742 Future text: 743 If there are long data items among the expected result columns you can use 744 the `csa` param to specify that they are to be subject to chunked transfer via a 745 delegate. 746 747 csa = An optional array of `ColumnSpecialization` structs. If you need to 748 use this with a prepared statement, please use `mysql.prepared.Prepared.columnSpecials`. 749 +/ 750 Nullable!Variant queryValue(Connection conn, string sql, ColumnSpecialization[] csa = null) 751 { 752 return queryValueImpl(csa, conn, ExecQueryImplInfo(false, sql)); 753 } 754 ///ditto 755 Nullable!Variant queryValue(T...)(Connection conn, string sql, T args) 756 if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[])) 757 { 758 auto prepared = conn.prepare(sql); 759 prepared.setArgs(args); 760 return queryValue(conn, prepared); 761 } 762 ///ditto 763 Nullable!Variant queryValue(Connection conn, string sql, Variant[] args) 764 { 765 auto prepared = conn.prepare(sql); 766 prepared.setArgs(args); 767 return queryValue(conn, prepared); 768 } 769 770 ///ditto 771 Nullable!Variant queryValue(Connection conn, ref Prepared prepared) 772 { 773 auto preparedInfo = conn.registerIfNeeded(prepared.sql); 774 auto result = queryValueImpl(prepared.columnSpecials, conn, prepared.getExecQueryImplInfo(preparedInfo.statementId)); 775 prepared._lastInsertID = conn.lastInsertID; // Conceivably, this might be needed when multi-statements are enabled. 776 return result; 777 } 778 ///ditto 779 Nullable!Variant queryValue(T...)(Connection conn, ref Prepared prepared, T args) 780 if(T.length > 0 && !is(T[0] == Variant[]) && !is(T[0] == ColumnSpecialization) && !is(T[0] == ColumnSpecialization[])) 781 { 782 prepared.setArgs(args); 783 return queryValue(conn, prepared); 784 } 785 ///ditto 786 Nullable!Variant queryValue(Connection conn, ref Prepared prepared, Variant[] args) 787 { 788 prepared.setArgs(args); 789 return queryValue(conn, prepared); 790 } 791 792 ///ditto 793 Nullable!Variant queryValue(Connection conn, ref BackwardCompatPrepared prepared) 794 { 795 auto p = prepared.prepared; 796 auto result = queryValue(conn, p); 797 prepared._prepared = p; 798 return result; 799 } 800 801 /// Common implementation for `queryValue` overloads. 802 package Nullable!Variant queryValueImpl(ColumnSpecialization[] csa, Connection conn, 803 ExecQueryImplInfo info) 804 { 805 auto results = queryImpl(csa, conn, info); 806 if(results.empty) 807 return Nullable!Variant(); 808 else 809 { 810 auto row = results.front; 811 results.close(); 812 813 if(row.length == 0) 814 return Nullable!Variant(); 815 else 816 return Nullable!Variant(row[0]); 817 } 818 } 819 820 @("execOverloads") 821 debug(MYSQLN_TESTS) 822 unittest 823 { 824 import std.array; 825 import mysql.connection; 826 import mysql.test.common; 827 mixin(scopedCn); 828 829 cn.exec("DROP TABLE IF EXISTS `execOverloads`"); 830 cn.exec("CREATE TABLE `execOverloads` ( 831 `i` INTEGER, 832 `s` VARCHAR(50) 833 ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); 834 835 immutable prepareSQL = "INSERT INTO `execOverloads` VALUES (?, ?)"; 836 837 // Do the inserts, using exec 838 839 // exec: string sql 840 assert(cn.exec("INSERT INTO `execOverloads` VALUES (1, \"aa\")") == 1); 841 assert(cn.exec(prepareSQL, 2, "bb") == 1); 842 assert(cn.exec(prepareSQL, [Variant(3), Variant("cc")]) == 1); 843 844 // exec: prepared sql 845 auto prepared = cn.prepare(prepareSQL); 846 prepared.setArgs(4, "dd"); 847 assert(cn.exec(prepared) == 1); 848 849 assert(cn.exec(prepared, 5, "ee") == 1); 850 assert(prepared.getArg(0) == 5); 851 assert(prepared.getArg(1) == "ee"); 852 853 assert(cn.exec(prepared, [Variant(6), Variant("ff")]) == 1); 854 assert(prepared.getArg(0) == 6); 855 assert(prepared.getArg(1) == "ff"); 856 857 // exec: bcPrepared sql 858 auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL); 859 bcPrepared.setArgs(7, "gg"); 860 assert(cn.exec(bcPrepared) == 1); 861 assert(bcPrepared.getArg(0) == 7); 862 assert(bcPrepared.getArg(1) == "gg"); 863 864 // Check results 865 auto rows = cn.query("SELECT * FROM `execOverloads`").array(); 866 assert(rows.length == 7); 867 868 assert(rows[0].length == 2); 869 assert(rows[1].length == 2); 870 assert(rows[2].length == 2); 871 assert(rows[3].length == 2); 872 assert(rows[4].length == 2); 873 assert(rows[5].length == 2); 874 assert(rows[6].length == 2); 875 876 assert(rows[0][0] == 1); 877 assert(rows[0][1] == "aa"); 878 assert(rows[1][0] == 2); 879 assert(rows[1][1] == "bb"); 880 assert(rows[2][0] == 3); 881 assert(rows[2][1] == "cc"); 882 assert(rows[3][0] == 4); 883 assert(rows[3][1] == "dd"); 884 assert(rows[4][0] == 5); 885 assert(rows[4][1] == "ee"); 886 assert(rows[5][0] == 6); 887 assert(rows[5][1] == "ff"); 888 assert(rows[6][0] == 7); 889 assert(rows[6][1] == "gg"); 890 } 891 892 @("queryOverloads") 893 debug(MYSQLN_TESTS) 894 unittest 895 { 896 import std.array; 897 import mysql.connection; 898 import mysql.test.common; 899 mixin(scopedCn); 900 901 cn.exec("DROP TABLE IF EXISTS `queryOverloads`"); 902 cn.exec("CREATE TABLE `queryOverloads` ( 903 `i` INTEGER, 904 `s` VARCHAR(50) 905 ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); 906 cn.exec("INSERT INTO `queryOverloads` VALUES (1, \"aa\"), (2, \"bb\"), (3, \"cc\")"); 907 908 immutable prepareSQL = "SELECT * FROM `queryOverloads` WHERE `i`=? AND `s`=?"; 909 910 // Test query 911 { 912 Row[] rows; 913 914 // String sql 915 rows = cn.query("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\"").array; 916 assert(rows.length == 1); 917 assert(rows[0].length == 2); 918 assert(rows[0][0] == 1); 919 assert(rows[0][1] == "aa"); 920 921 rows = cn.query(prepareSQL, 2, "bb").array; 922 assert(rows.length == 1); 923 assert(rows[0].length == 2); 924 assert(rows[0][0] == 2); 925 assert(rows[0][1] == "bb"); 926 927 rows = cn.query(prepareSQL, [Variant(3), Variant("cc")]).array; 928 assert(rows.length == 1); 929 assert(rows[0].length == 2); 930 assert(rows[0][0] == 3); 931 assert(rows[0][1] == "cc"); 932 933 // Prepared sql 934 auto prepared = cn.prepare(prepareSQL); 935 prepared.setArgs(1, "aa"); 936 rows = cn.query(prepared).array; 937 assert(rows.length == 1); 938 assert(rows[0].length == 2); 939 assert(rows[0][0] == 1); 940 assert(rows[0][1] == "aa"); 941 942 rows = cn.query(prepared, 2, "bb").array; 943 assert(rows.length == 1); 944 assert(rows[0].length == 2); 945 assert(rows[0][0] == 2); 946 assert(rows[0][1] == "bb"); 947 948 rows = cn.query(prepared, [Variant(3), Variant("cc")]).array; 949 assert(rows.length == 1); 950 assert(rows[0].length == 2); 951 assert(rows[0][0] == 3); 952 assert(rows[0][1] == "cc"); 953 954 // BCPrepared sql 955 auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL); 956 bcPrepared.setArgs(1, "aa"); 957 rows = cn.query(bcPrepared).array; 958 assert(rows.length == 1); 959 assert(rows[0].length == 2); 960 assert(rows[0][0] == 1); 961 assert(rows[0][1] == "aa"); 962 } 963 964 // Test queryRow 965 { 966 Nullable!Row row; 967 968 // String sql 969 row = cn.queryRow("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\""); 970 assert(!row.isNull); 971 assert(row.length == 2); 972 assert(row[0] == 1); 973 assert(row[1] == "aa"); 974 975 row = cn.queryRow(prepareSQL, 2, "bb"); 976 assert(!row.isNull); 977 assert(row.length == 2); 978 assert(row[0] == 2); 979 assert(row[1] == "bb"); 980 981 row = cn.queryRow(prepareSQL, [Variant(3), Variant("cc")]); 982 assert(!row.isNull); 983 assert(row.length == 2); 984 assert(row[0] == 3); 985 assert(row[1] == "cc"); 986 987 // Prepared sql 988 auto prepared = cn.prepare(prepareSQL); 989 prepared.setArgs(1, "aa"); 990 row = cn.queryRow(prepared); 991 assert(!row.isNull); 992 assert(row.length == 2); 993 assert(row[0] == 1); 994 assert(row[1] == "aa"); 995 996 row = cn.queryRow(prepared, 2, "bb"); 997 assert(!row.isNull); 998 assert(row.length == 2); 999 assert(row[0] == 2); 1000 assert(row[1] == "bb"); 1001 1002 row = cn.queryRow(prepared, [Variant(3), Variant("cc")]); 1003 assert(!row.isNull); 1004 assert(row.length == 2); 1005 assert(row[0] == 3); 1006 assert(row[1] == "cc"); 1007 1008 // BCPrepared sql 1009 auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL); 1010 bcPrepared.setArgs(1, "aa"); 1011 row = cn.queryRow(bcPrepared); 1012 assert(!row.isNull); 1013 assert(row.length == 2); 1014 assert(row[0] == 1); 1015 assert(row[1] == "aa"); 1016 } 1017 1018 // Test queryRowTuple 1019 { 1020 int i; 1021 string s; 1022 1023 // String sql 1024 cn.queryRowTuple("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\"", i, s); 1025 assert(i == 1); 1026 assert(s == "aa"); 1027 1028 // Prepared sql 1029 auto prepared = cn.prepare(prepareSQL); 1030 prepared.setArgs(2, "bb"); 1031 cn.queryRowTuple(prepared, i, s); 1032 assert(i == 2); 1033 assert(s == "bb"); 1034 1035 // BCPrepared sql 1036 auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL); 1037 bcPrepared.setArgs(3, "cc"); 1038 cn.queryRowTuple(bcPrepared, i, s); 1039 assert(i == 3); 1040 assert(s == "cc"); 1041 } 1042 1043 // Test queryValue 1044 { 1045 Nullable!Variant value; 1046 1047 // String sql 1048 value = cn.queryValue("SELECT * FROM `queryOverloads` WHERE `i`=1 AND `s`=\"aa\""); 1049 assert(!value.isNull); 1050 assert(value.get.type != typeid(typeof(null))); 1051 assert(value.get == 1); 1052 1053 value = cn.queryValue(prepareSQL, 2, "bb"); 1054 assert(!value.isNull); 1055 assert(value.get.type != typeid(typeof(null))); 1056 assert(value.get == 2); 1057 1058 value = cn.queryValue(prepareSQL, [Variant(3), Variant("cc")]); 1059 assert(!value.isNull); 1060 assert(value.get.type != typeid(typeof(null))); 1061 assert(value.get == 3); 1062 1063 // Prepared sql 1064 auto prepared = cn.prepare(prepareSQL); 1065 prepared.setArgs(1, "aa"); 1066 value = cn.queryValue(prepared); 1067 assert(!value.isNull); 1068 assert(value.get.type != typeid(typeof(null))); 1069 assert(value.get == 1); 1070 1071 value = cn.queryValue(prepared, 2, "bb"); 1072 assert(!value.isNull); 1073 assert(value.get.type != typeid(typeof(null))); 1074 assert(value.get == 2); 1075 1076 value = cn.queryValue(prepared, [Variant(3), Variant("cc")]); 1077 assert(!value.isNull); 1078 assert(value.get.type != typeid(typeof(null))); 1079 assert(value.get == 3); 1080 1081 // BCPrepared sql 1082 auto bcPrepared = cn.prepareBackwardCompatImpl(prepareSQL); 1083 bcPrepared.setArgs(1, "aa"); 1084 value = cn.queryValue(bcPrepared); 1085 assert(!value.isNull); 1086 assert(value.get.type != typeid(typeof(null))); 1087 assert(value.get == 1); 1088 } 1089 }