Description:
The issue is when performing a select with a guid in binary mode ( OldGuids == true ) and the first row has a null value for the guid, the MySqlGuid class stays in string mode ( OldGuids == false ) when trying to convert any future guids from that field
How to repeat:
using System;
using System.Data;
using System.Data.Common;
using MySql.Data.MySqlClient;
namespace JDMX.UnitTest {
public class mysqltest {
[STAThread]
public static void Main( string[] args ) {
string connectionString = "server=localhost;port=3306;uid=test;pwd=test;database=mysqltest;old guids=true;";
// The Guid is this:
// 01020304-0506-0708-0900-0a0b0c0d0e0f
Guid guid = new Guid(
"01020304" +
"05060708" +
"09000A0B" +
"0C0D0E0F" );
Console.WriteLine( "The guid defined in the src" );
Console.WriteLine( guid.ToString( "N" ) + " - " + guid.ToString() );
Console.WriteLine( "" );
DbConnection connection = new MySqlConnection( connectionString );
if ( connection.State == ConnectionState.Closed )
connection.Open();
DbCommand command = connection.CreateCommand();
command.CommandText = "USE mysql";
command.ExecuteNonQuery();
command.CommandText = "DROP DATABASE mysqltest";
command.ExecuteNonQuery();
command.CommandText = "CREATE DATABASE mysqltest DEFAULT CHARACTER SET utf8";
command.ExecuteNonQuery();
command.CommandText = "USE mysqltest";
command.ExecuteNonQuery();
command.CommandText = "CREATE TABLE `guid_table` ( `guidint` int NOT NULL, `guid1` binary(16) NOT NULL, `guid2` binary(16) NOT NULL, `guid3` binary(16) NULL )";
command.ExecuteNonQuery();
command.CommandText = string.Format( "INSERT INTO guid_table ( guidint, guid1, guid2 )"
+ " VALUES ( 1, CAST(0x{0} AS BINARY), CAST(0x{0} AS BINARY) );", guid.ToString( "N" ) );
command.ExecuteNonQuery();
command.CommandText = string.Format( "INSERT INTO guid_table ( guidint, guid1, guid2 )"
+ " VALUES ( 2, CAST(0x{0} AS BINARY), CAST(0x{0} AS BINARY) );", guid.ToString( "N" ) );
command.ExecuteNonQuery();
// test 1 where guid3 is null for all the records
command.CommandText = "SELECT guidint, guid1, guid2, guid3 FROM guid_table ORDER BY guidint";
WriteDataReader( command );
// add a record where guid3 is not null
command.CommandText = string.Format( "INSERT INTO guid_table ( guidint, guid1, guid2, guid3 )"
+ " VALUES ( 3, CAST(0x{0} AS BINARY), CAST(0x{0} AS BINARY), CAST(0x{0} AS BINARY) );", guid.ToString( "N" ) );
command.ExecuteNonQuery();
// select the records where the first item has all the guid fields populated
command.CommandText = "SELECT guidint, guid1, guid2, guid3 FROM guid_table ORDER BY guidint DESC";
WriteDataReader( command );
// select the records where the first item has a null guid3 but a later row has a value for guid3
command.CommandText = "SELECT guidint, guid1, guid2, guid3 FROM guid_table ORDER BY guidint";
WriteDataReader( command );
connection.Close();
Console.WriteLine( "Press enter to continue." );
Console.ReadLine();
}
public static void WriteDataReader( DbCommand command ) {
DbDataReader dr = command.ExecuteReader();
int j = 0;
while ( dr.Read() ) {
object[] obj = new object[dr.FieldCount];
dr.GetValues( obj );
for ( int i = 0; i < obj.Length; i++ )
Console.WriteLine( "r{5} c{4} -- {0} {1} - v {2} {3}", dr.GetName( i ), dr.GetFieldType( i ), obj[i], obj[i].GetType(), i, j );
j++;
}
dr.Close();
}
}
}
Suggested fix:
The error is a very simple fix but horribly hard to track because of threading. Anyway, in your src, the ReadValue function does not set OldGuids value for a null field. Somewhere later in the src ( do not know where ) the field definition gets switched from OldGuids == true to OldGuids == false causing the error.
Adding g.OldGuids = OldGuids; after the MySqlGuid g = new MySqlGuid(); fixes the top level problem by keeping the status for even a null field but the results of a ReadValue should not be changing the Field definition
MySqlGuid g = new MySqlGuid();
g.OldGuids = OldGuids;
g.isNull = true;
After verifying the error, this is the solution to fix it
In MySql.Data -> Source -> Types -> MySqlGuid
replace the ReadValue function with the following
IMySqlValue IMySqlValue.ReadValue( MySqlPacket packet, long length, bool nullVal ) {
MySqlGuid g = new MySqlGuid();
g.OldGuids = OldGuids;
g.isNull = true;
if ( !nullVal ) {
if ( OldGuids )
return ReadOldGuid( packet, length );
string s = String.Empty;
if ( length == -1 )
s = packet.ReadLenString();
else
s = packet.ReadString( length );
g.mValue = new Guid( s );
g.OldGuids = OldGuids;
g.isNull = false;
}
return g;
}