Skip to content

Commit

Permalink
Derp
Browse files Browse the repository at this point in the history
  • Loading branch information
tentone committed Jul 25, 2024
1 parent 245d61f commit 1c3074a
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 127 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
91 changes: 58 additions & 33 deletions hierarchy_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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 <ADD CODE HERE>
}

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++ {
Expand All @@ -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")
}
Expand Down Expand Up @@ -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
Expand All @@ -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
}
182 changes: 91 additions & 91 deletions hierarchy_id_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
Expand All @@ -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)
Expand Down

0 comments on commit 1c3074a

Please sign in to comment.