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