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
608 lines
20 KiB
#if MYSQL_6_9
|
|
|
|
// Copyright ?2004, 2017 Oracle and/or its affiliates. All rights reserved.
|
|
//
|
|
// MySQL Connector/NET is licensed under the terms of the GPLv2
|
|
// <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
|
|
// MySQL Connectors. There are special exceptions to the terms and
|
|
// conditions of the GPLv2 as it is applied to this software, see the
|
|
// FLOSS License Exception
|
|
// <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published
|
|
// by the Free Software Foundation; version 2 of the License.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
// for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along
|
|
// with this program; if not, write to the Free Software Foundation, Inc.,
|
|
// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Globalization;
|
|
using System.Text;
|
|
using Externals.MySql.Data.Common;
|
|
using Externals.MySql.Data.Types;
|
|
using Externals.MySql.Data.MySqlClient.Properties;
|
|
using System.Diagnostics;
|
|
using System.Collections.Generic;
|
|
using System.Security;
|
|
|
|
namespace Externals.MySql.Data.MySqlClient
|
|
{
|
|
/// <summary>
|
|
/// Summary description for BaseDriver.
|
|
/// </summary>
|
|
internal class Driver : IDisposable
|
|
{
|
|
protected Encoding encoding;
|
|
protected MySqlConnectionStringBuilder connectionString;
|
|
protected bool isOpen;
|
|
protected DateTime creationTime;
|
|
protected string serverCharSet;
|
|
protected int serverCharSetIndex;
|
|
protected Dictionary<string, string> serverProps;
|
|
protected Dictionary<int, string> charSets;
|
|
protected long maxPacketSize;
|
|
internal int timeZoneOffset;
|
|
private DateTime idleSince;
|
|
|
|
protected MySqlPromotableTransaction currentTransaction;
|
|
protected bool inActiveUse;
|
|
protected MySqlPool pool;
|
|
private bool firstResult;
|
|
protected IDriver handler;
|
|
internal MySqlDataReader reader;
|
|
private bool disposed;
|
|
internal bool isFabric;
|
|
|
|
/// <summary>
|
|
/// For pooled connections, time when the driver was
|
|
/// put into idle queue
|
|
/// </summary>
|
|
public DateTime IdleSince
|
|
{
|
|
get { return idleSince; }
|
|
set { idleSince = value; }
|
|
}
|
|
|
|
public Driver(MySqlConnectionStringBuilder settings)
|
|
{
|
|
encoding = Encoding.GetEncoding("UTF-8");
|
|
if (encoding == null)
|
|
throw new MySqlException(Resources.DefaultEncodingNotFound);
|
|
connectionString = settings;
|
|
serverCharSet = "utf8";
|
|
serverCharSetIndex = -1;
|
|
maxPacketSize = 1024;
|
|
handler = new NativeDriver(this);
|
|
}
|
|
|
|
~Driver()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
#region Properties
|
|
|
|
public int ThreadID
|
|
{
|
|
get { return handler.ThreadId; }
|
|
}
|
|
|
|
public DBVersion Version
|
|
{
|
|
get { return handler.Version; }
|
|
}
|
|
|
|
public MySqlConnectionStringBuilder Settings
|
|
{
|
|
get { return connectionString; }
|
|
set { connectionString = value; }
|
|
}
|
|
|
|
public Encoding Encoding
|
|
{
|
|
get { return encoding; }
|
|
set { encoding = value; }
|
|
}
|
|
|
|
public MySqlPromotableTransaction CurrentTransaction
|
|
{
|
|
get { return currentTransaction; }
|
|
set { currentTransaction = value; }
|
|
}
|
|
|
|
public bool IsInActiveUse
|
|
{
|
|
get { return inActiveUse; }
|
|
set { inActiveUse = value; }
|
|
}
|
|
|
|
public bool IsOpen
|
|
{
|
|
get { return isOpen; }
|
|
}
|
|
|
|
public MySqlPool Pool
|
|
{
|
|
get { return pool; }
|
|
set { pool = value; }
|
|
}
|
|
|
|
public long MaxPacketSize
|
|
{
|
|
get { return maxPacketSize; }
|
|
}
|
|
|
|
internal int ConnectionCharSetIndex
|
|
{
|
|
get { return serverCharSetIndex; }
|
|
set { serverCharSetIndex = value; }
|
|
}
|
|
|
|
internal Dictionary<int, string> CharacterSets
|
|
{
|
|
get { return charSets; }
|
|
}
|
|
|
|
public bool SupportsOutputParameters
|
|
{
|
|
get { return Version.isAtLeast(5, 5, 0); }
|
|
}
|
|
|
|
public bool SupportsBatch
|
|
{
|
|
get { return (handler.Flags & ClientFlags.MULTI_STATEMENTS) != 0; }
|
|
}
|
|
|
|
public bool SupportsConnectAttrs
|
|
{
|
|
get { return (handler.Flags & ClientFlags.CONNECT_ATTRS) != 0; }
|
|
}
|
|
|
|
public bool SupportsPasswordExpiration
|
|
{
|
|
get { return (handler.Flags & ClientFlags.CAN_HANDLE_EXPIRED_PASSWORD) != 0; }
|
|
}
|
|
|
|
public bool IsPasswordExpired { get; internal set; }
|
|
|
|
#endregion
|
|
|
|
public string Property(string key)
|
|
{
|
|
return (string)serverProps[key];
|
|
}
|
|
|
|
public bool ConnectionLifetimeExpired()
|
|
{
|
|
TimeSpan ts = DateTime.Now.Subtract(creationTime);
|
|
if (Settings.ConnectionLifeTime != 0 &&
|
|
ts.TotalSeconds > Settings.ConnectionLifeTime)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
public static Driver Create(MySqlConnectionStringBuilder settings)
|
|
{
|
|
Driver d = null;
|
|
#if !RT
|
|
try
|
|
{
|
|
if (MySqlTrace.QueryAnalysisEnabled || settings.Logging || settings.UseUsageAdvisor)
|
|
d = new TracingDriver(settings);
|
|
}
|
|
catch (TypeInitializationException ex)
|
|
{
|
|
if (!(ex.InnerException is SecurityException))
|
|
throw ex;
|
|
//Only rethrow if InnerException is not a SecurityException. If it is a SecurityException then
|
|
//we couldn't initialize MySqlTrace because we don't have unmanaged code permissions.
|
|
}
|
|
#else
|
|
if (settings.Logging || settings.UseUsageAdvisor)
|
|
{
|
|
throw new NotImplementedException( "Logging not supported in this WinRT release." );
|
|
}
|
|
#endif
|
|
if (d == null)
|
|
d = new Driver(settings);
|
|
|
|
//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.
|
|
//the bug is present when the client try to get more connections that the server support or has configured in the max_connections variable.
|
|
try
|
|
{
|
|
d.Open();
|
|
}
|
|
catch
|
|
{
|
|
d.Dispose();
|
|
throw;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
public bool HasStatus(ServerStatusFlags flag)
|
|
{
|
|
return (handler.ServerStatus & flag) != 0;
|
|
}
|
|
|
|
public virtual void Open()
|
|
{
|
|
creationTime = DateTime.Now;
|
|
handler.Open();
|
|
isOpen = true;
|
|
}
|
|
|
|
public virtual void Close()
|
|
{
|
|
Dispose();
|
|
}
|
|
|
|
public virtual void Configure(MySqlConnection connection)
|
|
{
|
|
bool firstConfigure = false;
|
|
|
|
// if we have not already configured our server variables
|
|
// then do so now
|
|
if (serverProps == null)
|
|
{
|
|
firstConfigure = true;
|
|
|
|
// if we are in a pool and the user has said it's ok to cache the
|
|
// properties, then grab it from the pool
|
|
try
|
|
{
|
|
if (Pool != null && Settings.CacheServerProperties)
|
|
{
|
|
if (Pool.ServerProperties == null)
|
|
Pool.ServerProperties = LoadServerProperties(connection);
|
|
serverProps = Pool.ServerProperties;
|
|
}
|
|
else
|
|
serverProps = LoadServerProperties(connection);
|
|
|
|
LoadCharacterSets(connection);
|
|
}
|
|
catch (MySqlException ex)
|
|
{
|
|
// expired password capability
|
|
if (ex.Number == 1820)
|
|
{
|
|
IsPasswordExpired = true;
|
|
return;
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
|
|
|
|
#if AUTHENTICATED
|
|
string licenseType = serverProps["license"];
|
|
if (licenseType == null || licenseType.Length == 0 ||
|
|
licenseType != "commercial")
|
|
throw new MySqlException( "This client library licensed only for use with commercially-licensed MySQL servers." );
|
|
#endif
|
|
// if the user has indicated that we are not to reset
|
|
// the connection and this is not our first time through,
|
|
// then we are done.
|
|
if (!Settings.ConnectionReset && !firstConfigure) return;
|
|
|
|
string charSet = connectionString.CharacterSet;
|
|
if (charSet == null || charSet.Length == 0)
|
|
{
|
|
if (serverCharSetIndex >= 0 && charSets.ContainsKey(serverCharSetIndex))
|
|
charSet = (string)charSets[serverCharSetIndex];
|
|
else
|
|
charSet = serverCharSet;
|
|
}
|
|
|
|
if (serverProps.ContainsKey("max_allowed_packet"))
|
|
maxPacketSize = Convert.ToInt64(serverProps["max_allowed_packet"]);
|
|
|
|
// now tell the server which character set we will send queries in and which charset we
|
|
// want results in
|
|
MySqlCommand charSetCmd = new MySqlCommand("SET character_set_results=NULL",
|
|
connection);
|
|
charSetCmd.InternallyCreated = true;
|
|
|
|
string clientCharSet;
|
|
serverProps.TryGetValue("character_set_client", out clientCharSet);
|
|
string connCharSet;
|
|
serverProps.TryGetValue("character_set_connection", out connCharSet);
|
|
if ((clientCharSet != null && clientCharSet.ToString() != charSet) ||
|
|
(connCharSet != null && connCharSet.ToString() != charSet))
|
|
{
|
|
MySqlCommand setNamesCmd = new MySqlCommand("SET NAMES " + charSet, connection);
|
|
setNamesCmd.InternallyCreated = true;
|
|
setNamesCmd.ExecuteNonQuery();
|
|
}
|
|
// sets character_set_results to null to return values in their original character set
|
|
charSetCmd.ExecuteNonQuery();
|
|
|
|
if (charSet != null)
|
|
Encoding = CharSetMap.GetEncoding(Version, charSet);
|
|
else
|
|
Encoding = CharSetMap.GetEncoding(Version, "utf-8");
|
|
|
|
handler.Configure();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the properties from the connected server into a hashtable
|
|
/// </summary>
|
|
/// <param name="connection"></param>
|
|
/// <returns></returns>
|
|
private Dictionary<string, string> LoadServerProperties(MySqlConnection connection)
|
|
{
|
|
// load server properties
|
|
Dictionary<string, string> hash = new Dictionary<string, string>();
|
|
MySqlCommand cmd = new MySqlCommand("SHOW VARIABLES", connection);
|
|
try
|
|
{
|
|
using (MySqlDataReader reader = cmd.ExecuteReader())
|
|
{
|
|
while (reader.Read())
|
|
{
|
|
string key = reader.GetString(0);
|
|
string value = reader.GetString(1);
|
|
hash[key] = value;
|
|
}
|
|
}
|
|
// Get time zone offset as numerical value
|
|
timeZoneOffset = GetTimeZoneOffset(connection);
|
|
return hash;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MySqlTrace.LogError(ThreadID, ex.Message);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private int GetTimeZoneOffset(MySqlConnection con)
|
|
{
|
|
MySqlCommand cmd = new MySqlCommand("SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP())", con);
|
|
TimeSpan? timeZoneDiff = cmd.ExecuteScalar() as TimeSpan?;
|
|
string timeZoneString = "0:00";
|
|
if (timeZoneDiff.HasValue)
|
|
timeZoneString = timeZoneDiff.ToString();
|
|
|
|
return int.Parse(timeZoneString.Substring(0, timeZoneString.IndexOf(':')));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads all the current character set names and ids for this server
|
|
/// into the charSets hashtable
|
|
/// </summary>
|
|
private void LoadCharacterSets(MySqlConnection connection)
|
|
{
|
|
MySqlCommand cmd = new MySqlCommand("SHOW COLLATION", connection);
|
|
|
|
// now we load all the currently active collations
|
|
try
|
|
{
|
|
using (MySqlDataReader reader = cmd.ExecuteReader())
|
|
{
|
|
charSets = new Dictionary<int, string>();
|
|
while (reader.Read())
|
|
{
|
|
charSets[Convert.ToInt32(reader["id"], NumberFormatInfo.InvariantInfo)] =
|
|
reader.GetString(reader.GetOrdinal("charset"));
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MySqlTrace.LogError(ThreadID, ex.Message);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public virtual List<MySqlError> ReportWarnings(MySqlConnection connection)
|
|
{
|
|
List<MySqlError> warnings = new List<MySqlError>();
|
|
|
|
MySqlCommand cmd = new MySqlCommand("SHOW WARNINGS", connection);
|
|
cmd.InternallyCreated = true;
|
|
using (MySqlDataReader reader = cmd.ExecuteReader())
|
|
{
|
|
while (reader.Read())
|
|
{
|
|
warnings.Add(new MySqlError(reader.GetString(0),
|
|
reader.GetInt32(1), reader.GetString(2)));
|
|
}
|
|
}
|
|
|
|
MySqlInfoMessageEventArgs args = new MySqlInfoMessageEventArgs();
|
|
args.errors = warnings.ToArray();
|
|
if (connection != null)
|
|
connection.OnInfoMessage(args);
|
|
return warnings;
|
|
}
|
|
|
|
public virtual void SendQuery(MySqlPacket p)
|
|
{
|
|
handler.SendQuery(p);
|
|
firstResult = true;
|
|
}
|
|
|
|
public virtual ResultSet NextResult(int statementId, bool force)
|
|
{
|
|
if (!force && !firstResult && !HasStatus(ServerStatusFlags.AnotherQuery | ServerStatusFlags.MoreResults))
|
|
return null;
|
|
firstResult = false;
|
|
|
|
int affectedRows = -1; // , warnings = 0;
|
|
long insertedId = -1;
|
|
int fieldCount = GetResult(statementId, ref affectedRows, ref insertedId);
|
|
if (fieldCount == -1)
|
|
return null;
|
|
if (fieldCount > 0)
|
|
return new ResultSet(this, statementId, fieldCount);
|
|
else
|
|
return new ResultSet(affectedRows, insertedId);
|
|
}
|
|
|
|
protected virtual int GetResult(int statementId, ref int affectedRows, ref long insertedId)
|
|
{
|
|
return handler.GetResult(ref affectedRows, ref insertedId);
|
|
}
|
|
|
|
public virtual bool FetchDataRow(int statementId, int columns)
|
|
{
|
|
return handler.FetchDataRow(statementId, columns);
|
|
}
|
|
|
|
public virtual bool SkipDataRow()
|
|
{
|
|
return FetchDataRow(-1, 0);
|
|
}
|
|
|
|
public virtual void ExecuteDirect(string sql)
|
|
{
|
|
MySqlPacket p = new MySqlPacket(Encoding);
|
|
p.WriteString(sql);
|
|
SendQuery(p);
|
|
NextResult(0, false);
|
|
}
|
|
|
|
public MySqlField[] GetColumns(int count)
|
|
{
|
|
MySqlField[] fields = new MySqlField[count];
|
|
for (int i = 0; i < count; i++)
|
|
fields[i] = new MySqlField(this);
|
|
handler.GetColumnsData(fields);
|
|
|
|
return fields;
|
|
}
|
|
|
|
public virtual int PrepareStatement(string sql, ref MySqlField[] parameters)
|
|
{
|
|
return handler.PrepareStatement(sql, ref parameters);
|
|
}
|
|
|
|
public IMySqlValue ReadColumnValue(int index, MySqlField field, IMySqlValue value)
|
|
{
|
|
return handler.ReadColumnValue(index, field, value);
|
|
}
|
|
|
|
public void SkipColumnValue(IMySqlValue valObject)
|
|
{
|
|
handler.SkipColumnValue(valObject);
|
|
}
|
|
|
|
public void ResetTimeout(int timeoutMilliseconds)
|
|
{
|
|
handler.ResetTimeout(timeoutMilliseconds);
|
|
}
|
|
|
|
public bool Ping()
|
|
{
|
|
return handler.Ping();
|
|
}
|
|
|
|
public virtual void SetDatabase(string dbName)
|
|
{
|
|
handler.SetDatabase(dbName);
|
|
}
|
|
|
|
public virtual void ExecuteStatement(MySqlPacket packetToExecute)
|
|
{
|
|
handler.ExecuteStatement(packetToExecute);
|
|
}
|
|
|
|
|
|
public virtual void CloseStatement(int id)
|
|
{
|
|
handler.CloseStatement(id);
|
|
}
|
|
|
|
public virtual void Reset()
|
|
{
|
|
handler.Reset();
|
|
}
|
|
|
|
public virtual void CloseQuery(MySqlConnection connection, int statementId)
|
|
{
|
|
if (handler.WarningCount > 0)
|
|
ReportWarnings(connection);
|
|
}
|
|
|
|
#region IDisposable Members
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (disposed)
|
|
return;
|
|
|
|
// Avoid cyclic calls to Dispose.
|
|
disposed = true;
|
|
try
|
|
{
|
|
ResetTimeout(1000);
|
|
handler.Close(isOpen);
|
|
// if we are pooling, then release ourselves
|
|
if (connectionString.Pooling)
|
|
MySqlPoolManager.RemoveConnection(this);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (disposing)
|
|
{
|
|
MySqlException mysqlEx = ex as MySqlException;
|
|
if (mysqlEx == null)
|
|
MySqlTrace.LogError(0, ex.GetBaseException().Message);
|
|
else
|
|
MySqlTrace.LogError(mysqlEx.Number, ex.GetBaseException().Message);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
reader = null;
|
|
isOpen = false;
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
internal interface IDriver
|
|
{
|
|
int ThreadId { get; }
|
|
DBVersion Version { get; }
|
|
ServerStatusFlags ServerStatus { get; }
|
|
ClientFlags Flags { get; }
|
|
void Configure();
|
|
void Open();
|
|
void SendQuery(MySqlPacket packet);
|
|
void Close(bool isOpen);
|
|
bool Ping();
|
|
int GetResult(ref int affectedRows, ref long insertedId);
|
|
bool FetchDataRow(int statementId, int columns);
|
|
int PrepareStatement(string sql, ref MySqlField[] parameters);
|
|
void ExecuteStatement(MySqlPacket packet);
|
|
void CloseStatement(int statementId);
|
|
void SetDatabase(string dbName);
|
|
void Reset();
|
|
IMySqlValue ReadColumnValue(int index, MySqlField field, IMySqlValue valObject);
|
|
void SkipColumnValue(IMySqlValue valueObject);
|
|
void GetColumnsData(MySqlField[] columns);
|
|
void ResetTimeout(int timeout);
|
|
int WarningCount { get; }
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|