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.

554 lines
18 KiB

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