From fdb00e0dd65e95b627bae8e11dff9ee905b1b570 Mon Sep 17 00:00:00 2001 From: Jiri Cincura Date: Sun, 16 Jul 2017 21:43:46 +0200 Subject: [PATCH] GUID representation aligned with server (DNET-509). --- .../Client/Managed/Version10/GdsStatement.cs | 6 +- .../Client/Managed/XdrStream.cs | 9 ++- .../Common/DbField.cs | 2 +- .../Common/DbValue.cs | 63 ++++++++------- .../Common/TypeDecoder.cs | 11 +++ .../Common/TypeEncoder.cs | 17 ++++ .../FbDataReaderTests.cs | 78 +++++++++++++++++++ 7 files changed, 153 insertions(+), 33 deletions(-) diff --git a/Provider/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs b/Provider/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs index a8709114..166765c9 100644 --- a/Provider/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs +++ b/Provider/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs @@ -826,7 +826,7 @@ protected void WriteRawParameter(XdrStream xdr, DbField field) break; case DbDataType.Guid: - xdr.WriteOpaque(field.DbValue.GetGuid().ToByteArray()); + xdr.Write(field.DbValue.GetGuid()); break; case DbDataType.Double: @@ -847,7 +847,7 @@ protected void WriteRawParameter(XdrStream xdr, DbField field) break; case DbDataType.Boolean: - xdr.Write(Convert.ToBoolean(field.Value)); + xdr.Write(field.DbValue.GetBoolean()); break; default: @@ -911,7 +911,7 @@ protected object ReadRawValue(DbField field) return _database.XdrStream.ReadSingle(); case DbDataType.Guid: - return _database.XdrStream.ReadGuid(field.Length); + return _database.XdrStream.ReadGuid(); case DbDataType.Double: return _database.XdrStream.ReadDouble(); diff --git a/Provider/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrStream.cs b/Provider/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrStream.cs index 2b61d6ec..5d551466 100644 --- a/Provider/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrStream.cs +++ b/Provider/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrStream.cs @@ -472,9 +472,9 @@ public long ReadInt64() return IPAddress.HostToNetworkOrder(BitConverter.ToInt64(ReadBytes(8), 0)); } - public Guid ReadGuid(int length) + public Guid ReadGuid() { - return new Guid(ReadOpaque(length)); + return TypeDecoder.DecodeGuid(ReadOpaque(16)); } public float ReadSingle() @@ -717,6 +717,11 @@ public void Write(DateTime value) WriteTime(TypeHelper.DateTimeToTimeSpan(value)); } + public void Write(Guid value) + { + WriteOpaque(TypeEncoder.EncodeGuid(value)); + } + public void WriteDate(DateTime value) { Write(TypeEncoder.EncodeDate(Convert.ToDateTime(value))); diff --git a/Provider/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs b/Provider/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs index 115fa4b9..88b6dbbe 100644 --- a/Provider/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs +++ b/Provider/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs @@ -326,7 +326,7 @@ public void SetValue(byte[] buffer) case IscCodes.SQL_VARYING: if (DbDataType == DbDataType.Guid) { - Value = new Guid(buffer); + Value = TypeDecoder.DecodeGuid(buffer); } else { diff --git a/Provider/src/FirebirdSql.Data.FirebirdClient/Common/DbValue.cs b/Provider/src/FirebirdSql.Data.FirebirdClient/Common/DbValue.cs index b71bc381..2fb40d61 100644 --- a/Provider/src/FirebirdSql.Data.FirebirdClient/Common/DbValue.cs +++ b/Provider/src/FirebirdSql.Data.FirebirdClient/Common/DbValue.cs @@ -82,13 +82,13 @@ public bool IsDBNull() public string GetString() { - if (Field.DbDataType == DbDataType.Text && _value is long) + if (Field.DbDataType == DbDataType.Text && _value is long l) { - _value = GetClobData((long)_value); + _value = GetClobData(l); } - if (_value is byte[]) + if (_value is byte[] bytes) { - return Field.Charset.GetString((byte[])_value); + return Field.Charset.GetString(bytes); } return _value.ToString(); @@ -136,16 +136,15 @@ public float GetFloat() public Guid GetGuid() { - if (Value is Guid) + switch (_value) { - return (Guid)Value; - } - else if (Value is byte[]) - { - return new Guid((byte[])_value); + case Guid guid: + return guid; + case byte[] bytes: + return TypeDecoder.DecodeGuid(bytes); + default: + throw new InvalidOperationException($"Incorrect {nameof(Guid)} value."); } - - throw new InvalidOperationException("Incorrect Guid value"); } public double GetDouble() @@ -155,19 +154,22 @@ public double GetDouble() public DateTime GetDateTime() { - if (_value is TimeSpan) - return new DateTime(0 * 10000L + 621355968000000000 + ((TimeSpan)_value).Ticks); - else if (_value is DateTimeOffset) - return Convert.ToDateTime(((DateTimeOffset)_value).DateTime, CultureInfo.CurrentCulture.DateTimeFormat); - else - return Convert.ToDateTime(_value, CultureInfo.CurrentCulture.DateTimeFormat); + switch (_value) + { + case TimeSpan ts: + return new DateTime(0 * 10000L + 621355968000000000 + ts.Ticks); + case DateTimeOffset dto: + return Convert.ToDateTime(dto.DateTime, CultureInfo.CurrentCulture.DateTimeFormat); + default: + return Convert.ToDateTime(_value, CultureInfo.CurrentCulture.DateTimeFormat); + } } public Array GetArray() { - if (_value is long) + if (_value is long l) { - _value = GetArrayData((long)_value); + _value = GetArrayData(l); } return (Array)_value; @@ -175,9 +177,13 @@ public Array GetArray() public byte[] GetBinary() { - if (_value is long) + if (_value is long l) + { + _value = GetBlobData(l); + } + if (_value is Guid guid) { - _value = GetBlobData((long)_value); + return TypeEncoder.EncodeGuid(guid); } return (byte[])_value; @@ -190,10 +196,13 @@ public int GetDate() public int GetTime() { - if (_value is TimeSpan) - return TypeEncoder.EncodeTime((TimeSpan)_value); - else - return TypeEncoder.EncodeTime(TypeHelper.DateTimeToTimeSpan(GetDateTime())); + switch (_value) + { + case TimeSpan ts: + return TypeEncoder.EncodeTime(ts); + default: + return TypeEncoder.EncodeTime(TypeHelper.DateTimeToTimeSpan(GetDateTime())); + } } public byte[] GetBytes() @@ -312,7 +321,7 @@ public byte[] GetBytes() return result; case DbDataType.Guid: - return GetGuid().ToByteArray(); + return TypeEncoder.EncodeGuid(GetGuid()); case DbDataType.Boolean: return BitConverter.GetBytes(GetBoolean()); diff --git a/Provider/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs b/Provider/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs index b8e74e39..342c8f8d 100644 --- a/Provider/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs +++ b/Provider/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs @@ -13,6 +13,7 @@ * language governing rights and limitations under the License. * * Copyright (c) 2002, 2007 Carlos Guzman Alvarez + * Copyright (c) 2016 - 2017 Jiri Cincura (jiri@cincura.net) * All Rights Reserved. * * Contributors: @@ -21,6 +22,7 @@ using System; using System.Globalization; +using System.Net; namespace FirebirdSql.Data.Common { @@ -92,5 +94,14 @@ public static bool DecodeBoolean(byte[] value) { return value[0] != 0; } + + public static Guid DecodeGuid(byte[] value) + { + var a = IPAddress.HostToNetworkOrder(BitConverter.ToInt32(value, 0)); + var b = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(value, 4)); + var c = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(value, 6)); + var d = new[] { value[8], value[9], value[10], value[11], value[12], value[13], value[14], value[15] }; + return new Guid(a, b, c, d); + } } } diff --git a/Provider/src/FirebirdSql.Data.FirebirdClient/Common/TypeEncoder.cs b/Provider/src/FirebirdSql.Data.FirebirdClient/Common/TypeEncoder.cs index 04dd95dc..c9a31b15 100644 --- a/Provider/src/FirebirdSql.Data.FirebirdClient/Common/TypeEncoder.cs +++ b/Provider/src/FirebirdSql.Data.FirebirdClient/Common/TypeEncoder.cs @@ -13,6 +13,7 @@ * language governing rights and limitations under the License. * * Copyright (c) 2002, 2007 Carlos Guzman Alvarez + * Copyright (c) 2016 - 2017 Jiri Cincura (jiri@cincura.net) * All Rights Reserved. * * Contributors: @@ -21,6 +22,7 @@ using System; using System.Globalization; +using System.Net; namespace FirebirdSql.Data.Common { @@ -89,5 +91,20 @@ public static byte[] EncodeBoolean(bool value) { return new[] { (byte)(value ? 1 : 0) }; } + + public static byte[] EncodeGuid(Guid value) + { + var data = value.ToByteArray(); + var a = BitConverter.GetBytes(IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 0))); + var b = BitConverter.GetBytes(IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 4))); + var c = BitConverter.GetBytes(IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 6))); + return new[] + { + a[0], a[1], a[2], a[3], + b[0], b[1], + c[0], c[1], + data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15] + }; + } } } diff --git a/Provider/src/FirebirdSql.Data.UnitTests/FbDataReaderTests.cs b/Provider/src/FirebirdSql.Data.UnitTests/FbDataReaderTests.cs index a0cfa4b5..3351b2bf 100644 --- a/Provider/src/FirebirdSql.Data.UnitTests/FbDataReaderTests.cs +++ b/Provider/src/FirebirdSql.Data.UnitTests/FbDataReaderTests.cs @@ -13,6 +13,7 @@ * language governing rights and limitations under the License. * * Copyright (c) 2002, 2007 Carlos Guzman Alvarez + * Copyright (c) 2017 Jiri Cincura (jiri@cincura.net) * All Rights Reserved. */ @@ -395,6 +396,83 @@ public void ReadBinaryTest() transaction.Rollback(); } + [Test] + public void ReadGuidRoundTripTest() + { + using (var cmd = Connection.CreateCommand()) + { + var guid = Guid.NewGuid(); + var commandText = $"select char_to_uuid('{guid}') from rdb$database"; + cmd.CommandText = commandText; + using (var reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + Assert.AreEqual(guid, reader.GetGuid(0)); + } + else + { + Assert.Fail(); + } + } + } + } + + [Test] + public void ReadGuidRoundTrip2Test() + { + using (var cmd = Connection.CreateCommand()) + { + var commandText = @" +execute block +returns (a varchar(16) character set octets, b varchar(36) character set ascii) +as +declare guid varchar(16) character set octets; +begin + guid = gen_uuid(); + for select :guid, uuid_to_char(:guid) from rdb$database into a, b do + begin + suspend; + end +end"; + cmd.CommandText = commandText; + using (var reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + StringAssert.AreEqualIgnoringCase(reader.GetString(1), reader.GetGuid(0).ToString()); + } + else + { + Assert.Fail(); + } + } + } + } + + [Test] + public void ReadGuidRoundTrip3Test() + { + using (var cmd = Connection.CreateCommand()) + { + var guid = Guid.NewGuid(); + var commandText = $"select cast(@guid as varchar(16) character set octets) from rdb$database"; + cmd.CommandText = commandText; + cmd.Parameters.Add("guid", guid); + using (var reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + Assert.AreEqual(guid, reader.GetGuid(0)); + } + else + { + Assert.Fail(); + } + } + } + } + [Test] public void DNET60_EmptyFieldReadingError() {