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