├── docs ├── code │ └── hydra.md ├── assets │ └── css │ │ └── style.scss ├── index.md └── _config.yml ├── .gitignore ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── 03_CODEBASE_IMPROVEMENT.md │ ├── 01_BUG_REPORT.md │ └── 02_FEATURE_REQUEST.md ├── workflows │ ├── tagged-release.yml │ ├── pre-release.yml │ ├── update-copyright-years-in-license-file.yml │ └── build.yml ├── PULL_REQUEST_TEMPLATE.md └── labels.yml ├── logo.jpg ├── main.go ├── logo.webp ├── logo_gophers.jpg ├── logo_gophers.webp ├── hydra ├── Debug.go ├── Hydratable.go ├── Database.go ├── DatabaseSqlLite.go ├── DatabaseMariaDB.go ├── DatabaseOracle.go ├── DatabaseMSSQL.go ├── DatabaseCockroachDB.go ├── DatabasePostgres.go ├── DatabaseMySQL.go └── Hydrate.go ├── go.mod ├── LICENSE ├── example.go ├── CONTRIBUTING.md ├── README.md └── go.sum /docs/code/hydra.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @sphireinc 2 | * @nsa-yoda -------------------------------------------------------------------------------- /logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sphireinc/Hydra/HEAD/logo.jpg -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | example() 5 | } 6 | -------------------------------------------------------------------------------- /logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sphireinc/Hydra/HEAD/logo.webp -------------------------------------------------------------------------------- /docs/assets/css/style.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | @import "{{ site.theme }}"; -------------------------------------------------------------------------------- /logo_gophers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sphireinc/Hydra/HEAD/logo_gophers.jpg -------------------------------------------------------------------------------- /logo_gophers.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sphireinc/Hydra/HEAD/logo_gophers.webp -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ## Sphire Hydra 2 | 3 | ### Packages within Hydra 4 | 5 | - [Hydra](code/hydra.md) -------------------------------------------------------------------------------- /hydra/Debug.go: -------------------------------------------------------------------------------- 1 | package hydra 2 | 3 | import "fmt" 4 | 5 | const debug = false 6 | 7 | func p(a ...any) { 8 | if debug { 9 | fmt.Println(a...) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | remote_theme: pages-themes/minimal@v0.2.0 2 | plugins: 3 | - jekyll-remote-theme 4 | title: Sphire Hydra Documentation 5 | description: Sphire Hydra' Documentation 6 | show_downloads: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | blank_issues_enabled: false 3 | contact_links: 4 | - name: Sphire Hydra Community Support 5 | url: https://github.com/sphireinc/Hydra/wiki 6 | about: Please ask and answer questions here -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/03_CODEBASE_IMPROVEMENT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Codebase improvement 3 | about: Provide your feedback for the existing codebase. Suggest a better solution for algorithms, development tools, etc. 4 | title: "dev: " 5 | labels: "enhancement" 6 | assignees: "" 7 | --- -------------------------------------------------------------------------------- /.github/workflows/tagged-release.yml : -------------------------------------------------------------------------------- 1 | name: "tagged-release" 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | tagged-release: 10 | name: "Tagged Release" 11 | runs-on: "ubuntu-latest" 12 | 13 | steps: 14 | - uses: "marvinpinto/action-automatic-releases@latest" 15 | with: 16 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 17 | prerelease: false -------------------------------------------------------------------------------- /.github/workflows/pre-release.yml: -------------------------------------------------------------------------------- 1 | name: "pre-release" 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | 8 | jobs: 9 | pre-release: 10 | name: "Pre Release" 11 | runs-on: "ubuntu-latest" 12 | 13 | steps: 14 | - uses: "marvinpinto/action-automatic-releases@latest" 15 | with: 16 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 17 | automatic_release_tag: "latest" 18 | prerelease: true 19 | title: "Development Build" -------------------------------------------------------------------------------- /.github/workflows/update-copyright-years-in-license-file.yml: -------------------------------------------------------------------------------- 1 | name: Update License Copyright Year 2 | 3 | on: 4 | schedule: 5 | - cron: "0 3 1 1 *" # 03:00 AM on January 1 6 | 7 | jobs: 8 | update-license-year: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | with: 13 | fetch-depth: 0 14 | 15 | - name: Update License 16 | uses: FantasticFiasco/action-update-license-year@v2 17 | with: 18 | token: ${{ secrets.GITHUB_TOKEN }} 19 | 20 | - name: Merge pull request 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | run: | 24 | gh pr merge --merge --delete-branch -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module Hydrator 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/denisenkom/go-mssqldb v0.12.3 7 | github.com/go-sql-driver/mysql v1.8.1 8 | github.com/godror/godror v0.44.7 9 | github.com/jackc/pgx/v5 v5.7.1 10 | github.com/mattn/go-sqlite3 v1.14.23 11 | ) 12 | 13 | require ( 14 | filippo.io/edwards25519 v1.1.0 // indirect 15 | github.com/go-logfmt/logfmt v0.6.0 // indirect 16 | github.com/godror/knownpb v0.1.2 // indirect 17 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect 18 | github.com/golang-sql/sqlexp v0.1.0 // indirect 19 | github.com/jackc/pgpassfile v1.0.0 // indirect 20 | github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect 21 | golang.org/x/crypto v0.27.0 // indirect 22 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect 23 | golang.org/x/text v0.18.0 // indirect 24 | google.golang.org/protobuf v1.34.2 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/01_BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help Sphire Hydra improve 4 | title: "bug: " 5 | labels: "bug" 6 | assignees: "" 7 | --- 8 | 9 | # Bug Report 10 | 11 | **Sphire Hydra version:** 12 | 13 | 14 | 15 | **Current behavior:** 16 | 17 | 18 | 19 | **Expected behavior:** 20 | 21 | 22 | 23 | **Steps to reproduce:** 24 | 25 | 26 | 27 | **Related code:** 28 | 29 | 30 | 31 | ``` 32 | insert short code snippets here 33 | ``` 34 | 35 | **Other information:** 36 | 37 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Pull request type 4 | 5 | 6 | 7 | Please check the type of change your PR introduces: 8 | 9 | - [ ] Bugfix 10 | - [ ] Feature 11 | - [ ] Code style update (formatting, renaming) 12 | - [ ] Refactoring (no functional changes, no api changes) 13 | - [ ] Build related changes 14 | - [ ] Documentation content changes 15 | - [ ] Other (please describe): 16 | 17 | ## What is the current behavior? 18 | 19 | 20 | 21 | Issue Number: N/A 22 | 23 | ## What is the new behavior? 24 | 25 | 26 | 27 | - 28 | - 29 | - 30 | 31 | ## Other information 32 | 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02_FEATURE_REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: "feat: " 5 | labels: "enhancement" 6 | assignees: "" 7 | --- 8 | 9 | # Feature Request 10 | 11 | **Describe the Feature Request** 12 | 13 | 14 | 15 | **Describe Preferred Solution** 16 | 17 | 18 | 19 | **Describe Alternatives** 20 | 21 | 22 | 23 | **Related Code** 24 | 25 | 26 | 27 | **Additional Context** 28 | 29 | 30 | 31 | **If the feature request is approved, would you be willing to submit a PR?** 32 | Yes / No _(Help can be provided if you need assistance submitting a PR)_ -------------------------------------------------------------------------------- /example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "Hydrator/hydra" 5 | "database/sql" 6 | "encoding/json" 7 | "fmt" 8 | ) 9 | 10 | type Clan struct { 11 | ClanAcc string `json:"clan_acc" hydra:"clan_acc"` 12 | ID string `json:"id" hydra:"id"` 13 | PreviousId string `json:"name" hydra:"previous_id"` 14 | Description string `json:"age" hydra:"description"` 15 | Author string `json:"author" hydra:"author"` 16 | Comment string `json:"comment" hydra:"comment"` 17 | Created string `json:"created" hydra:"created"` 18 | Updated string `json:"updated" hydra:"updated"` 19 | hydra.Hydratable 20 | } 21 | 22 | // Implement the fmt.Stringer interface to output JSON by default 23 | func (c Clan) String() string { 24 | // Marshal the struct to JSON 25 | jsonData, err := json.Marshal(c) 26 | if err != nil { 27 | return fmt.Sprintf("Error marshaling to JSON: %v", err) 28 | } 29 | return string(jsonData) 30 | } 31 | 32 | func example() { 33 | p := &Clan{} 34 | p.Init(p) 35 | 36 | // test using a publicly available MySQL database 37 | db, err := sql.Open("mysql", "rfamro:@tcp(mysql-rfam-public.ebi.ac.uk:4497)/Rfam") 38 | if err != nil { 39 | panic(err) 40 | } 41 | 42 | fmt.Println(p.Hydrate(db, map[string]interface{}{"id": "U6"})) 43 | fmt.Println("res", p) 44 | } 45 | -------------------------------------------------------------------------------- /hydra/Hydratable.go: -------------------------------------------------------------------------------- 1 | package hydra 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | const tagName = "hydra" 9 | 10 | type IHydratable interface { 11 | Init(interface{}) interface{} 12 | } 13 | 14 | type Hydratable struct { 15 | XDBTypeOverride string // The database type to override the default detection, e.g. sqlite, mssql, mariadb, oracle, mysql 16 | name string // The name of the object to be hydrated 17 | isInitialized bool // Flag to check if the object has been initialized 18 | self interface{} // The object that is to be hydrated 19 | } 20 | 21 | func (h *Hydratable) Init(o interface{}) interface{} { 22 | v := reflect.ValueOf(o) 23 | t := reflect.TypeOf(o) 24 | 25 | // If it's a pointer, get the underlying element 26 | if t.Kind() == reflect.Ptr { 27 | v = v.Elem() 28 | t = t.Elem() 29 | } 30 | 31 | if t.Kind() != reflect.Struct { 32 | p("Passed value is not a struct") 33 | return o 34 | } 35 | 36 | if debug { 37 | p("Initializing type:", t.Name()) 38 | p("Number of fields:", t.NumField()) 39 | 40 | for i := 0; i < t.NumField(); i++ { 41 | // Get the field metadata 42 | field := t.Field(i) 43 | 44 | // Get the field tag value for "hydra" 45 | tag := field.Tag.Get(tagName) 46 | 47 | // If the tag is not empty, add it to the hydrate list 48 | if tag != "" { 49 | p(fmt.Sprintf("%d. Field: %v (%v), tag: '%v' - Will be hydrated\n", i+1, field.Name, field.Type, tag)) 50 | } else { 51 | p(fmt.Sprintf("%d. Field: %v (%v), tag: '%v' - Skipped\n", i+1, field.Name, field.Type, tag)) 52 | } 53 | } 54 | } 55 | 56 | // Save a reference to the passed object in 'self' 57 | h.self = o 58 | h.isInitialized = true 59 | h.name = t.Name() 60 | 61 | return o 62 | } 63 | -------------------------------------------------------------------------------- /hydra/Database.go: -------------------------------------------------------------------------------- 1 | package hydra 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | 7 | "github.com/jackc/pgx/v5" 8 | ) 9 | 10 | // Fetch hydrates the object with data from the database 11 | // @param db The database connection 12 | // @param tableName The name of the table to fetch data from 13 | // @param whereClauses The where clauses to filter the data 14 | // @return map[string]interface{} The hydrated data 15 | // @return error The error if any occurred 16 | func (h *Hydratable) Fetch(db any, tableName string, whereClauses map[string]interface{}) (map[string]interface{}, error) { 17 | p("Fetching data from database") 18 | switch db := db.(type) { 19 | case *sql.DB: 20 | switch h.XDBTypeOverride { 21 | case "sqlite": 22 | p("Fetching data from SQLite") 23 | return h.fetchSQLite(db, tableName, whereClauses) 24 | case "mssql": 25 | p("Fetching data from MSSQL") 26 | return h.fetchMSSQL(db, tableName, whereClauses) 27 | case "mariadb": 28 | p("Fetching data from MariaDB") 29 | return h.fetchMariaDB(db, tableName, whereClauses) 30 | case "oracle": 31 | p("Fetching data from Oracle") 32 | return h.fetchOracle(db, tableName, whereClauses) 33 | case "mysql": 34 | default: 35 | p("Fetching data from MySQL") 36 | return h.fetchMySQL(db, tableName, whereClauses) 37 | } 38 | case *pgx.Conn: 39 | switch h.XDBTypeOverride { 40 | case "cockroachdb": 41 | p("Fetching data from CockroachDB") 42 | return h.fetchCockroachDB(db, tableName, whereClauses) 43 | case "postgres": 44 | default: 45 | p("Fetching data from PostgreSQL") 46 | return h.fetchPostgres(db, tableName, whereClauses) 47 | } 48 | default: 49 | err := fmt.Errorf("unsupported database type: %T", db) 50 | p("Unsupported database type:", err) 51 | return nil, err 52 | } 53 | return nil, fmt.Errorf("unsupported database type: %T", db) 54 | } 55 | -------------------------------------------------------------------------------- /hydra/DatabaseSqlLite.go: -------------------------------------------------------------------------------- 1 | package hydra 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "strings" 7 | 8 | _ "github.com/mattn/go-sqlite3" 9 | ) 10 | 11 | func (h *Hydratable) fetchSQLite(db *sql.DB, tableName string, whereClauses map[string]interface{}) (map[string]interface{}, error) { 12 | // Build the base query with the table name 13 | query := fmt.Sprintf("SELECT * FROM %s WHERE ", tableName) 14 | p("Query:", query) 15 | 16 | // Prepare the values for the SQL parameters 17 | var params []interface{} 18 | var conditions []string 19 | 20 | // Build the WHERE clause dynamically 21 | for column, value := range whereClauses { 22 | // Append the column name to the condition (safe concatenation) 23 | conditions = append(conditions, fmt.Sprintf("%s = ?", column)) 24 | // Append the value to the params slice 25 | params = append(params, value) 26 | } 27 | 28 | // Join all conditions with "AND" and append to the query 29 | query += strings.Join(conditions, " AND ") 30 | p("Query with conditions:", query) 31 | 32 | // Execute the query with the dynamic parameters 33 | rows, err := db.Query(query, params...) 34 | p("Rows:", rows) 35 | if err != nil { 36 | p("Error executing query:", err) 37 | return nil, err 38 | } 39 | defer rows.Close() 40 | 41 | // Assuming a single row for hydration 42 | result := make(map[string]interface{}) 43 | columns, err := rows.Columns() 44 | p("Columns:", columns) 45 | if err != nil { 46 | p("Error getting columns:", err) 47 | return nil, err 48 | } 49 | 50 | values := make([]interface{}, len(columns)) 51 | valuePtrs := make([]interface{}, len(columns)) 52 | for i := range values { 53 | valuePtrs[i] = &values[i] 54 | } 55 | 56 | if rows.Next() { 57 | if err := rows.Scan(valuePtrs...); err != nil { 58 | return nil, err 59 | } 60 | 61 | for i, col := range columns { 62 | result[col] = values[i] 63 | } 64 | } 65 | 66 | p("Result:", result) 67 | return result, nil 68 | } 69 | -------------------------------------------------------------------------------- /hydra/DatabaseMariaDB.go: -------------------------------------------------------------------------------- 1 | package hydra 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "strings" 7 | 8 | _ "github.com/go-sql-driver/mysql" 9 | ) 10 | 11 | func (h *Hydratable) fetchMariaDB(db *sql.DB, tableName string, whereClauses map[string]interface{}) (map[string]interface{}, error) { 12 | // Build the base query with the table name 13 | query := fmt.Sprintf("SELECT * FROM %s WHERE ", tableName) 14 | p("Query:", query) 15 | 16 | // Prepare the values for the SQL parameters 17 | var params []interface{} 18 | var conditions []string 19 | 20 | // Build the WHERE clause dynamically 21 | for column, value := range whereClauses { 22 | // Append the column name to the condition (safe concatenation) 23 | conditions = append(conditions, fmt.Sprintf("%s = ?", column)) 24 | // Append the value to the params slice 25 | params = append(params, value) 26 | } 27 | 28 | // Join all conditions with "AND" and append to the query 29 | query += strings.Join(conditions, " AND ") 30 | p("Query with conditions:", query) 31 | 32 | // Execute the query with the dynamic parameters 33 | rows, err := db.Query(query, params...) 34 | p("Rows:", rows) 35 | if err != nil { 36 | p("Error executing query:", err) 37 | return nil, err 38 | } 39 | defer rows.Close() 40 | 41 | // Assuming a single row for hydration 42 | result := make(map[string]interface{}) 43 | columns, err := rows.Columns() 44 | p("Columns:", columns) 45 | if err != nil { 46 | p("Error getting columns:", err) 47 | return nil, err 48 | } 49 | 50 | values := make([]interface{}, len(columns)) 51 | valuePtrs := make([]interface{}, len(columns)) 52 | for i := range values { 53 | valuePtrs[i] = &values[i] 54 | } 55 | 56 | if rows.Next() { 57 | if err := rows.Scan(valuePtrs...); err != nil { 58 | return nil, err 59 | } 60 | 61 | for i, col := range columns { 62 | result[col] = values[i] 63 | } 64 | } 65 | 66 | p("Result:", result) 67 | return result, nil 68 | } 69 | -------------------------------------------------------------------------------- /hydra/DatabaseOracle.go: -------------------------------------------------------------------------------- 1 | package hydra 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "strings" 7 | 8 | _ "github.com/godror/godror" 9 | ) 10 | 11 | func (h *Hydratable) fetchOracle(db *sql.DB, tableName string, whereClauses map[string]interface{}) (map[string]interface{}, error) { 12 | // Build the base query with the table name 13 | query := fmt.Sprintf("SELECT * FROM %s WHERE ", tableName) 14 | p("Query:", query) 15 | 16 | // Prepare the values for the SQL parameters 17 | var params []interface{} 18 | var conditions []string 19 | paramIndex := 1 // Oracle uses :1, :2, etc. for placeholders 20 | 21 | // Build the WHERE clause dynamically 22 | for column, value := range whereClauses { 23 | // Append the column name to the condition (safe concatenation) 24 | conditions = append(conditions, fmt.Sprintf("%s = :%d", column, paramIndex)) 25 | // Append the value to the params slice 26 | params = append(params, value) 27 | paramIndex++ 28 | } 29 | 30 | // Join all conditions with "AND" and append to the query 31 | query += strings.Join(conditions, " AND ") 32 | p("Query with conditions:", query) 33 | 34 | // Execute the query with the dynamic parameters 35 | rows, err := db.Query(query, params...) 36 | p("Rows:", rows) 37 | if err != nil { 38 | p("Error executing query:", err) 39 | return nil, err 40 | } 41 | defer rows.Close() 42 | 43 | // Assuming a single row for hydration 44 | result := make(map[string]interface{}) 45 | columns, err := rows.Columns() 46 | p("Columns:", columns) 47 | if err != nil { 48 | p("Error getting columns:", err) 49 | return nil, err 50 | } 51 | 52 | values := make([]interface{}, len(columns)) 53 | valuePtrs := make([]interface{}, len(columns)) 54 | for i := range values { 55 | valuePtrs[i] = &values[i] 56 | } 57 | 58 | if rows.Next() { 59 | if err := rows.Scan(valuePtrs...); err != nil { 60 | return nil, err 61 | } 62 | 63 | for i, col := range columns { 64 | result[col] = values[i] 65 | } 66 | } 67 | 68 | p("Result:", result) 69 | return result, nil 70 | } 71 | -------------------------------------------------------------------------------- /hydra/DatabaseMSSQL.go: -------------------------------------------------------------------------------- 1 | package hydra 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "strings" 7 | 8 | _ "github.com/denisenkom/go-mssqldb" 9 | ) 10 | 11 | func (h *Hydratable) fetchMSSQL(db *sql.DB, tableName string, whereClauses map[string]interface{}) (map[string]interface{}, error) { 12 | // Build the base query with the table name 13 | query := fmt.Sprintf("SELECT * FROM %s WHERE ", tableName) 14 | p("Query:", query) 15 | 16 | // Prepare the values for the SQL parameters 17 | var params []interface{} 18 | var conditions []string 19 | paramIndex := 1 // MSSQL doesn't use $1, but we will maintain index for clarity 20 | 21 | // Build the WHERE clause dynamically 22 | for column, value := range whereClauses { 23 | // Append the column name to the condition (safe concatenation) 24 | conditions = append(conditions, fmt.Sprintf("%s = @p%d", column, paramIndex)) 25 | // Append the value to the params slice 26 | params = append(params, value) 27 | paramIndex++ 28 | } 29 | 30 | // Join all conditions with "AND" and append to the query 31 | query += strings.Join(conditions, " AND ") 32 | p("Query with conditions:", query) 33 | 34 | // Execute the query with the dynamic parameters 35 | rows, err := db.Query(query, params...) 36 | p("Rows:", rows) 37 | if err != nil { 38 | p("Error executing query:", err) 39 | return nil, err 40 | } 41 | defer rows.Close() 42 | 43 | // Assuming a single row for hydration 44 | result := make(map[string]interface{}) 45 | columns, err := rows.Columns() 46 | p("Columns:", columns) 47 | if err != nil { 48 | p("Error getting columns:", err) 49 | return nil, err 50 | } 51 | 52 | values := make([]interface{}, len(columns)) 53 | valuePtrs := make([]interface{}, len(columns)) 54 | for i := range values { 55 | valuePtrs[i] = &values[i] 56 | } 57 | 58 | if rows.Next() { 59 | if err := rows.Scan(valuePtrs...); err != nil { 60 | return nil, err 61 | } 62 | 63 | for i, col := range columns { 64 | result[col] = values[i] 65 | } 66 | } 67 | 68 | p("Result:", result) 69 | return result, nil 70 | } 71 | -------------------------------------------------------------------------------- /hydra/DatabaseCockroachDB.go: -------------------------------------------------------------------------------- 1 | package hydra 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/jackc/pgx/v5" 9 | ) 10 | 11 | func (h *Hydratable) fetchCockroachDB(db *pgx.Conn, tableName string, whereClauses map[string]interface{}) (map[string]interface{}, error) { 12 | // Build the base query with the table name 13 | query := fmt.Sprintf("SELECT * FROM %s WHERE ", tableName) 14 | p("Query:", query) 15 | 16 | // Prepare the values for the SQL parameters 17 | var params []interface{} 18 | var conditions []string 19 | paramIndex := 1 // CockroachDB uses $1, $2, etc. just like PostgreSQL 20 | 21 | // Build the WHERE clause dynamically 22 | for column, value := range whereClauses { 23 | // Append the column name to the condition (safe concatenation) 24 | conditions = append(conditions, fmt.Sprintf("%s = $%d", column, paramIndex)) 25 | // Append the value to the params slice 26 | params = append(params, value) 27 | paramIndex++ 28 | } 29 | 30 | // Join all conditions with "AND" and append to the query 31 | query += strings.Join(conditions, " AND ") 32 | p("Query with conditions:", query) 33 | 34 | // Execute the query with the dynamic parameters 35 | rows, err := db.Query(context.Background(), query, params...) 36 | p("Rows:", rows) 37 | if err != nil { 38 | p("Error executing query:", err) 39 | return nil, err 40 | } 41 | defer rows.Close() 42 | 43 | // Assuming a single row for hydration 44 | result := make(map[string]interface{}) 45 | fieldDescriptions := rows.FieldDescriptions() 46 | columns := make([]string, len(fieldDescriptions)) 47 | for i, field := range fieldDescriptions { 48 | columns[i] = field.Name 49 | } 50 | p("Columns:", columns) 51 | 52 | values := make([]interface{}, len(columns)) 53 | valuePtrs := make([]interface{}, len(columns)) 54 | for i := range values { 55 | valuePtrs[i] = &values[i] 56 | } 57 | 58 | if rows.Next() { 59 | if err := rows.Scan(valuePtrs...); err != nil { 60 | return nil, err 61 | } 62 | 63 | for i, col := range columns { 64 | result[col] = values[i] 65 | } 66 | } 67 | 68 | p("Result:", result) 69 | return result, nil 70 | } 71 | -------------------------------------------------------------------------------- /hydra/DatabasePostgres.go: -------------------------------------------------------------------------------- 1 | package hydra 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/jackc/pgx/v5" 9 | ) 10 | 11 | func (h *Hydratable) fetchPostgres(db *pgx.Conn, tableName string, whereClauses map[string]interface{}) (map[string]interface{}, error) { 12 | // Build the base query with the table name 13 | query := fmt.Sprintf("SELECT * FROM %s WHERE ", tableName) 14 | p("Query:", query) 15 | 16 | // Prepare the values for the SQL parameters 17 | var params []interface{} 18 | var conditions []string 19 | paramIndex := 1 // Postgres uses $1, $2... for placeholders 20 | 21 | // Build the WHERE clause dynamically 22 | for column, value := range whereClauses { 23 | // Append the column name to the condition (Postgres uses positional params like $1, $2...) 24 | conditions = append(conditions, fmt.Sprintf("%s = $%d", column, paramIndex)) 25 | // Append the value to the params slice 26 | params = append(params, value) 27 | paramIndex++ 28 | } 29 | 30 | // Join all conditions with "AND" and append to the query 31 | query += strings.Join(conditions, " AND ") 32 | p("Query with conditions:", query) 33 | 34 | // Execute the query with the dynamic parameters 35 | rows, err := db.Query(context.Background(), query, params...) 36 | p("Rows:", rows) 37 | if err != nil { 38 | p("Error executing query:", err) 39 | return nil, err 40 | } 41 | defer rows.Close() 42 | 43 | // Assuming a single row for hydration 44 | result := make(map[string]interface{}) 45 | fieldDescriptions := rows.FieldDescriptions() 46 | columns := make([]string, len(fieldDescriptions)) 47 | for i, field := range fieldDescriptions { 48 | columns[i] = string(field.Name) 49 | } 50 | p("Columns:", columns) 51 | 52 | values := make([]interface{}, len(columns)) 53 | valuePtrs := make([]interface{}, len(columns)) 54 | for i := range values { 55 | valuePtrs[i] = &values[i] 56 | } 57 | 58 | if rows.Next() { 59 | if err := rows.Scan(valuePtrs...); err != nil { 60 | return nil, err 61 | } 62 | 63 | for i, col := range columns { 64 | result[col] = values[i] 65 | } 66 | } 67 | 68 | p("Result:", result) 69 | return result, nil 70 | } 71 | -------------------------------------------------------------------------------- /hydra/DatabaseMySQL.go: -------------------------------------------------------------------------------- 1 | package hydra 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "strings" 7 | 8 | _ "github.com/go-sql-driver/mysql" 9 | ) 10 | 11 | // fetchMySQL fetches data from a MySQL database using a query 12 | // @param db The database connection 13 | // @param params The parameters to pass to the query 14 | // @return map[string]interface{} The hydrated data 15 | // @return error The error if any occurred 16 | func (h *Hydratable) fetchMySQL(db *sql.DB, tableName string, whereClauses map[string]interface{}) (map[string]interface{}, error) { 17 | // Build the base query with the table name 18 | query := fmt.Sprintf("SELECT * FROM %s WHERE ", tableName) 19 | p("Query:", query) 20 | 21 | // Prepare the values for the SQL parameters 22 | var params []interface{} 23 | var conditions []string 24 | 25 | // Build the WHERE clause dynamically 26 | for column, value := range whereClauses { 27 | // Append the column name to the condition (safe concatenation) 28 | conditions = append(conditions, fmt.Sprintf("%s = ?", column)) 29 | // Append the value to the params slice 30 | params = append(params, value) 31 | } 32 | 33 | // Join all conditions with "AND" and append to the query 34 | query += strings.Join(conditions, " AND ") 35 | p("Query with conditions:", query) 36 | 37 | // Execute the query with the dynamic parameters 38 | rows, err := db.Query(query, params...) 39 | p("Rows:", rows) 40 | if err != nil { 41 | p("Error executing query:", err) 42 | return nil, err 43 | } 44 | defer rows.Close() 45 | 46 | // Assuming a single row for hydration 47 | result := make(map[string]interface{}) 48 | columns, err := rows.Columns() 49 | p("Columns:", columns) 50 | if err != nil { 51 | p("Error getting columns:", err) 52 | return nil, err 53 | } 54 | 55 | values := make([]interface{}, len(columns)) 56 | valuePtrs := make([]interface{}, len(columns)) 57 | for i := range values { 58 | valuePtrs[i] = &values[i] 59 | } 60 | 61 | if rows.Next() { 62 | if err := rows.Scan(valuePtrs...); err != nil { 63 | return nil, err 64 | } 65 | 66 | for i, col := range columns { 67 | result[col] = values[i] 68 | } 69 | } 70 | 71 | p("Result:", result) 72 | return result, nil 73 | } 74 | -------------------------------------------------------------------------------- /hydra/Hydrate.go: -------------------------------------------------------------------------------- 1 | package hydra 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | func (h *Hydratable) Hydrate(db any, whereClauses map[string]interface{}) error { 10 | if !h.isInitialized { 11 | p("Not initialized, cannot hydrate.") 12 | return fmt.Errorf("not initialized, cannot hydrate") 13 | } 14 | 15 | // Reflect on the actual value of `self` (which holds the parent struct) 16 | v := reflect.ValueOf(h.self) 17 | if v.Kind() == reflect.Ptr { 18 | v = v.Elem() // Get the actual struct if it's a pointer 19 | } 20 | t := v.Type() 21 | p("Hydrating struct:", t.Name()) 22 | 23 | // Get the table name from the struct's type name 24 | tableName := strings.ToLower(t.Name()) 25 | p("Table name:", tableName) 26 | 27 | // Fetch data from the database using the table name and primary key (id assumed here) 28 | data, err := h.Fetch(db, tableName, whereClauses) // Pass the table name and where clauses 29 | p("Data:", data) 30 | if err != nil { 31 | p("Error fetching data:", err) 32 | return fmt.Errorf("error fetching data: %v", err) 33 | } 34 | 35 | // Loop through the fields of the struct and hydrate based on hydra tags 36 | for i := 0; i < t.NumField(); i++ { 37 | field := t.Field(i) 38 | tag := field.Tag.Get(tagName) 39 | 40 | // If the field has a hydra tag, try to hydrate it 41 | if tag != "" { 42 | if value, ok := data[tag]; ok { 43 | fieldValue := v.FieldByName(field.Name) 44 | 45 | // Ensure the field is settable 46 | if fieldValue.CanSet() { 47 | // Set the field's value from the database data 48 | switch fieldValue.Kind() { 49 | case reflect.String: 50 | if bytes, ok := value.([]byte); ok { 51 | fieldValue.SetString(string(bytes)) 52 | } 53 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 54 | fieldValue.SetInt(reflect.ValueOf(value).Int()) 55 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 56 | fieldValue.SetUint(reflect.ValueOf(value).Uint()) 57 | case reflect.Float32, reflect.Float64: 58 | fieldValue.SetFloat(reflect.ValueOf(value).Float()) 59 | case reflect.Bool: 60 | fieldValue.SetBool(value.(bool)) 61 | case reflect.Slice: 62 | fieldValue.Set(reflect.ValueOf(value)) // Assuming the value is a slice and directly assignable 63 | case reflect.Struct: 64 | fieldValue.Set(reflect.ValueOf(value)) // Assuming the value is a struct 65 | case reflect.Ptr: 66 | fieldValue.Set(reflect.ValueOf(value)) // Handle pointers by setting the pointer to the value 67 | default: 68 | fieldValue.Set(reflect.ValueOf(value)) // Hail mary 69 | p(fmt.Sprintf("unhandled type: %v\n", fieldValue.Kind())) 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | return nil // Hydration success 77 | } 78 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "breaking-change" 3 | color: ee0701 4 | description: "A breaking change for existing users." 5 | - name: "bug" 6 | color: ee0701 7 | description: "Inconsistencies or issues which will cause a problem for users or implementors." 8 | - name: "documentation" 9 | color: 0052cc 10 | description: "Solely about the documentation of the project." 11 | - name: "enhancement" 12 | color: 1d76db 13 | description: "Enhancement of the code, not introducing new features." 14 | - name: "refactor" 15 | color: 1d76db 16 | description: "Improvement of existing code, not introducing new features." 17 | - name: "performance" 18 | color: 1d76db 19 | description: "Improving performance, not introducing new features." 20 | - name: "new-feature" 21 | color: 0e8a16 22 | description: "New features or options." 23 | - name: "maintenance" 24 | color: 2af79e 25 | description: "Generic maintenance tasks." 26 | - name: "ci" 27 | color: 1d76db 28 | description: "Work that improves the continue integration." 29 | - name: "dependencies" 30 | color: 1d76db 31 | description: "Upgrade or downgrade of project dependencies." 32 | 33 | - name: "in-progress" 34 | color: fbca04 35 | description: "Issue is currently being resolved by a developer." 36 | - name: "stale" 37 | color: fef2c0 38 | description: "There has not been activity on this issue or PR for quite some time." 39 | - name: "no-stale" 40 | color: fef2c0 41 | description: "This issue or PR is exempted from the stable bot." 42 | 43 | - name: "security" 44 | color: ee0701 45 | description: "Marks a security issue that needs to be resolved ASAP." 46 | - name: "incomplete" 47 | color: fef2c0 48 | description: "Marks a PR or issue that is missing information." 49 | - name: "invalid" 50 | color: fef2c0 51 | description: "Marks a PR or issue that is missing information." 52 | 53 | - name: "beginner-friendly" 54 | color: 0e8a16 55 | description: "Good first issue for people wanting to contribute to the project." 56 | - name: "help-wanted" 57 | color: 0e8a16 58 | description: "We need some extra helping hands or expertise in order to resolve this." 59 | 60 | - name: "priority-critical" 61 | color: ee0701 62 | description: "This should be dealt with ASAP. Not fixing this issue would be a serious error." 63 | - name: "priority-high" 64 | color: b60205 65 | description: "After critical issues are fixed, these should be dealt with before any further issues." 66 | - name: "priority-medium" 67 | color: 0e8a16 68 | description: "This issue may be useful, and needs some attention." 69 | - name: "priority-low" 70 | color: e4ea8a 71 | description: "Nice addition, maybe... someday..." 72 | 73 | - name: "major" 74 | color: b60205 75 | description: "This PR causes a major version bump in the version number." 76 | - name: "minor" 77 | color: 0e8a16 78 | description: "This PR causes a minor version bump in the version number." 79 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Request for Contributions 2 | 3 | Please contribute to this repository if any of the following is true: 4 | - You have knowledge in Golang, GitHub Actions, coding processes and best practices, graphic design, or development 5 | - You have expertise in community development, communication, or education 6 | - You want open source communities to be more collaborative and inclusive 7 | - You want to help lower the burden to first time contributors 8 | - You are a first time contributor or are learning to code (we welcome all contributions) 9 | 10 | # How to Contribute 11 | 12 | Prerequisites: 13 | 14 | - Familiarity with [pull requests](https://help.github.com/articles/using-pull-requests) 15 | - Familiarity with [issues](https://guides.github.com/features/issues/) 16 | - Knowledge of [Markdown](https://help.github.com/articles/markdown-basics/) for editing `.md` documents 17 | 18 | **Note**: If you do not have familiarity or would like help, please reach out to 19 | [@nsa-yoda](https://github.com/nsa-yoda) or open an [Issue](https://guides.github.com/features/issues/) 20 | with your question/concern. 21 | 22 | In particular, this community seeks the following types of contributions: 23 | 24 | - **Ideas**: participate in an issue thread or start your own to have your voice heard 25 | - **Resources**: submit a pull request to add to RESOURCES.md with links to related content 26 | - **Outline sections**: help us ensure that this repository is comprehensive. if 27 | there is a topic that is overlooked, please add it, even if it is just a stub 28 | in the form of a header and single sentence. Initially, most things fall into 29 | this category 30 | - **Writing**: contribute your expertise in an area by helping us expand the included content 31 | - **Copy editing**: fix typos, clarify language, and generally improve the quality of the content 32 | - **Formatting**: help keep content easy to read with consistent formatting 33 | 34 | # Conduct 35 | 36 | We are committed to providing a friendly, safe and welcoming environment for 37 | all regardless of religion, ethnicity, gender, sexual orientation, disability, 38 | or other personal characteristic. 39 | 40 | Please be kind and courteous. There's no need to be mean or rude. 41 | Respect that people have differences of opinion and that every design or 42 | implementation choice carries a trade-off and numerous costs. There is seldom 43 | a right answer, merely an optimal answer given a set of values and 44 | circumstances. 45 | 46 | Please keep unstructured critique to a minimum. If you have solid ideas you 47 | want to experiment with, make a fork and see how it works. 48 | 49 | We will exclude you from interaction if you insult, demean or harass anyone. 50 | 51 | Private harassment is also unacceptable. No matter who you are, if you feel 52 | you have been or are being harassed or made uncomfortable by a community 53 | member, please contact the core team immediately. Whether you're a regular 54 | contributor or a newcomer, we care about you, and we've got your back. 55 | 56 | Likewise, any spamming, trolling, flaming, baiting or other attention-stealing 57 | behaviour is not welcome. 58 | 59 | # Communication 60 | 61 | GitHub Issues are the primary way for communicating about specific proposed 62 | changes to this project. 63 | 64 | Please follow the conduct guidelines above. Language issues are often contentious, 65 | and we'd like to keep discussion brief, civil and focused on what we're actually 66 | doing, not wandering off into too much imaginary stuff. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sphire Hydra 2 | 3 | [![Build](https://github.com/sphireinc/Hydra/actions/workflows/build.yml/badge.svg)](https://github.com/sphireinc/Hydra/actions/workflows/build.yml) 4 | [![Documentation](https://img.shields.io/badge/Pages-passing-green)](https://sphireinc.github.io/Hydra/) 5 | [![License](https://img.shields.io/github/license/sphireinc/hydra)](https://github.com/sphireinc/Hydra/releases/latest) 6 | [![Go Version](https://img.shields.io/github/go-mod/go-version/sphireinc/hydra)](https://github.com/sphireinc/Hydra/releases/latest) 7 | [![Release Version](https://img.shields.io/github/v/release/sphireinc/hydra)](https://github.com/sphireinc/Hydra/releases/latest) 8 | [![Release Date](https://img.shields.io/github/release-date/sphireinc/hydra)](https://github.com/sphireinc/Hydra/releases/latest) 9 | 10 |
11 | logo 12 |
13 | 14 | Sphire Hydra is a Go library designed to dynamically hydrate Go structs with data from a variety of databases. 15 | The library supports multiple databases, including MySQL, PostgreSQL, SQLite, 16 | Microsoft SQL Server, Oracle, MariaDB, and CockroachDB. Using reflection and `hydra` tags, 17 | it automatically fills struct fields with data fetched from database queries. 18 | 19 | > [!WARNING] 20 | > Hydra went from idea to fruition in the span of 4 hours. It is still a very immature project, use it at your own risk. I welcome all opinions, contributions, and ideas on how to make this a better project. 21 | 22 | ## Features 23 | 24 | - **Automatic Hydration**: Automatically populates Go structs with data fetched from databases using reflection. 25 | - **Multiple Database Support**: Supports MySQL, PostgreSQL, SQLite, Microsoft SQL Server, Oracle, MariaDB, and CockroachDB. 26 | - **Flexible Queries**: Allows dynamic construction of SQL `WHERE` clauses. 27 | - **Type Safety**: Ensures proper type conversions between database values and Go struct fields. 28 | - **Easily Extendable**: Easily extendable to support more databases in the future. 29 | 30 | ## Installation 31 | 32 | To install Sphire Hydra, use `go get`: 33 | 34 | ```bash 35 | go get github.com/sphireinc/Hydra 36 | ``` 37 | 38 | ## Supported Databases 39 | 40 | - MySQL 41 | - PostgreSQL 42 | - SQLite 43 | - Microsoft SQL Server 44 | - Oracle 45 | - MariaDB 46 | - CockroachDB 47 | 48 | ## Usage 49 | 50 | ### Struct Definition 51 | 52 | Define your structs with hydra tags to map the struct fields to the corresponding database columns, annd 53 | embed the hydra.Hydratable struct: 54 | 55 | ```go 56 | type Person struct { 57 | Name string `json:"name" hydra:"name"` 58 | Age int `json:"age" hydra:"age"` 59 | Email string `json:"email" hydra:"email"` 60 | hydra.Hydratable 61 | } 62 | ``` 63 | 64 | ### Hydration 65 | 66 | To hydrate a struct, initialize the struct and then call the Hydrate method, which automatically fetches the 67 | data from the database and populates the fields: 68 | 69 | ```go 70 | package main 71 | 72 | import ( 73 | "database/sql" 74 | "github.com/sphireinc/Hydra" 75 | _ "github.com/go-sql-driver/mysql" 76 | ) 77 | 78 | type Person struct { 79 | Name string `json:"name" hydra:"name"` 80 | Age int `json:"age" hydra:"age"` 81 | Email string `json:"email" hydra:"email"` 82 | hydra.Hydratable 83 | } 84 | 85 | func createDBConnection() *sql.DB { 86 | db, _ := sql.Open("mysql", "user:password@/dbname") 87 | return db 88 | } 89 | 90 | func main() { 91 | // Create a database connection 92 | db := createDBConnection() 93 | 94 | // Create an addressable Person instance and initialize the hydra.Hydratable struct 95 | p := &Person{} 96 | p.Init(p) 97 | 98 | // Create a map of where clauses 99 | whereClause := map[string]interface{}{"id": "U6"} 100 | 101 | // Call Hydrate to populate the struct with data from the database 102 | p.Hydrate(db, whereClause) 103 | 104 | // Print the hydrated struct 105 | fmt.Printf("Hydrated person: %+v\n", p) 106 | } 107 | ``` 108 | 109 | # Extensibility 110 | 111 | Sphire Hydra is designed to be easily extensible. You can add support for additional databases by implementing a 112 | fetch function specific to the database’s query syntax and integrating it with the existing hydration process. 113 | 114 | # Contributing 115 | 116 | We welcome contributions! Feel free to open an issue or submit a pull request to improve the library. 117 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Build 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | # pull_request: 10 | 11 | concurrency: 12 | group: ${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | fmt: 17 | name: Go Fmt 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@master 21 | - name: fmt 22 | uses: danhunsaker/golang-github-actions@v1.3.0 23 | with: 24 | run: fmt 25 | token: ${{ secrets.GITHUB_TOKEN }} 26 | 27 | imports: 28 | name: Go Imports 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@master 32 | - name: imports 33 | uses: danhunsaker/golang-github-actions@v1.3.0 34 | with: 35 | run: imports 36 | token: ${{ secrets.GITHUB_TOKEN }} 37 | 38 | vet: 39 | name: Go Vet 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@master 43 | - name: vet 44 | uses: danhunsaker/golang-github-actions@v1.3.0 45 | with: 46 | run: vet 47 | token: ${{ secrets.GITHUB_TOKEN }} 48 | 49 | shadow: 50 | name: Shadow Analyzer 51 | runs-on: ubuntu-latest 52 | steps: 53 | - uses: actions/checkout@master 54 | - name: shadow 55 | uses: danhunsaker/golang-github-actions@v1.3.0 56 | with: 57 | run: shadow 58 | token: ${{ secrets.GITHUB_TOKEN }} 59 | 60 | misspell: 61 | name: Spell Check 62 | runs-on: ubuntu-latest 63 | steps: 64 | - uses: actions/checkout@master 65 | - name: misspell 66 | uses: danhunsaker/golang-github-actions@v1.3.0 67 | with: 68 | run: misspell 69 | token: ${{ secrets.GITHUB_TOKEN }} 70 | 71 | # gocyclo: 72 | # name: Cyclomatic Complexity 73 | # runs-on: ubuntu-latest 74 | # steps: 75 | # - uses: actions/checkout@master 76 | # - name: gocyclo 77 | # uses: danhunsaker/golang-github-actions@v1.3.0 78 | # with: 79 | # run: cyclo 80 | # token: ${{ secrets.GITHUB_TOKEN }} 81 | # flags: "-over 20" 82 | 83 | # staticcheck: 84 | # name: Static Check 85 | # runs-on: ubuntu-latest 86 | # steps: 87 | # - uses: actions/checkout@master 88 | # - name: staticcheck 89 | # uses: danhunsaker/golang-github-actions@v1.3.0 90 | # with: 91 | # run: staticcheck 92 | # token: ${{ secrets.GITHUB_TOKEN }} 93 | 94 | # ineffassign: 95 | # name: Ineffectual Assignments 96 | # runs-on: ubuntu-latest 97 | # steps: 98 | # - uses: actions/checkout@master 99 | # - name: ineffassign 100 | # uses: danhunsaker/golang-github-actions@v1.3.0 101 | # with: 102 | # run: ineffassign 103 | # token: ${{ secrets.GITHUB_TOKEN }} 104 | # flags: "-buildvcs=false" 105 | 106 | build: 107 | runs-on: ubuntu-latest 108 | strategy: 109 | matrix: 110 | go: [ '1.22.4', '1.23' ] 111 | steps: 112 | - name: Checkout Code ${{ matrix.go }} 113 | uses: actions/checkout@v4 114 | 115 | - name: Set up Go ${{ matrix.go }} 116 | uses: actions/setup-go@v4 117 | with: 118 | go-version: ${{ matrix.go }} 119 | 120 | - name: Tidy and Vendor ${{ matrix.go }} 121 | run: | 122 | go get -u 123 | go mod tidy 124 | go mod vendor 125 | 126 | - name: Set Up GoTestFmt ${{ matrix.go }} 127 | uses: GoTestTools/gotestfmt-action@v2 128 | with: 129 | token: ${{ secrets.GITHUB_TOKEN }} 130 | version: v2.0.0 131 | 132 | - name: Test ${{ matrix.go }} 133 | run: | 134 | set -euo pipefail 135 | go test -json -v ./... 2>&1 | tee /tmp/gotest.log | gotestfmt 136 | 137 | - name: Archive markdown artifacts ${{ matrix.go }} 138 | uses: actions/upload-artifact@v3 139 | with: 140 | name: archive-markdown 141 | path: | 142 | docs 143 | 144 | - name: Build Markdown Pages ${{ matrix.go }} 145 | run: | 146 | go install github.com/princjef/gomarkdoc/cmd/gomarkdoc@latest 147 | gomarkdoc --output=docs/code/hydra.md ./hydra 148 | 149 | - name: Setup git Config ${{ matrix.go }} 150 | run: | 151 | git config user.name "GitHub Actions Bot" 152 | git config user.email "" 153 | 154 | - name: Commit Docs ${{ matrix.go }} 155 | run: | 156 | # Stage the file, commit and push 157 | git checkout gh-pages 2>/dev/null || git checkout -b gh-pages 158 | git add ./docs/code 159 | git commit -m "gh-pages gomarkdoc GHABOT" 160 | git push -f origin gh-pages 161 | git checkout main 162 | 163 | - name: Build ${{ matrix.go }} 164 | run: go build -v ./... -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= 2 | filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 3 | github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= 4 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= 5 | github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= 6 | github.com/UNO-SOFT/zlog v0.8.1 h1:TEFkGJHtUfTRgMkLZiAjLSHALjwSBdw6/zByMC5GJt4= 7 | github.com/UNO-SOFT/zlog v0.8.1/go.mod h1:yqFOjn3OhvJ4j7ArJqQNA+9V+u6t9zSAyIZdWdMweWc= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= 12 | github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= 13 | github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= 14 | github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= 15 | github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 16 | github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= 17 | github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 18 | github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= 19 | github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= 20 | github.com/godror/godror v0.44.7 h1:fGxtxozidwBR3C1FVTrMiH77maOnMA4HqltDS/YM7O0= 21 | github.com/godror/godror v0.44.7/go.mod h1:KJwMtQpK9o3WdEiNw7qvgSk827YDLj9MV/bXSzvUzlo= 22 | github.com/godror/knownpb v0.1.2 h1:icMyYsYVpGmzhoVA01xyd0o4EaubR31JPK1UxQWe4kM= 23 | github.com/godror/knownpb v0.1.2/go.mod h1:zs9hH+lwj7mnPHPnKCcxdOGz38Axa9uT+97Ng+Nnu5s= 24 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= 25 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 26 | github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= 27 | github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= 28 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 29 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 30 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 31 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 32 | github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= 33 | github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= 34 | github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= 35 | github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= 36 | github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= 37 | github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= 38 | github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0= 39 | github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 40 | github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= 41 | github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc= 42 | github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68= 43 | github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= 44 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 45 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 46 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 47 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 48 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 49 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 50 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 51 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 52 | golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 53 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 54 | golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= 55 | golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= 56 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= 57 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= 58 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 59 | golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 60 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 61 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= 62 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 63 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 64 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 65 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 66 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 67 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 68 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= 69 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 70 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 71 | golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= 72 | golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= 73 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 74 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 75 | golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= 76 | golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 77 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 78 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 79 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 80 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 81 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 82 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 83 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 84 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 85 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 86 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 87 | --------------------------------------------------------------------------------