From 1c3074a65f6a651e9f7f0552035be5c7d5147dab Mon Sep 17 00:00:00 2001 From: Tentone Date: Thu, 25 Jul 2024 12:24:47 +0100 Subject: [PATCH] Derp --- README.md | 10 ++- hierarchy_id.go | 91 ++++++++++++++-------- hierarchy_id_test.go | 182 +++++++++++++++++++++---------------------- 3 files changed, 156 insertions(+), 127 deletions(-) diff --git a/README.md b/README.md index 146c835..fe32cb2 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,18 @@ # Gorm hierarchyid + - The hierarchyid is data to represent a position in a hierarchy. + - It is a variable length type with reduced storage requirements. - Handle hierarchyid type in SQL Server and go. - - The hierarchyid data type is a variable length, system data type. - - Hierarchyid can represent a position in a hierarchy. - Implements generation and parsing of hierarchyid type in go. - Implements a type wrapper for usage with gorm. +## Usage + - + - + ## Resources - [adamil.net - How the SQL Server hierarchyid data type works (kind of)](http://www.adammil.net/blog/v100_how_the_SQL_Server_hierarchyid_data_type_works_kind_of_.html) - [hierarchyid data type method reference](https://learn.microsoft.com/en-us/sql/t-sql/data-types/hierarchyid-data-type-method-reference?view=sql-server-ver16&redirectedfrom=MSDN) - - .NET Implementation ([Logic](https://github.com/dotMorten/Microsoft.SqlServer.Types/tree/main/src/Microsoft.SqlServer.Types/SqlHierarchy) + [Interface](https://github.com/dotMorten/Microsoft.SqlServer.Types/blob/main/src/Microsoft.SqlServer.Types/SqlHierarchyId.cs)) + - .NET Implementation ([Logic](https://github.com/dotMorten/Microsoft.SqlServer.Types/tree/main/src/Microsoft.SqlServer.Types/SqlHierarchy) + [Interface](https://github.com/dotMorten/Microsoft.SqlServer.Types/blob/main/src/Microsoft.SqlServer.Types/SqlHierarchyId.cs)) - [Github](https://github.com/dotMorten/Microsoft.SqlServer.Types/pull/33) pull request Long type support ## License diff --git a/hierarchy_id.go b/hierarchy_id.go index 0512c25..b65811a 100644 --- a/hierarchy_id.go +++ b/hierarchy_id.go @@ -10,6 +10,32 @@ import ( // The hierarchyid data type is a series of integers separated by slashes. For example, \1\2\3\. type HierarchyId = []int64 +// Create a string representation of the hierarchyid data type +// +// The string representation is a series of integers separated by slashes. For example, \1\2\3\ +func ToString(data HierarchyId) string { + var r string = "/" + for _, level := range data { + r += strconv.FormatInt(level, 10) + "/" + } + return r +} + +// Compare two hierarchyid data types +// +// The comparison is done by comparing each level of the hierarchyid. If the levels are the same, the next level is compared. If the levels are different, the comparison stops and the result is returned. +func Compare(a HierarchyId, b HierarchyId) int { + for i := 0; i < len(a) && i < len(b); i++ { + if a[i] < b[i] { + return -1 + } else if a[i] > b[i] { + return 1 + } + } + + return 0 +} + // Parse takes a byte slice of data stored in SQL Server hierarchyid format and returns a HierarchyId. // // SQL server uses a custom binary format for hierarchyid. @@ -20,17 +46,17 @@ func Parse(data []byte) (HierarchyId, error) { } // Convert binary data to a string of 0s and 1s - var bin = BinaryString(data) + var bin = binaryString(data) for { // Find pattern that fits the binary data - var pattern, err = TestPatterns(bin) + var pattern, err = testPatterns(bin) if err != nil { return nil, err } var value int64 - value, err = DecodeValue(pattern.Pattern, bin) + value, err = decodeValue(pattern.Pattern, bin) value += pattern.Min if err != nil { return nil, err @@ -49,8 +75,33 @@ func Parse(data []byte) (HierarchyId, error) { return levels, nil } -// DecodeValue a string representation of the hierarchyid data type for a pattern -func DecodeValue(pattern string, bin string) (int64, error) { +// Encode a hierarchyid from list of positions in the hierarchy. +func Encode(levels HierarchyId) ([]byte, error) { + var bin string = "" + + for _, level := range levels { + + // Find pattern that fits the binary data + var pattern *HierarchyIdPattern = nil + for i := 0; i < len(Patterns); i++ { + if Patterns[i].Min <= level && Patterns[i].Max >= level { + pattern = &Patterns[i] + break + } + } + + if pattern == nil { + return nil, errors.New("No pattern found for " + strconv.FormatInt(level, 10)) + } + + // TODO + } + + return []byte(bin), nil +} + +// Decode values a string representation of the hierarchyid data type for a pattern +func decodeValue(pattern string, bin string) (int64, error) { var binValue string = "" for i := 0; i < len(pattern); i++ { @@ -71,7 +122,7 @@ func DecodeValue(pattern string, bin string) (int64, error) { // Test pattern for binary data // // Return the pattern that fits the binary data (if any), the length of the pattern and an error. -func TestPatterns(bin string) (*HierarchyIdPattern, error) { +func testPatterns(bin string) (*HierarchyIdPattern, error) { if len(bin) == 0 { return nil, errors.New("Binary string is empty") } @@ -122,7 +173,7 @@ func TestPatterns(bin string) (*HierarchyIdPattern, error) { } // Receives a byte array and prints as binary (0 and 1) data. -func BinaryString(data []byte) string { +func binaryString(data []byte) string { var str = "" // Convert each byte to binary @@ -147,29 +198,3 @@ func BinaryString(data []byte) string { return str } - -// Create a string representation of the hierarchyid data type -// -// The string representation is a series of integers separated by slashes. For example, \1\2\3\ -func ToString(data HierarchyId) string { - var result string = "/" - for _, level := range data { - result += strconv.FormatInt(level, 10) + "/" - } - return result -} - -// Compare two hierarchyid data types -// -// The comparison is done by comparing each level of the hierarchyid. If the levels are the same, the next level is compared. If the levels are different, the comparison stops and the result is returned. -func Compare(a HierarchyId, b HierarchyId) int { - for i := 0; i < len(a) && i < len(b); i++ { - if a[i] < b[i] { - return -1 - } else if a[i] > b[i] { - return 1 - } - } - - return 0 -} diff --git a/hierarchy_id_test.go b/hierarchy_id_test.go index 37c1101..189e270 100644 --- a/hierarchy_id_test.go +++ b/hierarchy_id_test.go @@ -66,7 +66,7 @@ func TestBinaryString(t *testing.T) { t.Errorf("Error decoding %v: %v", d.input, err) } - var result = BinaryString(input) + var result = binaryString(input) if result != d.output { t.Errorf("Expected 0x%v to return %v, got %v", d.input, d.output, result) @@ -89,7 +89,7 @@ func TestTestPatterns(t *testing.T) { } for _, d := range data { - result, err := TestPatterns(d.input) + result, err := testPatterns(d.input) if err != nil { t.Errorf("Error testing %v: %v", d.input, err) } @@ -100,97 +100,97 @@ func TestTestPatterns(t *testing.T) { } } -func TestParse(t *testing.T) { - type TestData struct { - output []int64 - input string - } +type TestData struct { + output []int64 + input string +} - var data []TestData = []TestData{ - {nil, ""}, - {[]int64{-73}, "1BEEFC"}, - {[]int64{-72}, "2088"}, - {[]int64{-64}, "2188"}, - {[]int64{-56}, "2488"}, - {[]int64{-48}, "2588"}, - {[]int64{-40}, "2888"}, - {[]int64{-32}, "2988"}, - {[]int64{-24}, "2C88"}, - {[]int64{-16}, "2D88"}, - {[]int64{-10}, "2DE8"}, - {[]int64{-9}, "2DF8"}, - {[]int64{-8}, "3880"}, - {[]int64{-7}, "3980"}, - {[]int64{-6}, "3A80"}, - {[]int64{-5}, "3B80"}, - {[]int64{-4}, "3C80"}, - {[]int64{-3}, "3D80"}, - {[]int64{-2}, "3E80"}, - {[]int64{-1}, "3F80"}, - {[]int64{0}, "48"}, - {[]int64{1}, "58"}, - {[]int64{2}, "68"}, - {[]int64{3}, "78"}, - {[]int64{4}, "84"}, - {[]int64{5}, "8C"}, - {[]int64{6}, "94"}, - {[]int64{7}, "9C"}, - {[]int64{8}, "A2"}, - {[]int64{9}, "A6"}, - {[]int64{10}, "AA"}, - {[]int64{11}, "AE"}, - {[]int64{12}, "B2"}, - {[]int64{13}, "B6"}, - {[]int64{14}, "BA"}, - {[]int64{15}, "BE"}, - {[]int64{16}, "C110"}, - {[]int64{17}, "C130"}, - {[]int64{18}, "C150"}, - {[]int64{19}, "C170"}, - {[]int64{20}, "C190"}, - {[]int64{21}, "C1B0"}, - {[]int64{22}, "C1D0"}, - {[]int64{23}, "C1F0"}, - {[]int64{24}, "C310"}, - {[]int64{32}, "C910"}, - {[]int64{40}, "CB10"}, - {[]int64{48}, "D110"}, - {[]int64{56}, "D310"}, - {[]int64{64}, "D910"}, - {[]int64{72}, "DB10"}, - {[]int64{80}, "E00440"}, - {[]int64{88}, "E00C40"}, - {[]int64{96}, "E02440"}, - {[]int64{128}, "E06440"}, - {[]int64{136}, "E06C40"}, - {[]int64{192}, "E0E440"}, - {[]int64{320}, "E2E440"}, - {[]int64{576}, "E6E440"}, - {[]int64{1088}, "EEE440"}, - {[]int64{1104}, "F00088"}, - {[]int64{2128}, "F20088"}, - {[]int64{3152}, "F40088"}, - {[]int64{4176}, "F60088"}, - {[]int64{5200}, "F80000000220"}, - {[]int64{3, 1}, "7AC0"}, - {[]int64{1, 1}, "5AC0"}, - {[]int64{2, 1}, "6AC0"}, - {[]int64{2, 1, 1}, "6AD6"}, - {[]int64{1, 1, 2}, "5ADA"}, - {[]int64{1, 1, 3}, "5ADE"}, - {[]int64{1, 1, 1}, "5AD6"}, - {[]int64{1, 1, 4}, "5AE1"}, - {[]int64{1, -1, 4}, "59FE10"}, - {[]int64{1, 1, 1, 1, 1, 1, 2, 1, 1, 2}, "5AD6B5ADAB5B40"}, - {[]int64{1, 2, 754}, "5B7A9150"}, - {[]int64{1, 1, 1, 1}, "5AD6B0"}, - {[]int64{2, 1, 1, 3}, "6AD6F0"}, - {[]int64{2, 1, 1, 1}, "6AD6B0"}, - {[]int64{2, 1, 1, 2}, "6AD6D0"}, - {[]int64{1, 1, 1, 1, 1}, "5AD6B580"}, - } +var TestParseData []TestData = []TestData{ + {nil, ""}, + {[]int64{-73}, "1BEEFC"}, + {[]int64{-72}, "2088"}, + {[]int64{-64}, "2188"}, + {[]int64{-56}, "2488"}, + {[]int64{-48}, "2588"}, + {[]int64{-40}, "2888"}, + {[]int64{-32}, "2988"}, + {[]int64{-24}, "2C88"}, + {[]int64{-16}, "2D88"}, + {[]int64{-10}, "2DE8"}, + {[]int64{-9}, "2DF8"}, + {[]int64{-8}, "3880"}, + {[]int64{-7}, "3980"}, + {[]int64{-6}, "3A80"}, + {[]int64{-5}, "3B80"}, + {[]int64{-4}, "3C80"}, + {[]int64{-3}, "3D80"}, + {[]int64{-2}, "3E80"}, + {[]int64{-1}, "3F80"}, + {[]int64{0}, "48"}, + {[]int64{1}, "58"}, + {[]int64{2}, "68"}, + {[]int64{3}, "78"}, + {[]int64{4}, "84"}, + {[]int64{5}, "8C"}, + {[]int64{6}, "94"}, + {[]int64{7}, "9C"}, + {[]int64{8}, "A2"}, + {[]int64{9}, "A6"}, + {[]int64{10}, "AA"}, + {[]int64{11}, "AE"}, + {[]int64{12}, "B2"}, + {[]int64{13}, "B6"}, + {[]int64{14}, "BA"}, + {[]int64{15}, "BE"}, + {[]int64{16}, "C110"}, + {[]int64{17}, "C130"}, + {[]int64{18}, "C150"}, + {[]int64{19}, "C170"}, + {[]int64{20}, "C190"}, + {[]int64{21}, "C1B0"}, + {[]int64{22}, "C1D0"}, + {[]int64{23}, "C1F0"}, + {[]int64{24}, "C310"}, + {[]int64{32}, "C910"}, + {[]int64{40}, "CB10"}, + {[]int64{48}, "D110"}, + {[]int64{56}, "D310"}, + {[]int64{64}, "D910"}, + {[]int64{72}, "DB10"}, + {[]int64{80}, "E00440"}, + {[]int64{88}, "E00C40"}, + {[]int64{96}, "E02440"}, + {[]int64{128}, "E06440"}, + {[]int64{136}, "E06C40"}, + {[]int64{192}, "E0E440"}, + {[]int64{320}, "E2E440"}, + {[]int64{576}, "E6E440"}, + {[]int64{1088}, "EEE440"}, + {[]int64{1104}, "F00088"}, + {[]int64{2128}, "F20088"}, + {[]int64{3152}, "F40088"}, + {[]int64{4176}, "F60088"}, + {[]int64{5200}, "F80000000220"}, + {[]int64{3, 1}, "7AC0"}, + {[]int64{1, 1}, "5AC0"}, + {[]int64{2, 1}, "6AC0"}, + {[]int64{2, 1, 1}, "6AD6"}, + {[]int64{1, 1, 2}, "5ADA"}, + {[]int64{1, 1, 3}, "5ADE"}, + {[]int64{1, 1, 1}, "5AD6"}, + {[]int64{1, 1, 4}, "5AE1"}, + {[]int64{1, -1, 4}, "59FE10"}, + {[]int64{1, 1, 1, 1, 1, 1, 2, 1, 1, 2}, "5AD6B5ADAB5B40"}, + {[]int64{1, 2, 754}, "5B7A9150"}, + {[]int64{1, 1, 1, 1}, "5AD6B0"}, + {[]int64{2, 1, 1, 3}, "6AD6F0"}, + {[]int64{2, 1, 1, 1}, "6AD6B0"}, + {[]int64{2, 1, 1, 2}, "6AD6D0"}, + {[]int64{1, 1, 1, 1, 1}, "5AD6B580"}, +} - for _, d := range data { +func TestParse(t *testing.T) { + for _, d := range TestParseData { input, err := hex.DecodeString(d.input) if err != nil { t.Errorf("Error decoding %v: %v", d.input, err)