1 module mysql.protocol.commands; 2 3 import std.algorithm; 4 import std.conv; 5 import std.datetime; 6 import std.digest.sha; 7 import std.exception; 8 import std.range; 9 import std.socket; 10 import std.stdio; 11 import std.string; 12 import std.traits; 13 import std.typecons; 14 import std.variant; 15 16 import mysql.common; 17 import mysql.connection; 18 import mysql.protocol.constants; 19 import mysql.protocol.extra_types; 20 import mysql.protocol.packets; 21 import mysql.protocol.packet_helpers; 22 import mysql.protocol.prepared; 23 24 package struct ExecQueryImplInfo 25 { 26 bool isPrepared; 27 28 // For non-prepared statements: 29 string sql; 30 31 // For prepared statements: 32 uint hStmt; 33 PreparedStmtHeaders psh; 34 Variant[] inParams; 35 ParameterSpecialization[] psa; 36 } 37 38 /++ 39 Internal implementation for the exec and query functions. 40 41 Execute a one-off SQL command. 42 43 Use this method when you are not going to be using the same command repeatedly. 44 It can be used with commands that don't produce a result set, or those that 45 do. If there is a result set its existence will be indicated by the return value. 46 47 Any result set can be accessed vis Connection.getNextRow(), but you should really be 48 using execSQLResult() or execSQLSequence() for such queries. 49 50 Params: ra = An out parameter to receive the number of rows affected. 51 Returns: true if there was a (possibly empty) result set. 52 +/ 53 package bool execQueryImpl(Connection conn, ExecQueryImplInfo info, out ulong ra) 54 { 55 conn.enforceNothingPending(); 56 scope(failure) conn.kill(); 57 58 // Send data 59 if(info.isPrepared) 60 Prepared.sendCommand(conn, info.hStmt, info.psh, info.inParams, info.psa); 61 else 62 { 63 conn.sendCmd(CommandType.QUERY, info.sql); 64 conn._fieldCount = 0; 65 } 66 67 // Handle response 68 ubyte[] packet = conn.getPacket(); 69 bool rv; 70 if (packet.front == ResultPacketMarker.ok || packet.front == ResultPacketMarker.error) 71 { 72 conn.resetPacket(); 73 auto okp = OKErrorPacket(packet); 74 enforcePacketOK(okp); 75 ra = okp.affected; 76 conn._serverStatus = okp.serverStatus; 77 conn._insertID = okp.insertID; 78 rv = false; 79 } 80 else 81 { 82 // There was presumably a result set 83 assert(packet.front >= 1 && packet.front <= 250); // ResultSet packet header should have this value 84 conn._headersPending = conn._rowsPending = true; 85 conn._binaryPending = info.isPrepared; 86 auto lcb = packet.consumeIfComplete!LCB(); 87 assert(!lcb.isNull); 88 assert(!lcb.isIncomplete); 89 conn._fieldCount = cast(ushort)lcb.value; 90 assert(conn._fieldCount == lcb.value); 91 rv = true; 92 ra = 0; 93 } 94 return rv; 95 } 96 97 ///ditto 98 package bool execQueryImpl(Connection conn, ExecQueryImplInfo info) 99 { 100 ulong rowsAffected; 101 return execQueryImpl(conn, info, rowsAffected); 102 } 103 104 /++ 105 Execute a one-off SQL command. 106 107 Use this method when you are not going to be using the same command repeatedly. 108 It can be used with commands that don't produce a result set, or those that 109 do. If there is a result set its existence will be indicated by the return value. 110 111 Any result set can be accessed vis Connection.getNextRow(), but you should really be 112 using execSQLResult() or execSQLSequence() for such queries. 113 114 Params: ra = An out parameter to receive the number of rows affected. 115 Returns: true if there was a (possibly empty) result set. 116 +/ 117 ulong exec(Connection conn, string sql) 118 { 119 return execImpl(conn, ExecQueryImplInfo(false, sql)); 120 } 121 122 /// Common implementation for mysql.protocol.commands.exec and Prepared.exec 123 package ulong execImpl(Connection conn, ExecQueryImplInfo info) 124 { 125 ulong rowsAffected; 126 bool receivedResultSet = execQueryImpl(conn, info, rowsAffected); 127 if(receivedResultSet) 128 { 129 conn.purgeResult(); 130 throw new MYXResultRecieved(); 131 } 132 133 return rowsAffected; 134 } 135 136 /++ 137 Execute a one-off SQL command for the case where you expect a result set, 138 and want it all at once. 139 140 Use this method when you are not going to be using the same command repeatedly. 141 This method will throw if the SQL command does not produce a result set. 142 143 If there are long data items among the expected result columns you can specify 144 that they are to be subject to chunked transfer via a delegate. 145 146 Params: csa = An optional array of ColumnSpecialization structs. 147 Returns: A (possibly empty) ResultSet. 148 +/ 149 ResultSet querySet(Connection conn, string sql, ColumnSpecialization[] csa = null) 150 { 151 return querySetImpl(csa, false, conn, ExecQueryImplInfo(false, sql)); 152 } 153 154 ///ditto 155 deprecated("Use querySet instead.") 156 alias queryResult = querySet; 157 158 /// Common implementation for mysql.protocol.commands.querySet and Prepared.querySet 159 package ResultSet querySetImpl(ColumnSpecialization[] csa, bool binary, 160 Connection conn, ExecQueryImplInfo info) 161 { 162 ulong ra; 163 enforceEx!MYXNoResultRecieved(execQueryImpl(conn, info, ra)); 164 165 conn._rsh = ResultSetHeaders(conn, conn._fieldCount); 166 if (csa !is null) 167 conn._rsh.addSpecializations(csa); 168 conn._headersPending = false; 169 170 Row[] rows; 171 while(true) 172 { 173 scope(failure) conn.kill(); 174 175 auto packet = conn.getPacket(); 176 if(packet.isEOFPacket()) 177 break; 178 rows ~= Row(conn, packet, conn._rsh, binary); 179 // As the row fetches more data while incomplete, it might already have 180 // fetched the EOF marker, so we have to check it again 181 if(!packet.empty && packet.isEOFPacket()) 182 break; 183 } 184 conn._rowsPending = conn._binaryPending = false; 185 186 return ResultSet(rows, conn._rsh.fieldNames); 187 } 188 189 /++ 190 Execute a one-off SQL command for the case where you expect a result set, 191 and want to deal with it a row at a time. 192 193 Use this method when you are not going to be using the same command repeatedly. 194 This method will throw if the SQL command does not produce a result set. 195 196 If there are long data items among the expected result columns you can specify 197 that they are to be subject to chunked transfer via a delegate. 198 199 Params: csa = An optional array of ColumnSpecialization structs. 200 Returns: A (possibly empty) ResultRange. 201 +/ 202 ResultRange query(Connection conn, string sql, ColumnSpecialization[] csa = null) 203 { 204 return queryImpl(csa, conn, ExecQueryImplInfo(false, sql)); 205 } 206 207 ///ditto 208 deprecated("Use query instead.") 209 alias querySequence = query; 210 211 /// Common implementation for mysql.protocol.commands.query and Prepared.query 212 package ResultRange queryImpl(ColumnSpecialization[] csa, 213 Connection conn, ExecQueryImplInfo info) 214 { 215 ulong ra; 216 enforceEx!MYXNoResultRecieved(execQueryImpl(conn, info, ra)); 217 218 conn._rsh = ResultSetHeaders(conn, conn._fieldCount); 219 if (csa !is null) 220 conn._rsh.addSpecializations(csa); 221 222 conn._headersPending = false; 223 return ResultRange(conn, conn._rsh, conn._rsh.fieldNames); 224 } 225 226 /++ 227 Executes a one-off SQL command and returns the first row received, or null 228 if none. Useful for the case where you expect a (possibly empty) result set, 229 and you're either only expecting one row, or only care about the first row. 230 231 Use this method when you are not going to be using the same command repeatedly. 232 This method will throw if the SQL command does not produce a result set. 233 234 If there are long data items among the expected result columns you can specify 235 that they are to be subject to chunked transfer via a delegate. 236 237 Params: csa = An optional array of ColumnSpecialization structs. 238 Returns: Nullable!Row: This will be null (check via Nullable.isNull) if the 239 query resulted in an empty result set. 240 +/ 241 Nullable!Row queryRow(Connection conn, string sql, ColumnSpecialization[] csa = null) 242 { 243 return queryRowImpl(csa, conn, ExecQueryImplInfo(false, sql)); 244 } 245 246 /// Common implementation for mysql.protocol.commands.querySet and Prepared.querySet 247 package Nullable!Row queryRowImpl(ColumnSpecialization[] csa, Connection conn, 248 ExecQueryImplInfo info) 249 { 250 auto results = queryImpl(csa, conn, info); 251 if(results.empty) 252 return Nullable!Row(); 253 else 254 { 255 auto row = results.front; 256 results.close(); 257 return Nullable!Row(row); 258 } 259 } 260 261 /++ 262 Execute a one-off SQL command to place result values into a set of D variables. 263 264 Use this method when you are not going to be using the same command repeatedly. 265 It will throw if the specified command does not produce a result set, or if 266 any column type is incompatible with the corresponding D variable. 267 268 Params: args = A tuple of D variables to receive the results. 269 +/ 270 void queryRowTuple(T...)(Connection conn, string sql, ref T args) 271 { 272 return queryRowTupleImpl(conn, ExecQueryImplInfo(false, sql), args); 273 } 274 275 ///ditto 276 deprecated("Use queryRowTuple instead.") 277 alias queryTuple = queryRowTuple; 278 279 /// Common implementation for mysql.protocol.commands.queryRowTuple and Prepared.queryRowTuple 280 package void queryRowTupleImpl(T...)(Connection conn, ExecQueryImplInfo info, ref T args) 281 { 282 ulong ra; 283 enforceEx!MYXNoResultRecieved(execQueryImpl(conn, info, ra)); 284 285 Row rr = conn.getNextRow(); 286 /+if (!rr._valid) // The result set was empty - not a crime. 287 return;+/ 288 enforceEx!MYX(rr._values.length == args.length, "Result column count does not match the target tuple."); 289 foreach (size_t i, dummy; args) 290 { 291 enforceEx!MYX(typeid(args[i]).toString() == rr._values[i].type.toString(), 292 "Tuple "~to!string(i)~" type and column type are not compatible."); 293 args[i] = rr._values[i].get!(typeof(args[i])); 294 } 295 // If there were more rows, flush them away 296 // Question: Should I check in purgeResult and throw if there were - it's very inefficient to 297 // allow sloppy SQL that does not ensure just one row! 298 conn.purgeResult(); 299 } 300 301 /++ 302 Executes a one-off SQL command and returns a single value: The the first column 303 of the first row received. Useful for the case where you expect a 304 (possibly empty) result set, and you're either only expecting one value, or 305 only care about the first value. 306 307 If the query did not produce any rows, or the rows it produced have zero columns, 308 this will return `Nullable!Variant()`, ie, null. Test for this with `result.isNull`. 309 310 If the query DID produce a result, but the value actually received is NULL, 311 then `result.isNull` will be FALSE, and `result.get` will produce a Variant 312 which CONTAINS null. Check for this with `result.get.type == typeid(typeof(null))`. 313 314 Use this method when you are not going to be using the same command repeatedly. 315 This method will throw if the SQL command does not produce a result set. 316 317 If there are long data items among the expected result columns you can specify 318 that they are to be subject to chunked transfer via a delegate. 319 320 Params: csa = An optional array of ColumnSpecialization structs. 321 Returns: Nullable!Variant: This will be null (check via Nullable.isNull) if the 322 query resulted in an empty result set. 323 +/ 324 Nullable!Variant queryValue(Connection conn, string sql, ColumnSpecialization[] csa = null) 325 { 326 return queryValueImpl(csa, conn, ExecQueryImplInfo(false, sql)); 327 } 328 329 /// Common implementation for mysql.protocol.commands.querySet and Prepared.querySet 330 package Nullable!Variant queryValueImpl(ColumnSpecialization[] csa, Connection conn, 331 ExecQueryImplInfo info) 332 { 333 auto results = queryImpl(csa, conn, info); 334 if(results.empty) 335 return Nullable!Variant(); 336 else 337 { 338 auto row = results.front; 339 results.close(); 340 341 if(row.length == 0) 342 return Nullable!Variant(); 343 else 344 return Nullable!Variant(row[0]); 345 } 346 } 347 348 /++ 349 Encapsulation of an SQL command or query. 350 351 A Command be be either a one-off SQL query, or may use a prepared statement. 352 Commands that are expected to return a result set - queries - have distinctive methods 353 that are enforced. That is it will be an error to call such a method with an SQL command 354 that does not produce a result set. 355 +/ 356 struct Command 357 { 358 package: 359 Connection _con; // This can disappear along with Command 360 string _sql; // This can disappear along with Command 361 string _prevFunc; // Has to do with stored procedures 362 Prepared _prepared; // The current prepared statement info 363 364 public: 365 366 /++ 367 Construct a naked Command object 368 369 Params: con = A Connection object to communicate with the server 370 +/ 371 // This can disappear along with Command 372 this(Connection con) 373 { 374 _con = con; 375 _con.resetPacket(); 376 } 377 378 /++ 379 Construct a Command object complete with SQL 380 381 Params: con = A Connection object to communicate with the server 382 sql = SQL command string. 383 +/ 384 // This can disappear along with Command 385 this(Connection con, const(char)[] sql) 386 { 387 _sql = sql.idup; 388 this(con); 389 } 390 391 @property 392 { 393 /// Get the current SQL for the Command 394 // This can disappear along with Command 395 const(char)[] sql() pure const nothrow { return _sql; } 396 397 /++ 398 Set a new SQL command. 399 400 This can have quite profound side effects. It resets the Command to 401 an initial state. If a query has been issued on the Command that 402 produced a result set, then all of the result set packets - field 403 description sequence, EOF packet, result rows sequence, EOF packet 404 must be flushed from the server before any further operation can be 405 performed on the Connection. If you want to write speedy and efficient 406 MySQL programs, you should bear this in mind when designing your 407 queries so that you are not requesting many rows when one would do. 408 409 Params: sql = SQL command string. 410 +/ 411 // This can disappear along with Command 412 const(char)[] sql(const(char)[] sql) 413 { 414 if (_prepared.isPrepared) 415 { 416 _prepared.release(); 417 _prevFunc = null; 418 } 419 return this._sql = sql.idup; 420 } 421 } 422 423 /++ 424 Submit an SQL command to the server to be compiled into a prepared statement. 425 426 The result of a successful outcome will be a statement handle - an ID - 427 for the prepared statement, a count of the parameters required for 428 excution of the statement, and a count of the columns that will be present 429 in any result set that the command generates. Thes values will be stored 430 in in the Command struct. 431 432 The server will then proceed to send prepared statement headers, 433 including parameter descriptions, and result set field descriptions, 434 followed by an EOF packet. 435 436 If there is an existing statement handle in the Command struct, that 437 prepared statement is released. 438 439 Throws: MySQLException if there are pending result set items, or if the 440 server has a problem. 441 +/ 442 deprecated("Use Prepare.this(Connection conn, string sql) instead") 443 void prepare() 444 { 445 _prepared = .prepare(_con, _sql); 446 } 447 448 /++ 449 Release a prepared statement. 450 451 This method tells the server that it can dispose of the information it 452 holds about the current prepared statement, and resets the Command 453 object to an initial state in that respect. 454 +/ 455 deprecated("Use Prepared.release instead") 456 void releaseStatement() 457 { 458 if (_prepared.isPrepared) 459 _prepared.release(); 460 } 461 462 /++ 463 Flush any outstanding result set elements. 464 465 When the server responds to a command that produces a result set, it 466 queues the whole set of corresponding packets over the current connection. 467 Before that Connection can embark on any new command, it must receive 468 all of those packets and junk them. 469 http://www.mysqlperformanceblog.com/2007/07/08/mysql-net_write_timeout-vs-wait_timeout-and-protocol-notes/ 470 +/ 471 deprecated("Use Connection.purgeResult() instead.") 472 ulong purgeResult() 473 { 474 return _con.purgeResult(); 475 } 476 477 /++ 478 Bind a D variable to a prepared statement parameter. 479 480 In this implementation, binding comprises setting a value into the 481 appropriate element of an array of Variants which represent the 482 parameters, and setting any required specializations. 483 484 To bind to some D variable, we set the corrsponding variant with its 485 address, so there is no need to rebind between calls to execPreparedXXX. 486 +/ 487 deprecated("Use Prepared.setArg instead") 488 void bindParameter(T)(ref T val, size_t pIndex, ParameterSpecialization psn = PSN(0, SQLType.INFER_FROM_D_TYPE, 0, null)) 489 { 490 enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared before parameters are bound."); 491 _prepared.setArg(pIndex, &val, psn); 492 } 493 494 /++ 495 Bind a tuple of D variables to the parameters of a prepared statement. 496 497 You can use this method to bind a set of variables if you don't need any specialization, 498 that is there will be no null values, and chunked transfer is not neccessary. 499 500 The tuple must match the required number of parameters, and it is the programmer's 501 responsibility to ensure that they are of appropriate types. 502 +/ 503 deprecated("Use Prepared.setArgs instead") 504 void bindParameterTuple(T...)(ref T args) 505 { 506 enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared before parameters are bound."); 507 enforceEx!MYX(args.length == _prepared.numArgs, "Argument list supplied does not match the number of parameters."); 508 foreach (size_t i, dummy; args) 509 _prepared.setArg(&args[i], i); 510 } 511 512 /++ 513 Bind a Variant[] as the parameters of a prepared statement. 514 515 You can use this method to bind a set of variables in Variant form to 516 the parameters of a prepared statement. 517 518 Parameter specializations can be added if required. This method could be 519 used to add records from a data entry form along the lines of 520 ------------ 521 auto c = Command(con, "insert into table42 values(?, ?, ?)"); 522 c.prepare(); 523 Variant[] va; 524 va.length = 3; 525 DataRecord dr; // Some data input facility 526 ulong ra; 527 do 528 { 529 dr.get(); 530 va[0] = dr("Name"); 531 va[1] = dr("City"); 532 va[2] = dr("Whatever"); 533 c.bindParameters(va); 534 c.execPrepared(ra); 535 } while(tod < "17:30"); 536 ------------ 537 Params: va = External list of Variants to be used as parameters 538 psnList = any required specializations 539 +/ 540 deprecated("Use Prepared.setArgs instead") 541 void bindParameters(Variant[] va, ParameterSpecialization[] psnList= null) 542 { 543 _prepared.setArgs(va, psnList); 544 } 545 546 /++ 547 Access a prepared statement parameter for update. 548 549 Another style of usage would simply update the parameter Variant directly 550 551 ------------ 552 c.param(0) = 42; 553 c.param(1) = "The answer"; 554 ------------ 555 Params: index = The zero based index 556 +/ 557 deprecated("Use Prepared.getArg to get and Prepared.setArg to set.") 558 ref Variant param(size_t index) pure 559 { 560 enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared before parameters are bound."); 561 enforceEx!MYX(index < _prepared.numArgs, "Parameter index out of range."); 562 return _prepared._inParams[index]; 563 } 564 565 /++ 566 Prepared statement parameter getter. 567 568 Params: index = The zero based index 569 +/ 570 deprecated("Use Prepared.getArg instead.") 571 Variant getArg(size_t index) 572 { 573 enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared before parameters are bound."); 574 return _prepared.getArg(index); 575 } 576 577 /++ 578 Sets a prepared statement parameter to NULL. 579 580 Params: index = The zero based index 581 +/ 582 deprecated("Use Prepared.setNullArg instead.") 583 void setNullParam(size_t index) 584 { 585 enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared before parameters are bound."); 586 _prepared.setNullArg(index); 587 } 588 589 /++ 590 Execute a one-off SQL command. 591 592 Use this method when you are not going to be using the same command repeatedly. 593 It can be used with commands that don't produce a result set, or those that 594 do. If there is a result set its existence will be indicated by the return value. 595 596 Any result set can be accessed vis Connection.getNextRow(), but you should really be 597 using execSQLResult() or execSQLSequence() for such queries. 598 599 Params: ra = An out parameter to receive the number of rows affected. 600 Returns: true if there was a (possibly empty) result set. 601 +/ 602 deprecated("Use the free-standing function .exec instead") 603 bool execSQL(out ulong ra) 604 { 605 return .execQueryImpl(_con, ExecQueryImplInfo(false, _sql), ra); 606 } 607 608 ///ditto 609 deprecated("Use the free-standing function .exec instead") 610 bool execSQL() 611 { 612 ulong ra; 613 return .execQueryImpl(_con, ExecQueryImplInfo(false, _sql), ra); 614 } 615 616 /++ 617 Execute a one-off SQL command for the case where you expect a result set, 618 and want it all at once. 619 620 Use this method when you are not going to be using the same command repeatedly. 621 This method will throw if the SQL command does not produce a result set. 622 623 If there are long data items among the expected result columns you can specify 624 that they are to be subject to chunked transfer via a delegate. 625 626 Params: csa = An optional array of ColumnSpecialization structs. 627 Returns: A (possibly empty) ResultSet. 628 +/ 629 deprecated("Use the free-standing function .querySet instead") 630 ResultSet execSQLResult(ColumnSpecialization[] csa = null) 631 { 632 return .querySet(_con, _sql, csa); 633 } 634 635 /++ 636 Execute a one-off SQL command for the case where you expect a result set, 637 and want to deal with it a row at a time. 638 639 Use this method when you are not going to be using the same command repeatedly. 640 This method will throw if the SQL command does not produce a result set. 641 642 If there are long data items among the expected result columns you can specify 643 that they are to be subject to chunked transfer via a delegate. 644 645 Params: csa = An optional array of ColumnSpecialization structs. 646 Returns: A (possibly empty) ResultRange. 647 +/ 648 deprecated("Use the free-standing function .query instead") 649 ResultRange execSQLSequence(ColumnSpecialization[] csa = null) 650 { 651 return .query(_con, _sql, csa); 652 } 653 654 /++ 655 Execute a one-off SQL command to place result values into a set of D variables. 656 657 Use this method when you are not going to be using the same command repeatedly. 658 It will throw if the specified command does not produce a result set, or if 659 any column type is incompatible with the corresponding D variable. 660 661 Params: args = A tuple of D variables to receive the results. 662 Returns: true if there was a (possibly empty) result set. 663 +/ 664 deprecated("Use the free-standing function .queryRowTuple instead") 665 void execSQLTuple(T...)(ref T args) 666 { 667 .queryRowTuple(_con, _sql, args); 668 } 669 670 /++ 671 Execute a prepared command. 672 673 Use this method when you will use the same SQL command repeatedly. 674 It can be used with commands that don't produce a result set, or those that 675 do. If there is a result set its existence will be indicated by the return value. 676 677 Any result set can be accessed vis Connection.getNextRow(), but you should really be 678 using execPreparedResult() or execPreparedSequence() for such queries. 679 680 Params: ra = An out parameter to receive the number of rows affected. 681 Returns: true if there was a (possibly empty) result set. 682 +/ 683 deprecated("Use Prepared.exec instead") 684 bool execPrepared(out ulong ra) 685 { 686 enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared."); 687 return _prepared.execQueryImpl2(ra); 688 } 689 690 /++ 691 Execute a prepared SQL command for the case where you expect a result set, 692 and want it all at once. 693 694 Use this method when you will use the same command repeatedly. 695 This method will throw if the SQL command does not produce a result set. 696 697 If there are long data items among the expected result columns you can specify 698 that they are to be subject to chunked transfer via a delegate. 699 700 Params: csa = An optional array of ColumnSpecialization structs. 701 Returns: A (possibly empty) ResultSet. 702 +/ 703 deprecated("Use Prepared.querySet instead") 704 ResultSet execPreparedResult(ColumnSpecialization[] csa = null) 705 { 706 enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared."); 707 return _prepared.querySet(csa); 708 } 709 710 /++ 711 Execute a prepared SQL command for the case where you expect a result set, 712 and want to deal with it one row at a time. 713 714 Use this method when you will use the same command repeatedly. 715 This method will throw if the SQL command does not produce a result set. 716 717 If there are long data items among the expected result columns you can 718 specify that they are to be subject to chunked transfer via a delegate. 719 720 Params: csa = An optional array of ColumnSpecialization structs. 721 Returns: A (possibly empty) ResultRange. 722 +/ 723 deprecated("Use Prepared.query instead") 724 ResultRange execPreparedSequence(ColumnSpecialization[] csa = null) 725 { 726 enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared."); 727 return _prepared.query(csa); 728 } 729 730 /++ 731 Execute a prepared SQL command to place result values into a set of D variables. 732 733 Use this method when you will use the same command repeatedly. 734 It will throw if the specified command does not produce a result set, or 735 if any column type is incompatible with the corresponding D variable 736 737 Params: args = A tuple of D variables to receive the results. 738 Returns: true if there was a (possibly empty) result set. 739 +/ 740 deprecated("Use Prepared.queryRowTuple instead") 741 void execPreparedTuple(T...)(ref T args) 742 { 743 enforceEx!MYX(_prepared.isPrepared, "The statement must be prepared."); 744 _prepared.queryRowTuple(args); 745 } 746 747 /++ 748 Get the next Row of a pending result set. 749 750 This method can be used after either execSQL() or execPrepared() have returned true 751 to retrieve result set rows sequentially. 752 753 Similar functionality is available via execSQLSequence() and execPreparedSequence() in 754 which case the interface is presented as a forward range of Rows. 755 756 This method allows you to deal with very large result sets either a row at a time, 757 or by feeding the rows into some suitable container such as a linked list. 758 759 Returns: A Row object. 760 +/ 761 deprecated("Use Connection.getNextRow() instead.") 762 Row getNextRow() 763 { 764 return _con.getNextRow(); 765 } 766 767 /++ 768 Execute a stored function, with any required input variables, and store the 769 return value into a D variable. 770 771 For this method, no query string is to be provided. The required one is of 772 the form "select foo(?, ? ...)". The method generates it and the appropriate 773 bindings - in, and out. Chunked transfers are not supported in either 774 direction. If you need them, create the parameters separately, then use 775 execPreparedResult() to get a one-row, one-column result set. 776 777 If it is not possible to convert the column value to the type of target, 778 then execFunction will throw. If the result is NULL, that is indicated 779 by a false return value, and target is unchanged. 780 781 In the interest of performance, this method assumes that the user has the 782 equired information about the number and types of IN parameters and the 783 type of the output variable. In the same interest, if the method is called 784 repeatedly for the same stored function, prepare() is omitted after the first call. 785 786 WARNING: This function is not currently unittested. 787 788 Params: 789 T = The type of the variable to receive the return result. 790 U = type tuple of arguments 791 name = The name of the stored function. 792 target = the D variable to receive the stored function return result. 793 args = The list of D variables to act as IN arguments to the stored function. 794 795 +/ 796 deprecated("Use prepareFunction instead") 797 bool execFunction(T, U...)(string name, ref T target, U args) 798 { 799 bool repeatCall = name == _prevFunc; 800 enforceEx!MYX(repeatCall || !_prepared.isPrepared, "You must not prepare a statement before calling execFunction"); 801 802 if(!repeatCall) 803 { 804 _prepared = prepareFunction(_con, name, U.length); 805 _prevFunc = name; 806 } 807 808 _prepared.setArgs(args); 809 ulong ra; 810 enforceEx!MYX(_prepared.execQueryImpl2(ra), "The executed query did not produce a result set."); 811 Row rr = _con.getNextRow(); 812 /+enforceEx!MYX(rr._valid, "The result set was empty.");+/ 813 enforceEx!MYX(rr._values.length == 1, "Result was not a single column."); 814 enforceEx!MYX(typeid(target).toString() == rr._values[0].type.toString(), 815 "Target type and column type are not compatible."); 816 if (!rr.isNull(0)) 817 target = rr._values[0].get!(T); 818 // If there were more rows, flush them away 819 // Question: Should I check in purgeResult and throw if there were - it's very inefficient to 820 // allow sloppy SQL that does not ensure just one row! 821 _con.purgeResult(); 822 return !rr.isNull(0); 823 } 824 825 /++ 826 Execute a stored procedure, with any required input variables. 827 828 For this method, no query string is to be provided. The required one is 829 of the form "call proc(?, ? ...)". The method generates it and the 830 appropriate in bindings. Chunked transfers are not supported. If you 831 need them, create the parameters separately, then use execPrepared() or 832 execPreparedResult(). 833 834 In the interest of performance, this method assumes that the user has 835 the required information about the number and types of IN parameters. 836 In the same interest, if the method is called repeatedly for the same 837 stored function, prepare() and other redundant operations are omitted 838 after the first call. 839 840 OUT parameters are not currently supported. It should generally be 841 possible with MySQL to present them as a result set. 842 843 WARNING: This function is not currently unittested. 844 845 Params: 846 T = Type tuple 847 name = The name of the stored procedure. 848 args = Tuple of args 849 Returns: True if the SP created a result set. 850 +/ 851 deprecated("Use prepareProcedure instead") 852 bool execProcedure(T...)(string name, ref T args) 853 { 854 bool repeatCall = name == _prevFunc; 855 enforceEx!MYX(repeatCall || !_prepared.isPrepared, "You must not prepare a statement before calling execProcedure"); 856 857 if(!repeatCall) 858 { 859 _prepared = prepareProcedure(_con, name, T.length); 860 _prevFunc = name; 861 } 862 863 _prepared.setArgs(args); 864 ulong ra; 865 return _prepared.execQueryImpl2(ra); 866 } 867 868 /// After a command that inserted a row into a table with an auto-increment 869 /// ID column, this method allows you to retrieve the last insert ID. 870 deprecated("Use Connection.lastInsertID instead") 871 @property ulong lastInsertID() pure const nothrow { return _con.lastInsertID; } 872 873 /// Gets the number of parameters in this Command 874 deprecated("Use Prepared.numArgs instead") 875 @property ushort numParams() pure const nothrow 876 { 877 return _prepared.numArgs; 878 } 879 880 /// Gets whether rows are pending 881 deprecated("Use Connection.rowsPending instead") 882 @property bool rowsPending() pure const nothrow { return _con.rowsPending; } 883 884 /// Gets the result header's field descriptions. 885 deprecated("Use Connection.resultFieldDescriptions instead") 886 @property FieldDescription[] resultFieldDescriptions() pure { return _con.resultFieldDescriptions; } 887 888 /// Gets the prepared header's field descriptions. 889 deprecated("Use Prepared.preparedFieldDescriptions instead") 890 @property FieldDescription[] preparedFieldDescriptions() pure { return _prepared._psh.fieldDescriptions; } 891 892 /// Gets the prepared header's param descriptions. 893 deprecated("Use Prepared.preparedParamDescriptions instead") 894 @property ParamDescription[] preparedParamDescriptions() pure { return _prepared._psh.paramDescriptions; } 895 }