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.

608 lines
20 KiB

4 years ago
  1. #if MYSQL_6_9
  2. // Copyright ?2004, 2017 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.Globalization;
  26. using System.Text;
  27. using Externals.MySql.Data.Common;
  28. using Externals.MySql.Data.Types;
  29. using Externals.MySql.Data.MySqlClient.Properties;
  30. using System.Diagnostics;
  31. using System.Collections.Generic;
  32. using System.Security;
  33. namespace Externals.MySql.Data.MySqlClient
  34. {
  35. /// <summary>
  36. /// Summary description for BaseDriver.
  37. /// </summary>
  38. internal class Driver : IDisposable
  39. {
  40. protected Encoding encoding;
  41. protected MySqlConnectionStringBuilder connectionString;
  42. protected bool isOpen;
  43. protected DateTime creationTime;
  44. protected string serverCharSet;
  45. protected int serverCharSetIndex;
  46. protected Dictionary<string, string> serverProps;
  47. protected Dictionary<int, string> charSets;
  48. protected long maxPacketSize;
  49. internal int timeZoneOffset;
  50. private DateTime idleSince;
  51. protected MySqlPromotableTransaction currentTransaction;
  52. protected bool inActiveUse;
  53. protected MySqlPool pool;
  54. private bool firstResult;
  55. protected IDriver handler;
  56. internal MySqlDataReader reader;
  57. private bool disposed;
  58. internal bool isFabric;
  59. /// <summary>
  60. /// For pooled connections, time when the driver was
  61. /// put into idle queue
  62. /// </summary>
  63. public DateTime IdleSince
  64. {
  65. get { return idleSince; }
  66. set { idleSince = value; }
  67. }
  68. public Driver(MySqlConnectionStringBuilder settings)
  69. {
  70. encoding = Encoding.GetEncoding("UTF-8");
  71. if (encoding == null)
  72. throw new MySqlException(Resources.DefaultEncodingNotFound);
  73. connectionString = settings;
  74. serverCharSet = "utf8";
  75. serverCharSetIndex = -1;
  76. maxPacketSize = 1024;
  77. handler = new NativeDriver(this);
  78. }
  79. ~Driver()
  80. {
  81. Dispose(false);
  82. }
  83. #region Properties
  84. public int ThreadID
  85. {
  86. get { return handler.ThreadId; }
  87. }
  88. public DBVersion Version
  89. {
  90. get { return handler.Version; }
  91. }
  92. public MySqlConnectionStringBuilder Settings
  93. {
  94. get { return connectionString; }
  95. set { connectionString = value; }
  96. }
  97. public Encoding Encoding
  98. {
  99. get { return encoding; }
  100. set { encoding = value; }
  101. }
  102. public MySqlPromotableTransaction CurrentTransaction
  103. {
  104. get { return currentTransaction; }
  105. set { currentTransaction = value; }
  106. }
  107. public bool IsInActiveUse
  108. {
  109. get { return inActiveUse; }
  110. set { inActiveUse = value; }
  111. }
  112. public bool IsOpen
  113. {
  114. get { return isOpen; }
  115. }
  116. public MySqlPool Pool
  117. {
  118. get { return pool; }
  119. set { pool = value; }
  120. }
  121. public long MaxPacketSize
  122. {
  123. get { return maxPacketSize; }
  124. }
  125. internal int ConnectionCharSetIndex
  126. {
  127. get { return serverCharSetIndex; }
  128. set { serverCharSetIndex = value; }
  129. }
  130. internal Dictionary<int, string> CharacterSets
  131. {
  132. get { return charSets; }
  133. }
  134. public bool SupportsOutputParameters
  135. {
  136. get { return Version.isAtLeast(5, 5, 0); }
  137. }
  138. public bool SupportsBatch
  139. {
  140. get { return (handler.Flags & ClientFlags.MULTI_STATEMENTS) != 0; }
  141. }
  142. public bool SupportsConnectAttrs
  143. {
  144. get { return (handler.Flags & ClientFlags.CONNECT_ATTRS) != 0; }
  145. }
  146. public bool SupportsPasswordExpiration
  147. {
  148. get { return (handler.Flags & ClientFlags.CAN_HANDLE_EXPIRED_PASSWORD) != 0; }
  149. }
  150. public bool IsPasswordExpired { get; internal set; }
  151. #endregion
  152. public string Property(string key)
  153. {
  154. return (string)serverProps[key];
  155. }
  156. public bool ConnectionLifetimeExpired()
  157. {
  158. TimeSpan ts = DateTime.Now.Subtract(creationTime);
  159. if (Settings.ConnectionLifeTime != 0 &&
  160. ts.TotalSeconds > Settings.ConnectionLifeTime)
  161. return true;
  162. return false;
  163. }
  164. public static Driver Create(MySqlConnectionStringBuilder settings)
  165. {
  166. Driver d = null;
  167. #if !RT
  168. try
  169. {
  170. if (MySqlTrace.QueryAnalysisEnabled || settings.Logging || settings.UseUsageAdvisor)
  171. d = new TracingDriver(settings);
  172. }
  173. catch (TypeInitializationException ex)
  174. {
  175. if (!(ex.InnerException is SecurityException))
  176. throw ex;
  177. //Only rethrow if InnerException is not a SecurityException. If it is a SecurityException then
  178. //we couldn't initialize MySqlTrace because we don't have unmanaged code permissions.
  179. }
  180. #else
  181. if (settings.Logging || settings.UseUsageAdvisor)
  182. {
  183. throw new NotImplementedException( "Logging not supported in this WinRT release." );
  184. }
  185. #endif
  186. if (d == null)
  187. d = new Driver(settings);
  188. //this try was added as suggested fix submitted on MySql Bug 72025, socket connections are left in CLOSE_WAIT status when connector fails to open a new connection.
  189. //the bug is present when the client try to get more connections that the server support or has configured in the max_connections variable.
  190. try
  191. {
  192. d.Open();
  193. }
  194. catch
  195. {
  196. d.Dispose();
  197. throw;
  198. }
  199. return d;
  200. }
  201. public bool HasStatus(ServerStatusFlags flag)
  202. {
  203. return (handler.ServerStatus & flag) != 0;
  204. }
  205. public virtual void Open()
  206. {
  207. creationTime = DateTime.Now;
  208. handler.Open();
  209. isOpen = true;
  210. }
  211. public virtual void Close()
  212. {
  213. Dispose();
  214. }
  215. public virtual void Configure(MySqlConnection connection)
  216. {
  217. bool firstConfigure = false;
  218. // if we have not already configured our server variables
  219. // then do so now
  220. if (serverProps == null)
  221. {
  222. firstConfigure = true;
  223. // if we are in a pool and the user has said it's ok to cache the
  224. // properties, then grab it from the pool
  225. try
  226. {
  227. if (Pool != null && Settings.CacheServerProperties)
  228. {
  229. if (Pool.ServerProperties == null)
  230. Pool.ServerProperties = LoadServerProperties(connection);
  231. serverProps = Pool.ServerProperties;
  232. }
  233. else
  234. serverProps = LoadServerProperties(connection);
  235. LoadCharacterSets(connection);
  236. }
  237. catch (MySqlException ex)
  238. {
  239. // expired password capability
  240. if (ex.Number == 1820)
  241. {
  242. IsPasswordExpired = true;
  243. return;
  244. }
  245. throw;
  246. }
  247. }
  248. #if AUTHENTICATED
  249. string licenseType = serverProps["license"];
  250. if (licenseType == null || licenseType.Length == 0 ||
  251. licenseType != "commercial")
  252. throw new MySqlException( "This client library licensed only for use with commercially-licensed MySQL servers." );
  253. #endif
  254. // if the user has indicated that we are not to reset
  255. // the connection and this is not our first time through,
  256. // then we are done.
  257. if (!Settings.ConnectionReset && !firstConfigure) return;
  258. string charSet = connectionString.CharacterSet;
  259. if (charSet == null || charSet.Length == 0)
  260. {
  261. if (serverCharSetIndex >= 0 && charSets.ContainsKey(serverCharSetIndex))
  262. charSet = (string)charSets[serverCharSetIndex];
  263. else
  264. charSet = serverCharSet;
  265. }
  266. if (serverProps.ContainsKey("max_allowed_packet"))
  267. maxPacketSize = Convert.ToInt64(serverProps["max_allowed_packet"]);
  268. // now tell the server which character set we will send queries in and which charset we
  269. // want results in
  270. MySqlCommand charSetCmd = new MySqlCommand("SET character_set_results=NULL",
  271. connection);
  272. charSetCmd.InternallyCreated = true;
  273. string clientCharSet;
  274. serverProps.TryGetValue("character_set_client", out clientCharSet);
  275. string connCharSet;
  276. serverProps.TryGetValue("character_set_connection", out connCharSet);
  277. if ((clientCharSet != null && clientCharSet.ToString() != charSet) ||
  278. (connCharSet != null && connCharSet.ToString() != charSet))
  279. {
  280. MySqlCommand setNamesCmd = new MySqlCommand("SET NAMES " + charSet, connection);
  281. setNamesCmd.InternallyCreated = true;
  282. setNamesCmd.ExecuteNonQuery();
  283. }
  284. // sets character_set_results to null to return values in their original character set
  285. charSetCmd.ExecuteNonQuery();
  286. if (charSet != null)
  287. Encoding = CharSetMap.GetEncoding(Version, charSet);
  288. else
  289. Encoding = CharSetMap.GetEncoding(Version, "utf-8");
  290. handler.Configure();
  291. }
  292. /// <summary>
  293. /// Loads the properties from the connected server into a hashtable
  294. /// </summary>
  295. /// <param name="connection"></param>
  296. /// <returns></returns>
  297. private Dictionary<string, string> LoadServerProperties(MySqlConnection connection)
  298. {
  299. // load server properties
  300. Dictionary<string, string> hash = new Dictionary<string, string>();
  301. MySqlCommand cmd = new MySqlCommand("SHOW VARIABLES", connection);
  302. try
  303. {
  304. using (MySqlDataReader reader = cmd.ExecuteReader())
  305. {
  306. while (reader.Read())
  307. {
  308. string key = reader.GetString(0);
  309. string value = reader.GetString(1);
  310. hash[key] = value;
  311. }
  312. }
  313. // Get time zone offset as numerical value
  314. timeZoneOffset = GetTimeZoneOffset(connection);
  315. return hash;
  316. }
  317. catch (Exception ex)
  318. {
  319. MySqlTrace.LogError(ThreadID, ex.Message);
  320. throw;
  321. }
  322. }
  323. private int GetTimeZoneOffset(MySqlConnection con)
  324. {
  325. MySqlCommand cmd = new MySqlCommand("SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP())", con);
  326. TimeSpan? timeZoneDiff = cmd.ExecuteScalar() as TimeSpan?;
  327. string timeZoneString = "0:00";
  328. if (timeZoneDiff.HasValue)
  329. timeZoneString = timeZoneDiff.ToString();
  330. return int.Parse(timeZoneString.Substring(0, timeZoneString.IndexOf(':')));
  331. }
  332. /// <summary>
  333. /// Loads all the current character set names and ids for this server
  334. /// into the charSets hashtable
  335. /// </summary>
  336. private void LoadCharacterSets(MySqlConnection connection)
  337. {
  338. MySqlCommand cmd = new MySqlCommand("SHOW COLLATION", connection);
  339. // now we load all the currently active collations
  340. try
  341. {
  342. using (MySqlDataReader reader = cmd.ExecuteReader())
  343. {
  344. charSets = new Dictionary<int, string>();
  345. while (reader.Read())
  346. {
  347. charSets[Convert.ToInt32(reader["id"], NumberFormatInfo.InvariantInfo)] =
  348. reader.GetString(reader.GetOrdinal("charset"));
  349. }
  350. }
  351. }
  352. catch (Exception ex)
  353. {
  354. MySqlTrace.LogError(ThreadID, ex.Message);
  355. throw;
  356. }
  357. }
  358. public virtual List<MySqlError> ReportWarnings(MySqlConnection connection)
  359. {
  360. List<MySqlError> warnings = new List<MySqlError>();
  361. MySqlCommand cmd = new MySqlCommand("SHOW WARNINGS", connection);
  362. cmd.InternallyCreated = true;
  363. using (MySqlDataReader reader = cmd.ExecuteReader())
  364. {
  365. while (reader.Read())
  366. {
  367. warnings.Add(new MySqlError(reader.GetString(0),
  368. reader.GetInt32(1), reader.GetString(2)));
  369. }
  370. }
  371. MySqlInfoMessageEventArgs args = new MySqlInfoMessageEventArgs();
  372. args.errors = warnings.ToArray();
  373. if (connection != null)
  374. connection.OnInfoMessage(args);
  375. return warnings;
  376. }
  377. public virtual void SendQuery(MySqlPacket p)
  378. {
  379. handler.SendQuery(p);
  380. firstResult = true;
  381. }
  382. public virtual ResultSet NextResult(int statementId, bool force)
  383. {
  384. if (!force && !firstResult && !HasStatus(ServerStatusFlags.AnotherQuery | ServerStatusFlags.MoreResults))
  385. return null;
  386. firstResult = false;
  387. int affectedRows = -1; // , warnings = 0;
  388. long insertedId = -1;
  389. int fieldCount = GetResult(statementId, ref affectedRows, ref insertedId);
  390. if (fieldCount == -1)
  391. return null;
  392. if (fieldCount > 0)
  393. return new ResultSet(this, statementId, fieldCount);
  394. else
  395. return new ResultSet(affectedRows, insertedId);
  396. }
  397. protected virtual int GetResult(int statementId, ref int affectedRows, ref long insertedId)
  398. {
  399. return handler.GetResult(ref affectedRows, ref insertedId);
  400. }
  401. public virtual bool FetchDataRow(int statementId, int columns)
  402. {
  403. return handler.FetchDataRow(statementId, columns);
  404. }
  405. public virtual bool SkipDataRow()
  406. {
  407. return FetchDataRow(-1, 0);
  408. }
  409. public virtual void ExecuteDirect(string sql)
  410. {
  411. MySqlPacket p = new MySqlPacket(Encoding);
  412. p.WriteString(sql);
  413. SendQuery(p);
  414. NextResult(0, false);
  415. }
  416. public MySqlField[] GetColumns(int count)
  417. {
  418. MySqlField[] fields = new MySqlField[count];
  419. for (int i = 0; i < count; i++)
  420. fields[i] = new MySqlField(this);
  421. handler.GetColumnsData(fields);
  422. return fields;
  423. }
  424. public virtual int PrepareStatement(string sql, ref MySqlField[] parameters)
  425. {
  426. return handler.PrepareStatement(sql, ref parameters);
  427. }
  428. public IMySqlValue ReadColumnValue(int index, MySqlField field, IMySqlValue value)
  429. {
  430. return handler.ReadColumnValue(index, field, value);
  431. }
  432. public void SkipColumnValue(IMySqlValue valObject)
  433. {
  434. handler.SkipColumnValue(valObject);
  435. }
  436. public void ResetTimeout(int timeoutMilliseconds)
  437. {
  438. handler.ResetTimeout(timeoutMilliseconds);
  439. }
  440. public bool Ping()
  441. {
  442. return handler.Ping();
  443. }
  444. public virtual void SetDatabase(string dbName)
  445. {
  446. handler.SetDatabase(dbName);
  447. }
  448. public virtual void ExecuteStatement(MySqlPacket packetToExecute)
  449. {
  450. handler.ExecuteStatement(packetToExecute);
  451. }
  452. public virtual void CloseStatement(int id)
  453. {
  454. handler.CloseStatement(id);
  455. }
  456. public virtual void Reset()
  457. {
  458. handler.Reset();
  459. }
  460. public virtual void CloseQuery(MySqlConnection connection, int statementId)
  461. {
  462. if (handler.WarningCount > 0)
  463. ReportWarnings(connection);
  464. }
  465. #region IDisposable Members
  466. protected virtual void Dispose(bool disposing)
  467. {
  468. if (disposed)
  469. return;
  470. // Avoid cyclic calls to Dispose.
  471. disposed = true;
  472. try
  473. {
  474. ResetTimeout(1000);
  475. handler.Close(isOpen);
  476. // if we are pooling, then release ourselves
  477. if (connectionString.Pooling)
  478. MySqlPoolManager.RemoveConnection(this);
  479. }
  480. catch (Exception ex)
  481. {
  482. if (disposing)
  483. {
  484. MySqlException mysqlEx = ex as MySqlException;
  485. if (mysqlEx == null)
  486. MySqlTrace.LogError(0, ex.GetBaseException().Message);
  487. else
  488. MySqlTrace.LogError(mysqlEx.Number, ex.GetBaseException().Message);
  489. }
  490. }
  491. finally
  492. {
  493. reader = null;
  494. isOpen = false;
  495. }
  496. }
  497. public void Dispose()
  498. {
  499. Dispose(true);
  500. GC.SuppressFinalize(this);
  501. }
  502. #endregion
  503. }
  504. internal interface IDriver
  505. {
  506. int ThreadId { get; }
  507. DBVersion Version { get; }
  508. ServerStatusFlags ServerStatus { get; }
  509. ClientFlags Flags { get; }
  510. void Configure();
  511. void Open();
  512. void SendQuery(MySqlPacket packet);
  513. void Close(bool isOpen);
  514. bool Ping();
  515. int GetResult(ref int affectedRows, ref long insertedId);
  516. bool FetchDataRow(int statementId, int columns);
  517. int PrepareStatement(string sql, ref MySqlField[] parameters);
  518. void ExecuteStatement(MySqlPacket packet);
  519. void CloseStatement(int statementId);
  520. void SetDatabase(string dbName);
  521. void Reset();
  522. IMySqlValue ReadColumnValue(int index, MySqlField field, IMySqlValue valObject);
  523. void SkipColumnValue(IMySqlValue valueObject);
  524. void GetColumnsData(MySqlField[] columns);
  525. void ResetTimeout(int timeout);
  526. int WarningCount { get; }
  527. }
  528. }
  529. #endif