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