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
927 lines
28 KiB
#if MYSQL_6_9
|
|
|
|
// Copyright � 2004, 2018 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.Diagnostics;
|
|
using System.IO;
|
|
using Externals.MySql.Data.Common;
|
|
using Externals.MySql.Data.Types;
|
|
using Externals.MySql.Data.MySqlClient.Properties;
|
|
using System.Text;
|
|
using Externals.MySql.Data.MySqlClient.Authentication;
|
|
using System.Reflection;
|
|
using System.ComponentModel;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.Net.Security;
|
|
using System.Security.Authentication;
|
|
using System.Globalization;
|
|
|
|
namespace Externals.MySql.Data.MySqlClient
|
|
{
|
|
/// <summary>
|
|
/// Summary description for Driver.
|
|
/// </summary>
|
|
internal class NativeDriver : IDriver
|
|
{
|
|
private DBVersion version;
|
|
private int threadId;
|
|
protected byte[] encryptionSeed;
|
|
protected ServerStatusFlags serverStatus;
|
|
protected MySqlStream stream;
|
|
protected Stream baseStream;
|
|
private BitArray nullMap;
|
|
private MySqlPacket packet;
|
|
private ClientFlags connectionFlags;
|
|
private Driver owner;
|
|
private int warnings;
|
|
private MySqlAuthenticationPlugin authPlugin;
|
|
private bool isEnterprise;
|
|
|
|
// Windows authentication method string, used by the protocol.
|
|
// Also known as "client plugin name".
|
|
const string AuthenticationWindowsPlugin = "authentication_windows_client";
|
|
|
|
// Predefined username for IntegratedSecurity
|
|
const string AuthenticationWindowsUser = "auth_windows";
|
|
|
|
public NativeDriver(Driver owner)
|
|
{
|
|
this.owner = owner;
|
|
threadId = -1;
|
|
}
|
|
|
|
public ClientFlags Flags
|
|
{
|
|
get { return connectionFlags; }
|
|
}
|
|
|
|
public int ThreadId
|
|
{
|
|
get { return threadId; }
|
|
}
|
|
|
|
public DBVersion Version
|
|
{
|
|
get { return version; }
|
|
}
|
|
|
|
public ServerStatusFlags ServerStatus
|
|
{
|
|
get { return serverStatus; }
|
|
}
|
|
|
|
public int WarningCount
|
|
{
|
|
get { return warnings; }
|
|
}
|
|
|
|
public MySqlPacket Packet
|
|
{
|
|
get { return packet; }
|
|
}
|
|
|
|
internal MySqlConnectionStringBuilder Settings
|
|
{
|
|
get { return owner.Settings; }
|
|
}
|
|
|
|
internal Encoding Encoding
|
|
{
|
|
get { return owner.Encoding; }
|
|
}
|
|
|
|
private void HandleException(MySqlException ex)
|
|
{
|
|
if (ex.IsFatal)
|
|
owner.Close();
|
|
}
|
|
|
|
internal void SendPacket(MySqlPacket p)
|
|
{
|
|
stream.SendPacket(p);
|
|
}
|
|
|
|
internal void SendEmptyPacket()
|
|
{
|
|
byte[] buffer = new byte[4];
|
|
stream.SendEntirePacketDirectly(buffer, 0);
|
|
}
|
|
|
|
internal MySqlPacket ReadPacket()
|
|
{
|
|
return packet = stream.ReadPacket();
|
|
}
|
|
|
|
internal void ReadOk(bool read)
|
|
{
|
|
try
|
|
{
|
|
if (read)
|
|
packet = stream.ReadPacket();
|
|
byte marker = (byte)packet.ReadByte();
|
|
if (marker != 0)
|
|
{
|
|
throw new MySqlException("Out of sync with server", true, null);
|
|
}
|
|
|
|
packet.ReadFieldLength(); /* affected rows */
|
|
packet.ReadFieldLength(); /* last insert id */
|
|
if (packet.HasMoreData)
|
|
{
|
|
serverStatus = (ServerStatusFlags)packet.ReadInteger(2);
|
|
packet.ReadInteger(2); /* warning count */
|
|
if (packet.HasMoreData)
|
|
{
|
|
packet.ReadLenString(); /* message */
|
|
}
|
|
}
|
|
}
|
|
catch (MySqlException ex)
|
|
{
|
|
HandleException(ex);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the current database for the this connection
|
|
/// </summary>
|
|
/// <param name="dbName"></param>
|
|
public void SetDatabase(string dbName)
|
|
{
|
|
byte[] dbNameBytes = Encoding.GetBytes(dbName);
|
|
|
|
packet.Clear();
|
|
packet.WriteByte((byte)DBCmd.INIT_DB);
|
|
packet.Write(dbNameBytes);
|
|
ExecutePacket(packet);
|
|
|
|
ReadOk(true);
|
|
}
|
|
|
|
public void Configure()
|
|
{
|
|
stream.MaxPacketSize = (ulong)owner.MaxPacketSize;
|
|
stream.Encoding = Encoding;
|
|
}
|
|
|
|
public void Open()
|
|
{
|
|
// connect to one of our specified hosts
|
|
try
|
|
{
|
|
baseStream = StreamCreator.GetStream(Settings);
|
|
if (Settings.IncludeSecurityAsserts)
|
|
MySqlSecurityPermission.CreatePermissionSet(false).Assert();
|
|
}
|
|
catch (System.Security.SecurityException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new MySqlException(Resources.UnableToConnectToHost,
|
|
(int)MySqlErrorCode.UnableToConnectToHost, ex);
|
|
}
|
|
if (baseStream == null)
|
|
throw new MySqlException(Resources.UnableToConnectToHost,
|
|
(int)MySqlErrorCode.UnableToConnectToHost);
|
|
|
|
int maxSinglePacket = 255 * 255 * 255;
|
|
stream = new MySqlStream(baseStream, Encoding, false);
|
|
|
|
stream.ResetTimeout((int)Settings.ConnectionTimeout * 1000);
|
|
|
|
// read off the welcome packet and parse out it's values
|
|
packet = stream.ReadPacket();
|
|
int protocol = packet.ReadByte();
|
|
string versionString = packet.ReadString();
|
|
owner.isFabric = versionString.EndsWith("fabric", StringComparison.OrdinalIgnoreCase);
|
|
isEnterprise = versionString.ToLowerInvariant().Contains("-enterprise-");
|
|
version = DBVersion.Parse(versionString);
|
|
if (!owner.isFabric && !version.isAtLeast(5, 0, 0))
|
|
throw new NotSupportedException(Resources.ServerTooOld);
|
|
threadId = packet.ReadInteger(4);
|
|
|
|
byte[] seedPart1 = packet.ReadStringAsBytes();
|
|
|
|
maxSinglePacket = (256 * 256 * 256) - 1;
|
|
|
|
// read in Server capabilities if they are provided
|
|
ClientFlags serverCaps = 0;
|
|
if (packet.HasMoreData)
|
|
serverCaps = (ClientFlags)packet.ReadInteger(2);
|
|
|
|
/* New protocol with 16 bytes to describe server characteristics */
|
|
owner.ConnectionCharSetIndex = (int)packet.ReadByte();
|
|
|
|
serverStatus = (ServerStatusFlags)packet.ReadInteger(2);
|
|
|
|
// Since 5.5, high bits of server caps are stored after status.
|
|
// Previously, it was part of reserved always 0x00 13-byte filler.
|
|
uint serverCapsHigh = (uint)packet.ReadInteger(2);
|
|
serverCaps |= (ClientFlags)(serverCapsHigh << 16);
|
|
|
|
packet.Position += 11;
|
|
byte[] seedPart2 = packet.ReadStringAsBytes();
|
|
encryptionSeed = new byte[seedPart1.Length + seedPart2.Length];
|
|
seedPart1.CopyTo(encryptionSeed, 0);
|
|
seedPart2.CopyTo(encryptionSeed, seedPart1.Length);
|
|
|
|
string authenticationMethod = "";
|
|
if ((serverCaps & ClientFlags.PLUGIN_AUTH) != 0)
|
|
{
|
|
authenticationMethod = packet.ReadString();
|
|
}
|
|
else
|
|
{
|
|
// Some MySql versions like 5.1, don't give name of plugin, default to native password.
|
|
authenticationMethod = "mysql_native_password";
|
|
}
|
|
|
|
// based on our settings, set our connection flags
|
|
SetConnectionFlags(serverCaps);
|
|
|
|
packet.Clear();
|
|
packet.WriteInteger((int)connectionFlags, 4);
|
|
packet.WriteInteger(maxSinglePacket, 4);
|
|
packet.WriteByte(33); //character set utf-8
|
|
packet.Write(new byte[23]);
|
|
|
|
if ((serverCaps & ClientFlags.SSL) == 0)
|
|
{
|
|
if ((Settings.SslMode != MySqlSslMode.None)
|
|
&& (Settings.SslMode != MySqlSslMode.Preferred))
|
|
{
|
|
// Client requires SSL connections.
|
|
string message = String.Format(Resources.NoServerSSLSupport,
|
|
Settings.Server);
|
|
throw new MySqlException(message);
|
|
}
|
|
}
|
|
else if (Settings.SslMode != MySqlSslMode.None)
|
|
{
|
|
stream.SendPacket(packet);
|
|
StartSSL();
|
|
packet.Clear();
|
|
packet.WriteInteger((int)connectionFlags, 4);
|
|
packet.WriteInteger(maxSinglePacket, 4);
|
|
packet.WriteByte(33); //character set utf-8
|
|
packet.Write(new byte[23]);
|
|
}
|
|
|
|
Authenticate(authenticationMethod, false);
|
|
|
|
// if we are using compression, then we use our CompressedStream class
|
|
// to hide the ugliness of managing the compression
|
|
if ((connectionFlags & ClientFlags.COMPRESS) != 0)
|
|
stream = new MySqlStream(baseStream, Encoding, true);
|
|
|
|
// give our stream the server version we are connected to.
|
|
// We may have some fields that are read differently based
|
|
// on the version of the server we are connected to.
|
|
packet.Version = version;
|
|
stream.MaxBlockSize = maxSinglePacket;
|
|
}
|
|
|
|
#region SSL
|
|
|
|
/// <summary>
|
|
/// Retrieve client SSL certificates. Dependent on connection string
|
|
/// settings we use either file or store based certificates.
|
|
/// </summary>
|
|
private X509CertificateCollection GetClientCertificates()
|
|
{
|
|
X509CertificateCollection certs = new X509CertificateCollection();
|
|
|
|
// Check for file-based certificate
|
|
if (Settings.CertificateFile != null)
|
|
{
|
|
if (!Version.isAtLeast(5, 1, 0))
|
|
throw new MySqlException(Properties.Resources.FileBasedCertificateNotSupported);
|
|
|
|
X509Certificate2 clientCert = new X509Certificate2(Settings.CertificateFile,
|
|
Settings.CertificatePassword);
|
|
certs.Add(clientCert);
|
|
return certs;
|
|
}
|
|
|
|
if (Settings.CertificateStoreLocation == MySqlCertificateStoreLocation.None)
|
|
return certs;
|
|
|
|
StoreLocation location =
|
|
(Settings.CertificateStoreLocation == MySqlCertificateStoreLocation.CurrentUser) ?
|
|
StoreLocation.CurrentUser : StoreLocation.LocalMachine;
|
|
|
|
// Check for store-based certificate
|
|
X509Store store = new X509Store(StoreName.My, location);
|
|
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
|
|
|
|
|
|
if (Settings.CertificateThumbprint == null)
|
|
{
|
|
// Return all certificates from the store.
|
|
certs.AddRange(store.Certificates);
|
|
return certs;
|
|
}
|
|
|
|
// Find certificate with given thumbprint
|
|
certs.AddRange(store.Certificates.Find(X509FindType.FindByThumbprint,
|
|
Settings.CertificateThumbprint, true));
|
|
|
|
if (certs.Count == 0)
|
|
{
|
|
throw new MySqlException("Certificate with Thumbprint " +
|
|
Settings.CertificateThumbprint + " not found");
|
|
}
|
|
return certs;
|
|
}
|
|
|
|
|
|
private void StartSSL()
|
|
{
|
|
RemoteCertificateValidationCallback sslValidateCallback =
|
|
new RemoteCertificateValidationCallback(ServerCheckValidation);
|
|
SslStream ss = new SslStream(baseStream, false, sslValidateCallback, null);
|
|
X509CertificateCollection certs = GetClientCertificates();
|
|
SslProtocols sslProtocols = SslProtocols.Tls;
|
|
#if NET461
|
|
sslProtocols |= SslProtocols.Tls11;
|
|
if (version.isAtLeast(5, 6, 0) && isEnterprise)
|
|
sslProtocols |= SslProtocols.Tls12;
|
|
#endif
|
|
ss.AuthenticateAsClient(Settings.Server, certs, sslProtocols, false);
|
|
baseStream = ss;
|
|
stream = new MySqlStream(ss, Encoding, false);
|
|
stream.SequenceByte = 2;
|
|
}
|
|
|
|
private bool ServerCheckValidation(object sender, X509Certificate certificate,
|
|
X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
|
{
|
|
if (sslPolicyErrors == SslPolicyErrors.None)
|
|
return true;
|
|
|
|
if (Settings.SslMode == MySqlSslMode.Preferred ||
|
|
Settings.SslMode == MySqlSslMode.Required)
|
|
{
|
|
//Tolerate all certificate errors.
|
|
return true;
|
|
}
|
|
|
|
if (Settings.SslMode == MySqlSslMode.VerifyCA &&
|
|
sslPolicyErrors == SslPolicyErrors.RemoteCertificateNameMismatch)
|
|
{
|
|
// Tolerate name mismatch in certificate, if full validation is not requested.
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
#region Authentication
|
|
|
|
/// <summary>
|
|
/// Return the appropriate set of connection flags for our
|
|
/// server capabilities and our user requested options.
|
|
/// </summary>
|
|
private void SetConnectionFlags(ClientFlags serverCaps)
|
|
{
|
|
// allow load data local infile
|
|
ClientFlags flags = ClientFlags.LOCAL_FILES;
|
|
|
|
if (!Settings.UseAffectedRows)
|
|
flags |= ClientFlags.FOUND_ROWS;
|
|
|
|
flags |= ClientFlags.PROTOCOL_41;
|
|
// Need this to get server status values
|
|
flags |= ClientFlags.TRANSACTIONS;
|
|
|
|
// user allows/disallows batch statements
|
|
if (Settings.AllowBatch)
|
|
flags |= ClientFlags.MULTI_STATEMENTS;
|
|
|
|
// We always allow multiple result sets
|
|
flags |= ClientFlags.MULTI_RESULTS;
|
|
|
|
// if the server allows it, tell it that we want long column info
|
|
if ((serverCaps & ClientFlags.LONG_FLAG) != 0)
|
|
flags |= ClientFlags.LONG_FLAG;
|
|
|
|
// if the server supports it and it was requested, then turn on compression
|
|
if ((serverCaps & ClientFlags.COMPRESS) != 0 && Settings.UseCompression)
|
|
flags |= ClientFlags.COMPRESS;
|
|
|
|
flags |= ClientFlags.LONG_PASSWORD; // for long passwords
|
|
|
|
// did the user request an interactive session?
|
|
if (Settings.InteractiveSession)
|
|
flags |= ClientFlags.INTERACTIVE;
|
|
|
|
// if the server allows it and a database was specified, then indicate
|
|
// that we will connect with a database name
|
|
if ((serverCaps & ClientFlags.CONNECT_WITH_DB) != 0 &&
|
|
Settings.Database != null && Settings.Database.Length > 0)
|
|
flags |= ClientFlags.CONNECT_WITH_DB;
|
|
|
|
// if the server is requesting a secure connection, then we oblige
|
|
if ((serverCaps & ClientFlags.SECURE_CONNECTION) != 0)
|
|
flags |= ClientFlags.SECURE_CONNECTION;
|
|
|
|
// if the server is capable of SSL and the user is requesting SSL
|
|
if ((serverCaps & ClientFlags.SSL) != 0 && Settings.SslMode != MySqlSslMode.None)
|
|
flags |= ClientFlags.SSL;
|
|
|
|
// if the server supports output parameters, then we do too
|
|
if ((serverCaps & ClientFlags.PS_MULTI_RESULTS) != 0)
|
|
flags |= ClientFlags.PS_MULTI_RESULTS;
|
|
|
|
if ((serverCaps & ClientFlags.PLUGIN_AUTH) != 0)
|
|
flags |= ClientFlags.PLUGIN_AUTH;
|
|
|
|
// if the server supports connection attributes
|
|
if ((serverCaps & ClientFlags.CONNECT_ATTRS) != 0)
|
|
flags |= ClientFlags.CONNECT_ATTRS;
|
|
|
|
if ((serverCaps & ClientFlags.CAN_HANDLE_EXPIRED_PASSWORD) != 0)
|
|
flags |= ClientFlags.CAN_HANDLE_EXPIRED_PASSWORD;
|
|
|
|
connectionFlags = flags;
|
|
}
|
|
|
|
public void Authenticate(string authMethod, bool reset)
|
|
{
|
|
if (authMethod != null)
|
|
{
|
|
// Integrated security is a shortcut for windows auth
|
|
if (Settings.IntegratedSecurity)
|
|
authMethod = "authentication_windows_client";
|
|
|
|
authPlugin = MySqlAuthenticationPlugin.GetPlugin(authMethod, this, encryptionSeed);
|
|
}
|
|
authPlugin.Authenticate(reset);
|
|
}
|
|
|
|
#endregion
|
|
|
|
public void Reset()
|
|
{
|
|
warnings = 0;
|
|
stream.Encoding = this.Encoding;
|
|
stream.SequenceByte = 0;
|
|
packet.Clear();
|
|
packet.WriteByte((byte)DBCmd.CHANGE_USER);
|
|
Authenticate(null, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Query is the method that is called to send all queries to the server
|
|
/// </summary>
|
|
public void SendQuery(MySqlPacket queryPacket)
|
|
{
|
|
warnings = 0;
|
|
queryPacket.SetByte(4, (byte)DBCmd.QUERY);
|
|
ExecutePacket(queryPacket);
|
|
// the server will respond in one of several ways with the first byte indicating
|
|
// the type of response.
|
|
// 0 == ok packet. This indicates non-select queries
|
|
// 0xff == error packet. This is handled in stream.OpenPacket
|
|
// > 0 = number of columns in select query
|
|
// We don't actually read the result here since a single query can generate
|
|
// multiple resultsets and we don't want to duplicate code. See ReadResult
|
|
// Instead we set our internal server status flag to indicate that we have a query waiting.
|
|
// This flag will be maintained by ReadResult
|
|
serverStatus |= ServerStatusFlags.AnotherQuery;
|
|
}
|
|
|
|
public void Close(bool isOpen)
|
|
{
|
|
try
|
|
{
|
|
if (isOpen)
|
|
{
|
|
try
|
|
{
|
|
packet.Clear();
|
|
packet.WriteByte((byte)DBCmd.QUIT);
|
|
ExecutePacket(packet);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Eat exception here. We should try to closing
|
|
// the stream anyway.
|
|
}
|
|
}
|
|
|
|
if (stream != null)
|
|
stream.Close();
|
|
stream = null;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// we are just going to eat any exceptions
|
|
// generated here
|
|
}
|
|
}
|
|
|
|
public bool Ping()
|
|
{
|
|
try
|
|
{
|
|
packet.Clear();
|
|
packet.WriteByte((byte)DBCmd.PING);
|
|
ExecutePacket(packet);
|
|
ReadOk(true);
|
|
return true;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
owner.Close();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public int GetResult(ref int affectedRow, ref long insertedId)
|
|
{
|
|
try
|
|
{
|
|
packet = stream.ReadPacket();
|
|
}
|
|
catch (TimeoutException)
|
|
{
|
|
// Do not reset serverStatus, allow to reenter, e.g when
|
|
// ResultSet is closed.
|
|
throw;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
serverStatus &= ~(ServerStatusFlags.AnotherQuery |
|
|
ServerStatusFlags.MoreResults);
|
|
throw;
|
|
}
|
|
|
|
int fieldCount = (int)packet.ReadFieldLength();
|
|
if (-1 == fieldCount)
|
|
{
|
|
string filename = packet.ReadString();
|
|
SendFileToServer(filename);
|
|
|
|
return GetResult(ref affectedRow, ref insertedId);
|
|
}
|
|
else if (fieldCount == 0)
|
|
{
|
|
// the code to read last packet will set these server status vars
|
|
// again if necessary.
|
|
serverStatus &= ~(ServerStatusFlags.AnotherQuery |
|
|
ServerStatusFlags.MoreResults);
|
|
affectedRow = (int)packet.ReadFieldLength();
|
|
insertedId = (long)packet.ReadFieldLength();
|
|
|
|
serverStatus = (ServerStatusFlags)packet.ReadInteger(2);
|
|
warnings += packet.ReadInteger(2);
|
|
if (packet.HasMoreData)
|
|
{
|
|
packet.ReadLenString(); //TODO: server message
|
|
}
|
|
}
|
|
return fieldCount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends the specified file to the server.
|
|
/// This supports the LOAD DATA LOCAL INFILE
|
|
/// </summary>
|
|
/// <param name="filename"></param>
|
|
private void SendFileToServer(string filename)
|
|
{
|
|
byte[] buffer = new byte[8196];
|
|
|
|
long len = 0;
|
|
try
|
|
{
|
|
using (FileStream fs = new FileStream(filename, FileMode.Open,
|
|
FileAccess.Read))
|
|
{
|
|
len = fs.Length;
|
|
while (len > 0)
|
|
{
|
|
int count = fs.Read(buffer, 4, (int)(len > 8192 ? 8192 : len));
|
|
stream.SendEntirePacketDirectly(buffer, count);
|
|
len -= count;
|
|
}
|
|
stream.SendEntirePacketDirectly(buffer, 0);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new MySqlException("Error during LOAD DATA LOCAL INFILE", ex);
|
|
}
|
|
}
|
|
|
|
private void ReadNullMap(int fieldCount)
|
|
{
|
|
// if we are binary, then we need to load in our null bitmap
|
|
nullMap = null;
|
|
byte[] nullMapBytes = new byte[(fieldCount + 9) / 8];
|
|
packet.ReadByte();
|
|
packet.Read(nullMapBytes, 0, nullMapBytes.Length);
|
|
nullMap = new BitArray(nullMapBytes);
|
|
}
|
|
|
|
public IMySqlValue ReadColumnValue(int index, MySqlField field, IMySqlValue valObject)
|
|
{
|
|
long length = -1;
|
|
bool isNull;
|
|
|
|
if (nullMap != null)
|
|
isNull = nullMap[index + 2];
|
|
else
|
|
{
|
|
length = packet.ReadFieldLength();
|
|
isNull = length == -1;
|
|
}
|
|
|
|
packet.Encoding = field.Encoding;
|
|
packet.Version = version;
|
|
return valObject.ReadValue(packet, length, isNull);
|
|
}
|
|
|
|
public void SkipColumnValue(IMySqlValue valObject)
|
|
{
|
|
int length = -1;
|
|
if (nullMap == null)
|
|
{
|
|
length = (int)packet.ReadFieldLength();
|
|
if (length == -1) return;
|
|
}
|
|
if (length > -1)
|
|
packet.Position += length;
|
|
else
|
|
valObject.SkipValue(packet);
|
|
}
|
|
|
|
public void GetColumnsData(MySqlField[] columns)
|
|
{
|
|
for (int i = 0; i < columns.Length; i++)
|
|
GetColumnData(columns[i]);
|
|
ReadEOF();
|
|
}
|
|
|
|
private void GetColumnData(MySqlField field)
|
|
{
|
|
stream.Encoding = Encoding;
|
|
packet = stream.ReadPacket();
|
|
field.Encoding = Encoding;
|
|
field.CatalogName = packet.ReadLenString();
|
|
field.DatabaseName = packet.ReadLenString();
|
|
field.TableName = packet.ReadLenString();
|
|
field.RealTableName = packet.ReadLenString();
|
|
field.ColumnName = packet.ReadLenString();
|
|
field.OriginalColumnName = packet.ReadLenString();
|
|
packet.ReadByte();
|
|
field.CharacterSetIndex = packet.ReadInteger(2);
|
|
field.ColumnLength = packet.ReadInteger(4);
|
|
MySqlDbType type = (MySqlDbType)packet.ReadByte();
|
|
ColumnFlags colFlags;
|
|
if ((connectionFlags & ClientFlags.LONG_FLAG) != 0)
|
|
colFlags = (ColumnFlags)packet.ReadInteger(2);
|
|
else
|
|
colFlags = (ColumnFlags)packet.ReadByte();
|
|
field.Scale = (byte)packet.ReadByte();
|
|
|
|
if (packet.HasMoreData)
|
|
{
|
|
packet.ReadInteger(2); // reserved
|
|
}
|
|
|
|
if (type == MySqlDbType.Decimal || type == MySqlDbType.NewDecimal)
|
|
{
|
|
field.Precision = ((colFlags & ColumnFlags.UNSIGNED) != 0) ? (byte)(field.ColumnLength) : (byte)(field.ColumnLength - 1);
|
|
if (field.Scale != 0)
|
|
field.Precision--;
|
|
}
|
|
|
|
field.SetTypeAndFlags(type, colFlags);
|
|
}
|
|
|
|
private void ExecutePacket(MySqlPacket packetToExecute)
|
|
{
|
|
try
|
|
{
|
|
warnings = 0;
|
|
stream.SequenceByte = 0;
|
|
stream.SendPacket(packetToExecute);
|
|
}
|
|
catch (MySqlException ex)
|
|
{
|
|
HandleException(ex);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public void ExecuteStatement(MySqlPacket packetToExecute)
|
|
{
|
|
warnings = 0;
|
|
packetToExecute.SetByte(4, (byte)DBCmd.EXECUTE);
|
|
ExecutePacket(packetToExecute);
|
|
serverStatus |= ServerStatusFlags.AnotherQuery;
|
|
}
|
|
|
|
private void CheckEOF()
|
|
{
|
|
if (!packet.IsLastPacket)
|
|
throw new MySqlException("Expected end of data packet");
|
|
|
|
packet.ReadByte(); // read off the 254
|
|
|
|
if (packet.HasMoreData)
|
|
{
|
|
warnings += packet.ReadInteger(2);
|
|
serverStatus = (ServerStatusFlags)packet.ReadInteger(2);
|
|
|
|
// if we are at the end of this cursor based resultset, then we remove
|
|
// the last row sent status flag so our next fetch doesn't abort early
|
|
// and we remove this command result from our list of active CommandResult objects.
|
|
// if ((serverStatus & ServerStatusFlags.LastRowSent) != 0)
|
|
// {
|
|
// serverStatus &= ~ServerStatusFlags.LastRowSent;
|
|
// commandResults.Remove(lastCommandResult);
|
|
// }
|
|
}
|
|
}
|
|
|
|
private void ReadEOF()
|
|
{
|
|
packet = stream.ReadPacket();
|
|
CheckEOF();
|
|
}
|
|
|
|
public int PrepareStatement(string sql, ref MySqlField[] parameters)
|
|
{
|
|
//TODO: check this
|
|
//ClearFetchedRow();
|
|
|
|
packet.Length = sql.Length * 4 + 5;
|
|
byte[] buffer = packet.Buffer;
|
|
int len = Encoding.GetBytes(sql, 0, sql.Length, packet.Buffer, 5);
|
|
packet.Position = len + 5;
|
|
buffer[4] = (byte)DBCmd.PREPARE;
|
|
ExecutePacket(packet);
|
|
|
|
packet = stream.ReadPacket();
|
|
|
|
int marker = packet.ReadByte();
|
|
if (marker != 0)
|
|
throw new MySqlException("Expected prepared statement marker");
|
|
|
|
int statementId = packet.ReadInteger(4);
|
|
int numCols = packet.ReadInteger(2);
|
|
int numParams = packet.ReadInteger(2);
|
|
//TODO: find out what this is needed for
|
|
packet.ReadInteger(3);
|
|
if (numParams > 0)
|
|
{
|
|
parameters = owner.GetColumns(numParams);
|
|
// we set the encoding for each parameter back to our connection encoding
|
|
// since we can't trust what is coming back from the server
|
|
for (int i = 0; i < parameters.Length; i++)
|
|
parameters[i].Encoding = Encoding;
|
|
}
|
|
|
|
if (numCols > 0)
|
|
{
|
|
while (numCols-- > 0)
|
|
{
|
|
packet = stream.ReadPacket();
|
|
//TODO: handle streaming packets
|
|
}
|
|
|
|
ReadEOF();
|
|
}
|
|
|
|
return statementId;
|
|
}
|
|
|
|
// private void ClearFetchedRow()
|
|
// {
|
|
// if (lastCommandResult == 0) return;
|
|
|
|
//TODO
|
|
/* CommandResult result = (CommandResult)commandResults[lastCommandResult];
|
|
result.ReadRemainingColumns();
|
|
|
|
stream.OpenPacket();
|
|
if (! stream.IsLastPacket)
|
|
throw new MySqlException("Cursor reading out of sync");
|
|
|
|
ReadEOF(false);
|
|
lastCommandResult = 0;*/
|
|
// }
|
|
|
|
/// <summary>
|
|
/// FetchDataRow is the method that the data reader calls to see if there is another
|
|
/// row to fetch. In the non-prepared mode, it will simply read the next data packet.
|
|
/// In the prepared mode (statementId > 0), it will
|
|
/// </summary>
|
|
public bool FetchDataRow(int statementId, int columns)
|
|
{
|
|
/* ClearFetchedRow();
|
|
|
|
if (!commandResults.ContainsKey(statementId)) return false;
|
|
|
|
if ( (serverStatus & ServerStatusFlags.LastRowSent) != 0)
|
|
return false;
|
|
|
|
stream.StartPacket(9, true);
|
|
stream.WriteByte((byte)DBCmd.FETCH);
|
|
stream.WriteInteger(statementId, 4);
|
|
stream.WriteInteger(1, 4);
|
|
stream.Flush();
|
|
|
|
lastCommandResult = statementId;
|
|
*/
|
|
packet = stream.ReadPacket();
|
|
if (packet.IsLastPacket)
|
|
{
|
|
CheckEOF();
|
|
return false;
|
|
}
|
|
nullMap = null;
|
|
if (statementId > 0)
|
|
ReadNullMap(columns);
|
|
|
|
return true;
|
|
}
|
|
|
|
public void CloseStatement(int statementId)
|
|
{
|
|
packet.Clear();
|
|
packet.WriteByte((byte)DBCmd.CLOSE_STMT);
|
|
packet.WriteInteger((long)statementId, 4);
|
|
stream.SequenceByte = 0;
|
|
stream.SendPacket(packet);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execution timeout, in milliseconds. When the accumulated time for network IO exceeds this value
|
|
/// TimeoutException is thrown. This timeout needs to be reset for every new command
|
|
/// </summary>
|
|
///
|
|
public void ResetTimeout(int timeout)
|
|
{
|
|
if (stream != null)
|
|
stream.ResetTimeout(timeout);
|
|
}
|
|
|
|
internal void SetConnectAttrs()
|
|
{
|
|
// Sets connect attributes
|
|
if ((connectionFlags & ClientFlags.CONNECT_ATTRS) != 0)
|
|
{
|
|
string connectAttrs = string.Empty;
|
|
MySqlConnectAttrs attrs = new MySqlConnectAttrs();
|
|
foreach (PropertyInfo property in attrs.GetType().GetProperties())
|
|
{
|
|
string name = property.Name;
|
|
object[] customAttrs = property.GetCustomAttributes(typeof(DisplayNameAttribute), false);
|
|
if (customAttrs.Length > 0)
|
|
name = (customAttrs[0] as DisplayNameAttribute).DisplayName;
|
|
|
|
string value = (string)property.GetValue(attrs, null);
|
|
connectAttrs += string.Format("{0}{1}", (char)name.Length, name);
|
|
connectAttrs += string.Format("{0}{1}", (char)value.Length, value);
|
|
}
|
|
packet.WriteLenString(connectAttrs);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|