{"id":19144564,"url":"https://github.com/jacobbrewer1/patcher","last_synced_at":"2025-05-07T01:12:35.529Z","repository":{"id":257815764,"uuid":"867502876","full_name":"Jacobbrewer1/patcher","owner":"Jacobbrewer1","description":"Powerful SQL Query builder that automatically generates SQL queries from structs","archived":false,"fork":false,"pushed_at":"2025-05-06T20:25:11.000Z","size":4217,"stargazers_count":5,"open_issues_count":1,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-06T21:30:51.442Z","etag":null,"topics":["go","go-lib","go-library","go-mysql","golang","golang-library","mysql","sql"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Jacobbrewer1.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-10-04T07:30:16.000Z","updated_at":"2025-05-06T20:25:13.000Z","dependencies_parsed_at":null,"dependency_job_id":"8d54c396-beb0-4b4d-8352-79bd00f4fa57","html_url":"https://github.com/Jacobbrewer1/patcher","commit_stats":null,"previous_names":["jacobbrewer1/patcher"],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jacobbrewer1%2Fpatcher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jacobbrewer1%2Fpatcher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jacobbrewer1%2Fpatcher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Jacobbrewer1%2Fpatcher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Jacobbrewer1","download_url":"https://codeload.github.com/Jacobbrewer1/patcher/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252793653,"owners_count":21805058,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","go-lib","go-library","go-mysql","golang","golang-library","mysql","sql"],"created_at":"2024-11-09T07:35:29.282Z","updated_at":"2025-05-07T01:12:35.509Z","avatar_url":"https://github.com/Jacobbrewer1.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Patcher\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/jacobbrewer1/patcher.svg)](https://pkg.go.dev/github.com/jacobbrewer1/patcher)\n[![Go Report Card](https://goreportcard.com/badge/github.com/jacobbrewer1/patcher)](https://goreportcard.com/report/github.com/jacobbrewer1/patcher)\n\nPatcher is a GO library that provides a simple way to generate and SQL patches from structs. The library was built out\nof the need to generate patches for a database; when a new field is added to a struct, this would result in a bunch of\nnew `if` checks to be created in the codebase. This library aims to solve that problem by generating the SQL patches for\nyou.\n\n## What is Patcher?\n\n* **Automatic SQL Generation**: It automatically generates SQL UPDATE queries from structs, reducing the need for\n  manually\n  writing and maintaining SQL statements.\n* **Code Simplification**: It reduces the amount of boilerplate code and if-else conditions required to handle different\n  struct fields, making the codebase cleaner and easier to maintain.\n* **Struct Diffs**: It allows injecting changes from one struct to another and generating update scripts based on\n  differences, streamlining the process of synchronizing data changes.\n* **Join Support**: It supports generating SQL joins by creating structs that implement the Joiner interface,\n  simplifying the process of managing related data across multiple tables.\n\n## Why Use Patcher?\n\n* **Saves Time**: It saves time by automatically generating SQL queries from structs, reducing the need to write and\n  maintain SQL statements manually.\n* **Reduces Errors**: It reduces the risk of errors by automatically generating SQL queries based on struct fields,\n  eliminating the need to manually update queries when struct fields change.\n* **Simplifies Code**: It simplifies the codebase by reducing the amount of boilerplate code and if-else conditions\n  required to handle different struct fields, making the code easier to read and maintain.\n* **Streamlines Data Synchronization**: It streamlines the process of synchronizing data changes by allowing you to\n  inject changes from one struct to another and generate update scripts based on differences.\n* **Supports Joins**: It supports generating SQL joins by creating structs that implement the Joiner interface, making\n  it easier to manage related data across multiple tables.\n* **Flexible Configuration**: It provides flexible configuration options to customize the SQL generation process, such\n  as including zero or nil values in the diff.\n* **Easy Integration**: It is easy to integrate into existing projects and can be used with any Go project that needs to\n  generate SQL queries from structs.\n* **Open Source**: It is open-source and available under the Apache 2.0 license.\n\n### Demonstration\n\nHere is a simple example of how Patcher would compare to an example of not using Patcher.\n\n#### Without Patcher\n\n```go\npackage main\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t_ \"github.com/go-sql-driver/mysql\"\n\t\"github.com/gorilla/mux\"\n)\n\ntype User struct {\n\tID    int     `json:\"id\"`\n\tName  *string `json:\"name\"`\n\tEmail *string `json:\"email\"`\n}\n\nfunc updateUser(db *sql.DB, user User) error {\n\tquery := \"UPDATE users SET\"\n\tvar updates []string\n\tvar args []any\n\n\tif user.Name != nil {\n\t\tupdates = append(updates, \"name = ?\")\n\t\targs = append(args, *user.Name)\n\t}\n\tif user.Email != nil {\n\t\tupdates = append(updates, \"email = ?\")\n\t\targs = append(args, *user.Email)\n\t}\n\n\tif len(updates) == 0 {\n\t\treturn errors.New(\"no fields to update\")\n\t}\n\n\tquery += \" \" + strings.Join(updates, \", \") + \" WHERE id = ?\"\n\targs = append(args, user.ID)\n\n\t_, err := db.Exec(query, args...)\n\treturn err\n}\n\nfunc patchHandler(w http.ResponseWriter, r *http.Request) {\n\tdb, err := sql.Open(\"mysql\", \"user:password@tcp(localhost:3306)/dbname\")\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tdefer db.Close()\n\n\tvars := mux.Vars(r)\n\tuserIDStr := vars[\"id\"]\n\tuserID, err := strconv.Atoi(userIDStr)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tuser := User{ID: userID}\n\tif err := json.NewDecoder(r.Body).Decode(\u0026user); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif err := updateUser(db, user); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.WriteHeader(http.StatusOK)\n}\n\nfunc main() {\n\tr := mux.NewRouter()\n\tr.HandleFunc(\"/users/{id}\", patchHandler).Methods(\"PATCH\")\n\n\tserver := \u0026http.Server{\n\t\tAddr:              \":8080\",\n\t\tHandler:           r,\n\t\tReadHeaderTimeout: 10 * time.Second,\n\t}\n\n\tif err := server.ListenAndServe(); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n```\n\nAs demonstrated above, without Patcher, you would need to manually write the SQL update query and handle each field. If\na new field is added to the struct, you would need to update the query manually, which can be error-prone (e.g., missing\nor forgetting to update a field) and time-consuming.\n\n#### With Patcher\n\n```go\npackage main\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t_ \"github.com/go-sql-driver/mysql\"\n\t\"github.com/gorilla/mux\"\n\t\"github.com/jacobbrewer1/patcher\"\n)\n\ntype User struct {\n\tID    *int    `db:\"id\" json:\"id,omitempty\"`\n\tName  *string `db:\"name\" json:\"name,omitempty\"`\n\tEmail *string `db:\"email\" json:\"email,omitempty\"`\n}\n\ntype UserWhere struct {\n\tID *int `db:\"id\"`\n}\n\nfunc NewUserWhere(id int) *UserWhere {\n\treturn \u0026UserWhere{ID: \u0026id}\n}\n\nfunc (u *UserWhere) Where() (sqlStr string, sqlArgs []any) {\n\treturn \"id = ?\", []any{*u.ID}\n}\n\nfunc patchHandler(w http.ResponseWriter, r *http.Request) {\n\tdb, err := sql.Open(\"mysql\", \"user:password@tcp(localhost:3306)/dbname\")\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tdefer db.Close()\n\n\tvars := mux.Vars(r)\n\tidStr := vars[\"id\"]\n\tparsedID, err := strconv.Atoi(idStr)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tuser := new(User)\n\tif err := json.NewDecoder(r.Body).Decode(user); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tuser.ID = \u0026parsedID\n\tcondition := NewUserWhere(parsedID)\n\n\tsqlStr, args, err := patcher.GenerateSQL(\n\t\tuser,\n\t\tpatcher.WithTable(\"users\"),\n\t\tpatcher.WithWhere(condition),\n\t)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t_, execErr := db.Exec(sqlStr, args...)\n\tif execErr != nil {\n\t\thttp.Error(w, execErr.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.WriteHeader(http.StatusOK)\n}\n\nfunc main() {\n\tr := mux.NewRouter()\n\tr.HandleFunc(\"/users/{id}\", patchHandler).Methods(\"PATCH\")\n\n\tserver := \u0026http.Server{\n\t\tAddr:              \":8080\",\n\t\tHandler:           r,\n\t\tReadHeaderTimeout: 5 * time.Second,\n\t}\n\n\tif err := server.ListenAndServe(); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n```\n\nWith Patcher, you can generate the SQL update query automatically from the struct fields, reducing the need to write and\nmaintain the query manually. If a new field is added to the struct, Patcher will automatically include it in the update\nquery, simplifying the code and reducing the risk of errors.\n\n## Usage\n\n### Configuration\n\n#### LoadDiff Options\n\n* `includeZeroValues`: Set to true to include zero values in the diff.\n* `includeNilValues`: Set to true to include nil values in the diff.\n\n#### GenerateSQL Options\n\n* `WithTable(tableName string)`: Specify the table name for the SQL query.\n* `WithWhere(whereClause Wherer)`: Provide a where clause for the SQL query.\n    * You can pass a struct that implements the `WhereTyper` interface to use `OR` in the where clause. Patcher will\n      default to `AND` if the `WhereTyper` interface is not implemented.\n* `WithJoin(joinClause Joiner)`: Add join clauses to the SQL query.\n* `includeZeroValues`: Set to true to include zero values in the Patch.\n* `includeNilValues`: Set to true to include nil values in the Patch.\n\n### Basic Examples\n\n#### Basic\n\nTo use the library, you need to create a struct that represents the table you want to generate patches for. The struct\nshould have the following tags:\n\n- `db:\"column_name\"`: This tag is used to specify the column name in the database.\n\nExample:\n\n```go\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/jacobbrewer1/patcher\"\n)\n\ntype Person struct {\n\tID   *int    `db:\"-\"`\n\tName *string `db:\"name\"`\n}\n\ntype PersonWhere struct {\n\tID *int `db:\"id\"`\n}\n\nfunc NewPersonWhere(id int) *PersonWhere {\n\treturn \u0026PersonWhere{\n\t\tID: \u0026id,\n\t}\n}\n\nfunc (p *PersonWhere) Where() (string, []any) {\n\treturn \"id = ?\", []any{*p.ID}\n}\n\nfunc main() {\n\tconst jsonStr = `{\"id\": 1, \"name\": \"john\"}`\n\n\tperson := new(Person)\n\tif err := json.Unmarshal([]byte(jsonStr), person); err != nil {\n\t\tpanic(err)\n\t}\n\n\tcondition := NewPersonWhere(*person.ID)\n\n\tsqlStr, args, err := patcher.GenerateSQL(\n\t\tperson,\n\t\tpatcher.WithTable(\"people\"),\n\t\tpatcher.WithWhere(condition),\n\t)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfmt.Println(sqlStr)\n\n\tfmt.Println(args)\n}\n\n```\n\nThis will output:\n\n```sql\nUPDATE people\nSET name = ?\nWHERE (1 = 1)\n  AND (\n    id = ?\n    )\n```\n\nwith the args:\n\n```\n[\"john\", 1]\n```\n\n#### Struct diffs\n\nThe Patcher library has functionality where you are able to inject changes from one struct to another. This is\nconfigurable to include Zero values and Nil values if requested. Please see the\nexample [here](./examples/loader_with_opts) for the detailed example. Below is an example on how you can utilize this\nmethod with the default behaviour (Please see the comment attached to the `LoadDiff` [method](./loader.go) for the\ndefault behaviour).\n\nExample:\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/jacobbrewer1/patcher\"\n)\n\ntype Something struct {\n\tNumber       int\n\tText         string\n\tPrePopulated string\n\tNewText      string\n}\n\nfunc main() {\n\ts := Something{\n\t\tNumber:       5,\n\t\tText:         \"Hello\",\n\t\tPrePopulated: \"PrePopulated\",\n\t}\n\n\tn := Something{\n\t\tNumber:  6,\n\t\tText:    \"Old Text\",\n\t\tNewText: \"New Text\",\n\t}\n\n\t// The patcher.LoadDiff function will apply the changes from n to s.\n\tif err := patcher.LoadDiff(\u0026s, \u0026n); err != nil {\n\t\tpanic(err)\n\t}\n\n\tfmt.Println(s.Number)\n\tfmt.Println(s.Text)\n\tfmt.Println(s.PrePopulated)\n\tfmt.Println(s.NewText)\n}\n\n```\n\nThis will output:\n\n```\n6\nHello\nPrePopulated\nNew Text\n```\n\nIf you would like to generate an update script from two structs, you can use the `NewDiffSQLPatch` function. This\nfunction will generate an update script from the two structs.\n\nExample:\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/jacobbrewer1/patcher\"\n)\n\ntype Something struct {\n\tNumber       int\n\tText         string\n\tPrePopulated string\n\tNewText      string\n}\n\ntype SomeWhere struct {\n\tid int\n}\n\nfunc NewSomeWhere(id int) *SomeWhere {\n\treturn \u0026SomeWhere{id: id}\n}\n\nfunc (s *SomeWhere) Where() (string, []any) {\n\treturn \"id = ?\", []any{s.id}\n}\n\nfunc main() {\n\ts := Something{\n\t\tNumber:       5,\n\t\tText:         \"Old Text\",\n\t\tPrePopulated: \"PrePopulated\",\n\t\tNewText:      \"New Text\",\n\t}\n\n\tn := Something{\n\t\tNumber:       5,\n\t\tText:         \"Old Text\",\n\t\tPrePopulated: \"PrePopulatedDifferent\",\n\t\tNewText:      \"New Text\",\n\t}\n\n\twherer := NewSomeWhere(5)\n\n\t// The patcher.LoadDiff function will apply the changes from n to s.\n\tpatch, err := patcher.NewDiffSQLPatch(\n\t\t\u0026s,\n\t\t\u0026n,\n\t\tpatcher.WithTable(\"table_name\"),\n\t\tpatcher.WithWhere(wherer),\n\t)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tsqlStr, sqlArgs, err := patch.GenerateSQL()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tfmt.Println(sqlStr)\n\tfmt.Println(sqlArgs)\n}\n\n```\n\nThis will output:\n\n```sql\nUPDATE table_name\nSET pre_populated = ?\nWHERE (1 = 1)\n  AND (\n    id = ?\n    )\n```\n\nwith the args:\n\n```\n[\"PrePopulatedDifferent\", 5]\n```\n\nYou can also take a look at the Loader [examples](./examples) for more examples on how to use the library for this\napproach.\n\n#### Using `OR` in the where clause\n\nIf you would like to use `OR` in the where clause, you can apply the `patcher.WhereTyper` interface to your where\nstruct. Please take a look at the [example here](./examples/where_type).\n\n### Joins\n\nTo generate a join, you need to create a struct that represents the join. This struct should implement\nthe [Joiner](./joiner.go) interface.\n\nOnce you have the join struct, you can pass it to the `GenerateSQL` function using the `WithJoin` option. You can add as\nmany of these as you would like.\n\n## Installation\n\nTo install the Patcher library, use the following command:\n\n```sh\ngo get github.com/jacobbrewer1/patcher\n```\n\n## Examples\n\nYou can find examples of how to use this library in the [examples](./examples) directory.\n\n## Contributing\n\nWe welcome contributions! Please follow these steps to contribute:\n\n1. Fork the repository.\n2. Create a new branch for your feature or bugfix.\n3. Write tests for your changes.\n4. Run the tests to ensure everything works.\n5. Submit a pull request.\n\nTo run tests, use the following command:\n\n```sh\ngo test ./...\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjacobbrewer1%2Fpatcher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjacobbrewer1%2Fpatcher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjacobbrewer1%2Fpatcher/lists"}