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