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

#if MYSQL_6_10
// Copyright (c) 2004, 2019, 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.Generic;
using System.Globalization;
using System.Security;
using System.Text;
using Externals.MySql.Data.Common;
using Externals.MySql.Data.Types;
using System.IO;
namespace Externals.MySql.Data.MySqlClient
{
/// <summary>
/// Summary description for BaseDriver.
/// </summary>
internal partial class Driver : IDisposable
{
protected Encoding encoding;
protected MySqlConnectionStringBuilder ConnectionString;
protected DateTime creationTime;
protected string serverCharSet;
protected Dictionary<string, string> serverProps;
internal int timeZoneOffset;
private bool firstResult;
protected IDriver handler;
internal MySqlDataReader reader;
private bool disposed;
/// <summary>
/// For pooled connections, time when the driver was
/// put into idle queue
/// </summary>
public DateTime IdleSince { get; set; }
public Driver(MySqlConnectionStringBuilder settings)
{
encoding = Encoding.GetEncoding("UTF-8");
if (encoding == null)
throw new MySqlException(Resources.DefaultEncodingNotFound);
ConnectionString = settings;
serverCharSet = "utf8";
ConnectionCharSetIndex = -1;
MaxPacketSize = 1024;
handler = new NativeDriver(this);
}
~Driver()
{
Dispose(false);
}
#region Properties
public int ThreadID => handler.ThreadId;
public DBVersion Version => handler.Version;
public MySqlConnectionStringBuilder Settings
{
get { return ConnectionString; }
set { ConnectionString = value; }
}
public Encoding Encoding
{
get { return encoding; }
set { encoding = value; }
}
#if !NETSTANDARD1_3
public MySqlPromotableTransaction currentTransaction { get; set; }
public bool IsInActiveUse { get; set; }
#endif
public bool IsOpen { get; protected set; }
public MySqlPool Pool { get; set; }
public long MaxPacketSize { get; protected set; }
protected internal int ConnectionCharSetIndex { get; set; }
protected internal Dictionary<int, string> CharacterSets { get; protected set; }
public bool SupportsOutputParameters => Version.isAtLeast(5, 5, 0);
public bool SupportsBatch => (handler.Flags & ClientFlags.MULTI_STATEMENTS) != 0;
public bool SupportsConnectAttrs => (handler.Flags & ClientFlags.CONNECT_ATTRS) != 0;
public bool SupportsPasswordExpiration => (handler.Flags & ClientFlags.CAN_HANDLE_EXPIRED_PASSWORD) != 0;
public bool IsPasswordExpired { get; internal set; }
#endregion
public string Property(string key)
{
return serverProps[key];
}
public bool ConnectionLifetimeExpired()
{
TimeSpan ts = DateTime.Now.Subtract(creationTime);
return Settings.ConnectionLifeTime != 0 &&
ts.TotalSeconds > Settings.ConnectionLifeTime;
}
public static Driver Create(MySqlConnectionStringBuilder settings)
{
Driver d = null;
try
{
#if !NETSTANDARD1_3
if (MySqlTrace.QueryAnalysisEnabled || settings.Logging || settings.UseUsageAdvisor)
d = new TracingDriver(settings);
#endif
}
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.
}
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()
{
int count = 0;
do
{
try
{
creationTime = DateTime.Now;
handler.Open();
IsOpen = true;
break;
}
catch (IOException)
{
if (count++ >= 5) throw;
}
} while (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 (string.IsNullOrEmpty(charSet))
{
if (ConnectionCharSetIndex >= 0 && CharacterSets.ContainsKey(ConnectionCharSetIndex))
charSet = CharacterSets[ConnectionCharSetIndex];
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)
{ 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();
Encoding = CharSetMap.GetEncoding(Version, charSet ?? "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(@"SELECT @@max_allowed_packet, @@character_set_client,
@@character_set_connection, @@license, @@sql_mode, @@lower_case_table_names;", connection);
try
{
using (MySqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
for (int i = 0; i < reader.FieldCount - 1; i++)
{
string key = reader.GetName(i).Remove(0, 2);
string value = reader[i].ToString();
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(':')), CultureInfo.InvariantCulture);
}
/// <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())
{
CharacterSets = new Dictionary<int, string>();
while (reader.Read())
{
CharacterSets[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) { 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();
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;
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