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