1 /++ 2 Implementation - Connection class. 3 4 WARNING: 5 This module is used to consolidate the common implementation of the safe and 6 unafe API. DO NOT directly import this module, please import one of 7 `mysql.connection`, `mysql.safe.connection`, or `mysql.unsafe.connection`. This 8 module will be removed in a future version without deprecation. 9 10 $(SAFE_MIGRATION) 11 +/ 12 module mysql.impl.connection; 13 14 import std.algorithm; 15 import std.conv; 16 import std.exception; 17 import std.range; 18 import std.socket; 19 import std.string; 20 import std.typecons; 21 22 import mysql.exceptions; 23 import mysql.logger; 24 import mysql.protocol.comms; 25 import mysql.protocol.constants; 26 import mysql.protocol.packets; 27 import mysql.protocol.sockets; 28 import mysql.impl.result; 29 import mysql.impl.prepared; 30 import mysql.types; 31 32 @safe: 33 34 version(Have_vibe_core) 35 { 36 static if(__traits(compiles, (){ import vibe.core.net; } )) 37 import vibe.core.net; 38 else 39 static assert(false, "mysql-native can't find Vibe.d's 'vibe.core.net'."); 40 } 41 42 /// The default `mysql.protocol.constants.SvrCapFlags` used when creating a connection. 43 immutable SvrCapFlags defaultClientFlags = 44 SvrCapFlags.OLD_LONG_PASSWORD | SvrCapFlags.ALL_COLUMN_FLAGS | 45 SvrCapFlags.WITH_DB | SvrCapFlags.PROTOCOL41 | 46 SvrCapFlags.SECURE_CONNECTION;// | SvrCapFlags.MULTI_STATEMENTS | 47 //SvrCapFlags.MULTI_RESULTS; 48 49 package(mysql) string preparedPlaceholderArgs(int numArgs) 50 { 51 auto sql = "("; 52 bool comma = false; 53 foreach(i; 0..numArgs) 54 { 55 if (comma) 56 sql ~= ",?"; 57 else 58 { 59 sql ~= "?"; 60 comma = true; 61 } 62 } 63 sql ~= ")"; 64 65 return sql; 66 } 67 68 @("preparedPlaceholderArgs") 69 debug(MYSQLN_TESTS) 70 unittest 71 { 72 assert(preparedPlaceholderArgs(3) == "(?,?,?)"); 73 assert(preparedPlaceholderArgs(2) == "(?,?)"); 74 assert(preparedPlaceholderArgs(1) == "(?)"); 75 assert(preparedPlaceholderArgs(0) == "()"); 76 } 77 78 /// Per-connection info from the server about a registered prepared statement. 79 package(mysql) struct PreparedServerInfo 80 { 81 /// Server's identifier for this prepared statement. 82 /// Apperently, this is never 0 if it's been registered, 83 /// although mysql-native no longer relies on that. 84 uint statementId; 85 86 ushort psWarnings; 87 88 /// Number of parameters this statement takes. 89 /// 90 /// This will be the same on all connections, but it's returned 91 /// by the server upon registration, so it's stored here. 92 ushort numParams; 93 94 /// Prepared statement headers 95 /// 96 /// This will be the same on all connections, but it's returned 97 /// by the server upon registration, so it's stored here. 98 PreparedStmtHeaders headers; 99 100 /// Not actually from the server. Connection uses this to keep track 101 /// of statements that should be treated as having been released. 102 bool queuedForRelease = false; 103 } 104 105 /++ 106 A class representing a database connection. 107 108 If you are using Vibe.d, consider using `mysql.pool.MySQLPool` instead of 109 creating a new Connection directly. That will provide certain benefits, 110 such as reusing old connections and automatic cleanup (no need to close 111 the connection when done). 112 113 ------------------ 114 // Suggested usage: 115 116 { 117 auto con = new Connection("host=localhost;port=3306;user=joe;pwd=pass123;db=myappsdb"); 118 scope(exit) con.close(); 119 120 // Use the connection 121 ... 122 } 123 ------------------ 124 +/ 125 //TODO: All low-level commms should be moved into the mysql.protocol package. 126 class Connection 127 { 128 @safe: 129 /+ 130 The Connection is responsible for handshaking with the server to establish 131 authentication. It then passes client preferences to the server, and 132 subsequently is the channel for all command packets that are sent, and all 133 response packets received. 134 135 Uncompressed packets consist of a 4 byte header - 3 bytes of length, and one 136 byte as a packet number. Connection deals with the headers and ensures that 137 packet numbers are sequential. 138 139 The initial packet is sent by the server - essentially a 'hello' packet 140 inviting login. That packet has a sequence number of zero. That sequence 141 number is the incremented by client and server packets through the handshake 142 sequence. 143 144 After login all further sequences are initialized by the client sending a 145 command packet with a zero sequence number, to which the server replies with 146 zero or more packets with sequential sequence numbers. 147 +/ 148 package(mysql): 149 enum OpenState 150 { 151 /// We have not yet connected to the server, or have sent QUIT to the 152 /// server and closed the connection 153 notConnected, 154 /// We have connected to the server and parsed the greeting, but not 155 /// yet authenticated 156 connected, 157 /// We have successfully authenticated against the server, and need to 158 /// send QUIT to the server when closing the connection 159 authenticated 160 } 161 OpenState _open; 162 MySQLSocket _socket; 163 164 SvrCapFlags _sCaps, _cCaps; 165 uint _sThread; 166 ushort _serverStatus; 167 ubyte _sCharSet, _protocol; 168 string _serverVersion; 169 170 string _host, _user, _pwd, _db; 171 ushort _port; 172 173 MySQLSocketType _socketType; 174 175 OpenSocketCallbackPhobos _openSocketPhobos; 176 OpenSocketCallbackVibeD _openSocketVibeD; 177 178 ulong _insertID; 179 180 // This gets incremented every time a command is issued or results are purged, 181 // so a ResultRange can tell whether it's been invalidated. 182 ulong _lastCommandID; 183 184 // Whether there are rows, headers or bimary data waiting to be retreived. 185 // MySQL protocol doesn't permit performing any other action until all 186 // such data is read. 187 bool _rowsPending, _headersPending, _binaryPending; 188 189 // Field count of last performed command. 190 //TODO: Does Connection need to store this? 191 ushort _fieldCount; 192 193 // ResultSetHeaders of last performed command. 194 //TODO: Does Connection need to store this? Is this even used? 195 ResultSetHeaders _rsh; 196 197 // This tiny thing here is pretty critical. Pay great attention to it's maintenance, otherwise 198 // you'll get the dreaded "packet out of order" message. It, and the socket connection are 199 // the reason why most other objects require a connection object for their construction. 200 ubyte _cpn; /// Packet Number in packet header. Serial number to ensure correct 201 /// ordering. First packet should have 0 202 @property ubyte pktNumber() { return _cpn; } 203 void bumpPacket() { _cpn++; } 204 void resetPacket() { _cpn = 0; } 205 206 version(Have_vibe_core) {} else 207 pure const nothrow invariant() 208 { 209 assert(_socketType != MySQLSocketType.vibed); 210 } 211 212 static PlainPhobosSocket defaultOpenSocketPhobos(string host, ushort port) 213 { 214 logDebug("opening phobos socket %s:%d", host, port); 215 auto s = new PlainPhobosSocket(); 216 s.connect(new InternetAddress(host, port)); 217 s.setOption(SocketOptionLevel.TCP, SocketOption.TCP_NODELAY, true); 218 s.setOption(SocketOptionLevel.SOCKET, SocketOption.KEEPALIVE, true); 219 return s; 220 } 221 222 static PlainVibeDSocket defaultOpenSocketVibeD(string host, ushort port) 223 { 224 version(Have_vibe_core) 225 { 226 logDebug("opening vibe-d socket %s:%d", host, port); 227 auto s = vibe.core.net.connectTCP(host, port); 228 s.tcpNoDelay = true; 229 s.keepAlive = true; 230 return s; 231 } 232 else 233 assert(0); 234 } 235 236 void initConnection() 237 { 238 kill(); // Ensure internal state gets reset 239 240 resetPacket(); 241 final switch(_socketType) 242 { 243 case MySQLSocketType.phobos: 244 _socket = new MySQLSocketPhobos(_openSocketPhobos(_host, _port)); 245 break; 246 247 case MySQLSocketType.vibed: 248 version(Have_vibe_core) { 249 _socket = new MySQLSocketVibeD(_openSocketVibeD(_host, _port)); 250 break; 251 } else assert(0, "Unsupported socket type. Need version Have_vibe_core."); 252 } 253 } 254 255 SvrCapFlags _clientCapabilities; 256 257 void connect(SvrCapFlags clientCapabilities) 258 out 259 { 260 assert(_open == OpenState.authenticated); 261 } 262 do 263 { 264 initConnection(); 265 auto greeting = this.parseGreeting(); 266 _open = OpenState.connected; 267 268 _clientCapabilities = clientCapabilities; 269 _cCaps = setClientFlags(_sCaps, clientCapabilities); 270 this.authenticate(greeting); 271 } 272 273 /++ 274 Forcefully close the socket without sending the quit command. 275 276 Also resets internal state regardless of whether the connection is open or not. 277 278 Needed in case an error leaves communatations in an undefined or non-recoverable state. 279 +/ 280 void kill() 281 { 282 if(_socket && _socket.connected) 283 _socket.close(); 284 _open = OpenState.notConnected; 285 // any pending data is gone. Any statements to release will be released 286 // on the server automatically. 287 _headersPending = _rowsPending = _binaryPending = false; 288 289 preparedRegistrations.clear(); 290 291 _lastCommandID++; // Invalidate result sets 292 } 293 294 // autoPurge is called every time a command is sent, 295 // so detect & prevent infinite recursion. 296 private bool isAutoPurging = false; 297 298 299 /// Called whenever mysql-native needs to send a command to the server 300 /// and be sure there aren't any pending results (which would prevent 301 /// a new command from being sent). 302 void autoPurge() 303 { 304 if(isAutoPurging) 305 return; 306 307 isAutoPurging = true; 308 scope(exit) isAutoPurging = false; 309 310 try 311 { 312 purgeResult(); 313 releaseQueued(); 314 } 315 catch(Exception e) 316 { 317 // Likely the connection was closed, so reset any state (and force-close if needed). 318 // Don't treat this as a real error, because everything will be reset when we 319 // reconnect. 320 kill(); 321 } 322 } 323 324 /// Lookup per-connection prepared statement info by SQL 325 private PreparedRegistrations!PreparedServerInfo preparedRegistrations; 326 327 /// Releases all prepared statements that are queued for release. 328 void releaseQueued() 329 { 330 foreach(sql, info; preparedRegistrations.directLookup) 331 if(info.queuedForRelease) 332 { 333 immediateReleasePrepared(this, info.statementId); 334 preparedRegistrations.directLookup.remove(sql); 335 } 336 } 337 338 /// Returns null if not found 339 Nullable!PreparedServerInfo getPreparedServerInfo(const(char[]) sql) pure nothrow 340 { 341 return preparedRegistrations[sql]; 342 } 343 344 /// If already registered, simply returns the cached `PreparedServerInfo`. 345 PreparedServerInfo registerIfNeeded(const(char[]) sql) 346 { 347 return preparedRegistrations.registerIfNeeded(sql, sql => performRegister(this, sql)); 348 } 349 350 public: 351 352 /++ 353 Construct opened connection. 354 355 Throws `mysql.exceptions.MYX` upon failure to connect. 356 357 If you are using Vibe.d, consider using `mysql.pool.MySQLPool` instead of 358 creating a new Connection directly. That will provide certain benefits, 359 such as reusing old connections and automatic cleanup (no need to close 360 the connection when done). 361 362 ------------------ 363 // Suggested usage: 364 365 { 366 auto con = new Connection("host=localhost;port=3306;user=joe;pwd=pass123;db=myappsdb"); 367 scope(exit) con.close(); 368 369 // Use the connection 370 ... 371 } 372 ------------------ 373 374 Params: 375 cs = A connection string of the form "host=localhost;user=user;pwd=password;db=mysqld" 376 (TODO: The connection string needs work to allow for semicolons in its parts!) 377 socketType = Whether to use a Phobos or Vibe.d socket. Default is Phobos, 378 unless compiled with `-version=Have_vibe_core` (set automatically 379 if using $(LINK2 http://code.dlang.org/getting_started, DUB)). 380 openSocket = Optional callback which should return a newly-opened Phobos 381 or Vibe.d TCP socket. This allows custom sockets to be used, 382 subclassed from Phobos's or Vibe.d's sockets. 383 host = An IP address in numeric dotted form, or as a host name. 384 user = The user name to authenticate. 385 pwd = User's password. 386 db = Desired initial database. 387 capFlags = The set of flag bits from the server's capabilities that the client requires 388 +/ 389 //After the connection is created, and the initial invitation is received from the server 390 //client preferences can be set, and authentication can then be attempted. 391 this(string host, string user, string pwd, string db, ushort port = 3306, SvrCapFlags capFlags = defaultClientFlags) 392 { 393 version(Have_vibe_core) 394 enum defaultSocketType = MySQLSocketType.vibed; 395 else 396 enum defaultSocketType = MySQLSocketType.phobos; 397 398 this(defaultSocketType, host, user, pwd, db, port, capFlags); 399 } 400 401 ///ditto 402 this(MySQLSocketType socketType, string host, string user, string pwd, string db, ushort port = 3306, SvrCapFlags capFlags = defaultClientFlags) 403 { 404 version(Have_vibe_core) {} else 405 enforce!MYX(socketType != MySQLSocketType.vibed, "Cannot use Vibe.d sockets without -version=Have_vibe_core"); 406 407 this(socketType, &defaultOpenSocketPhobos, &defaultOpenSocketVibeD, 408 host, user, pwd, db, port, capFlags); 409 } 410 411 ///ditto 412 this(OpenSocketCallbackPhobos openSocket, 413 string host, string user, string pwd, string db, ushort port = 3306, SvrCapFlags capFlags = defaultClientFlags) 414 { 415 this(MySQLSocketType.phobos, openSocket, null, host, user, pwd, db, port, capFlags); 416 } 417 418 version(Have_vibe_core) 419 ///ditto 420 this(OpenSocketCallbackVibeD openSocket, 421 string host, string user, string pwd, string db, ushort port = 3306, SvrCapFlags capFlags = defaultClientFlags) 422 { 423 this(MySQLSocketType.vibed, null, openSocket, host, user, pwd, db, port, capFlags); 424 } 425 426 ///ditto 427 private this(MySQLSocketType socketType, 428 OpenSocketCallbackPhobos openSocketPhobos, OpenSocketCallbackVibeD openSocketVibeD, 429 string host, string user, string pwd, string db, ushort port = 3306, SvrCapFlags capFlags = defaultClientFlags) 430 in 431 { 432 final switch(socketType) 433 { 434 case MySQLSocketType.phobos: assert(openSocketPhobos !is null); break; 435 case MySQLSocketType.vibed: assert(openSocketVibeD !is null); break; 436 } 437 } 438 do 439 { 440 enforce!MYX(capFlags & SvrCapFlags.PROTOCOL41, "This client only supports protocol v4.1"); 441 enforce!MYX(capFlags & SvrCapFlags.SECURE_CONNECTION, "This client only supports protocol v4.1 connection"); 442 version(Have_vibe_core) {} else 443 enforce!MYX(socketType != MySQLSocketType.vibed, "Cannot use Vibe.d sockets without -version=Have_vibe_core"); 444 445 _socketType = socketType; 446 _host = host; 447 _user = user; 448 _pwd = pwd; 449 _db = db; 450 _port = port; 451 452 _openSocketPhobos = openSocketPhobos; 453 _openSocketVibeD = openSocketVibeD; 454 455 connect(capFlags); 456 } 457 458 ///ditto 459 //After the connection is created, and the initial invitation is received from the server 460 //client preferences can be set, and authentication can then be attempted. 461 this(string cs, SvrCapFlags capFlags = defaultClientFlags) 462 { 463 string[] a = parseConnectionString(cs); 464 this(a[0], a[1], a[2], a[3], to!ushort(a[4]), capFlags); 465 } 466 467 ///ditto 468 this(MySQLSocketType socketType, string cs, SvrCapFlags capFlags = defaultClientFlags) 469 { 470 string[] a = parseConnectionString(cs); 471 this(socketType, a[0], a[1], a[2], a[3], to!ushort(a[4]), capFlags); 472 } 473 474 ///ditto 475 this(OpenSocketCallbackPhobos openSocket, string cs, SvrCapFlags capFlags = defaultClientFlags) 476 { 477 string[] a = parseConnectionString(cs); 478 this(openSocket, a[0], a[1], a[2], a[3], to!ushort(a[4]), capFlags); 479 } 480 481 version(Have_vibe_core) 482 ///ditto 483 this(OpenSocketCallbackVibeD openSocket, string cs, SvrCapFlags capFlags = defaultClientFlags) 484 { 485 string[] a = parseConnectionString(cs); 486 this(openSocket, a[0], a[1], a[2], a[3], to!ushort(a[4]), capFlags); 487 } 488 489 /++ 490 Check whether this `Connection` is still connected to the server, or if 491 the connection has been closed. 492 +/ 493 @property bool closed() 494 { 495 return _open == OpenState.notConnected || !_socket.connected; 496 } 497 498 /++ 499 Explicitly close the connection. 500 501 Idiomatic use as follows is suggested: 502 ------------------ 503 { 504 auto con = new Connection("localhost:user:password:mysqld"); 505 scope(exit) con.close(); 506 // Use the connection 507 ... 508 } 509 ------------------ 510 +/ 511 void close() 512 { 513 // This is a two-stage process. First tell the server we are quitting this 514 // connection, and then close the socket. 515 516 if (_open == OpenState.authenticated && _socket.connected) 517 quit(); 518 519 if (_open == OpenState.connected) 520 kill(); 521 resetPacket(); 522 } 523 524 /++ 525 Reconnects to the server using the same connection settings originally 526 used to create the `Connection`. 527 528 Optionally takes a `mysql.protocol.constants.SvrCapFlags`, allowing you to 529 reconnect using a different set of server capability flags. 530 531 Normally, if the connection is already open, this will do nothing. However, 532 if you request a different set of `mysql.protocol.constants.SvrCapFlags` 533 then was originally used to create the `Connection`, the connection will 534 be closed and then reconnected using the new `mysql.protocol.constants.SvrCapFlags`. 535 +/ 536 void reconnect() 537 { 538 reconnect(_clientCapabilities); 539 } 540 541 ///ditto 542 void reconnect(SvrCapFlags clientCapabilities) 543 { 544 bool sameCaps = clientCapabilities == _clientCapabilities; 545 if(!closed) 546 { 547 // Same caps as before? 548 if(clientCapabilities == _clientCapabilities) 549 return; // Nothing to do, just keep current connection 550 551 close(); 552 } 553 554 connect(clientCapabilities); 555 } 556 557 private void quit() 558 in 559 { 560 assert(_open == OpenState.authenticated); 561 } 562 do 563 { 564 this.sendCmd(CommandType.QUIT, []); 565 // No response is sent for a quit packet 566 _open = OpenState.connected; 567 } 568 569 /++ 570 Parses a connection string of the form 571 `"host=localhost;port=3306;user=joe;pwd=pass123;db=myappsdb"` 572 573 Port is optional and defaults to 3306. 574 575 Whitespace surrounding any name or value is automatically stripped. 576 577 Returns a five-element array of strings in this order: 578 $(UL 579 $(LI [0]: host) 580 $(LI [1]: user) 581 $(LI [2]: pwd) 582 $(LI [3]: db) 583 $(LI [4]: port) 584 ) 585 586 (TODO: The connection string needs work to allow for semicolons in its parts!) 587 +/ 588 //TODO: Replace the return value with a proper struct. 589 static string[] parseConnectionString(string cs) 590 { 591 string[] rv; 592 rv.length = 5; 593 rv[4] = "3306"; // Default port 594 string[] a = split(cs, ";"); 595 foreach (s; a) 596 { 597 string[] a2 = split(s, "="); 598 enforce!MYX(a2.length == 2, "Bad connection string: " ~ cs); 599 string name = strip(a2[0]); 600 string val = strip(a2[1]); 601 switch (name) 602 { 603 case "host": 604 rv[0] = val; 605 break; 606 case "user": 607 rv[1] = val; 608 break; 609 case "pwd": 610 rv[2] = val; 611 break; 612 case "db": 613 rv[3] = val; 614 break; 615 case "port": 616 rv[4] = val; 617 break; 618 default: 619 throw new MYX("Bad connection string: " ~ cs, __FILE__, __LINE__); 620 } 621 } 622 return rv; 623 } 624 625 /++ 626 Select a current database. 627 628 Throws `mysql.exceptions.MYX` upon failure. 629 630 Params: dbName = Name of the requested database 631 +/ 632 void selectDB(string dbName) 633 { 634 this.sendCmd(CommandType.INIT_DB, dbName); 635 this.getCmdResponse(); 636 _db = dbName; 637 } 638 639 /++ 640 Check the server status. 641 642 Throws `mysql.exceptions.MYX` upon failure. 643 644 Returns: An `mysql.protocol.packets.OKErrorPacket` from which server status can be determined 645 +/ 646 OKErrorPacket pingServer() 647 { 648 this.sendCmd(CommandType.PING, []); 649 return this.getCmdResponse(); 650 } 651 652 /++ 653 Refresh some feature(s) of the server. 654 655 Throws `mysql.exceptions.MYX` upon failure. 656 657 Returns: An `mysql.protocol.packets.OKErrorPacket` from which server status can be determined 658 +/ 659 OKErrorPacket refreshServer(RefreshFlags flags) 660 { 661 this.sendCmd(CommandType.REFRESH, [flags]); 662 return this.getCmdResponse(); 663 } 664 665 /++ 666 Flush any outstanding result set elements. 667 668 When the server responds to a command that produces a result set, it 669 queues the whole set of corresponding packets over the current connection. 670 Before that `Connection` can embark on any new command, it must receive 671 all of those packets and junk them. 672 673 As of v1.1.4, this is done automatically as needed. But you can still 674 call this manually to force a purge to occur when you want. 675 676 See_Also: $(LINK http://www.mysqlperformanceblog.com/2007/07/08/mysql-net_write_timeout-vs-wait_timeout-and-protocol-notes/) 677 +/ 678 ulong purgeResult() 679 { 680 return mysql.protocol.comms.purgeResult(this); 681 } 682 683 /++ 684 Get a textual report on the server status. 685 686 (COM_STATISTICS) 687 +/ 688 string serverStats() 689 { 690 return mysql.protocol.comms.serverStats(this); 691 } 692 693 /++ 694 Enable multiple statement commands. 695 696 This can be used later if this feature was not requested in the client capability flags. 697 698 Warning: This functionality is currently untested. 699 700 Params: on = Boolean value to turn the capability on or off. 701 +/ 702 //TODO: Need to test this 703 void enableMultiStatements(bool on) 704 { 705 mysql.protocol.comms.enableMultiStatements(this, on); 706 } 707 708 /// Return the in-force protocol number. 709 @property ubyte protocol() pure const nothrow { return _protocol; } 710 /// Server version 711 @property string serverVersion() pure const nothrow { return _serverVersion; } 712 /// Server capability flags 713 @property uint serverCapabilities() pure const nothrow { return _sCaps; } 714 /// Server status 715 @property ushort serverStatus() pure const nothrow { return _serverStatus; } 716 /// Current character set 717 @property ubyte charSet() pure const nothrow { return _sCharSet; } 718 /// Current database 719 @property string currentDB() pure const nothrow { return _db; } 720 /// Socket type being used, Phobos or Vibe.d 721 @property MySQLSocketType socketType() pure const nothrow { return _socketType; } 722 723 /// After a command that inserted a row into a table with an auto-increment 724 /// ID column, this method allows you to retrieve the last insert ID. 725 @property ulong lastInsertID() pure const nothrow { return _insertID; } 726 727 /// This gets incremented every time a command is issued or results are purged, 728 /// so a `mysql.result.ResultRange` can tell whether it's been invalidated. 729 @property ulong lastCommandID() pure const nothrow { return _lastCommandID; } 730 731 /// Gets whether rows are pending. 732 /// 733 /// Note, you may want `hasPending` instead. 734 @property bool rowsPending() pure const nothrow { return _rowsPending; } 735 736 /// Gets whether anything (rows, headers or binary) is pending. 737 /// New commands cannot be sent on a connection while anything is pending 738 /// (the pending data will automatically be purged.) 739 @property bool hasPending() pure const nothrow 740 { 741 return _rowsPending || _headersPending || _binaryPending; 742 } 743 744 /// Gets the result header's field descriptions. 745 @property FieldDescription[] resultFieldDescriptions() pure { return _rsh.fieldDescriptions; } 746 747 /++ 748 Manually register a prepared statement on this connection. 749 750 Does nothing if statement is already registered on this connection. 751 752 Calling this is not strictly necessary, as the prepared statement will 753 automatically be registered upon its first use on any `Connection`. 754 This is provided for those who prefer eager registration over lazy 755 for performance reasons. 756 +/ 757 void register(SafePrepared prepared) 758 { 759 register(prepared.sql); 760 } 761 762 ///ditto 763 void register(UnsafePrepared prepared) 764 { 765 register(prepared.sql); 766 } 767 768 ///ditto 769 void register(const(char[]) sql) 770 { 771 registerIfNeeded(sql); 772 } 773 774 /++ 775 Manually release a prepared statement on this connection. 776 777 This method tells the server that it can dispose of the information it 778 holds about the current prepared statement. 779 780 Calling this is not strictly necessary. The server considers prepared 781 statements to be per-connection, so they'll go away when the connection 782 closes anyway. This is provided in case direct control is actually needed. 783 784 If you choose to use a reference counted struct to call this automatically, 785 be aware that embedding reference counted structs inside garbage collectible 786 heap objects is dangerous and should be avoided, as it can lead to various 787 hidden problems, from crashes to race conditions. (See the discussion at issue 788 $(LINK2 https://github.com/mysql-d/mysql-native/issues/159, #159) 789 for details.) Instead, it may be better to simply avoid trying to manage 790 their release at all, as it's not usually necessary. Or to periodically 791 release all prepared statements, and simply allow mysql-native to 792 automatically re-register them upon their next use. 793 794 Notes: 795 796 In actuality, the server might not immediately be told to release the 797 statement (although `isRegistered` will still report `false`). 798 799 This is because there could be a `mysql.result.ResultRange` with results 800 still pending for retrieval, and the protocol doesn't allow sending commands 801 (such as "release a prepared statement") to the server while data is pending. 802 Therefore, this function may instead queue the statement to be released 803 when it is safe to do so: Either the next time a result set is purged or 804 the next time a command (such as `mysql.commands.query` or 805 `mysql.commands.exec`) is performed (because such commands automatically 806 purge any pending results). 807 808 This function does NOT auto-purge because, if this is ever called from 809 automatic resource management cleanup (refcounting, RAII, etc), that 810 would create ugly situations where hidden, implicit behavior triggers 811 an unexpected auto-purge. 812 +/ 813 void release(SafePrepared prepared) 814 { 815 release(prepared.sql); 816 } 817 818 ///ditto 819 void release(UnsafePrepared prepared) 820 { 821 release(prepared.sql); 822 } 823 824 ///ditto 825 void release(const(char[]) sql) 826 { 827 //TODO: Don't queue it if nothing is pending. Just do it immediately. 828 // But need to be certain both situations are unittested. 829 preparedRegistrations.queueForRelease(sql); 830 } 831 832 /++ 833 Manually release all prepared statements on this connection. 834 835 While minimal, every prepared statement registered on a connection does 836 use up a small amount of resources in both mysql-native and on the server. 837 Additionally, servers can be configured 838 $(LINK2 https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_max_prepared_stmt_count, 839 to limit the number of prepared statements) 840 allowed on a connection at one time (the default, however 841 is quite high). Note also, that certain overloads of `mysql.commands.exec`, 842 `mysql.commands.query`, etc. register prepared statements behind-the-scenes 843 which are cached for quick re-use later. 844 845 Therefore, it may occasionally be useful to clear out all prepared 846 statements on a connection, together with all resources used by them (or 847 at least leave the resources ready for garbage-collection). This function 848 does just that. 849 850 Note that this is ALWAYS COMPLETELY SAFE to call, even if you still have 851 live prepared statements you intend to use again. This is safe because 852 mysql-native will automatically register or re-register prepared statements 853 as-needed. 854 855 Notes: 856 857 In actuality, the prepared statements might not be immediately released 858 (although `isRegistered` will still report `false` for them). 859 860 This is because there could be a `mysql.result.ResultRange` with results 861 still pending for retrieval, and the protocol doesn't allow sending commands 862 (such as "release a prepared statement") to the server while data is pending. 863 Therefore, this function may instead queue the statement to be released 864 when it is safe to do so: Either the next time a result set is purged or 865 the next time a command (such as `mysql.commands.query` or 866 `mysql.commands.exec`) is performed (because such commands automatically 867 purge any pending results). 868 869 This function does NOT auto-purge because, if this is ever called from 870 automatic resource management cleanup (refcounting, RAII, etc), that 871 would create ugly situations where hidden, implicit behavior triggers 872 an unexpected auto-purge. 873 +/ 874 void releaseAll() 875 { 876 preparedRegistrations.queueAllForRelease(); 877 } 878 879 /// Is the given statement registered on this connection as a prepared statement? 880 bool isRegistered(SafePrepared prepared) 881 { 882 return isRegistered( prepared.sql ); 883 } 884 885 ///ditto 886 bool isRegistered(UnsafePrepared prepared) 887 { 888 return isRegistered( prepared.sql ); 889 } 890 891 ///ditto 892 bool isRegistered(const(char[]) sql) 893 { 894 return isRegistered( preparedRegistrations[sql] ); 895 } 896 897 ///ditto 898 package bool isRegistered(Nullable!PreparedServerInfo info) 899 { 900 return !info.isNull && !info.get.queuedForRelease; 901 } 902 }