From 1c38076c5e24333488e07ddf69ffc1851e65bda1 Mon Sep 17 00:00:00 2001 From: Erik Ejlskov Jensen Date: Sat, 22 Apr 2023 00:53:04 +0200 Subject: [PATCH] Remove HierarchyId --- .../Geometry/DBTests.cs | 59 --- .../GlobalUsings.cs | 4 - .../HierarchyId/BitReadWriteTest.cs | 74 --- .../HierarchyId/DeserializeFromBinaryTests.cs | 68 --- .../HierarchyId/HierarchyDbTests.cs | 161 ------- .../HierarchyId/SqlHierarchyIdTests.cs | 297 ------------ .../Microsoft.SqlServer.Types.Tests.csproj | 7 - .../HierarchyIdException.cs | 53 -- .../SqlHierarchy/BitPattern.cs | 85 ---- .../SqlHierarchy/BitReader.cs | 83 ---- .../SqlHierarchy/BitWriter.cs | 68 --- .../SqlHierarchy/HierarhyId.cs | 451 ------------------ .../SqlHierarchy/KnownPatterns.cs | 97 ---- .../SqlHierarchyId.cs | 399 ---------------- 14 files changed, 1906 deletions(-) delete mode 100644 src/Microsoft.SqlServer.Types.Tests/HierarchyId/BitReadWriteTest.cs delete mode 100644 src/Microsoft.SqlServer.Types.Tests/HierarchyId/DeserializeFromBinaryTests.cs delete mode 100644 src/Microsoft.SqlServer.Types.Tests/HierarchyId/HierarchyDbTests.cs delete mode 100644 src/Microsoft.SqlServer.Types.Tests/HierarchyId/SqlHierarchyIdTests.cs delete mode 100644 src/Microsoft.SqlServer.Types/HierarchyIdException.cs delete mode 100644 src/Microsoft.SqlServer.Types/SqlHierarchy/BitPattern.cs delete mode 100644 src/Microsoft.SqlServer.Types/SqlHierarchy/BitReader.cs delete mode 100644 src/Microsoft.SqlServer.Types/SqlHierarchy/BitWriter.cs delete mode 100644 src/Microsoft.SqlServer.Types/SqlHierarchy/HierarhyId.cs delete mode 100644 src/Microsoft.SqlServer.Types/SqlHierarchy/KnownPatterns.cs delete mode 100644 src/Microsoft.SqlServer.Types/SqlHierarchyId.cs diff --git a/src/Microsoft.SqlServer.Types.Tests/Geometry/DBTests.cs b/src/Microsoft.SqlServer.Types.Tests/Geometry/DBTests.cs index 93f4e30..57fc17a 100644 --- a/src/Microsoft.SqlServer.Types.Tests/Geometry/DBTests.cs +++ b/src/Microsoft.SqlServer.Types.Tests/Geometry/DBTests.cs @@ -191,64 +191,5 @@ public void QueryMultiPolygons() } } } - - [TestMethod] - [TestCategory("SqlHierarchyId")] - public void QuerySqlHierarchyId() - { - List hierarchyIds = new List(); - StringBuilder ssb = new StringBuilder(); - using (var cmd = conn.CreateCommand()) - { - cmd.CommandText = $"SELECT OrgNode.ToString(), OrgNode FROM employees"; - using (var reader = cmd.ExecuteReader()) - { - while (reader.Read()) - { - var str = reader.IsDBNull(0) ? null : reader.GetString(0); - var value = reader.GetValue(1); - if(!reader.IsDBNull(0)) - Assert.IsInstanceOfType(value, typeof(SqlHierarchyId)); - var sqlHierId = reader.IsDBNull(1) ? (SqlHierarchyId?)null : reader.GetFieldValue(1); - - Assert.AreEqual(str, sqlHierId?.ToString()); - - if (sqlHierId.HasValue) - { - var should = reader.GetSqlBytes(1).Value; - SqlBytes current; - using (var ms = new MemoryStream()) - { - sqlHierId.Value.Write(new BinaryWriter(ms)); - current = new SqlBytes(ms.ToArray()); - } - Assert.AreEqual(should.Length, current.Length); - for (int i = 0; i < should.Length; i++) - { - Assert.AreEqual(should[i], current[i]); - } - - hierarchyIds.Add(sqlHierId.Value); - } - } - } - } - - foreach (var shi in hierarchyIds) - { - using (var cmd = conn.CreateCommand()) - { - cmd.CommandText = $"SELECT Count(*) FROM employees WHERE OrgNode = @p"; - var p = cmd.CreateParameter(); - p.SqlDbType = SqlDbType.Udt; - p.UdtTypeName = "HierarchyId"; - p.ParameterName = "@p"; - p.Value = shi; - cmd.Parameters.Add(p); - - Assert.AreEqual(1, cmd.ExecuteScalar()); - } - } - } } } diff --git a/src/Microsoft.SqlServer.Types.Tests/GlobalUsings.cs b/src/Microsoft.SqlServer.Types.Tests/GlobalUsings.cs index 75e2941..1b03466 100644 --- a/src/Microsoft.SqlServer.Types.Tests/GlobalUsings.cs +++ b/src/Microsoft.SqlServer.Types.Tests/GlobalUsings.cs @@ -2,17 +2,13 @@ global using System.Collections.Generic; global using System.Data; global using System.IO; -global using System.Runtime.Serialization; global using System.Text; global using Microsoft.VisualStudio.TestTools.UnitTesting; -global using Microsoft.SqlServer.Types.SqlHierarchy; global using System.Data.SqlTypes; #if LEGACY || NETFRAMEWORK -global using Microsoft.SqlServer.Server; global using System.Data.SqlClient; #elif NET5_0_OR_GREATER -global using Microsoft.Data.SqlClient.Server; global using Microsoft.Data.SqlClient; #endif \ No newline at end of file diff --git a/src/Microsoft.SqlServer.Types.Tests/HierarchyId/BitReadWriteTest.cs b/src/Microsoft.SqlServer.Types.Tests/HierarchyId/BitReadWriteTest.cs deleted file mode 100644 index 71065b0..0000000 --- a/src/Microsoft.SqlServer.Types.Tests/HierarchyId/BitReadWriteTest.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Linq; - -namespace Microsoft.SqlServer.Types.Tests.HierarchyId -{ - /// - /// Deserialize tests based on examples in the UDT specification - /// - [TestClass] - public class BitReadWriteTest - { - [TestMethod] - [TestCategory("SqlHierarchyId")] - [TestCategory("Serialize")] - public void TestBitWriter() - { - using(MemoryStream ms = new MemoryStream()) - { - using(BinaryWriter byteW = new BinaryWriter(ms)) - { - BitWriter bw = new BitWriter(byteW); - - bw.Write(0b0, 0); - bw.Write(0b1, 1); - bw.Write(0b00, 2); - bw.Write(0b111, 3); - - bw.Write(0b0000, 4); - bw.Write(0b11111, 5); - bw.Write(0b000000, 6); - bw.Write(0b1111111, 7); - bw.Write(0b00000000, 8); - bw.Write(0b111111111, 9); - bw.Write(0b0000000000, 10); - bw.Write(0b11111111111, 11); - bw.Finish(); - } - - var result = string.Join(" ", ms.ToArray().Select(b => Convert.ToString(b, 2).PadLeft(8, '0'))); - - Assert.AreEqual("10011100 00111110 00000111 11110000 00001111 11111000 00000001 11111111 11000000", result); - } - } - - [TestMethod] - [TestCategory("SqlHierarchyId")] - [TestCategory("Deserialize")] - public void TestBitReader() - { - byte[] array = "10011100 00111110 00000111 11110000 00001111 11111000 00000001 11111111 11000000".Split(' ').Select(a => (byte)Convert.ToInt32(a, 2)).ToArray(); - - using (MemoryStream ms = new MemoryStream(array)) - using (BinaryReader byteR = new BinaryReader(ms)) - { - BitReader br = new BitReader(byteR); - - Assert.AreEqual(0b0, (int)br.Read(0)); - Assert.AreEqual(0b1, (int)br.Read(1)); - Assert.AreEqual(0b00, (int)br.Read(2)); - Assert.AreEqual(0b111, (int)br.Read(3)); - Assert.AreEqual(0b0000, (int)br.Read(4)); - Assert.AreEqual(0b11111, (int)br.Read(5)); - Assert.AreEqual(0b000000, (int)br.Read(6)); - Assert.AreEqual(0b1111111, (int)br.Read(7)); - Assert.AreEqual(0b00000000, (int)br.Read(8)); - Assert.AreEqual(0b111111111, (int)br.Read(9)); - Assert.AreEqual(0b0000000000, (int)br.Read(10)); - Assert.AreEqual(0b11111111111, (int)br.Read(11)); - Assert.AreEqual(0, (int)br.Read(br.Remaining)); - - var result = string.Join(" ", ms.ToArray().Select(b => Convert.ToString(b, 2).PadLeft(8, '0'))); - } - } - } -} diff --git a/src/Microsoft.SqlServer.Types.Tests/HierarchyId/DeserializeFromBinaryTests.cs b/src/Microsoft.SqlServer.Types.Tests/HierarchyId/DeserializeFromBinaryTests.cs deleted file mode 100644 index 962ba87..0000000 --- a/src/Microsoft.SqlServer.Types.Tests/HierarchyId/DeserializeFromBinaryTests.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Microsoft.SqlServer.Types.Tests.HierarchyId -{ - /// - /// Deserialize tests based on examples in the UDT specification - /// - [TestClass] - [TestCategory("Deserialize")] - [TestCategory("SqlHierarchyId")] - public class DeserializeFromBinaryTests - { - - [TestMethod] - public void TestSqlHiarchy1() - { - // The first child of the root node, with a logical representation of / 1 /, is represented as the following bit sequence: - // 01011000 - // The first two bits, 01, are the L1 field, meaning that the first node has a label between 0(zero) and 3.The next two bits, - // 01, are the O1 field and are interpreted as the integer 1.Adding this to the beginning of the range specified by the L1 yields 1. - // The next bit, with the value 1, is the F1 field, which means that this is a "real" level, with 1 followed by a slash in the logical - // representation.The final three bits, 000, are the W field, padding the representation to the nearest byte. - byte[] bytes = { 0x58 }; //01011000 - var hid = new Microsoft.SqlServer.Types.SqlHierarchyId(); - using (var r = new BinaryReader(new MemoryStream(bytes))) - { - hid.Read(r); - } - Assert.AreEqual("/1/", hid.ToString()); - } - - [TestMethod] - public void TestSqlHiarchy2() - { - // As a more complicated example, the node with logical representation / 1 / -2.18 / (the child with label - 2.18 of the child with label 1 of the root node) is represented as the following sequence of bits(a space has been inserted after every grouping of 8 bits to make the sequence easier to follow): - // 01011001 11111011 00000101 01000000 - // The first three fields are the same as in the first example.That is, the first two bits(01) are the L1 field, the second two bits(01) are the O1 field, and the fifth bit(1) is the F1 field.This encodes the / 1 / portion of the logical representation. - // The next 5 bits(00111) are the L2 field, so the next integer is between - 8 and - 1.The following 3 bits(111) are the O2 field, representing the offset 7 from the beginning of this range.Thus, the L2 and O2 fields together encode the integer - 1.The next bit(0) is the F2 field.Because it is 0(zero), this level is fake, and 1 has to be subtracted from the integer yielded by the L2 and O2 fields. Therefore, the L2, O2, and F2 fields together represent -2 in the logical representation of this node. - // The next 3 bits(110) are the L3 field, so the next integer is between 16 and 79.The subsequent 8 bits(00001010) are the L4 field. Removing the anti - ambiguity bits from there(the third bit(0) and the fifth bit(1)) leaves 000010, which is the binary representation of 2.Thus, the integer encoded by the L3 and O3 fields is 16 + 2, which is 18.The next bit(1) is the F3 field, representing the slash(/) after the 18 in the logical representation.The final 6 bits(000000) are the W field, padding the physical representation to the nearest byte. - byte[] bytes = { 0x59, 0xFB, 0x05, 0x40 }; //01011001 11111011 00000101 01000000 - var hid = new Microsoft.SqlServer.Types.SqlHierarchyId(); - using (var r = new BinaryReader(new MemoryStream(bytes))) - { - hid.Read(r); - } - Assert.AreEqual("/1/-2.18/", hid.ToString()); - } - - [TestMethod] - public void TestSqlHiarchy3() - { - byte[] bytes = - { - 0xFE, 0x61, 0x63, 0x47, 0x11, 0xD0, 0x21, 0x2F, 0xCF, 0x90, 0x37, 0xE3, 0xC0, 0x99, 0x10, 0xFA, 0x9B, - 0x70, 0xB9, 0x63, 0xFF, 0xE4, 0xC2, 0xEC, 0xD4, 0x0B, 0x66, 0x39, 0xF9, 0xBA, 0x46, 0x17, 0xE3, 0x31, - 0x32, 0x1F, 0x5C, 0x7C, 0xD0, 0x40, 0x5F, 0xF3, 0x01, 0xC0, 0x1C, 0xDA, 0x1A, 0xE6, 0x3F, 0xD1, 0x45, - 0x4C, 0x39, 0xF0, 0x0E, 0xD3, 0xEB, 0x34, 0xB0, 0x58, 0xD9, 0x80, - }; - var hid = new Microsoft.SqlServer.Types.SqlHierarchyId(); - using (var r = new BinaryReader(new MemoryStream(bytes))) - { - hid.Read(r); - } - - Assert.AreEqual( - "/167515058144400.68456395185647.2799456567/221588961120181.60793214790911.3103372627/52901172697923.230105430619337.3008044937/", - hid.ToString()); - } - } -} diff --git a/src/Microsoft.SqlServer.Types.Tests/HierarchyId/HierarchyDbTests.cs b/src/Microsoft.SqlServer.Types.Tests/HierarchyId/HierarchyDbTests.cs deleted file mode 100644 index cda6a89..0000000 --- a/src/Microsoft.SqlServer.Types.Tests/HierarchyId/HierarchyDbTests.cs +++ /dev/null @@ -1,161 +0,0 @@ -namespace Microsoft.SqlServer.Types.Tests.HierarchyId -{ - [TestCategory("Database")] - [TestCategory("SqlHierarchyId")] - [TestClass] - public class HierarchyDbTests - { - const string DataSource = @"Data Source=(localdb)\mssqllocaldb;Integrated Security=True;AttachDbFileName="; - -#pragma warning disable CS8618 // Guaranteed to be initialized in class initialize - private static SqlConnection _connection; - private static string _path; -#pragma warning restore CS8618 - private static string ConnectionString => DataSource + _path; - - [ClassInitialize] - public static void ClassInitialize(TestContext testContext) - { - _path = Path.Combine(new FileInfo(typeof(HierarchyDbTests).Assembly.Location).Directory.FullName, "HierarchyUnitTestData.mdf"); - if (File.Exists(_path)) - File.Delete(_path); - DatabaseUtil.CreateSqlDatabase(_path); - using (var conn = new SqlConnection(DataSource + _path)) - { - conn.Open(); - var cmd = conn.CreateCommand(); - cmd.CommandText = "CREATE TABLE [dbo].[TreeNode]([Id] [int] IDENTITY(1,1) NOT NULL, [Route] [hierarchyid] NOT NULL);"; - cmd.ExecuteNonQuery(); - conn.Close(); - } - - _connection = new SqlConnection(ConnectionString); - _connection.Open(); - } - - [ClassCleanup] - public static void ClassCleanup() - { - if (_connection != null) - { - _connection.Close(); - _connection.Dispose(); - } - } - - [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); //breakpoint here - - var id = new SqlCommand($"INSERT INTO [dbo].[TreeNode] (Route) output INSERTED.ID VALUES ('{route}') ", _connection).ExecuteScalar(); - - using (var reader = new SqlCommand($"SELECT Route FROM [dbo].[TreeNode] WHERE ID = " + id, _connection).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); //breakpoint here - } - } - } - - [DataTestMethod] - [DynamicData(nameof(GetData), DynamicDataSourceType.Method)] - public void SerializeDeserializeRandom(string route) - { - SerializeDeserialize(route); - } - - private const int CountOfGeneratedCases = 1000; - public static IEnumerable GetData() - { - Random r = new Random(); - for (var i = 0; i < CountOfGeneratedCases; i++) - { - yield return new object[] { 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 - { - /// - /// Returns a random long from min (inclusive) to max (exclusive) - /// - /// The given random instance - /// The inclusive minimum bound - /// The exclusive maximum bound. Must be greater than min - 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; - } - } -} diff --git a/src/Microsoft.SqlServer.Types.Tests/HierarchyId/SqlHierarchyIdTests.cs b/src/Microsoft.SqlServer.Types.Tests/HierarchyId/SqlHierarchyIdTests.cs deleted file mode 100644 index 2e60670..0000000 --- a/src/Microsoft.SqlServer.Types.Tests/HierarchyId/SqlHierarchyIdTests.cs +++ /dev/null @@ -1,297 +0,0 @@ -namespace Microsoft.SqlServer.Types.Tests.HierarchyId -{ - [TestClass] - [TestCategory("SqlHierarchyId")] - public class SqlHierarchyIdTests - { - - [TestMethod] - public void InsertFirstChild() - { - var id = SqlHierarchyId.GetRoot(); - var idChild = id.GetDescendant(SqlHierarchyId.Null, SqlHierarchyId.Null); - Assert.AreEqual(id, idChild.GetAncestor(1)); - } - - [TestMethod] - public void GetParentOfRootNode() - { - var root = SqlHierarchyId.GetRoot(); - var parent = root.GetAncestor(1); - Assert.IsTrue(parent.IsNull); - } - - - [TestMethod] - public void RootIsNotNull() - { - Assert.AreNotEqual(SqlHierarchyId.GetRoot(), SqlHierarchyId.Null); - } - - [TestMethod] - public void RootIsNotNull2() - { - Assert.IsFalse(SqlHierarchyId.GetRoot().IsNull); - } - - [TestMethod] - public void NullIsNull() - { - Assert.IsTrue(SqlHierarchyId.Null.IsNull); - } - - [TestMethod] - [ExpectedException(typeof(ArgumentOutOfRangeException))] - public void ThrowsOnNegativeAncestor() - { - var root = SqlHierarchyId.GetRoot(); - var parent = root.GetAncestor(-1); - } - - [TestMethod] - public void GetDescendantsNormal() - { - var newSibling = SqlHierarchyId.Parse("/9/").GetDescendant( - SqlHierarchyId.Parse("/9/1/"), - SqlHierarchyId.Parse("/9/3/")); - Assert.AreEqual("/9/2/", newSibling.ToString()); - } - - [TestMethod] - public void GetDescendantsIncrementFirst() - { - var newSibling = SqlHierarchyId.Parse("/9/").GetDescendant( - SqlHierarchyId.Parse("/9/1.1/"), - SqlHierarchyId.Parse("/9/2/")); - Assert.AreEqual("/9/1.2/", newSibling.ToString()); - } - - [TestMethod] - [WorkItem(21)] - public void GetDescendantsOfChildren() - { - var child1 = SqlHierarchyId.Parse("/1/1/"); - var child2 = SqlHierarchyId.Parse("/1/1.1/"); - var newSibling = SqlHierarchyId.Parse("/1/").GetDescendant(child1, child2); - Assert.AreEqual(newSibling.ToString(), "/1/1.0/"); - } - - [TestMethod] - public void GetDescendantsDecrementSecond() - { - var newSibling = SqlHierarchyId.Parse("/9/").GetDescendant( - SqlHierarchyId.Parse("/9/1/"), - SqlHierarchyId.Parse("/9/1.1/")); - Assert.AreEqual("/9/1.0/", newSibling.ToString()); - } - - [TestMethod] - public void GetDescendantsAddOne() - { - var newSibling = SqlHierarchyId.Parse("/9/").GetDescendant( - SqlHierarchyId.Parse("/9/1/"), - SqlHierarchyId.Parse("/9/2/")); - Assert.AreEqual("/9/1.1/", newSibling.ToString()); - } - - [TestMethod] - public void ParseLongNodePositive() - { - var expected = "/281479271683151/"; - var result = SqlHierarchyId.Parse(expected); - Assert.AreEqual(expected, result.ToString()); - } - - [TestMethod] - public void ParseLongNodeNegative() - { - var expected = "/-281479271682120/"; - var result = SqlHierarchyId.Parse(expected); - Assert.AreEqual(expected, result.ToString()); - } - - [TestMethod] - public void WriteNull() - { - var hnull = SqlHierarchyId.Null; - AssertEx.ThrowsException(() => hnull.Write(new System.IO.BinaryWriter(new MemoryStream())), typeof(HierarchyIdException), "24002: SqlHierarchyId.Write failed because 'this' was a NULL instance."); - } - - [TestMethod] - [WorkItem(45)] - public void NullToString() - { - Assert.AreEqual("NULL", SqlHierarchyId.Null.ToString()); - } - - [TestMethod] - [WorkItem(45)] - public void TestToString() - { - var h = SqlHierarchyId.Parse("/9/1/"); - Assert.AreEqual("/9/1/", h.ToString()); - } - - [TestMethod] - [WorkItem(44)] - public void DefaultIsNull() - { - var h = default(SqlHierarchyId); - Assert.IsTrue(h.IsNull); - } - - [TestMethod] - [WorkItem(43)] - public void ReadResetsNull() - { - SqlHierarchyId z = SqlHierarchyId.Null; - z.Read(new System.IO.BinaryReader(new System.IO.MemoryStream(Convert.FromBase64String("P6T6")))); - Assert.IsFalse(z.IsNull); // IsNull property should now be false - } - - [TestMethod] - [WorkItem(34)] - public void ParseVeryLargeHierarchyId() - { - string id = "/9138844059576.194933736431247.745612732/136587127227772.29968291099783.2405269301/194815533346310.190518957122630.1754824175/131180557026026.166347272232468.2634227923/112680214461405.155342927909666.4090640326/38488193629220.193847278467647.3890935971/"; - var h = SqlHierarchyId.Parse(id); - Assert.AreEqual(id, h.ToString()); - } - - [TestMethod] - public void CreateSiblingOnFirstLevel() - { - var a = SqlHierarchyId.Parse("/9/"); - var b = SqlHierarchyId.Parse("/10/"); - Assert.AreEqual("/9.1/", a.GetAncestor(1).GetDescendant(a, b).ToString()); - } - - [TestMethod] - public void CreateSiblingOnFirstLevelWithADistance() - { - var a = SqlHierarchyId.Parse("/9/"); - var b = SqlHierarchyId.Parse("/20/"); - Assert.AreEqual("/10/", a.GetAncestor(1).GetDescendant(a, b).ToString()); - } - - [TestMethod] - public void CreateSiblingOnSecondLevel() - { - var a = SqlHierarchyId.Parse("/9/1/"); - var b = SqlHierarchyId.Parse("/9/2/"); - Assert.AreEqual("/9/1.1/", a.GetAncestor(1).GetDescendant(a, b).ToString()); - } - - [TestMethod] - public void CreateSiblingOnSecondLevelWithADistance() - { - var a = SqlHierarchyId.Parse("/9/1/"); - var b = SqlHierarchyId.Parse("/9/10/"); - Assert.AreEqual("/9/2/", a.GetAncestor(1).GetDescendant(a, b).ToString()); - } - - [TestMethod] - public void CreateSiblingOnDeeperLevel() - { - var a = SqlHierarchyId.Parse("/42/1/33.1/1.5/"); - var b = SqlHierarchyId.Parse("/42/1/33.1/1.6/"); - Assert.AreEqual("/42/1/33.1/1.5.1/", a.GetAncestor(1).GetDescendant(a, b).ToString()); - } - - [TestMethod] - public void CreateSiblingWhenHierarchyIsNegative() - { - var a = SqlHierarchyId.Parse("/-1/"); - var b = SqlHierarchyId.Parse("/0/"); - Assert.AreEqual("/-1.1/", a.GetAncestor(1).GetDescendant(a, b).ToString()); - } - - [TestMethod] - public void InsertTopLevelNodeBetweenNodes() - { - var id = SqlHierarchyId.GetRoot(); - var idChild = id.GetDescendant(SqlHierarchyId.Parse("/0/"), SqlHierarchyId.Parse("/1/")); - Assert.AreEqual("/0.1/", idChild.ToString()); - } - - [DataTestMethod] - [DataRow("/1/", "/1/")] - [DataRow("/1/1/", "/1/1/")] - [DataRow("/1/", "/1/1/")] - [DataRow("/2/", "/1/")] - [DataRow("/1/1/", "/1/")] - [ExpectedException(typeof(HierarchyIdException), AllowDerivedTypes = false)] - public void ThrowExceptionWhenGetDescendantReceivesInvalidPairOfArguments(string hierarchyA, string hierarchyB) - { - var a = SqlHierarchyId.Parse(hierarchyA); - var b = SqlHierarchyId.Parse(hierarchyB); - a.GetAncestor(1).GetDescendant(a, b); - } - - [TestMethod] - [WorkItem(36)] - public void OperatorComparison() - { - var h1 = SqlHierarchyId.Parse("/9/2/"); - var h2 = SqlHierarchyId.Parse("/9/1/"); - var h3 = SqlHierarchyId.Parse("/9/1/"); - Assert.IsTrue((bool)(h1 >= h2)); - Assert.IsTrue((bool)(h2 <= h1)); - Assert.IsFalse((bool)(h1 <= h2)); - Assert.IsFalse((bool)(h2 >= h1)); - Assert.IsFalse((bool)(h1 == h2)); - Assert.IsTrue((bool)(h3 >= h2)); - } - - [TestMethod] - [WorkItem(68)] - public void SameLevelArentParents() - { - var h1 = SqlHierarchyId.Parse("/1/1/"); - var h2 = SqlHierarchyId.Parse("/1/2/"); - var a1 = h1.GetAncestor(1); - var a2 = h2.GetAncestor(1); - Assert.AreEqual(a1.ToString(), a2.ToString()); - Assert.IsFalse(h1.IsDescendantOf(h2).Value); - Assert.IsFalse(h2.IsDescendantOf(h1).Value); - } - - [TestMethod] - public void IsDescendantOf() - { - var h1 = SqlHierarchyId.Parse("/1/"); - var h2 = SqlHierarchyId.Parse("/1/2/"); - Assert.IsFalse(h1.IsDescendantOf(h2).Value); - Assert.IsTrue(h2.IsDescendantOf(h1).Value); - } - - [TestMethod] - public void IsDescendantOf2() - { - var h1 = SqlHierarchyId.Parse("/1/"); - var h2 = SqlHierarchyId.Parse("/2/"); - Assert.IsFalse(h1.IsDescendantOf(h2).Value); - Assert.IsFalse(h2.IsDescendantOf(h1).Value); - } - - [TestMethod] - [WorkItem(64)] - public void FailToReadFromInvalidBinaryStream() - { - var stream = new MemoryStream(); - var writer = new BinaryWriter(stream, System.Text.Encoding.UTF8, true); - var hierarchy = SqlHierarchyId.Parse(new System.Data.SqlTypes.SqlString("/1/")); - hierarchy.Write(writer); - //Add extra bytes after - for (int i = 0; i < 10; i++) - { - writer.Write(i); - } - writer.Close(); - stream.Position = 0; - var reader = new BinaryReader(stream); - var hierarchy2 = new SqlHierarchyId(); - AssertEx.ThrowsException(() => hierarchy2.Read(reader), typeof(HierarchyIdException), "24000: SqlHierarchyId operation failed because HierarchyId object was constructed from an invalid binary string. "); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.SqlServer.Types.Tests/Microsoft.SqlServer.Types.Tests.csproj b/src/Microsoft.SqlServer.Types.Tests/Microsoft.SqlServer.Types.Tests.csproj index b038d5b..42e6c15 100644 --- a/src/Microsoft.SqlServer.Types.Tests/Microsoft.SqlServer.Types.Tests.csproj +++ b/src/Microsoft.SqlServer.Types.Tests/Microsoft.SqlServer.Types.Tests.csproj @@ -13,13 +13,6 @@ $(DefineConstants);LEGACY - - - - - - - diff --git a/src/Microsoft.SqlServer.Types/HierarchyIdException.cs b/src/Microsoft.SqlServer.Types/HierarchyIdException.cs deleted file mode 100644 index 83d0cfc..0000000 --- a/src/Microsoft.SqlServer.Types/HierarchyIdException.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace Microsoft.SqlServer.Types -{ - /// - /// The exception that is thrown for invalid SqlHierarchyId values. - /// - [Serializable] - [CLSCompliant(true)] - public class HierarchyIdException : Exception - { - /// - /// Initializes a new instance of the class. - /// - /// - /// This is the default constructor for the class. - /// This creates an exception object by using a default error message. - /// - public HierarchyIdException() - { - } - - /// - /// Initializes a new instance of the class with a custom error message. - /// - /// A string that contains the custom error message. - /// This constructor is called when an object throwing the exception is passing custom error information. - public HierarchyIdException(string message) - : base(message) - { - } - - /// - /// Initializes a new instance of the with a custom error message and the triggering exception object. - /// - /// A string that contains the error message - /// The exception instance that caused the current exception. - /// The constructor is called by another exception object to transmit exception data upstream. - public HierarchyIdException(string message, Exception innerException) - : base(message, innerException) - { - } - - /// - /// Initializes a new instance of the class with serialized data. - /// - /// An object that contains the serialized object data about the exception that is thrown. - /// An object that contains the contextual information about the source or destination - /// This constructor is called during deserialization to reconstitute the exception object transmitted over a stream. - protected HierarchyIdException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } -} diff --git a/src/Microsoft.SqlServer.Types/SqlHierarchy/BitPattern.cs b/src/Microsoft.SqlServer.Types/SqlHierarchy/BitPattern.cs deleted file mode 100644 index 8d58427..0000000 --- a/src/Microsoft.SqlServer.Types/SqlHierarchy/BitPattern.cs +++ /dev/null @@ -1,85 +0,0 @@ -namespace Microsoft.SqlServer.Types -{ - internal class BitPattern - { - internal BitPattern(long minValue, long maxValue, string pattern) - { - MinValue = minValue; - MaxValue = maxValue; - PatternOnes = GetBitMask(pattern, c => c == '1'); - PatternMask = GetBitMask(pattern, c => c == 'x'); - BitLength = (ushort)pattern.Length; - - var prefix = pattern.Substring(0, pattern.IndexOf('x')); - - PrefixOnes = GetBitMask(prefix, c => c == '1'); - PrefixBitLength = (ushort)prefix.Length; - Pattern = pattern; - } - - private ulong GetBitMask(string pattern, Func isOne) - { - ulong result = 0; - foreach (char c in pattern) - result = (result << 1) | (isOne(c) ? (ulong)1 : 0); - return result; - } - - internal long MinValue { get; } - internal long MaxValue { get; } - internal string Pattern { get; } - - internal ulong PatternOnes { get; } - internal ulong PatternMask { get; } - internal int BitLength { get; } - - internal ulong PrefixOnes { get; } - internal int PrefixBitLength { get; } - - internal bool ContainsValue(long value) - { - return MinValue <= value && value <= MaxValue; - } - - public override string ToString() => Pattern; - - public ulong EncodeValue(long val) - { - ulong expand = Expand(PatternMask, val - MinValue); - - ulong value = PatternOnes | expand | 1; - - return value; - } - - private ulong Expand(ulong mask, long value) - { - if (mask == 0) - return 0; - - if ((mask & 0x1) > 0) - return Expand(mask >> 1, value >> 1) << 1 | ((ulong)value & 0x1); - - return Expand(mask >> 1, value) << 1; - } - - internal long Decode(ulong encodedValue, out bool isLast) - { - var decodedValue = Compress(encodedValue, PatternMask); - - isLast = (encodedValue & 0x1) == 0x1; - return ((isLast ? decodedValue : decodedValue - 1) + MinValue); - } - - private long Compress(ulong value, ulong mask) - { - if (mask == 0) - return 0; - - if ((mask & 0x1) > 0) - return Compress(value >> 1, mask >> 1) << 1 | (long)(value & 0x1); - - return Compress(value >> 1, mask >> 1); - } - } -} diff --git a/src/Microsoft.SqlServer.Types/SqlHierarchy/BitReader.cs b/src/Microsoft.SqlServer.Types/SqlHierarchy/BitReader.cs deleted file mode 100644 index a57d9bd..0000000 --- a/src/Microsoft.SqlServer.Types/SqlHierarchy/BitReader.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Linq; - -namespace Microsoft.SqlServer.Types -{ - internal class BitReader - { - private byte[] _bytes; - private int _bitPosition; - - public int Remaining => _bytes.Length * 8 - BitPosition; - - public int BitPosition => _bitPosition; - - internal BitReader(BinaryReader r) - { - var stream = r.BaseStream; - var length = (int)stream.Length; - _bytes = new byte[length]; - stream.Read(_bytes, 0, length); - } - - internal ulong Read(int numBits) - { - var result = Peek(numBits); - _bitPosition += numBits; - return result; - } - - internal ulong Peek(int numBits) - { - if (numBits == 0) - return 0; - - var currentByte = BitPosition / 8; - var newByte = (BitPosition + numBits - 1) / 8; - - if(currentByte == newByte) - { - var offset = (8 - BitPosition % 8) - numBits; - - var mask = (ulong)0xFF >> (8 - numBits) << offset; - - return (ulong)(_bytes[currentByte] & mask) >> offset; - } - else - { - ulong result = 0; - - var startOffset = BitPosition % 8; - var firstCompleteByte = startOffset == 0 ? currentByte : currentByte + 1; - var endOffset = (BitPosition + numBits) % 8; - var lastCompleteByte = endOffset == 0 ? newByte + 1 : newByte; - - if (startOffset > 0) - { - var startMask = (ulong)0xFF >> startOffset; - - result = (_bytes[currentByte] & startMask); - } - - for (int i = firstCompleteByte; i < lastCompleteByte; i++) - { - result = result << 8 | _bytes[i]; - } - - if (endOffset > 0) - { - var endMastk = (ulong)(0xFF >> (8 - endOffset)) << (8 - endOffset); - - result = result << endOffset | (ulong)(endMastk & _bytes[newByte]) >> (8 - endOffset); - } - - return result; - } - } - - public override string ToString() - { - var result = string.Join(" ", _bytes.Select(b => Convert.ToString(b, 2).PadLeft(8, '0'))); - return result.Insert(_bitPosition + (_bitPosition / 8), "|"); - } - } -} diff --git a/src/Microsoft.SqlServer.Types/SqlHierarchy/BitWriter.cs b/src/Microsoft.SqlServer.Types/SqlHierarchy/BitWriter.cs deleted file mode 100644 index f73abcc..0000000 --- a/src/Microsoft.SqlServer.Types/SqlHierarchy/BitWriter.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Microsoft.SqlServer.Types -{ - internal class BitWriter - { - private BinaryWriter _writer; - private byte _nextByte; - private int _nextLength; - - public BitWriter(BinaryWriter w) - { - _writer = w; - } - - public void Write(ulong value, int valueLength) - { - int nextByteRemaining = (8 - _nextLength); - - int pos = 0; - for (; pos <= valueLength - 8; pos += 8) - { - int valueOffset = valueLength - pos - 8; - byte b = (byte)((((ulong)0xFF << valueOffset) & value) >> valueOffset); - - byte mayorPart = (byte)(b >> _nextLength); - byte minorPart = (byte)(b << nextByteRemaining); - - _writer.Write((byte)(_nextByte | mayorPart)); - - _nextByte = minorPart; - } - - var remainingLength = valueLength - pos; - - var remainingByte = (byte)(value & (ushort)(0xFF >> (8 - remainingLength))); - - var diff = 8 - (_nextLength + remainingLength); - if (diff > 0) - { - _nextByte |= (byte)(remainingByte << diff); - _nextLength += remainingLength; - } - else if (diff == 0) - { - _writer.Write((byte)(_nextByte | remainingByte)); - _nextLength = 0; - _nextByte = 0; - } - else if (diff < 0) - { - //Finish Byte - - byte mayorPart = (byte)(remainingByte >> -diff); - _writer.Write((byte)(_nextByte | mayorPart)); - - - //Create new Byte - _nextByte = (byte)(remainingByte << (8 + diff)); - _nextLength = -diff; - } - } - - public void Finish() - { - if (_nextLength > 0) - _writer.Write((byte)_nextByte); - } - } -} diff --git a/src/Microsoft.SqlServer.Types/SqlHierarchy/HierarhyId.cs b/src/Microsoft.SqlServer.Types/SqlHierarchy/HierarhyId.cs deleted file mode 100644 index 1e901aa..0000000 --- a/src/Microsoft.SqlServer.Types/SqlHierarchy/HierarhyId.cs +++ /dev/null @@ -1,451 +0,0 @@ -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Linq; - -namespace Microsoft.SqlServer.Types.SqlHierarchy -{ - /// - /// Represents hierarchical data. - /// - [Serializable] - internal struct HierarchyId : IComparable - { - private readonly string _hierarchyId; - private readonly long[][] _nodes; - - static readonly long[][] __RootNodes = new long[0][]; - - internal long[][] GetNodes() => _nodes ?? __RootNodes; - - /// - /// The Path separator character - /// - public const string PathSeparator = "/"; - - private const string InvalidHierarchyIdExceptionMessage = - "The input string '{0}' is not a valid string representation of a HierarchyId node."; - - private const string GetReparentedValueOldRootExceptionMessage = - "HierarchyId.GetReparentedValue failed because 'oldRoot' was not an ancestor node of 'this'. 'oldRoot' was '{0}', and 'this' was '{1}'."; - - private const string GetDescendantMostBeChildExceptionMessage = - "HierarchyId.GetDescendant failed because '{0}' must be a child of 'this'. '{0}' was '{1}' and 'this' was '{2}'."; - - private const string GetDescendantChild1MustLessThanChild2ExceptionMessage = - "HierarchyId.GetDescendant failed because 'child1' must be less than 'child2'. 'child1' was '{0}' and 'child2' was '{1}'."; - - internal HierarchyId(long[][] nodes) - { - if (nodes == null) - throw new ArgumentNullException(nameof(nodes)); - - this._nodes = nodes; - this._hierarchyId = nodes == null || nodes.Length == 0 ? PathSeparator : - (PathSeparator + string.Join(PathSeparator, nodes.Select(LongArrayToString)) + PathSeparator); - } - - /// - /// Constructs an HierarchyId with the given canonical string representation value. - /// - /// Hierarchyid value. - /// Canonical string representation - public HierarchyId(string hierarchyId) - { - _hierarchyId = hierarchyId ?? throw new ArgumentNullException(nameof(hierarchyId)); - if (hierarchyId == "/") - { - _nodes = __RootNodes; - } - else - { - var nodesStr = hierarchyId.Split('/'); - if (!string.IsNullOrEmpty(nodesStr[0]) || !string.IsNullOrEmpty(nodesStr[nodesStr.Length - 1])) - throw new HierarchyIdException(string.Format(CultureInfo.InvariantCulture, InvalidHierarchyIdExceptionMessage, hierarchyId)); - - int nodesCount = nodesStr.Length - 2; - var nodes = new long[nodesCount][]; - for (int i = 0; i < nodesCount; i++) - { - string node = nodesStr[i + 1]; - var intsStr = node.Split('.'); - var ints = new long[intsStr.Length]; - for (int j = 0; j < intsStr.Length; j++) - { - if (!long.TryParse(intsStr[j], out long num)) - throw new HierarchyIdException(string.Format(CultureInfo.InvariantCulture, InvalidHierarchyIdExceptionMessage, hierarchyId)); - ints[j] = num; - } - nodes[i] = ints; - } - _nodes = nodes; - } - } - - /// - /// Returns a hierarchyid representing the nth ancestor of this. - /// - /// A hierarchyid representing the nth ancestor of this. - /// n - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "n")] - public HierarchyId GetAncestor(int n) - { - if (GetLevel() == n) - return new HierarchyId(__RootNodes); - - if (GetLevel() < n) - throw new ArgumentException(nameof(n)); - - string hierarchyStr = PathSeparator + string.Join(PathSeparator, GetNodes().Take(GetLevel() - n).Select(LongArrayToString)) + PathSeparator; - return new HierarchyId(hierarchyStr); - } - - /// - /// Returns a child node of the parent. - /// - /// null or the hierarchyid of a child of the current node. - /// null or the hierarchyid of a child of the current node. - /// - /// Returns one child node that is a descendant of the parent. - /// If both child1 and child2 are null, returns a child of parent. - /// If child1 is not null, and child2 is null, returns a child of parent greater than child1. - /// If child2 is not null and child1 is null, returns a child of parent less than child2. - /// If child1 and child2 are not null, returns a child of parent greater than child1 and less than child2. - /// If child1 is not null and not a child of parent, an exception is raised. - /// If child2 is not null and not a child of parent, an exception is raised. - /// If child1 >= child2, an exception is raised. - /// - public HierarchyId GetDescendant(HierarchyId? child1, HierarchyId? child2) - { - if (child1 != null && (child1.Value.GetLevel() != GetLevel() + 1 || !child1.Value.IsDescendantOf(this))) - throw new HierarchyIdException(string.Format(CultureInfo.InvariantCulture, GetDescendantMostBeChildExceptionMessage, "child1", child1, ToString())); - - if (child2 != null && (child2.Value.GetLevel() != GetLevel() + 1 || !child2.Value.IsDescendantOf(this))) - throw new HierarchyIdException(string.Format(CultureInfo.InvariantCulture, GetDescendantMostBeChildExceptionMessage, "child2", child1, ToString())); - - if (child1 == null && child2 == null) - return new HierarchyId(ToString() + 1 + PathSeparator); - - string hierarchyStr; - if (child1 == null) - { - var result = new HierarchyId(child2.ToString()); - var lastNode = result.GetNodes().Last(); - //decrease the last part of the last node of the 1nd child - lastNode[lastNode.Length - 1]--; - hierarchyStr = PathSeparator + string.Join(PathSeparator, result.GetNodes().Select(LongArrayToString)) + PathSeparator; - return new HierarchyId(hierarchyStr); - } - if (child2 == null) - { - var result = new HierarchyId(child1.ToString()); - var lastNode = result.GetNodes().Last(); - //increase the last part of the last node of the 2nd child - lastNode[lastNode.Length - 1]++; - hierarchyStr = PathSeparator + string.Join(PathSeparator, result.GetNodes().Select(LongArrayToString)) + PathSeparator; - return new HierarchyId(hierarchyStr); - } - var child1LastNode = child1.Value.GetNodes().Last(); - var child2LastNode = child2.Value.GetNodes().Last(); - var cmp = CompareLongArrays(child1LastNode, child2LastNode); - if (cmp >= 0) - { - throw new HierarchyIdException( string.Format(CultureInfo.InvariantCulture, GetDescendantChild1MustLessThanChild2ExceptionMessage, child1, child2)); - } - var newNode = GetBetween(child1LastNode, child2LastNode); - - var nodesToJoin = GetNodes().Select(LongArrayToString).Concat(new[] {LongArrayToString(newNode)}); - return new HierarchyId(PathSeparator + string.Join(PathSeparator, nodesToJoin) + PathSeparator); - } - - static long[] GetBetween(long[] node1, long[] node2) - { - int i = 0; - for (; i < node1.Length; i++) - { - if (node1[i] < node2[i]) - { - break; - } - } - if (i == node1.Length) - i--; - - var result = node1.Take(i + 1).ToArray(); - if (result[i] + 1 < node2[i]) - { - result[i]++; - return result; - } - else if (node1.Length > i + 1) - { - return result.Concat(new[] { node1[i + 1] + 1 }).ToArray(); - } - else if (node2.Length > i + 1) - { - return result.Concat(new[] { node2[i + 1] - 1 }).ToArray(); - } - else if (node2.Length >= i + 1) - { - return result.Concat(new[] { 1L }).ToArray(); - } - - return result; - } - - /// - /// Returns an integer that represents the depth of the node this in the tree. - /// - /// An integer that represents the depth of the node this in the tree. - public short GetLevel() - { - return (short)GetNodes().Length; - } - - /// - /// Returns the root of the hierarchy tree. - /// - /// The root of the hierarchy tree. - [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] - public static HierarchyId GetRoot() - { - return new HierarchyId("/"); - } - - /// - /// Returns true if this is a descendant of parent. - /// - /// True if this is a descendant of parent. - /// parent - public bool IsDescendantOf(HierarchyId parent) - { - if (parent.GetLevel() > GetLevel()) - { - return false; - } - for (int i = 0; i < parent.GetLevel(); i++) - { - int cmp = CompareLongArrays(GetNodes()[i], parent.GetNodes()[i]); - if (cmp != 0) - { - return false; - } - } - return true; - } - - /// - /// Returns a node whose path from the root is the path to newRoot, followed by the path from oldRoot to this. - /// - /// Hierarchyid value. - /// oldRoot - /// newRoot - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Reparented")] - public HierarchyId GetReparentedValue(HierarchyId oldRoot, HierarchyId newRoot) - { - if (!IsDescendantOf(oldRoot)) - { - throw new ArgumentException( - string.Format(CultureInfo.InvariantCulture, GetReparentedValueOldRootExceptionMessage, oldRoot, ToString()), "oldRoot"); - } - StringBuilder sb = new StringBuilder(); - sb.Append(PathSeparator); - foreach (var node in newRoot.GetNodes()) - { - sb.Append(LongArrayToString(node)); - sb.Append(PathSeparator); - } - foreach (var node in GetNodes().Skip(oldRoot.GetLevel())) - { - sb.Append(LongArrayToString(node)); - sb.Append(PathSeparator); - } - return new HierarchyId(sb.ToString()); - } - - /// - /// Converts the canonical string representation of a hierarchyid to a hierarchyid value. - /// - /// Hierarchyid value. - /// input - public static HierarchyId Parse(string input) - { - return new HierarchyId(input); - } - - private static string LongArrayToString(IEnumerable array) - { - return string.Join(".", array); - } - - private static int CompareLongArrays(long[] array1, long[] array2) - { - int count = Math.Min(array1.Length, array2.Length); - for (int i = 0; i < count; i++) - { - long item1 = array1[i]; - long item2 = array2[i]; - - if (item1 < item2) - return -1; - - if (item1 > item2) - return 1; - } - - if (array1.Length > count) - return 1; - - if (array2.Length > count) - return -1; - - return 0; - } - - /// - /// Compares two HierarchyIds by their values. - /// - /// a HierarchyId to compare - /// a HierarchyId to compare - /// - /// A 32-bit signed integer that indicates the lexical relationship between the two comparands. - /// Value Condition Less than zero: hid1 is less than hid2. - /// Zero: hid1 equals hid2. - /// Greater than zero: hid1 is greater than hid2. - /// - public static int Compare(HierarchyId hid1, HierarchyId hid2) - { - var nodes1 = hid1.GetNodes(); - var nodes2 = hid2.GetNodes(); - - int count = Math.Min(nodes1.Length, nodes2.Length); - for (int i = 0; i < count; i++) - { - var node1 = nodes1[i]; - var node2 = nodes2[i]; - int cmp = CompareLongArrays(node1, node2); - if (cmp != 0) - return cmp; - - } - - if (nodes1.Length > count) - return 1; - - if (nodes2.Length > count) - return -1; - - return 0; - } - - /// - /// Compares two HierarchyIds by their values. - /// - /// a HierarchyId to compare - /// a HierarchyId to compare - /// - /// true if the the first parameter is less than the second parameter, false otherwise - /// - public static bool operator <(HierarchyId hid1, HierarchyId hid2) => Compare(hid1, hid2) < 0; - - /// - /// Compares two HierarchyIds by their values. - /// - /// a HierarchyId to compare - /// a HierarchyId to compare - /// - /// true if the the first parameter is greater than the second parameter, false otherwise - /// - public static bool operator >(HierarchyId hid1, HierarchyId hid2) => Compare(hid1, hid2) > 0; - - /// - /// Compares two HierarchyIds by their values. - /// - /// a HierarchyId to compare - /// a HierarchyId to compare - /// - /// true if the the first parameter is less or equal than the second parameter, false otherwise - /// - public static bool operator <=(HierarchyId hid1, HierarchyId hid2) => Compare(hid1, hid2) <= 0; - - /// - /// Compares two HierarchyIds by their values. - /// - /// a HierarchyId to compare - /// a HierarchyId to compare - /// - /// true if the the first parameter is greater or equal than the second parameter, false otherwise - /// - public static bool operator >=(HierarchyId hid1, HierarchyId hid2) => Compare(hid1, hid2) >= 0; - - /// - /// Compares two HierarchyIds by their values. - /// - /// a HierarchyId to compare - /// a HierarchyId to compare - /// true if the two HierarchyIds are equal, false otherwise - public static bool operator ==(HierarchyId hid1, HierarchyId hid2) => Compare(hid1, hid2) == 0; - - /// - /// Compares two HierarchyIds by their values. - /// - /// a HierarchyId to compare - /// a HierarchyId to compare - /// true if the two HierarchyIds are not equal, false otherwise - public static bool operator !=(HierarchyId hid1, HierarchyId hid2) => Compare(hid1, hid2) != 0; - - /// - /// Compares this instance to a given HierarchyId by their values. - /// - /// the HierarchyId to compare against this instance - /// true if this instance is equal to the given HierarchyId, and false otherwise - public bool Equals(HierarchyId other) => Compare(this, other) == 0; - - /// - /// Returns a value-based hash code, to allow HierarchyId to be used in hash tables. - /// - /// the hash value of this HierarchyId - public override int GetHashCode() - { - return ToString().GetHashCode(); - } - - /// - /// Compares this instance to a given HierarchyId by their values. - /// - /// the HierarchyId to compare against this instance - /// true if this instance is equal to the given HierarchyId, and false otherwise - public override bool Equals(object obj) - { - return Equals((HierarchyId)obj); - } - - /// - /// Returns a string representation of the hierarchyid value. - /// - /// A string representation of the hierarchyid value. - public override string ToString() - { - return _hierarchyId ?? "NULL"; - } - - public bool IsNull => _hierarchyId == null; - - /// - /// Implementation of IComparable.CompareTo() - /// - /// The object to compare to - /// 0 if the HierarchyIds are "equal" (i.e., have the same _hierarchyId value) - public int CompareTo(object obj) - { - if (obj is HierarchyId h) - { - return Compare(this, h); - } - - Debug.Assert(false, "object is not a HierarchyId"); - return -1; - } - } -} - diff --git a/src/Microsoft.SqlServer.Types/SqlHierarchy/KnownPatterns.cs b/src/Microsoft.SqlServer.Types/SqlHierarchy/KnownPatterns.cs deleted file mode 100644 index d238d09..0000000 --- a/src/Microsoft.SqlServer.Types/SqlHierarchy/KnownPatterns.cs +++ /dev/null @@ -1,97 +0,0 @@ -namespace Microsoft.SqlServer.Types.SqlHierarchy -{ - internal static class KnownPatterns - { - //http://www.adammil.net/blog/v100_how_the_SQL_Server_hierarchyid_data_type_works_kind_of_.html - private static BitPattern[] PositivePatterns = new[] - { - new BitPattern(0, 3, "01xxT"), - new BitPattern(4, 7, "100xxT"), - new BitPattern(8, 15, "101xxxT"), - new BitPattern(16, 79, "110xx0x1xxxT"), - new BitPattern(80, 1103, "1110xxx0xxx0x1xxxT"), - new BitPattern(1104, 5199, "11110xxxxx0xxx0x1xxxT"), - new BitPattern(5200, 4294972495, "111110xxxxxxxxxxxxxxxxxxx0xxxxxx0xxx0x1xxxT"), - new BitPattern(4294972496, 281479271683151,"111111xxxxxxxxxxxxxx0xxxxxxxxxxxxxxxxxxxxx0xxxxxx0xxx0x1xxxT"), - }; - - private static BitPattern[] NegativePatterns = new[] - { - new BitPattern(-8, -1, "00111xxxT"), - new BitPattern(-72, -9, "0010xx0x1xxxT"), - new BitPattern(-4168, -73, "000110xxxxx0xxx0x1xxxT"), - new BitPattern(-4294971464, -4169, "000101xxxxxxxxxxxxxxxxxxx0xxxxxx0xxx0x1xxxT"), - new BitPattern(-281479271682120, -4294971465,"000100xxxxxxxxxxxxxx0xxxxxxxxxxxxxxxxxxxxx0xxxxxx0xxx0x1xxxT"), - }; - - internal static BitPattern RandomPattern(Random r) - { - var index = r.Next(PositivePatterns.Length + NegativePatterns.Length); - - return index < PositivePatterns.Length ? PositivePatterns[index] : - NegativePatterns[index - PositivePatterns.Length]; - } - - internal static BitPattern GetPatternByValue(long value) - { - if (value >= 0) - { - foreach (var p in PositivePatterns) - { - if (p.ContainsValue(value)) - return p; - } - - throw new InvalidCastException("No pattern found for value:" + value); - } - else - { - foreach (var p in NegativePatterns) - { - if(p.ContainsValue(value)) - return p; - } - - throw new InvalidCastException("No pattern found for value:" + value); - } - } - - internal static BitPattern? GetPatternByPrefix(BitReader bitR) - { - var remaining = bitR.Remaining; - - if (remaining == 0) - return null; - - if (remaining < 8 && bitR.Peek(remaining) == 0) - return null; - - if (bitR.Peek(2) == 0) - { - foreach (var pattern in NegativePatterns) - { - if (pattern.BitLength > remaining) - break; - - if (pattern.PrefixOnes == bitR.Peek(pattern.PrefixBitLength)) - return pattern; - } - - throw new HierarchyIdException("24000: SqlHierarchyId operation failed because HierarchyId object was constructed from an invalid binary string. ", new InvalidCastException("No pattern found for: " + Convert.ToString((int)bitR.Peek(remaining), 2).PadLeft(remaining, '0'))); - } - else - { - foreach (var pattern in PositivePatterns) - { - if (pattern.BitLength > remaining) - break; - - if (pattern.PrefixOnes == bitR.Peek(pattern.PrefixBitLength)) - return pattern; - } - - throw new HierarchyIdException("24000: SqlHierarchyId operation failed because HierarchyId object was constructed from an invalid binary string. ", new InvalidCastException("No pattern found for: " + Convert.ToString((int)bitR.Peek(remaining), 2).PadLeft(remaining, '0'))); - } - } - } -} diff --git a/src/Microsoft.SqlServer.Types/SqlHierarchyId.cs b/src/Microsoft.SqlServer.Types/SqlHierarchyId.cs deleted file mode 100644 index c635782..0000000 --- a/src/Microsoft.SqlServer.Types/SqlHierarchyId.cs +++ /dev/null @@ -1,399 +0,0 @@ -using Microsoft.SqlServer.Types.SqlHierarchy; -using System.Linq; - -namespace Microsoft.SqlServer.Types -{ - /// - /// The SqlHierarchyId type represents a position in a hierarchical structure, specifying depth and breadth. - /// - [SqlUserDefinedType(Format.UserDefined, IsByteOrdered = true, MaxByteSize = 892, Name = "SqlHierarchyId")] - public struct SqlHierarchyId : IBinarySerialize, INullable, IComparable - { - private HierarchyId _imp; - private bool _isNotNull; - - /// - /// Gets a value indicating whether the is null. - /// - /// Boolean representing true (1) if the node is null; otherwise, false (0). - public bool IsNull => !_isNotNull; // This is a bit backwards, but is done so default(SqlHierarchyId) will return a null id - - /// - /// Gets a with a hierarchy identification of null. - /// - public static SqlHierarchyId Null - { - [SqlMethodAttribute(DataAccess = DataAccessKind.None, SystemDataAccess = SystemDataAccessKind.None, - IsDeterministic = true, IsPrecise = true, IsMutator = false)] - get; - } = new SqlHierarchyId(new HierarchyId(), true); - - private SqlHierarchyId(HierarchyId imp, bool isNull = false) - { - _isNotNull = !isNull; - _imp = imp; - } - - /// - /// Gets a value representing the root node of the hierarchy. - /// - /// A representing the root node of the hierarchical tree. Root value is typically 0x. - [SqlMethod(DataAccess = DataAccessKind.None, SystemDataAccess = SystemDataAccessKind.None, InvokeIfReceiverIsNull = false, OnNullCall = false, IsDeterministic = true, IsPrecise = true, IsMutator = false)] - public static SqlHierarchyId GetRoot() => new SqlHierarchyId(HierarchyId.GetRoot()); - - /// - /// Converts the canonical string representation of a node to a value. - /// - /// String representation of node. - /// representing the node described canonically. - [SqlMethod(DataAccess = DataAccessKind.None, SystemDataAccess = SystemDataAccessKind.None, InvokeIfReceiverIsNull = false, OnNullCall = false, IsDeterministic = true, IsPrecise = true, IsMutator = false)] - public static SqlHierarchyId Parse(SqlString input) => new SqlHierarchyId(HierarchyId.Parse((string)input)); - - /// - /// Returns a value indicating the results of a comparison between a SqlHierarchyId and an object. - /// - /// An object to be compared to this. - /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has these meanings: - /// - /// - /// Value - /// Meaning - /// - /// Less than zerothis is less than . - /// Zerothis is equal to . - /// Greater than zerothis is greater than . - /// - /// - /// - /// Throws an exception if is not a node. - /// This member is sealed. - /// - public int CompareTo(object obj) => this.CompareTo((SqlHierarchyId)obj); - - /// - /// Returns a value indicating the results of a comparison between two nodes. - /// - /// A node to compare to this. - /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has these meanings: - /// - /// - /// Value - /// Meaning - /// - /// Less than zerothis is less than . - /// Zerothis is equal to . - /// Greater than zerothis is greater than . - /// - /// - /// - /// If both nodes are null, returns 0. - /// If one node is null, it is considered to be less than the non-null node. - /// - public int CompareTo(SqlHierarchyId hid) - { - if(IsNull) - { - if (!hid.IsNull) - return -1; - return 0; - } - if (hid.IsNull) - return 1; - if (this < hid) - return -1; - if (this > hid) - return 1; - return 0; - } - - /// - /// Evaluates whether and obj are equal. - /// - /// The object against which to compare this. - /// Boolean. true (1) if this and obj are equal; otherwise, false (0). - /// - /// Returns false (0) if obj is not a SqlHierarchyId node. - /// Returns true (1) if both this and obj are null. - /// - public override bool Equals(object obj) => obj is SqlHierarchyId && Equals((SqlHierarchyId)obj); - - private bool Equals(SqlHierarchyId other) => (IsNull && other.IsNull) || (this == other).IsTrue; - - /// - /// Retrieves the node n levels up the hierarchical tree. - /// - /// An integer representing the number of levels to ascend in the hierarchy. - /// - /// representing the nth ancestor of this. - /// If a number greater than is passed, null is returned. - /// If a negative number is passed, an exception is raised indicating that the argument is out of range. - /// - [SqlMethod(DataAccess = DataAccessKind.None, SystemDataAccess = SystemDataAccessKind.None, InvokeIfReceiverIsNull = false, OnNullCall = false, IsDeterministic = true, IsPrecise = true, IsMutator = false)] - public SqlHierarchyId GetAncestor(int n) - { - if (IsNull || _imp.GetLevel() < n) - { - return Null; - } - if (n < 0) - { - throw new ArgumentOutOfRangeException("24011: SqlHierarchyId.GetAncestor failed because 'n' was negative."); - } - return new SqlHierarchyId(_imp.GetAncestor(n)); - } - /// - /// Gets the value of a descendant node that is greater than and less than . - /// - /// The lower bound. - /// The upper bound. - /// A SqlHierarchyId with a value greater than the lower bound and less than the upper bound. - /// - /// - /// If parent is null, returns null. - /// If parent is not null, and both and are null, returns a descendant of parent. - /// If parent and are not null, and is null, returns a descendant of parent greater than . - /// If parent and are not null and is null, returns a descendant of parent less than . - /// If parent, , and child2 are not null, returns a descendant of parent greater than and less than . - /// An exception is raised if or are not null and are not a descendant of parent. - /// If >= , an exception is raised. - /// - /// - [SqlMethod(DataAccess = DataAccessKind.None, SystemDataAccess = SystemDataAccessKind.None, InvokeIfReceiverIsNull = false, OnNullCall = true, IsDeterministic = true, IsPrecise = true, IsMutator = false)] - public SqlHierarchyId GetDescendant(SqlHierarchyId child1, SqlHierarchyId child2) => new SqlHierarchyId(_imp.GetDescendant( - child1.IsNull ? default(HierarchyId?) : child1._imp, - child2.IsNull ? default(HierarchyId?) : child2._imp)); - - /// - /// Gets a hash of the path from the root node of the hierarchy tree to the node. - /// - /// A 32-bit signed integer representing the hash code for this instance. - public override int GetHashCode() => _imp.GetHashCode(); - - /// - /// Gets a value indicating the level of the node in the hierarchical tree. - /// - /// A 16-bit integer indicating the depth of the node in the hierarchical tree. - /// The root of the hierarchy is level 0. - [SqlMethod(DataAccess = DataAccessKind.None, SystemDataAccess = SystemDataAccessKind.None, InvokeIfReceiverIsNull = false, OnNullCall = false, IsDeterministic = true, IsPrecise = true, IsMutator = false)] - public SqlInt16 GetLevel() => _imp.GetLevel(); - - /// - /// Gets a value representing the location of a new node that has a path from newRoot equal to the path from oldRoot to this, effectively moving this to the new location. - /// - /// An ancestor of the node specifying the endpoint of the path segment that is to be moved. - /// The node that represents the new ancestor of this. - /// A node representing the new hierarchical location of this. Will return null if , , or this are null. - /// - /// Returns a node whose path from the root is the path to , followed by the path from to this. - /// The data type represents but does not enforce the hierarchical structure. Users must ensure that the node is appropriately structured for the new location. - /// - [SqlMethod(DataAccess = DataAccessKind.None, SystemDataAccess = SystemDataAccessKind.None, InvokeIfReceiverIsNull = false, OnNullCall = false, IsDeterministic = true, IsPrecise = true, IsMutator = false)] - public SqlHierarchyId GetReparentedValue(SqlHierarchyId oldRoot, SqlHierarchyId newRoot) - { - if (!IsNull && !oldRoot.IsNull && !newRoot.IsNull) - { - if (!IsDescendantOf(oldRoot)) - { - throw new HierarchyIdException("Instance is not a descendant of 'oldRoot'"); - } - return new SqlHierarchyId(_imp.GetReparentedValue(oldRoot._imp, newRoot._imp)); - } - return Null; - } - /// - /// Gets a value indicating whether the node is the descendant of the parent. - /// - /// The specified node for which the IsDescendantOf test is performed. - /// Boolean, true (1) for all the nodes in the sub-tree rooted at parent; false (0) for all other nodes. - /// parent is considered its own descendant. - [SqlMethod(DataAccess = DataAccessKind.None, SystemDataAccess = SystemDataAccessKind.None, InvokeIfReceiverIsNull = false, OnNullCall = false, IsDeterministic = true, IsPrecise = true, IsMutator = false)] - public SqlBoolean IsDescendantOf(SqlHierarchyId parent) => _imp.IsDescendantOf(parent._imp); - - /// - /// Returns the canonical string representation of a node from a value. - /// - /// - /// - /// Called implicitly when a conversion from a data type to a string type occurs. - /// Acts as the opposite of . - /// - /// - /// DECLARE @StringValue AS nvarchar(4000), @hierarchyidValue AS hierarchyid - /// SET @StringValue = '/1/1/3/' - /// SET @hierarchyidValue = 0x5ADE - /// SELECT hierarchyid::Parse(@StringValue) AS hierarchyidRepresentation, - /// @hierarchyidValue.ToString() AS StringRepresentation; - /// GO - /// - [SqlMethod(DataAccess = DataAccessKind.None, SystemDataAccess = SystemDataAccessKind.None, InvokeIfReceiverIsNull = false, OnNullCall = false, IsDeterministic = true, IsPrecise = true, IsMutator = false)] - public override string ToString() => _imp.ToString(); - - /// - /// Writes a to a specified binary writer. - /// - /// The specified binary writer. - /// - /// Throws an exception if w is null. - /// Throws an exception if the is null. - /// This member is sealed. - /// - [SqlMethod(IsDeterministic = true, IsPrecise = true)] - public void Write(BinaryWriter w) - { - if (w is null) - throw new ArgumentNullException(nameof(w)); - if (IsNull) - throw new HierarchyIdException("24002: SqlHierarchyId.Write failed because 'this' was a NULL instance."); - BitWriter bw = new BitWriter(w); - - var nodes = this._imp.GetNodes(); - - for (int i = 0; i < nodes.Length; i++) - { - var subNodes = nodes[i]; - for (int j = 0; j < subNodes.Length; j++) - { - bool isLast = j == (subNodes.Length - 1); - - - long val = subNodes[j]; - - if (isLast) - { - BitPattern p = KnownPatterns.GetPatternByValue(val); - ulong value = p.EncodeValue(val); - bw.Write(value, p.BitLength); - } - else - { - BitPattern p = KnownPatterns.GetPatternByValue(val + 1); - ulong value = p.EncodeValue(val + 1) - 1; - bw.Write(value, p.BitLength); - } - } - } - - bw.Finish(); - } - - /// - /// Reads from a specified binary reader into a . - /// - /// The specified binary reader. - /// - /// Throws an exception if r is null.
- /// Throws an exception if the SqlHierarchyId is not null.
- /// This member is sealed. - ///
- [SqlMethod(IsDeterministic = true, IsPrecise = true)] - public void Read(BinaryReader r) - { - if (r is null) - throw new ArgumentNullException(nameof(r)); - var bitR = new BitReader(r); - List> result = new List>(); - - while (true) - { - List step = new List(); - - while (true) - { - var p = KnownPatterns.GetPatternByPrefix(bitR); - - if (p == null) - goto finish; - - ulong encodedValue = bitR.Read(p.BitLength); - - long value = p.Decode(encodedValue, out bool isLast); - - step.Add(value); - - if (isLast) - break; - } - - result.Add(step); - } - - finish: - - this._imp = new HierarchyId(result.Select(a => a.ToArray()).ToArray()); - this._isNotNull = !_imp.IsNull; - } - - /// - /// Evaluates whether two nodes are equal. - /// - /// First node to compare. - /// Second node to compare. - /// Boolean. true (1) if and are equal; otherwise, false (0). - /// Returns null if either or are null. - public static SqlBoolean operator ==(SqlHierarchyId hid1, SqlHierarchyId hid2) => hid1.IsNull || hid2.IsNull ? SqlBoolean.Null : hid1._imp == hid2._imp; - - /// - /// Evaluates whether two nodes are unequal. - /// - /// First node to compare. - /// Second node to compare. - /// - /// Returns null if either or are null. - public static SqlBoolean operator !=(SqlHierarchyId hid1, SqlHierarchyId hid2) => hid1.IsNull || hid2.IsNull ? SqlBoolean.Null : hid1._imp != hid2._imp; - - /// - /// Evaluates whether one specified node is less than another. - /// - /// First node to compare. - /// Second node to compare. - /// - /// Returns null if either or are null. - public static SqlBoolean operator <(SqlHierarchyId hid1, SqlHierarchyId hid2) => hid1.IsNull || hid2.IsNull ? SqlBoolean.Null : hid1._imp < hid2._imp; - - /// - /// Evaluates whether one specified node is greater than another. - /// - /// First node to compare. - /// Second node to compare. - /// - /// Returns null if either or are null. - public static SqlBoolean operator >(SqlHierarchyId hid1, SqlHierarchyId hid2) => hid1.IsNull || hid2.IsNull ? SqlBoolean.Null : hid1._imp > hid2._imp; - - /// - /// Evaluates whether one specified node is less than or equal to another. - /// - /// First node to compare. - /// Second node to compare. - /// - /// Returns null if either or are null. - public static SqlBoolean operator <=(SqlHierarchyId hid1, SqlHierarchyId hid2) => hid1.IsNull || hid2.IsNull ? SqlBoolean.Null : hid1._imp <= hid2._imp; - - /// - /// Evaluates whether one specified node is greater than or equal to another. - /// - /// First node to compare. - /// Second node to compare. - /// - /// Returns null if either or are null. - public static SqlBoolean operator >=(SqlHierarchyId hid1, SqlHierarchyId hid2) => hid1.IsNull || hid2.IsNull ? SqlBoolean.Null : hid1._imp >= hid2._imp; - - //public static SqlHierarchyId Deserialize(SqlBytes bytes) - //{ - // using (var r = new BinaryReader(bytes.Stream)) - // { - // var hid = new SqlHierarchyId(new HierarchyId()); - // hid.Read(r); - // return hid; - // } - //} - - //public SqlBytes Serialize() - //{ - // using (var ms = new MemoryStream()) - // { - // Write(new BinaryWriter(ms)); - // return new SqlBytes(ms.ToArray()); - // } - //} - } -}