-
-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix SqlHierarchyId serialization and deserialization #55
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,11 @@ | ||
using Microsoft.SqlServer.Types; | ||
using Microsoft.SqlServer.Types.SqlHierarchy; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using System; | ||
using System.Collections; | ||
using System.Data.SqlClient; | ||
using System.IO; | ||
using System.Text; | ||
|
||
namespace Microsoft.SqlServer.Types.Tests.HierarchyId | ||
{ | ||
|
@@ -48,5 +52,129 @@ public void TestSqlHiarchy2() | |
} | ||
Assert.AreEqual("/1/-2.18/", hid.ToString()); | ||
} | ||
|
||
static bool CompareWithSqlServer = false; | ||
|
||
[DataTestMethod] | ||
[DataRow("/-4294971464/")] | ||
[DataRow("/4294972495/")] | ||
[DataRow("/3.2725686107/")] | ||
[DataRow("/0/")] | ||
[DataRow("/1/")] | ||
[DataRow("/1.0.2/")] | ||
[DataRow("/1.1.2/")] | ||
[DataRow("/1.2.2/")] | ||
[DataRow("/1.3.2/")] | ||
[DataRow("/3.0/")] | ||
public void SerializeDeserialize(string route) | ||
{ | ||
var parsed = SqlHierarchyId.Parse(route); | ||
var ms = new MemoryStream(); | ||
parsed.Write(new BinaryWriter(ms)); | ||
ms.Position = 0; | ||
var dumMem = Dump(ms); | ||
ms.Position = 0; | ||
var roundTrip = new Microsoft.SqlServer.Types.SqlHierarchyId(); | ||
roundTrip.Read(new BinaryReader(ms)); | ||
if (parsed != roundTrip) | ||
Assert.AreEqual(parsed, roundTrip); | ||
|
||
if (CompareWithSqlServer) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would you be able to always include these tests? In this project, you can find the Something in these lines: HierarchyScripts.cs
DatabaseUtil.cs (extracted from DBTests.cs)
And actual tests class
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks good to me, didn’t realize other test where already speaking with the database. Didn’t want to add a dependency because potential problems with CI. But locadb is a great solution. |
||
{ | ||
/* | ||
CREATE TABLE [dbo].[TreeNode]( | ||
[Id] [int] IDENTITY(1,1) NOT NULL, | ||
[Route] [hierarchyid] NOT NULL | ||
) | ||
*/ | ||
using (SqlConnection con = new SqlConnection(@"Data Source=.\SQLEXPRESS;Initial Catalog=HierarchyTest;Integrated Security=true")) | ||
{ | ||
con.Open(); | ||
var id = new SqlCommand($"INSERT INTO [dbo].[TreeNode] (Route) output INSERTED.ID VALUES ('{route}') ", con).ExecuteScalar(); | ||
|
||
using (var reader = new SqlCommand($"SELECT Route FROM [dbo].[TreeNode] WHERE ID = " + id, con).ExecuteReader()) | ||
{ | ||
while (reader.Read()) | ||
{ | ||
var sqlRoundTrip = new Microsoft.SqlServer.Types.SqlHierarchyId(); | ||
var dumSql = Dump(reader.GetStream(0)); | ||
Assert.AreEqual(dumMem, dumSql); | ||
sqlRoundTrip.Read(new BinaryReader(reader.GetStream(0))); | ||
if (parsed != sqlRoundTrip) | ||
Assert.AreEqual(parsed, sqlRoundTrip); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
[TestMethod] | ||
public void DeserializeRandom() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could also replace this test method with data-driven method
|
||
{ | ||
Random r = new Random(); | ||
for (int i = 0; i < 10000; i++) | ||
{ | ||
SerializeDeserialize(RandomHierarhyId(r)); | ||
} | ||
} | ||
|
||
public static string RandomHierarhyId(Random random) | ||
{ | ||
StringBuilder sb = new StringBuilder(); | ||
sb.Append("/"); | ||
var levels = random.Next(4); | ||
for (int i = 0; i < levels; i++) | ||
{ | ||
var subLevels = random.Next(1, 4); | ||
for (int j = 0; j < subLevels; j++) | ||
{ | ||
var pattern = KnownPatterns.RandomPattern(random); | ||
sb.Append(random.NextLong(pattern.MinValue, pattern.MaxValue + 1).ToString()); | ||
if (j < subLevels - 1) | ||
sb.Append("."); | ||
} | ||
sb.Append("/"); | ||
} | ||
|
||
return sb.ToString(); | ||
} | ||
|
||
|
||
static string Dump(Stream ms) | ||
{ | ||
return new BitReader(new BinaryReader(ms)).ToString(); | ||
} | ||
} | ||
|
||
public static class RandomExtensionMethods | ||
{ | ||
/// <summary> | ||
/// Returns a random long from min (inclusive) to max (exclusive) | ||
/// </summary> | ||
/// <param name="random">The given random instance</param> | ||
/// <param name="min">The inclusive minimum bound</param> | ||
/// <param name="max">The exclusive maximum bound. Must be greater than min</param> | ||
public static long NextLong(this Random random, long min, long max) | ||
{ | ||
if (max <= min) | ||
throw new ArgumentOutOfRangeException("max", "max must be > min!"); | ||
|
||
//Working with ulong so that modulo works correctly with values > long.MaxValue | ||
ulong uRange = (ulong)(max - min); | ||
|
||
//Prevent a modolo bias; see https://stackoverflow.com/a/10984975/238419 | ||
//for more information. | ||
//In the worst case, the expected number of calls is 2 (though usually it's | ||
//much closer to 1) so this loop doesn't really hurt performance at all. | ||
ulong ulongRand; | ||
do | ||
{ | ||
byte[] buf = new byte[8]; | ||
random.NextBytes(buf); | ||
ulongRand = (ulong)BitConverter.ToInt64(buf, 0); | ||
} while (ulongRand > ulong.MaxValue - ((ulong.MaxValue % uRange) + 1) % uRange); | ||
|
||
return (long)(ulongRand % uRange) + min; | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why have this
if
statement?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just so I could put a breakpoint in the assert when things went wrong. VS didn’t want to rewind the stacktrace after the exception inside AreEqual. But could be removed now.