An open API service indexing awesome lists of open source software.

https://github.com/tentone/hierarchyid

Handle hierarchyid type in SQL Server using gorm
https://github.com/tentone/hierarchyid

gorm hierarchyid sqlserver

Last synced: 9 months ago
JSON representation

Handle hierarchyid type in SQL Server using gorm

Awesome Lists containing this project

README

          

# Gorm hierarchyid
- Library to handle hierarchyid type in SQL Server and go.
- Generation and parsing of hierarchyid type in go.
- Type wrapper for usage with gorm ORM.
- The [hierarchyid](https://learn.microsoft.com/en-us/sql/relational-databases/hierarchical-data-sql-server?view=sql-server-ver16) is data to represent a position in a hierarchy in SQL Server.
- It is a variable length type with reduced storage requirements.
- Encodes the position in the hierarchy as a list of indexes
- For example in the tree below the path to `E` is `/1/1/2/`
- Indexes can be used to sort elements inside of a tree level.

## How it works
- The `HierarchyID` is defined as a `[]int64` in go.
- When serialized into JSON a textual representation is used for readability.
- Represented as list separated by `/`. (e.g. `/1/2/3/4/5/`)
- Each element in the slice represents a level in the hierarchy.
- An empty slice represents the root of the hierarchy.
- Elements placed in the root should not use an empty list.
- They should instead by represented by `/1/`, `/2/`, etc.

## Installation
- The library can be installed using `go get`.
-
```bash
go get github.com/tentone/hierarchyid
```

## Model definition
- Declare `HierarchyID` type in your gorm model, there is no need to specify the DB data type.
- Is is recommended to also mark the field as `unique` to avoid duplicates.
- The library will handle the serialization and deserialization of the field to match the SQL Server `hierarchyid` type.
```go
type Model struct {
gorm.Model

Path hierarchyid.HierarchyID `gorm:"unique;not null;"`
}
```
- In some scenarios it might be usefull to also keep a tradicional relationship to the parent.
- This can be done by adding a `ParentID` field to the model.
- It ensures that the tree is consistent and that actions (e.g. delete) are cascaded to the children.
- Some operations might also be easier to perform with the parent relationship.
```go
type Model struct {
gorm.Model

Path hierarchyid.HierarchyId `gorm:"unique;not null;"`

ParentID uint `gorm:"index"`
Parent *TestParentsTable `foreignKey:"parent_id;references:id;constraint:OnUpdate:NO ACTION,OnDelete:CASCADE;"`
}
```

## Usage

### Create
- Elements can be added to the tree as regular entries
- Just make sure that the tree indexes are filled correctly, indexes dont need to be sequential.
```go
db.Create(&Table{Path: hierarchyid.HierarchyID{Data: []int64{1}}})
db.Create(&Table{Path: hierarchyid.HierarchyID{Data: []int64{1, 1}}})
db.Create(&Table{Path: hierarchyid.HierarchyID{Data: []int64{1, 1, 2}}})
```

### Get Ancestors
- To get all parents of a node use the `GetAncestors` method.
- The method will return a slice with all the parents of the node. This can be used as param for a query.
```go
db.Model(&Table{}).Where("[path] IN (?)", child.Path.GetAncestors()).Find(&parents)
```
- Its also possible to get parents with the SQL version of the [`GetAncestor`](https://learn.microsoft.com/en-us/sql/t-sql/data-types/getancestor-database-engine?view=sql-server-ver16) method.
- Example on getting the parent of an element.
```go
db.Model(&Table{}).Where("[path] = ?.GetAncestor(1)", child.Path).Find(&parent)
```

### Get Descendants
- To get all children of a node use the [`IsDescendantOf`](https://learn.microsoft.com/en-us/sql/t-sql/data-types/isdescendantof-database-engine?view=sql-server-ver16) method in SQL.
- Example on getting all children of a node (including the node itself).
```go
elements := []Table{}
db.Where("[path].IsDescendantOf(?)=1", hierarchyid.HierarchyId{Data: []int64{1, 2}}).Find(&elements)
```
- It is also possible to filter the children based on sub-levels.
- Example on getting all nodes from root where at least one of the sub-level has a name that contains the text 'de'
```sql
SELECT *
FROM "table" as a
WHERE ([path].GetLevel()=1 AND [path].IsDescendantOf('/')=1) AND
(SELECT COUNT(*) FROM "table" AS b WHERE b.path.IsDescendantOf(a.path)=1 AND b.name LIKE '%de%')>0
```
- The `GetLevel` method can be used to filter nodes based on their level in the hierarchy. Also available in SQL with the same name [`GetLevel`](https://learn.microsoft.com/en-us/sql/t-sql/data-types/getlevel-database-engine?view=sql-server-ver16).
- A more generic version of the same code presented above writen in go.

```go
root := hierarchyid.GetRoot()
subQuery := db.Table("table AS b").Select("COUNT(*)").Where("[b].[path].IsDescendantOf([a].[path])=1 AND [b].[name] LIKE '%de%'")
conn = db.Table("table AS a").
Where("[a].[path].GetLevel()=? AND [a].[path].IsDescendantOf(?)=1 AND (?)>0", root.GetLevel()+1, root, subQuery).
Find(&elements)
```

### Move nodes
- To move a node to a new parent there is the `GetReparentedValue` method that receives the old parent and new parent and calculates the new hierarchyid value.
- Example on moving a node to a new parent.
```go
db.Model(&Table{}).Where("[id] = ?", id).Update("[path]=?", node.Path.GetReparentedValue(oldParent.Path, newParent.Path))
```

## 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))

## License
- The project is distributed using a MIT license. Available on the project repository.