You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

927 lines
28 KiB

4 years ago
  1. #if MYSQL_6_9
  2. // Copyright � 2004, 2018 Oracle and/or its affiliates. All rights reserved.
  3. //
  4. // MySQL Connector/NET is licensed under the terms of the GPLv2
  5. // <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
  6. // MySQL Connectors. There are special exceptions to the terms and
  7. // conditions of the GPLv2 as it is applied to this software, see the
  8. // FLOSS License Exception
  9. // <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
  10. //
  11. // This program is free software; you can redistribute it and/or modify
  12. // it under the terms of the GNU General Public License as published
  13. // by the Free Software Foundation; version 2 of the License.
  14. //
  15. // This program is distributed in the hope that it will be useful, but
  16. // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  17. // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  18. // for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License along
  21. // with this program; if not, write to the Free Software Foundation, Inc.,
  22. // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  23. using System;
  24. using System.Collections;
  25. using System.Diagnostics;
  26. using System.IO;
  27. using Externals.MySql.Data.Common;
  28. using Externals.MySql.Data.Types;
  29. using Externals.MySql.Data.MySqlClient.Properties;
  30. using System.Text;
  31. using Externals.MySql.Data.MySqlClient.Authentication;
  32. using System.Reflection;
  33. using System.ComponentModel;
  34. using System.Security.Cryptography.X509Certificates;
  35. using System.Net.Security;
  36. using System.Security.Authentication;
  37. using System.Globalization;
  38. namespace Externals.MySql.Data.MySqlClient
  39. {
  40. /// <summary>
  41. /// Summary description for Driver.
  42. /// </summary>
  43. internal class NativeDriver : IDriver
  44. {
  45. private DBVersion version;
  46. private int threadId;
  47. protected byte[] encryptionSeed;
  48. protected ServerStatusFlags serverStatus;
  49. protected MySqlStream stream;
  50. protected Stream baseStream;
  51. private BitArray nullMap;
  52. private MySqlPacket packet;
  53. private ClientFlags connectionFlags;
  54. private Driver owner;
  55. private int warnings;
  56. private MySqlAuthenticationPlugin authPlugin;
  57. private bool isEnterprise;
  58. // Windows authentication method string, used by the protocol.
  59. // Also known as "client plugin name".
  60. const string AuthenticationWindowsPlugin = "authentication_windows_client";
  61. // Predefined username for IntegratedSecurity
  62. const string AuthenticationWindowsUser = "auth_windows";
  63. public NativeDriver(Driver owner)
  64. {
  65. this.owner = owner;
  66. threadId = -1;
  67. }
  68. public ClientFlags Flags
  69. {
  70. get { return connectionFlags; }
  71. }
  72. public int ThreadId
  73. {
  74. get { return threadId; }
  75. }
  76. public DBVersion Version
  77. {
  78. get { return version; }
  79. }
  80. public ServerStatusFlags ServerStatus
  81. {
  82. get { return serverStatus; }
  83. }
  84. public int WarningCount
  85. {
  86. get { return warnings; }
  87. }
  88. public MySqlPacket Packet
  89. {
  90. get { return packet; }
  91. }
  92. internal MySqlConnectionStringBuilder Settings
  93. {
  94. get { return owner.Settings; }
  95. }
  96. internal Encoding Encoding
  97. {
  98. get { return owner.Encoding; }
  99. }
  100. private void HandleException(MySqlException ex)
  101. {
  102. if (ex.IsFatal)
  103. owner.Close();
  104. }
  105. internal void SendPacket(MySqlPacket p)
  106. {
  107. stream.SendPacket(p);
  108. }
  109. internal void SendEmptyPacket()
  110. {
  111. byte[] buffer = new byte[4];
  112. stream.SendEntirePacketDirectly(buffer, 0);
  113. }
  114. internal MySqlPacket ReadPacket()
  115. {
  116. return packet = stream.ReadPacket();
  117. }
  118. internal void ReadOk(bool read)
  119. {
  120. try
  121. {
  122. if (read)
  123. packet = stream.ReadPacket();
  124. byte marker = (byte)packet.ReadByte();
  125. if (marker != 0)
  126. {
  127. throw new MySqlException("Out of sync with server", true, null);
  128. }
  129. packet.ReadFieldLength(); /* affected rows */
  130. packet.ReadFieldLength(); /* last insert id */
  131. if (packet.HasMoreData)
  132. {
  133. serverStatus = (ServerStatusFlags)packet.ReadInteger(2);
  134. packet.ReadInteger(2); /* warning count */
  135. if (packet.HasMoreData)
  136. {
  137. packet.ReadLenString(); /* message */
  138. }
  139. }
  140. }
  141. catch (MySqlException ex)
  142. {
  143. HandleException(ex);
  144. throw;
  145. }
  146. }
  147. /// <summary>
  148. /// Sets the current database for the this connection
  149. /// </summary>
  150. /// <param name="dbName"></param>
  151. public void SetDatabase(string dbName)
  152. {
  153. byte[] dbNameBytes = Encoding.GetBytes(dbName);
  154. packet.Clear();
  155. packet.WriteByte((byte)DBCmd.INIT_DB);
  156. packet.Write(dbNameBytes);
  157. ExecutePacket(packet);
  158. ReadOk(true);
  159. }
  160. public void Configure()
  161. {
  162. stream.MaxPacketSize = (ulong)owner.MaxPacketSize;
  163. stream.Encoding = Encoding;
  164. }
  165. public void Open()
  166. {
  167. // connect to one of our specified hosts
  168. try
  169. {
  170. baseStream = StreamCreator.GetStream(Settings);
  171. if (Settings.IncludeSecurityAsserts)
  172. MySqlSecurityPermission.CreatePermissionSet(false).Assert();
  173. }
  174. catch (System.Security.SecurityException)
  175. {
  176. throw;
  177. }
  178. catch (Exception ex)
  179. {
  180. throw new MySqlException(Resources.UnableToConnectToHost,
  181. (int)MySqlErrorCode.UnableToConnectToHost, ex);
  182. }
  183. if (baseStream == null)
  184. throw new MySqlException(Resources.UnableToConnectToHost,
  185. (int)MySqlErrorCode.UnableToConnectToHost);
  186. int maxSinglePacket = 255 * 255 * 255;
  187. stream = new MySqlStream(baseStream, Encoding, false);
  188. stream.ResetTimeout((int)Settings.ConnectionTimeout * 1000);
  189. // read off the welcome packet and parse out it's values
  190. packet = stream.ReadPacket();
  191. int protocol = packet.ReadByte();
  192. string versionString = packet.ReadString();
  193. owner.isFabric = versionString.EndsWith("fabric", StringComparison.OrdinalIgnoreCase);
  194. isEnterprise = versionString.ToLowerInvariant().Contains("-enterprise-");
  195. version = DBVersion.Parse(versionString);
  196. if (!owner.isFabric && !version.isAtLeast(5, 0, 0))
  197. throw new NotSupportedException(Resources.ServerTooOld);
  198. threadId = packet.ReadInteger(4);
  199. byte[] seedPart1 = packet.ReadStringAsBytes();
  200. maxSinglePacket = (256 * 256 * 256) - 1;
  201. // read in Server capabilities if they are provided
  202. ClientFlags serverCaps = 0;
  203. if (packet.HasMoreData)
  204. serverCaps = (ClientFlags)packet.ReadInteger(2);
  205. /* New protocol with 16 bytes to describe server characteristics */
  206. owner.ConnectionCharSetIndex = (int)packet.ReadByte();
  207. serverStatus = (ServerStatusFlags)packet.ReadInteger(2);
  208. // Since 5.5, high bits of server caps are stored after status.
  209. // Previously, it was part of reserved always 0x00 13-byte filler.
  210. uint serverCapsHigh = (uint)packet.ReadInteger(2);
  211. serverCaps |= (ClientFlags)(serverCapsHigh << 16);
  212. packet.Position += 11;
  213. byte[] seedPart2 = packet.ReadStringAsBytes();
  214. encryptionSeed = new byte[seedPart1.Length + seedPart2.Length];
  215. seedPart1.CopyTo(encryptionSeed, 0);
  216. seedPart2.CopyTo(encryptionSeed, seedPart1.Length);
  217. string authenticationMethod = "";
  218. if ((serverCaps & ClientFlags.PLUGIN_AUTH) != 0)
  219. {
  220. authenticationMethod = packet.ReadString();
  221. }
  222. else
  223. {
  224. // Some MySql versions like 5.1, don't give name of plugin, default to native password.
  225. authenticationMethod = "mysql_native_password";
  226. }
  227. // based on our settings, set our connection flags
  228. SetConnectionFlags(serverCaps);
  229. packet.Clear();
  230. packet.WriteInteger((int)connectionFlags, 4);
  231. packet.WriteInteger(maxSinglePacket, 4);
  232. packet.WriteByte(33); //character set utf-8
  233. packet.Write(new byte[23]);
  234. if ((serverCaps & ClientFlags.SSL) == 0)
  235. {
  236. if ((Settings.SslMode != MySqlSslMode.None)
  237. && (Settings.SslMode != MySqlSslMode.Preferred))
  238. {
  239. // Client requires SSL connections.
  240. string message = String.Format(Resources.NoServerSSLSupport,
  241. Settings.Server);
  242. throw new MySqlException(message);
  243. }
  244. }
  245. else if (Settings.SslMode != MySqlSslMode.None)
  246. {
  247. stream.SendPacket(packet);
  248. StartSSL();
  249. packet.Clear();
  250. packet.WriteInteger((int)connectionFlags, 4);
  251. packet.WriteInteger(maxSinglePacket, 4);
  252. packet.WriteByte(33); //character set utf-8
  253. packet.Write(new byte[23]);
  254. }
  255. Authenticate(authenticationMethod, false);
  256. // if we are using compression, then we use our CompressedStream class
  257. // to hide the ugliness of managing the compression
  258. if ((connectionFlags & ClientFlags.COMPRESS) != 0)
  259. stream = new MySqlStream(baseStream, Encoding, true);
  260. // give our stream the server version we are connected to.
  261. // We may have some fields that are read differently based
  262. // on the version of the server we are connected to.
  263. packet.Version = version;
  264. stream.MaxBlockSize = maxSinglePacket;
  265. }
  266. #region SSL
  267. /// <summary>
  268. /// Retrieve client SSL certificates. Dependent on connection string
  269. /// settings we use either file or store based certificates.
  270. /// </summary>
  271. private X509CertificateCollection GetClientCertificates()
  272. {
  273. X509CertificateCollection certs = new X509CertificateCollection();
  274. // Check for file-based certificate
  275. if (Settings.CertificateFile != null)
  276. {
  277. if (!Version.isAtLeast(5, 1, 0))
  278. throw new MySqlException(Properties.Resources.FileBasedCertificateNotSupported);
  279. X509Certificate2 clientCert = new X509Certificate2(Settings.CertificateFile,
  280. Settings.CertificatePassword);
  281. certs.Add(clientCert);
  282. return certs;
  283. }
  284. if (Settings.CertificateStoreLocation == MySqlCertificateStoreLocation.None)
  285. return certs;
  286. StoreLocation location =
  287. (Settings.CertificateStoreLocation == MySqlCertificateStoreLocation.CurrentUser) ?
  288. StoreLocation.CurrentUser : StoreLocation.LocalMachine;
  289. // Check for store-based certificate
  290. X509Store store = new X509Store(StoreName.My, location);
  291. store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
  292. if (Settings.CertificateThumbprint == null)
  293. {
  294. // Return all certificates from the store.
  295. certs.AddRange(store.Certificates);
  296. return certs;
  297. }
  298. // Find certificate with given thumbprint
  299. certs.AddRange(store.Certificates.Find(X509FindType.FindByThumbprint,
  300. Settings.CertificateThumbprint, true));
  301. if (certs.Count == 0)
  302. {
  303. throw new MySqlException("Certificate with Thumbprint " +
  304. Settings.CertificateThumbprint + " not found");
  305. }
  306. return certs;
  307. }
  308. private void StartSSL()
  309. {
  310. RemoteCertificateValidationCallback sslValidateCallback =
  311. new RemoteCertificateValidationCallback(ServerCheckValidation);
  312. SslStream ss = new SslStream(baseStream, false, sslValidateCallback, null);
  313. X509CertificateCollection certs = GetClientCertificates();
  314. SslProtocols sslProtocols = SslProtocols.Tls;
  315. #if NET461
  316. sslProtocols |= SslProtocols.Tls11;
  317. if (version.isAtLeast(5, 6, 0) && isEnterprise)
  318. sslProtocols |= SslProtocols.Tls12;
  319. #endif
  320. ss.AuthenticateAsClient(Settings.Server, certs, sslProtocols, false);
  321. baseStream = ss;
  322. stream = new MySqlStream(ss, Encoding, false);
  323. stream.SequenceByte = 2;
  324. }
  325. private bool ServerCheckValidation(object sender, X509Certificate certificate,
  326. X509Chain chain, SslPolicyErrors sslPolicyErrors)
  327. {
  328. if (sslPolicyErrors == SslPolicyErrors.None)
  329. return true;
  330. if (Settings.SslMode == MySqlSslMode.Preferred ||
  331. Settings.SslMode == MySqlSslMode.Required)
  332. {
  333. //Tolerate all certificate errors.
  334. return true;
  335. }
  336. if (Settings.SslMode == MySqlSslMode.VerifyCA &&
  337. sslPolicyErrors == SslPolicyErrors.RemoteCertificateNameMismatch)
  338. {
  339. // Tolerate name mismatch in certificate, if full validation is not requested.
  340. return true;
  341. }
  342. return false;
  343. }
  344. #endregion
  345. #region Authentication
  346. /// <summary>
  347. /// Return the appropriate set of connection flags for our
  348. /// server capabilities and our user requested options.
  349. /// </summary>
  350. private void SetConnectionFlags(ClientFlags serverCaps)
  351. {
  352. // allow load data local infile
  353. ClientFlags flags = ClientFlags.LOCAL_FILES;
  354. if (!Settings.UseAffectedRows)
  355. flags |= ClientFlags.FOUND_ROWS;
  356. flags |= ClientFlags.PROTOCOL_41;
  357. // Need this to get server status values
  358. flags |= ClientFlags.TRANSACTIONS;
  359. // user allows/disallows batch statements
  360. if (Settings.AllowBatch)
  361. flags |= ClientFlags.MULTI_STATEMENTS;
  362. // We always allow multiple result sets
  363. flags |= ClientFlags.MULTI_RESULTS;
  364. // if the server allows it, tell it that we want long column info
  365. if ((serverCaps & ClientFlags.LONG_FLAG) != 0)
  366. flags |= ClientFlags.LONG_FLAG;
  367. // if the server supports it and it was requested, then turn on compression
  368. if ((serverCaps & ClientFlags.COMPRESS) != 0 && Settings.UseCompression)
  369. flags |= ClientFlags.COMPRESS;
  370. flags |= ClientFlags.LONG_PASSWORD; // for long passwords
  371. // did the user request an interactive session?
  372. if (Settings.InteractiveSession)
  373. flags |= ClientFlags.INTERACTIVE;
  374. // if the server allows it and a database was specified, then indicate
  375. // that we will connect with a database name
  376. if ((serverCaps & ClientFlags.CONNECT_WITH_DB) != 0 &&
  377. Settings.Database != null && Settings.Database.Length > 0)
  378. flags |= ClientFlags.CONNECT_WITH_DB;
  379. // if the server is requesting a secure connection, then we oblige
  380. if ((serverCaps & ClientFlags.SECURE_CONNECTION) != 0)
  381. flags |= ClientFlags.SECURE_CONNECTION;
  382. // if the server is capable of SSL and the user is requesting SSL
  383. if ((serverCaps & ClientFlags.SSL) != 0 && Settings.SslMode != MySqlSslMode.None)
  384. flags |= ClientFlags.SSL;
  385. // if the server supports output parameters, then we do too
  386. if ((serverCaps & ClientFlags.PS_MULTI_RESULTS) != 0)
  387. flags |= ClientFlags.PS_MULTI_RESULTS;
  388. if ((serverCaps & ClientFlags.PLUGIN_AUTH) != 0)
  389. flags |= ClientFlags.PLUGIN_AUTH;
  390. // if the server supports connection attributes
  391. if ((serverCaps & ClientFlags.CONNECT_ATTRS) != 0)
  392. flags |= ClientFlags.CONNECT_ATTRS;
  393. if ((serverCaps & ClientFlags.CAN_HANDLE_EXPIRED_PASSWORD) != 0)
  394. flags |= ClientFlags.CAN_HANDLE_EXPIRED_PASSWORD;
  395. connectionFlags = flags;
  396. }
  397. public void Authenticate(string authMethod, bool reset)
  398. {
  399. if (authMethod != null)
  400. {
  401. // Integrated security is a shortcut for windows auth
  402. if (Settings.IntegratedSecurity)
  403. authMethod = "authentication_windows_client";
  404. authPlugin = MySqlAuthenticationPlugin.GetPlugin(authMethod, this, encryptionSeed);
  405. }
  406. authPlugin.Authenticate(reset);
  407. }
  408. #endregion
  409. public void Reset()
  410. {
  411. warnings = 0;
  412. stream.Encoding = this.Encoding;
  413. stream.SequenceByte = 0;
  414. packet.Clear();
  415. packet.WriteByte((byte)DBCmd.CHANGE_USER);
  416. Authenticate(null, true);
  417. }
  418. /// <summary>
  419. /// Query is the method that is called to send all queries to the server
  420. /// </summary>
  421. public void SendQuery(MySqlPacket queryPacket)
  422. {
  423. warnings = 0;
  424. queryPacket.SetByte(4, (byte)DBCmd.QUERY);
  425. ExecutePacket(queryPacket);
  426. // the server will respond in one of several ways with the first byte indicating
  427. // the type of response.
  428. // 0 == ok packet. This indicates non-select queries
  429. // 0xff == error packet. This is handled in stream.OpenPacket
  430. // > 0 = number of columns in select query
  431. // We don't actually read the result here since a single query can generate
  432. // multiple resultsets and we don't want to duplicate code. See ReadResult
  433. // Instead we set our internal server status flag to indicate that we have a query waiting.
  434. // This flag will be maintained by ReadResult
  435. serverStatus |= ServerStatusFlags.AnotherQuery;
  436. }
  437. public void Close(bool isOpen)
  438. {
  439. try
  440. {
  441. if (isOpen)
  442. {
  443. try
  444. {
  445. packet.Clear();
  446. packet.WriteByte((byte)DBCmd.QUIT);
  447. ExecutePacket(packet);
  448. }
  449. catch (Exception)
  450. {
  451. // Eat exception here. We should try to closing
  452. // the stream anyway.
  453. }
  454. }
  455. if (stream != null)
  456. stream.Close();
  457. stream = null;
  458. }
  459. catch (Exception)
  460. {
  461. // we are just going to eat any exceptions
  462. // generated here
  463. }
  464. }
  465. public bool Ping()
  466. {
  467. try
  468. {
  469. packet.Clear();
  470. packet.WriteByte((byte)DBCmd.PING);
  471. ExecutePacket(packet);
  472. ReadOk(true);
  473. return true;
  474. }
  475. catch (Exception)
  476. {
  477. owner.Close();
  478. return false;
  479. }
  480. }
  481. public int GetResult(ref int affectedRow, ref long insertedId)
  482. {
  483. try
  484. {
  485. packet = stream.ReadPacket();
  486. }
  487. catch (TimeoutException)
  488. {
  489. // Do not reset serverStatus, allow to reenter, e.g when
  490. // ResultSet is closed.
  491. throw;
  492. }
  493. catch (Exception)
  494. {
  495. serverStatus &= ~(ServerStatusFlags.AnotherQuery |
  496. ServerStatusFlags.MoreResults);
  497. throw;
  498. }
  499. int fieldCount = (int)packet.ReadFieldLength();
  500. if (-1 == fieldCount)
  501. {
  502. string filename = packet.ReadString();
  503. SendFileToServer(filename);
  504. return GetResult(ref affectedRow, ref insertedId);
  505. }
  506. else if (fieldCount == 0)
  507. {
  508. // the code to read last packet will set these server status vars
  509. // again if necessary.
  510. serverStatus &= ~(ServerStatusFlags.AnotherQuery |
  511. ServerStatusFlags.MoreResults);
  512. affectedRow = (int)packet.ReadFieldLength();
  513. insertedId = (long)packet.ReadFieldLength();
  514. serverStatus = (ServerStatusFlags)packet.ReadInteger(2);
  515. warnings += packet.ReadInteger(2);
  516. if (packet.HasMoreData)
  517. {
  518. packet.ReadLenString(); //TODO: server message
  519. }
  520. }
  521. return fieldCount;
  522. }
  523. /// <summary>
  524. /// Sends the specified file to the server.
  525. /// This supports the LOAD DATA LOCAL INFILE
  526. /// </summary>
  527. /// <param name="filename"></param>
  528. private void SendFileToServer(string filename)
  529. {
  530. byte[] buffer = new byte[8196];
  531. long len = 0;
  532. try
  533. {
  534. using (FileStream fs = new FileStream(filename, FileMode.Open,
  535. FileAccess.Read))
  536. {
  537. len = fs.Length;
  538. while (len > 0)
  539. {
  540. int count = fs.Read(buffer, 4, (int)(len > 8192 ? 8192 : len));
  541. stream.SendEntirePacketDirectly(buffer, count);
  542. len -= count;
  543. }
  544. stream.SendEntirePacketDirectly(buffer, 0);
  545. }
  546. }
  547. catch (Exception ex)
  548. {
  549. throw new MySqlException("Error during LOAD DATA LOCAL INFILE", ex);
  550. }
  551. }
  552. private void ReadNullMap(int fieldCount)
  553. {
  554. // if we are binary, then we need to load in our null bitmap
  555. nullMap = null;
  556. byte[] nullMapBytes = new byte[(fieldCount + 9) / 8];
  557. packet.ReadByte();
  558. packet.Read(nullMapBytes, 0, nullMapBytes.Length);
  559. nullMap = new BitArray(nullMapBytes);
  560. }
  561. public IMySqlValue ReadColumnValue(int index, MySqlField field, IMySqlValue valObject)
  562. {
  563. long length = -1;
  564. bool isNull;
  565. if (nullMap != null)
  566. isNull = nullMap[index + 2];
  567. else
  568. {
  569. length = packet.ReadFieldLength();
  570. isNull = length == -1;
  571. }
  572. packet.Encoding = field.Encoding;
  573. packet.Version = version;
  574. return valObject.ReadValue(packet, length, isNull);
  575. }
  576. public void SkipColumnValue(IMySqlValue valObject)
  577. {
  578. int length = -1;
  579. if (nullMap == null)
  580. {
  581. length = (int)packet.ReadFieldLength();
  582. if (length == -1) return;
  583. }
  584. if (length > -1)
  585. packet.Position += length;
  586. else
  587. valObject.SkipValue(packet);
  588. }
  589. public void GetColumnsData(MySqlField[] columns)
  590. {
  591. for (int i = 0; i < columns.Length; i++)
  592. GetColumnData(columns[i]);
  593. ReadEOF();
  594. }
  595. private void GetColumnData(MySqlField field)
  596. {
  597. stream.Encoding = Encoding;
  598. packet = stream.ReadPacket();
  599. field.Encoding = Encoding;
  600. field.CatalogName = packet.ReadLenString();
  601. field.DatabaseName = packet.ReadLenString();
  602. field.TableName = packet.ReadLenString();
  603. field.RealTableName = packet.ReadLenString();
  604. field.ColumnName = packet.ReadLenString();
  605. field.OriginalColumnName = packet.ReadLenString();
  606. packet.ReadByte();
  607. field.CharacterSetIndex = packet.ReadInteger(2);
  608. field.ColumnLength = packet.ReadInteger(4);
  609. MySqlDbType type = (MySqlDbType)packet.ReadByte();
  610. ColumnFlags colFlags;
  611. if ((connectionFlags & ClientFlags.LONG_FLAG) != 0)
  612. colFlags = (ColumnFlags)packet.ReadInteger(2);
  613. else
  614. colFlags = (ColumnFlags)packet.ReadByte();
  615. field.Scale = (byte)packet.ReadByte();
  616. if (packet.HasMoreData)
  617. {
  618. packet.ReadInteger(2); // reserved
  619. }
  620. if (type == MySqlDbType.Decimal || type == MySqlDbType.NewDecimal)
  621. {
  622. field.Precision = ((colFlags & ColumnFlags.UNSIGNED) != 0) ? (byte)(field.ColumnLength) : (byte)(field.ColumnLength - 1);
  623. if (field.Scale != 0)
  624. field.Precision--;
  625. }
  626. field.SetTypeAndFlags(type, colFlags);
  627. }
  628. private void ExecutePacket(MySqlPacket packetToExecute)
  629. {
  630. try
  631. {
  632. warnings = 0;
  633. stream.SequenceByte = 0;
  634. stream.SendPacket(packetToExecute);
  635. }
  636. catch (MySqlException ex)
  637. {
  638. HandleException(ex);
  639. throw;
  640. }
  641. }
  642. public void ExecuteStatement(MySqlPacket packetToExecute)
  643. {
  644. warnings = 0;
  645. packetToExecute.SetByte(4, (byte)DBCmd.EXECUTE);
  646. ExecutePacket(packetToExecute);
  647. serverStatus |= ServerStatusFlags.AnotherQuery;
  648. }
  649. private void CheckEOF()
  650. {
  651. if (!packet.IsLastPacket)
  652. throw new MySqlException("Expected end of data packet");
  653. packet.ReadByte(); // read off the 254
  654. if (packet.HasMoreData)
  655. {
  656. warnings += packet.ReadInteger(2);
  657. serverStatus = (ServerStatusFlags)packet.ReadInteger(2);
  658. // if we are at the end of this cursor based resultset, then we remove
  659. // the last row sent status flag so our next fetch doesn't abort early
  660. // and we remove this command result from our list of active CommandResult objects.
  661. // if ((serverStatus & ServerStatusFlags.LastRowSent) != 0)
  662. // {
  663. // serverStatus &= ~ServerStatusFlags.LastRowSent;
  664. // commandResults.Remove(lastCommandResult);
  665. // }
  666. }
  667. }
  668. private void ReadEOF()
  669. {
  670. packet = stream.ReadPacket();
  671. CheckEOF();
  672. }
  673. public int PrepareStatement(string sql, ref MySqlField[] parameters)
  674. {
  675. //TODO: check this
  676. //ClearFetchedRow();
  677. packet.Length = sql.Length * 4 + 5;
  678. byte[] buffer = packet.Buffer;
  679. int len = Encoding.GetBytes(sql, 0, sql.Length, packet.Buffer, 5);
  680. packet.Position = len + 5;
  681. buffer[4] = (byte)DBCmd.PREPARE;
  682. ExecutePacket(packet);
  683. packet = stream.ReadPacket();
  684. int marker = packet.ReadByte();
  685. if (marker != 0)
  686. throw new MySqlException("Expected prepared statement marker");
  687. int statementId = packet.ReadInteger(4);
  688. int numCols = packet.ReadInteger(2);
  689. int numParams = packet.ReadInteger(2);
  690. //TODO: find out what this is needed for
  691. packet.ReadInteger(3);
  692. if (numParams > 0)
  693. {
  694. parameters = owner.GetColumns(numParams);
  695. // we set the encoding for each parameter back to our connection encoding
  696. // since we can't trust what is coming back from the server
  697. for (int i = 0; i < parameters.Length; i++)
  698. parameters[i].Encoding = Encoding;
  699. }
  700. if (numCols > 0)
  701. {
  702. while (numCols-- > 0)
  703. {
  704. packet = stream.ReadPacket();
  705. //TODO: handle streaming packets
  706. }
  707. ReadEOF();
  708. }
  709. return statementId;
  710. }
  711. // private void ClearFetchedRow()
  712. // {
  713. // if (lastCommandResult == 0) return;
  714. //TODO
  715. /* CommandResult result = (CommandResult)commandResults[lastCommandResult];
  716. result.ReadRemainingColumns();
  717. stream.OpenPacket();
  718. if (! stream.IsLastPacket)
  719. throw new MySqlException("Cursor reading out of sync");
  720. ReadEOF(false);
  721. lastCommandResult = 0;*/
  722. // }
  723. /// <summary>
  724. /// FetchDataRow is the method that the data reader calls to see if there is another
  725. /// row to fetch. In the non-prepared mode, it will simply read the next data packet.
  726. /// In the prepared mode (statementId > 0), it will
  727. /// </summary>
  728. public bool FetchDataRow(int statementId, int columns)
  729. {
  730. /* ClearFetchedRow();
  731. if (!commandResults.ContainsKey(statementId)) return false;
  732. if ( (serverStatus & ServerStatusFlags.LastRowSent) != 0)
  733. return false;
  734. stream.StartPacket(9, true);
  735. stream.WriteByte((byte)DBCmd.FETCH);
  736. stream.WriteInteger(statementId, 4);
  737. stream.WriteInteger(1, 4);
  738. stream.Flush();
  739. lastCommandResult = statementId;
  740. */
  741. packet = stream.ReadPacket();
  742. if (packet.IsLastPacket)
  743. {
  744. CheckEOF();
  745. return false;
  746. }
  747. nullMap = null;
  748. if (statementId > 0)
  749. ReadNullMap(columns);
  750. return true;
  751. }
  752. public void CloseStatement(int statementId)
  753. {
  754. packet.Clear();
  755. packet.WriteByte((byte)DBCmd.CLOSE_STMT);
  756. packet.WriteInteger((long)statementId, 4);
  757. stream.SequenceByte = 0;
  758. stream.SendPacket(packet);
  759. }
  760. /// <summary>
  761. /// Execution timeout, in milliseconds. When the accumulated time for network IO exceeds this value
  762. /// TimeoutException is thrown. This timeout needs to be reset for every new command
  763. /// </summary>
  764. ///
  765. public void ResetTimeout(int timeout)
  766. {
  767. if (stream != null)
  768. stream.ResetTimeout(timeout);
  769. }
  770. internal void SetConnectAttrs()
  771. {
  772. // Sets connect attributes
  773. if ((connectionFlags & ClientFlags.CONNECT_ATTRS) != 0)
  774. {
  775. string connectAttrs = string.Empty;
  776. MySqlConnectAttrs attrs = new MySqlConnectAttrs();
  777. foreach (PropertyInfo property in attrs.GetType().GetProperties())
  778. {
  779. string name = property.Name;
  780. object[] customAttrs = property.GetCustomAttributes(typeof(DisplayNameAttribute), false);
  781. if (customAttrs.Length > 0)
  782. name = (customAttrs[0] as DisplayNameAttribute).DisplayName;
  783. string value = (string)property.GetValue(attrs, null);
  784. connectAttrs += string.Format("{0}{1}", (char)name.Length, name);
  785. connectAttrs += string.Format("{0}{1}", (char)value.Length, value);
  786. }
  787. packet.WriteLenString(connectAttrs);
  788. }
  789. }
  790. }
  791. }
  792. #endif