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