1 module mysql.protocol.prepared; 2 3 import std.algorithm; 4 import std.conv; 5 import std.datetime; 6 import std.digest.sha; 7 import std.exception; 8 import std.range; 9 import std.socket; 10 import std.stdio; 11 import std.string; 12 import std.traits; 13 import std.typecons; 14 import std.variant; 15 16 import mysql.common; 17 import mysql.connection; 18 import mysql.protocol.constants; 19 import mysql.protocol.extra_types; 20 import mysql.protocol.packets; 21 import mysql.protocol.packet_helpers; 22 23 /++ 24 Encapsulation of a prepared statement. 25 26 Commands that are expected to return a result set - queries - have distinctive methods 27 that are enforced. That is it will be an error to call such a method with an SQL command 28 that does not produce a result set. 29 +/ 30 struct Prepared 31 { 32 RefCounted!(PreparedImpl, RefCountedAutoInitialize.no) preparedImpl; 33 alias preparedImpl this; 34 35 @property bool isPrepared() pure const 36 { 37 return preparedImpl.refCountedStore.isInitialized && !preparedImpl.isReleased; 38 } 39 } 40 41 /++ 42 Submit an SQL command to the server to be compiled into a prepared statement. 43 44 The result of a successful outcome will be a statement handle - an ID - 45 for the prepared statement, a count of the parameters required for 46 excution of the statement, and a count of the columns that will be present 47 in any result set that the command generates. Thes values will be stored 48 in in the Command struct. 49 50 The server will then proceed to send prepared statement headers, 51 including parameter descriptions, and result set field descriptions, 52 followed by an EOF packet. 53 54 Throws: MySQLException if there are pending result set items, or if the 55 server has a problem. 56 +/ 57 Prepared prepare(Connection conn, string sql) 58 { 59 return Prepared( refCounted(PreparedImpl(conn, sql)) ); 60 } 61 62 /++ 63 Convenience function to create a prepared statement which calls a stored function. 64 65 Throws: MySQLException if there are pending result set items, or if the 66 server has a problem. 67 68 Params: 69 name = The name of the stored function. 70 numArgs = The number of arguments the stored procedure takes. 71 +/ 72 Prepared prepareFunction(Connection conn, string name, int numArgs) 73 { 74 auto sql = "select " ~ name ~ preparedPlaceholderArgs(numArgs); 75 return prepare(conn, sql); 76 } 77 78 /// 79 unittest 80 { 81 debug(MYSQL_INTEGRATION_TESTS) 82 { 83 import mysql.test.common; 84 mixin(scopedCn); 85 86 exec(cn, `DROP FUNCTION IF EXISTS hello`); 87 exec(cn, ` 88 CREATE FUNCTION hello (s CHAR(20)) 89 RETURNS CHAR(50) DETERMINISTIC 90 RETURN CONCAT('Hello ',s,'!') 91 `); 92 93 auto preparedHello = prepareFunction(cn, "hello", 1); 94 preparedHello.setArgs("World"); 95 ResultSet rs = preparedHello.querySet(); 96 assert(rs.length == 1); 97 assert(rs[0][0] == "Hello World!"); 98 } 99 } 100 101 /++ 102 Convenience function to create a prepared statement which calls a stored procedure. 103 104 OUT parameters are not currently supported. It should generally be 105 possible with MySQL to present them as a result set. 106 107 Throws: MySQLException if there are pending result set items, or if the 108 server has a problem. 109 110 Params: 111 name = The name of the stored procedure. 112 numArgs = The number of arguments the stored procedure takes. 113 114 +/ 115 Prepared prepareProcedure(Connection conn, string name, int numArgs) 116 { 117 auto sql = "call " ~ name ~ preparedPlaceholderArgs(numArgs); 118 return prepare(conn, sql); 119 } 120 121 /// 122 unittest 123 { 124 debug(MYSQL_INTEGRATION_TESTS) 125 { 126 import mysql.test.common; 127 mixin(scopedCn); 128 initBaseTestTables(cn); 129 130 exec(cn, `DROP PROCEDURE IF EXISTS insert2`); 131 exec(cn, ` 132 CREATE PROCEDURE insert2 (IN p1 INT, IN p2 CHAR(50)) 133 BEGIN 134 INSERT INTO basetest (intcol, stringcol) VALUES(p1, p2); 135 END 136 `); 137 138 auto preparedInsert2 = prepareProcedure(cn, "insert2", 2); 139 preparedInsert2.setArgs(2001, "inserted string 1"); 140 preparedInsert2.exec(); 141 142 ResultSet rs = querySet(cn, "SELECT stringcol FROM basetest WHERE intcol=2001"); 143 assert(rs.length == 1); 144 assert(rs[0][0] == "inserted string 1"); 145 } 146 } 147 148 private string preparedPlaceholderArgs(int numArgs) 149 { 150 auto sql = "("; 151 bool comma = false; 152 foreach(i; 0..numArgs) 153 { 154 if (comma) 155 sql ~= ",?"; 156 else 157 { 158 sql ~= "?"; 159 comma = true; 160 } 161 } 162 sql ~= ")"; 163 164 return sql; 165 } 166 167 debug(MYSQL_INTEGRATION_TESTS) 168 unittest 169 { 170 assert(preparedPlaceholderArgs(3) == "(?,?,?)"); 171 assert(preparedPlaceholderArgs(2) == "(?,?)"); 172 assert(preparedPlaceholderArgs(1) == "(?)"); 173 assert(preparedPlaceholderArgs(0) == "()"); 174 } 175 176 /++ 177 Encapsulation of a prepared statement. 178 179 Commands that are expected to return a result set - queries - have distinctive methods 180 that are enforced. That is it will be an error to call such a method with an SQL command 181 that does not produce a result set. 182 +/ 183 struct PreparedImpl 184 { 185 private: 186 Connection _conn; 187 188 void enforceNotReleased() 189 { 190 enforceNotReleased(_hStmt); 191 } 192 193 static void enforceNotReleased(uint hStmt) 194 { 195 enforceEx!MYXNotPrepared(hStmt); 196 } 197 198 void enforceReadyForCommand() 199 { 200 enforceReadyForCommand(_conn, _hStmt); 201 } 202 203 static void enforceReadyForCommand(Connection conn, uint hStmt) 204 { 205 enforceNotReleased(hStmt); 206 conn.enforceNothingPending(); 207 } 208 209 debug(MYSQL_INTEGRATION_TESTS) 210 unittest 211 { 212 import mysql.protocol.prepared; 213 import mysql.test.common : scopedCn; 214 mixin(scopedCn); 215 216 cn.exec("DROP TABLE IF EXISTS `enforceNotReleased`"); 217 cn.exec("CREATE TABLE `enforceNotReleased` ( 218 `val` INTEGER 219 ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); 220 221 immutable insertSQL = "INSERT INTO `enforceNotReleased` VALUES (1), (2)"; 222 immutable selectSQL = "SELECT * FROM `enforceNotReleased`"; 223 Prepared preparedInsert; 224 Prepared preparedSelect; 225 int queryTupleResult; 226 assertNotThrown!MYXNotPrepared(preparedInsert = cn.prepare(insertSQL)); 227 assertNotThrown!MYXNotPrepared(preparedSelect = cn.prepare(selectSQL)); 228 assertNotThrown!MYXNotPrepared(preparedInsert.exec()); 229 assertNotThrown!MYXNotPrepared(preparedSelect.querySet()); 230 assertNotThrown!MYXNotPrepared(preparedSelect.query().each()); 231 assertNotThrown!MYXNotPrepared(preparedSelect.queryRowTuple(queryTupleResult)); 232 233 preparedInsert.release(); 234 assertThrown!MYXNotPrepared(preparedInsert.exec()); 235 assertNotThrown!MYXNotPrepared(preparedSelect.querySet()); 236 assertNotThrown!MYXNotPrepared(preparedSelect.query().each()); 237 assertNotThrown!MYXNotPrepared(preparedSelect.queryRowTuple(queryTupleResult)); 238 239 preparedSelect.release(); 240 assertThrown!MYXNotPrepared(preparedInsert.exec()); 241 assertThrown!MYXNotPrepared(preparedSelect.querySet()); 242 assertThrown!MYXNotPrepared(preparedSelect.query().each()); 243 assertThrown!MYXNotPrepared(preparedSelect.queryRowTuple(queryTupleResult)); 244 } 245 246 @disable this(this); // Not copyable 247 248 /++ 249 Submit an SQL command to the server to be compiled into a prepared statement. 250 251 The result of a successful outcome will be a statement handle - an ID - 252 for the prepared statement, a count of the parameters required for 253 excution of the statement, and a count of the columns that will be present 254 in any result set that the command generates. Thes values will be stored 255 in in the Command struct. 256 257 The server will then proceed to send prepared statement headers, 258 including parameter descriptions, and result set field descriptions, 259 followed by an EOF packet. 260 261 If there is an existing statement handle in the Command struct, that 262 prepared statement is released. 263 264 Throws: MySQLException if there are pending result set items, or if the 265 server has a problem. 266 +/ 267 public this(Connection conn, string sql) 268 { 269 this._conn = conn; 270 271 _conn.enforceNothingPending(); 272 273 scope(failure) conn.kill(); 274 275 conn.sendCmd(CommandType.STMT_PREPARE, sql); 276 conn._fieldCount = 0; 277 278 ubyte[] packet = conn.getPacket(); 279 if (packet.front == ResultPacketMarker.ok) 280 { 281 packet.popFront(); 282 _hStmt = packet.consume!int(); 283 conn._fieldCount = packet.consume!short(); 284 _psParams = packet.consume!short(); 285 286 _inParams.length = _psParams; 287 _psa.length = _psParams; 288 289 packet.popFront(); // one byte filler 290 _psWarnings = packet.consume!short(); 291 292 // At this point the server also sends field specs for parameters 293 // and columns if there were any of each 294 _psh = PreparedStmtHeaders(conn, conn._fieldCount, _psParams); 295 } 296 else if(packet.front == ResultPacketMarker.error) 297 { 298 auto error = OKErrorPacket(packet); 299 enforcePacketOK(error); 300 assert(0); // FIXME: what now? 301 } 302 else 303 assert(0); // FIXME: what now? 304 } 305 306 package: 307 uint _hStmt; // Server's identifier for this prepared statement. This is 0 when released. 308 ushort _psParams, _psWarnings; 309 PreparedStmtHeaders _psh; 310 Variant[] _inParams; 311 ParameterSpecialization[] _psa; 312 313 static ubyte[] makeBitmap(in Variant[] inParams) 314 { 315 size_t bml = (inParams.length+7)/8; 316 ubyte[] bma; 317 bma.length = bml; 318 foreach (i; 0..inParams.length) 319 { 320 if(inParams[i].type != typeid(typeof(null))) 321 continue; 322 size_t bn = i/8; 323 size_t bb = i%8; 324 ubyte sr = 1; 325 sr <<= bb; 326 bma[bn] |= sr; 327 } 328 return bma; 329 } 330 331 static ubyte[] makePSPrefix(uint hStmt, ubyte flags = 0) pure nothrow 332 { 333 ubyte[] prefix; 334 prefix.length = 14; 335 336 prefix[4] = CommandType.STMT_EXECUTE; 337 hStmt.packInto(prefix[5..9]); 338 prefix[9] = flags; // flags, no cursor 339 prefix[10] = 1; // iteration count - currently always 1 340 prefix[11] = 0; 341 prefix[12] = 0; 342 prefix[13] = 0; 343 344 return prefix; 345 } 346 347 static ubyte[] analyseParams(Variant[] inParams, ParameterSpecialization[] psa, 348 out ubyte[] vals, out bool longData) 349 { 350 size_t pc = inParams.length; 351 ubyte[] types; 352 types.length = pc*2; 353 size_t alloc = pc*20; 354 vals.length = alloc; 355 uint vcl = 0, len; 356 int ct = 0; 357 358 void reAlloc(size_t n) 359 { 360 if (vcl+n < alloc) 361 return; 362 size_t inc = (alloc*3)/2; 363 if (inc < n) 364 inc = n; 365 alloc += inc; 366 vals.length = alloc; 367 } 368 369 foreach (size_t i; 0..pc) 370 { 371 enum UNSIGNED = 0x80; 372 enum SIGNED = 0; 373 if (psa[i].chunkSize) 374 longData= true; 375 if (inParams[i].type == typeid(typeof(null))) 376 { 377 types[ct++] = SQLType.NULL; 378 types[ct++] = SIGNED; 379 continue; 380 } 381 Variant v = inParams[i]; 382 SQLType ext = psa[i].type; 383 string ts = v.type.toString(); 384 bool isRef; 385 if (ts[$-1] == '*') 386 { 387 ts.length = ts.length-1; 388 isRef= true; 389 } 390 391 switch (ts) 392 { 393 case "bool": 394 if (ext == SQLType.INFER_FROM_D_TYPE) 395 types[ct++] = SQLType.BIT; 396 else 397 types[ct++] = cast(ubyte) ext; 398 types[ct++] = SIGNED; 399 reAlloc(2); 400 bool bv = isRef? *(v.get!(bool*)): v.get!(bool); 401 vals[vcl++] = 1; 402 vals[vcl++] = bv? 0x31: 0x30; 403 break; 404 case "byte": 405 types[ct++] = SQLType.TINY; 406 types[ct++] = SIGNED; 407 reAlloc(1); 408 vals[vcl++] = isRef? *(v.get!(byte*)): v.get!(byte); 409 break; 410 case "ubyte": 411 types[ct++] = SQLType.TINY; 412 types[ct++] = UNSIGNED; 413 reAlloc(1); 414 vals[vcl++] = isRef? *(v.get!(ubyte*)): v.get!(ubyte); 415 break; 416 case "short": 417 types[ct++] = SQLType.SHORT; 418 types[ct++] = SIGNED; 419 reAlloc(2); 420 short si = isRef? *(v.get!(short*)): v.get!(short); 421 vals[vcl++] = cast(ubyte) (si & 0xff); 422 vals[vcl++] = cast(ubyte) ((si >> 8) & 0xff); 423 break; 424 case "ushort": 425 types[ct++] = SQLType.SHORT; 426 types[ct++] = UNSIGNED; 427 reAlloc(2); 428 ushort us = isRef? *(v.get!(ushort*)): v.get!(ushort); 429 vals[vcl++] = cast(ubyte) (us & 0xff); 430 vals[vcl++] = cast(ubyte) ((us >> 8) & 0xff); 431 break; 432 case "int": 433 types[ct++] = SQLType.INT; 434 types[ct++] = SIGNED; 435 reAlloc(4); 436 int ii = isRef? *(v.get!(int*)): v.get!(int); 437 vals[vcl++] = cast(ubyte) (ii & 0xff); 438 vals[vcl++] = cast(ubyte) ((ii >> 8) & 0xff); 439 vals[vcl++] = cast(ubyte) ((ii >> 16) & 0xff); 440 vals[vcl++] = cast(ubyte) ((ii >> 24) & 0xff); 441 break; 442 case "uint": 443 types[ct++] = SQLType.INT; 444 types[ct++] = UNSIGNED; 445 reAlloc(4); 446 uint ui = isRef? *(v.get!(uint*)): v.get!(uint); 447 vals[vcl++] = cast(ubyte) (ui & 0xff); 448 vals[vcl++] = cast(ubyte) ((ui >> 8) & 0xff); 449 vals[vcl++] = cast(ubyte) ((ui >> 16) & 0xff); 450 vals[vcl++] = cast(ubyte) ((ui >> 24) & 0xff); 451 break; 452 case "long": 453 types[ct++] = SQLType.LONGLONG; 454 types[ct++] = SIGNED; 455 reAlloc(8); 456 long li = isRef? *(v.get!(long*)): v.get!(long); 457 vals[vcl++] = cast(ubyte) (li & 0xff); 458 vals[vcl++] = cast(ubyte) ((li >> 8) & 0xff); 459 vals[vcl++] = cast(ubyte) ((li >> 16) & 0xff); 460 vals[vcl++] = cast(ubyte) ((li >> 24) & 0xff); 461 vals[vcl++] = cast(ubyte) ((li >> 32) & 0xff); 462 vals[vcl++] = cast(ubyte) ((li >> 40) & 0xff); 463 vals[vcl++] = cast(ubyte) ((li >> 48) & 0xff); 464 vals[vcl++] = cast(ubyte) ((li >> 56) & 0xff); 465 break; 466 case "ulong": 467 types[ct++] = SQLType.LONGLONG; 468 types[ct++] = UNSIGNED; 469 reAlloc(8); 470 ulong ul = isRef? *(v.get!(ulong*)): v.get!(ulong); 471 vals[vcl++] = cast(ubyte) (ul & 0xff); 472 vals[vcl++] = cast(ubyte) ((ul >> 8) & 0xff); 473 vals[vcl++] = cast(ubyte) ((ul >> 16) & 0xff); 474 vals[vcl++] = cast(ubyte) ((ul >> 24) & 0xff); 475 vals[vcl++] = cast(ubyte) ((ul >> 32) & 0xff); 476 vals[vcl++] = cast(ubyte) ((ul >> 40) & 0xff); 477 vals[vcl++] = cast(ubyte) ((ul >> 48) & 0xff); 478 vals[vcl++] = cast(ubyte) ((ul >> 56) & 0xff); 479 break; 480 case "float": 481 types[ct++] = SQLType.FLOAT; 482 types[ct++] = SIGNED; 483 reAlloc(4); 484 float f = isRef? *(v.get!(float*)): v.get!(float); 485 ubyte* ubp = cast(ubyte*) &f; 486 vals[vcl++] = *ubp++; 487 vals[vcl++] = *ubp++; 488 vals[vcl++] = *ubp++; 489 vals[vcl++] = *ubp; 490 break; 491 case "double": 492 types[ct++] = SQLType.DOUBLE; 493 types[ct++] = SIGNED; 494 reAlloc(8); 495 double d = isRef? *(v.get!(double*)): v.get!(double); 496 ubyte* ubp = cast(ubyte*) &d; 497 vals[vcl++] = *ubp++; 498 vals[vcl++] = *ubp++; 499 vals[vcl++] = *ubp++; 500 vals[vcl++] = *ubp++; 501 vals[vcl++] = *ubp++; 502 vals[vcl++] = *ubp++; 503 vals[vcl++] = *ubp++; 504 vals[vcl++] = *ubp; 505 break; 506 case "std.datetime.Date": 507 types[ct++] = SQLType.DATE; 508 types[ct++] = SIGNED; 509 Date date = isRef? *(v.get!(Date*)): v.get!(Date); 510 ubyte[] da = pack(date); 511 size_t l = da.length; 512 reAlloc(l); 513 vals[vcl..vcl+l] = da[]; 514 vcl += l; 515 break; 516 case "std.datetime.Time": 517 types[ct++] = SQLType.TIME; 518 types[ct++] = SIGNED; 519 TimeOfDay time = isRef? *(v.get!(TimeOfDay*)): v.get!(TimeOfDay); 520 ubyte[] ta = pack(time); 521 size_t l = ta.length; 522 reAlloc(l); 523 vals[vcl..vcl+l] = ta[]; 524 vcl += l; 525 break; 526 case "std.datetime.DateTime": 527 types[ct++] = SQLType.DATETIME; 528 types[ct++] = SIGNED; 529 DateTime dt = isRef? *(v.get!(DateTime*)): v.get!(DateTime); 530 ubyte[] da = pack(dt); 531 size_t l = da.length; 532 reAlloc(l); 533 vals[vcl..vcl+l] = da[]; 534 vcl += l; 535 break; 536 case "connect.Timestamp": 537 types[ct++] = SQLType.TIMESTAMP; 538 types[ct++] = SIGNED; 539 Timestamp tms = isRef? *(v.get!(Timestamp*)): v.get!(Timestamp); 540 DateTime dt = mysql.protocol.packet_helpers.toDateTime(tms.rep); 541 ubyte[] da = pack(dt); 542 size_t l = da.length; 543 reAlloc(l); 544 vals[vcl..vcl+l] = da[]; 545 vcl += l; 546 break; 547 case "immutable(char)[]": 548 if (ext == SQLType.INFER_FROM_D_TYPE) 549 types[ct++] = SQLType.VARCHAR; 550 else 551 types[ct++] = cast(ubyte) ext; 552 types[ct++] = SIGNED; 553 string s = isRef? *(v.get!(string*)): v.get!(string); 554 ubyte[] packed = packLCS(cast(void[]) s); 555 reAlloc(packed.length); 556 vals[vcl..vcl+packed.length] = packed[]; 557 vcl += packed.length; 558 break; 559 case "char[]": 560 if (ext == SQLType.INFER_FROM_D_TYPE) 561 types[ct++] = SQLType.VARCHAR; 562 else 563 types[ct++] = cast(ubyte) ext; 564 types[ct++] = SIGNED; 565 char[] ca = isRef? *(v.get!(char[]*)): v.get!(char[]); 566 ubyte[] packed = packLCS(cast(void[]) ca); 567 reAlloc(packed.length); 568 vals[vcl..vcl+packed.length] = packed[]; 569 vcl += packed.length; 570 break; 571 case "byte[]": 572 if (ext == SQLType.INFER_FROM_D_TYPE) 573 types[ct++] = SQLType.TINYBLOB; 574 else 575 types[ct++] = cast(ubyte) ext; 576 types[ct++] = SIGNED; 577 byte[] ba = isRef? *(v.get!(byte[]*)): v.get!(byte[]); 578 ubyte[] packed = packLCS(cast(void[]) ba); 579 reAlloc(packed.length); 580 vals[vcl..vcl+packed.length] = packed[]; 581 vcl += packed.length; 582 break; 583 case "ubyte[]": 584 if (ext == SQLType.INFER_FROM_D_TYPE) 585 types[ct++] = SQLType.TINYBLOB; 586 else 587 types[ct++] = cast(ubyte) ext; 588 types[ct++] = SIGNED; 589 ubyte[] uba = isRef? *(v.get!(ubyte[]*)): v.get!(ubyte[]); 590 ubyte[] packed = packLCS(cast(void[]) uba); 591 reAlloc(packed.length); 592 vals[vcl..vcl+packed.length] = packed[]; 593 vcl += packed.length; 594 break; 595 case "void": 596 throw new MYX("Unbound parameter " ~ to!string(i), __FILE__, __LINE__); 597 default: 598 throw new MYX("Unsupported parameter type " ~ ts, __FILE__, __LINE__); 599 } 600 } 601 vals.length = vcl; 602 return types; 603 } 604 605 static void sendLongData(Connection conn, uint hStmt, ParameterSpecialization[] psa) 606 { 607 assert(psa.length <= ushort.max); // parameter number is sent as short 608 foreach (ushort i, PSN psn; psa) 609 { 610 if (!psn.chunkSize) continue; 611 uint cs = psn.chunkSize; 612 uint delegate(ubyte[]) dg = psn.chunkDelegate; 613 614 ubyte[] chunk; 615 chunk.length = cs+11; 616 chunk.setPacketHeader(0 /*each chunk is separate cmd*/); 617 chunk[4] = CommandType.STMT_SEND_LONG_DATA; 618 hStmt.packInto(chunk[5..9]); // statement handle 619 packInto(i, chunk[9..11]); // parameter number 620 621 // byte 11 on is payload 622 for (;;) 623 { 624 uint sent = dg(chunk[11..cs+11]); 625 if (sent < cs) 626 { 627 if (sent == 0) // data was exact multiple of chunk size - all sent 628 break; 629 sent += 7; // adjust for non-payload bytes 630 chunk.length = chunk.length - (cs-sent); // trim the chunk 631 packInto!(uint, true)(cast(uint)sent, chunk[0..3]); 632 conn.send(chunk); 633 break; 634 } 635 conn.send(chunk); 636 } 637 } 638 } 639 640 static void sendCommand(Connection conn, uint hStmt, PreparedStmtHeaders psh, 641 Variant[] inParams, ParameterSpecialization[] psa) 642 { 643 ubyte[] packet; 644 conn.resetPacket(); 645 646 ubyte[] prefix = makePSPrefix(hStmt, 0); 647 size_t len = prefix.length; 648 bool longData; 649 650 if (psh._paramCount) 651 { 652 ubyte[] one = [ 1 ]; 653 ubyte[] vals; 654 ubyte[] types = analyseParams(inParams, psa, vals, longData); 655 ubyte[] nbm = makeBitmap(inParams); 656 packet = prefix ~ nbm ~ one ~ types ~ vals; 657 } 658 else 659 packet = prefix; 660 661 if (longData) 662 sendLongData(conn, hStmt, psa); 663 664 assert(packet.length <= uint.max); 665 packet.setPacketHeader(conn.pktNumber); 666 conn.bumpPacket(); 667 conn.send(packet); 668 } 669 670 //TODO: This awkward func is only needed by the deprecated Command struct. 671 // Remove this once Command struct is finally deleted. 672 bool execQueryImpl2(out ulong ra) 673 { 674 return execQueryImpl(_conn, 675 ExecQueryImplInfo(true, null, _hStmt, _psh, _inParams, _psa), ra); 676 } 677 678 /// Has this statement been released? 679 @property bool isReleased() pure const nothrow 680 { 681 return _hStmt == 0; 682 } 683 684 public: 685 ~this() 686 { 687 release(); 688 } 689 690 /++ 691 Execute a prepared command. 692 693 Use this method when you will use the same SQL command repeatedly. 694 It can be used with commands that don't produce a result set, or those that 695 do. If there is a result set its existence will be indicated by the return value. 696 697 Any result set can be accessed vis Connection.getNextRow(), but you should really be 698 using execPreparedResult() or execPreparedSequence() for such queries. 699 700 Returns: The number of rows affected. 701 +/ 702 ulong exec() 703 { 704 enforceReadyForCommand(); 705 return execImpl( 706 _conn, 707 ExecQueryImplInfo(true, null, _hStmt, _psh, _inParams, _psa) 708 ); 709 } 710 711 /++ 712 Execute a prepared SQL command for the case where you expect a result set, 713 and want it all at once. 714 715 Use this method when you will use the same command repeatedly. 716 This method will throw if the SQL command does not produce a result set. 717 718 If there are long data items among the expected result columns you can specify 719 that they are to be subject to chunked transfer via a delegate. 720 721 Params: csa = An optional array of ColumnSpecialization structs. 722 Returns: A (possibly empty) ResultSet. 723 +/ 724 ResultSet querySet(ColumnSpecialization[] csa = null) 725 { 726 enforceReadyForCommand(); 727 return querySetImpl( 728 csa, true, _conn, 729 ExecQueryImplInfo(true, null, _hStmt, _psh, _inParams, _psa) 730 ); 731 } 732 733 ///ditto 734 deprecated("Use querySet instead.") 735 alias queryResult = querySet; 736 737 /++ 738 Execute a prepared SQL command for the case where you expect a result set, 739 and want to deal with it one row at a time. 740 741 Use this method when you will use the same command repeatedly. 742 This method will throw if the SQL command does not produce a result set. 743 744 If there are long data items among the expected result columns you can 745 specify that they are to be subject to chunked transfer via a delegate. 746 747 Params: csa = An optional array of ColumnSpecialization structs. 748 Returns: A (possibly empty) ResultRange. 749 +/ 750 ResultRange query(ColumnSpecialization[] csa = null) 751 { 752 enforceReadyForCommand(); 753 return queryImpl( 754 csa, _conn, 755 ExecQueryImplInfo(true, null, _hStmt, _psh, _inParams, _psa) 756 ); 757 } 758 759 ///ditto 760 deprecated("Use query instead.") 761 alias querySequence = query; 762 763 /++ 764 Executes a one-off SQL command and returns the first row received, or null 765 if none. Useful for the case where you expect a (possibly empty) result set, 766 and you're either only expecting one row, or only care about the first row. 767 768 Use this method when you will use the same command repeatedly. 769 It will throw if the specified command does not produce a result set. 770 771 If there are long data items among the expected result columns you can specify 772 that they are to be subject to chunked transfer via a delegate. 773 774 Params: csa = An optional array of ColumnSpecialization structs. 775 Returns: Nullable!Row: This will be null (check via Nullable.isNull) if the 776 query resulted in an empty result set. 777 +/ 778 Nullable!Row queryRow(ColumnSpecialization[] csa = null) 779 { 780 enforceReadyForCommand(); 781 return queryRowImpl(csa, _conn, 782 ExecQueryImplInfo(true, null, _hStmt, _psh, _inParams, _psa)); 783 } 784 785 /++ 786 Execute a prepared SQL command to place result values into a set of D variables. 787 788 Use this method when you will use the same command repeatedly. 789 It will throw if the specified command does not produce a result set, or 790 if any column type is incompatible with the corresponding D variable 791 792 Params: args = A tuple of D variables to receive the results. 793 +/ 794 void queryRowTuple(T...)(ref T args) 795 { 796 enforceReadyForCommand(); 797 return queryRowTupleImpl( 798 _conn, 799 ExecQueryImplInfo(true, null, _hStmt, _psh, _inParams, _psa), 800 args 801 ); 802 } 803 804 ///ditto 805 deprecated("Use queryRowTuple instead.") 806 alias queryTuple = queryRowTuple; 807 808 /++ 809 Executes a one-off SQL command and returns a single value: The the first column 810 of the first row received. Useful for the case where you expect a 811 (possibly empty) result set, and you're either only expecting one value, or 812 only care about the first value. 813 814 If the query did not produce any rows, OR the rows it produced have zero columns, 815 this will return `Nullable!Variant()`, ie, null. Test for this with `result.isNull`. 816 817 If the query DID produce a result, but the value actually received is NULL, 818 then `result.isNull` will be FALSE, and `result.get` will produce a Variant 819 which CONTAINS null. Check for this with `result.get.type == typeid(typeof(null))`. 820 821 Use this method when you will use the same command repeatedly. 822 It will throw if the specified command does not produce a result set. 823 824 If there are long data items among the expected result columns you can specify 825 that they are to be subject to chunked transfer via a delegate. 826 827 Params: csa = An optional array of ColumnSpecialization structs. 828 Returns: Nullable!Variant: This will be null (check via Nullable.isNull) if the 829 query resulted in an empty result set. 830 +/ 831 Nullable!Variant queryValue(ColumnSpecialization[] csa = null) 832 { 833 return queryValueImpl(csa, _conn, 834 ExecQueryImplInfo(true, null, _hStmt, _psh, _inParams, _psa)); 835 } 836 837 /++ 838 Prepared statement parameter setter. 839 840 The value may, but doesn't have to be, wrapped in a Variant. If so, 841 null is handled correctly. 842 843 The value may, but doesn't have to be, a pointer to the desired value. 844 845 The value may, but doesn't have to be, wrapped in a Nullable!T. If so, 846 null is handled correctly. 847 848 The value can be null. 849 850 Params: index = The zero based index 851 +/ 852 void setArg(T)(size_t index, T val, ParameterSpecialization psn = PSN(0, SQLType.INFER_FROM_D_TYPE, 0, null)) 853 if(!isInstanceOf!(Nullable, T)) 854 { 855 // Now in theory we should be able to check the parameter type here, since the 856 // protocol is supposed to send us type information for the parameters, but this 857 // capability seems to be broken. This assertion is supported by the fact that 858 // the same information is not available via the MySQL C API either. It is up 859 // to the programmer to ensure that appropriate type information is embodied 860 // in the variant array, or provided explicitly. This sucks, but short of 861 // having a client side SQL parser I don't see what can be done. 862 863 enforceNotReleased(); 864 enforceEx!MYX(index < _psParams, "Parameter index out of range."); 865 866 _inParams[index] = val; 867 psn.pIndex = index; 868 _psa[index] = psn; 869 } 870 871 void setArg(T)(size_t index, Nullable!T val, ParameterSpecialization psn = PSN(0, SQLType.INFER_FROM_D_TYPE, 0, null)) 872 { 873 enforceNotReleased(); 874 if(val.isNull) 875 setArg(index, null, psn); 876 else 877 setArg(index, val.get(), psn); 878 } 879 880 /++ 881 Bind a tuple of D variables to the parameters of a prepared statement. 882 883 You can use this method to bind a set of variables if you don't need any specialization, 884 that is chunked transfer is not neccessary. 885 886 The tuple must match the required number of parameters, and it is the programmer's 887 responsibility to ensure that they are of appropriate types. 888 +/ 889 void setArgs(T...)(T args) 890 if(T.length == 0 || !is(T[0] == Variant[])) 891 { 892 enforceNotReleased(); 893 enforceEx!MYX(args.length == _psParams, "Argument list supplied does not match the number of parameters."); 894 895 foreach (size_t i, arg; args) 896 setArg(i, arg); 897 } 898 899 /++ 900 Bind a Variant[] as the parameters of a prepared statement. 901 902 You can use this method to bind a set of variables in Variant form to 903 the parameters of a prepared statement. 904 905 Parameter specializations can be added if required. This method could be 906 used to add records from a data entry form along the lines of 907 ------------ 908 auto c = Command(con, "insert into table42 values(?, ?, ?)"); 909 c.prepare(); 910 Variant[] va; 911 va.length = 3; 912 DataRecord dr; // Some data input facility 913 ulong ra; 914 do 915 { 916 dr.get(); 917 va[0] = dr("Name"); 918 va[1] = dr("City"); 919 va[2] = dr("Whatever"); 920 c.bindParameters(va); 921 c.execPrepared(ra); 922 } while(tod < "17:30"); 923 ------------ 924 Params: va = External list of Variants to be used as parameters 925 psnList = any required specializations 926 +/ 927 void setArgs(Variant[] va, ParameterSpecialization[] psnList= null) 928 { 929 enforceNotReleased(); 930 enforceEx!MYX(va.length == _psParams, "Param count supplied does not match prepared statement"); 931 _inParams[] = va[]; 932 if (psnList !is null) 933 { 934 foreach (PSN psn; psnList) 935 _psa[psn.pIndex] = psn; 936 } 937 } 938 939 /++ 940 Prepared statement parameter getter. 941 942 Params: index = The zero based index 943 +/ 944 Variant getArg(size_t index) 945 { 946 enforceNotReleased(); 947 enforceEx!MYX(index < _psParams, "Parameter index out of range."); 948 return _inParams[index]; 949 } 950 951 /++ 952 Sets a prepared statement parameter to NULL. 953 954 This is here mainly for legacy reasons. You can set a field to null 955 simply by saying `prepared.setArg(index, null);` 956 957 Params: index = The zero based index 958 +/ 959 void setNullArg(size_t index) 960 { 961 enforceNotReleased(); 962 setArg(index, null); 963 } 964 965 debug(MYSQL_INTEGRATION_TESTS) 966 unittest 967 { 968 import mysql.protocol.prepared; 969 import mysql.test.common : scopedCn; 970 mixin(scopedCn); 971 972 cn.exec("DROP TABLE IF EXISTS `setNullArg`"); 973 cn.exec("CREATE TABLE `setNullArg` ( 974 `val` INTEGER 975 ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); 976 977 immutable insertSQL = "INSERT INTO `setNullArg` VALUES (?)"; 978 immutable selectSQL = "SELECT * FROM `setNullArg`"; 979 auto preparedInsert = cn.prepare(insertSQL); 980 ResultSet rs; 981 982 { 983 Nullable!int nullableInt; 984 nullableInt.nullify(); 985 preparedInsert.setArg(0, nullableInt); 986 assert(preparedInsert.getArg(0).type == typeid(typeof(null))); 987 nullableInt = 7; 988 preparedInsert.setArg(0, nullableInt); 989 assert(preparedInsert.getArg(0) == 7); 990 991 nullableInt.nullify(); 992 preparedInsert.setArgs(nullableInt); 993 assert(preparedInsert.getArg(0).type == typeid(typeof(null))); 994 nullableInt = 7; 995 preparedInsert.setArgs(nullableInt); 996 assert(preparedInsert.getArg(0) == 7); 997 } 998 999 preparedInsert.setArg(0, 5); 1000 preparedInsert.exec(); 1001 rs = cn.querySet(selectSQL); 1002 assert(rs.length == 1); 1003 assert(rs[0][0] == 5); 1004 1005 preparedInsert.setArg(0, null); 1006 preparedInsert.exec(); 1007 rs = cn.querySet(selectSQL); 1008 assert(rs.length == 2); 1009 assert(rs[0][0] == 5); 1010 assert(rs[1].isNull(0)); 1011 assert(rs[1][0].type == typeid(typeof(null))); 1012 1013 preparedInsert.setArg(0, Variant(null)); 1014 preparedInsert.exec(); 1015 rs = cn.querySet(selectSQL); 1016 assert(rs.length == 3); 1017 assert(rs[0][0] == 5); 1018 assert(rs[1].isNull(0)); 1019 assert(rs[2].isNull(0)); 1020 assert(rs[1][0].type == typeid(typeof(null))); 1021 assert(rs[2][0].type == typeid(typeof(null))); 1022 } 1023 1024 /++ 1025 Release a prepared statement. 1026 1027 This method tells the server that it can dispose of the information it 1028 holds about the current prepared statement. 1029 +/ 1030 void release() 1031 { 1032 if(!_hStmt) 1033 return; 1034 1035 scope(failure) _conn.kill(); 1036 1037 ubyte[] packet; 1038 packet.length = 9; 1039 packet.setPacketHeader(0/*packet number*/); 1040 _conn.bumpPacket(); 1041 packet[4] = CommandType.STMT_CLOSE; 1042 _hStmt.packInto(packet[5..9]); 1043 _conn.purgeResult(); 1044 _conn.send(packet); 1045 // It seems that the server does not find it necessary to send a response 1046 // for this command. 1047 _hStmt = 0; 1048 } 1049 1050 /// Gets the number of arguments this prepared statement expects to be passed in. 1051 @property ushort numArgs() pure const nothrow 1052 { 1053 return _psParams; 1054 } 1055 1056 /// Gets the prepared header's field descriptions. 1057 @property FieldDescription[] preparedFieldDescriptions() pure { return _psh.fieldDescriptions; } 1058 1059 /// Gets the prepared header's param descriptions. 1060 @property ParamDescription[] preparedParamDescriptions() pure { return _psh.paramDescriptions; } 1061 }