{"id":13412144,"url":"https://github.com/didi/gendry","last_synced_at":"2025-05-13T21:10:23.698Z","repository":{"id":37664232,"uuid":"112713415","full_name":"didi/gendry","owner":"didi","description":"a golang library for sql builder","archived":false,"fork":false,"pushed_at":"2025-03-12T03:30:45.000Z","size":261,"stargazers_count":1634,"open_issues_count":19,"forks_count":195,"subscribers_count":63,"default_branch":"master","last_synced_at":"2025-04-28T17:02:04.776Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/didi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"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}},"created_at":"2017-12-01T08:15:43.000Z","updated_at":"2025-04-23T08:15:10.000Z","dependencies_parsed_at":"2023-09-24T16:34:27.144Z","dependency_job_id":"08e122cd-7eec-49d3-a8fe-35abb23e3179","html_url":"https://github.com/didi/gendry","commit_stats":{"total_commits":85,"total_committers":29,"mean_commits":"2.9310344827586206","dds":0.5294117647058824,"last_synced_commit":"47b22f059022f7a230ecdf4e64cf140a1491c4fe"},"previous_names":["didichuxing/gendry"],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didi%2Fgendry","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didi%2Fgendry/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didi%2Fgendry/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didi%2Fgendry/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/didi","download_url":"https://codeload.github.com/didi/gendry/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254029002,"owners_count":22002283,"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":[],"created_at":"2024-07-30T20:01:21.471Z","updated_at":"2025-05-13T21:10:18.685Z","avatar_url":"https://github.com/didi.png","language":"Go","readme":"## Gendry\n[![Build Status](https://github.com/didi/gendry/workflows/Go/badge.svg)](https://github.com/didi/gendry/actions)\n[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/didi-gendry/Lobby)\n[![Hex.pm](https://img.shields.io/hexpm/l/plug.svg)](https://github.com/didi/gendry/blob/master/LICENSE)\n[![GoDoc](https://godoc.org/github.com/didi/gendry?status.svg)](https://godoc.org/github.com/didi/gendry)\n\n**gendry** is a Go library that helps you operate database. Based on `go-sql-driver/mysql`, it provides a series of simple but useful tools to prepare parameters for calling methods in standard library `database/sql`.\n\nThe name **gendry** comes from the role in the hottest drama: `The Game of Throne`, in which Gendry is not only the bastardy of the late king Robert Baratheon but also a skilled blacksmith. Like the one in drama, this library also forges something called `SQL`.\n\n**gendry** consists of three isolated parts, and you can use each one of them partially:\n\n* [manager](#manager)\n* [builder](#builder)\n* [scanner](#scanner)\n* [CLI tool](#tools)\n\n### Translation\n* [Chinese Simplified (中文)](translation/zhcn/README.md)\n\n\n\n\u003ch3 id=\"manager\"\u003eManager\u003c/h3\u003e\n\nThe manager is used for initializing the database connection pool(i.e., `sql.DB`).\nYou can set almost all parameters for those MySQL drivers supported. For example, initializing a database connection pool:\n\n``` go\nvar db *sql.DB\nvar err error\ndb, err = manager\n\t\t.New(dbName, user, password, host)\n\t\t.Set(\n\t\t\tmanager.SetCharset(\"utf8\"),\n\t\t\tmanager.SetAllowCleartextPasswords(true),\n\t\t\tmanager.SetInterpolateParams(true),\n\t\t\tmanager.SetTimeout(1 * time.Second),\n\t\t\tmanager.SetReadTimeout(1 * time.Second)\n\t\t).Port(3302).Open(true)\n```\nIn fact, all things manager does is just to generate the `dataSourceName`\n\nthe format of a `dataSourceName` is：\n\n```\n[username[:password]@][protocol[(address)]]/dbname[?param1=value1\u0026...\u0026paramN=valueN]\n```\n\nManager is based on `go-mysql-driver/mysql`. If you don't know some of the manager and SetXXX series functions, see it on [mysql driver's github home page](https://github.com/go-sql-driver/mysql). For more details see [manager's doc](manager/README.md)\n\n\u003ch3 id=\"builder\"\u003eBuilder\u003c/h3\u003e\nBuilder as its name says is for building SQL. Writing SQL manually is intuitive but somewhat difficult to maintain. And for `where in,` if you have a huge amount of elements in the `in` set, it's very hard to write.\n\nBuilder isn't an ORM. In fact, one of the most important reasons we create Gendry is we don't like ORM. So Gendry just provides some simple APIs to help you build SQLs:\n\n```go\nwhere := map[string]interface{}{\n\t\"city\": []string{\"beijing\", \"shanghai\"},\n\t// The in operator can be omitted by default,\n\t// which is equivalent to:\n\t// \"city in\": []string{\"beijing\", \"shanghai\"},\n\t\"score\": 5,\n\t\"age \u003e\": 35,\n\t\"address\": builder.IsNotNull,\n\t\"_or\": []map[string]interface{}{\n\t\t{\n\t\t\t\"x1\":    11,\n\t\t\t\"x2 \u003e=\": 45,\n\t\t},\n\t\t{\n\t\t\t\"x3\":    \"234\",\n\t\t\t\"x4 \u003c\u003e\": \"tx2\",\n\t\t},\n\t},\n\t\"_orderby\": \"bonus desc\",\n\t\"_groupby\": \"department\",\n}\ntable := \"some_table\"\nselectFields := []string{\"name\", \"age\", \"sex\"}\ncond, values, err := builder.BuildSelect(table, where, selectFields)\n\n//cond = SELECT name,age,sex FROM some_table WHERE (((x1=? AND x2\u003e=?) OR (x3=? AND x4!=?)) AND score=? AND city IN (?,?) AND age\u003e? AND address IS NOT NULL) GROUP BY department ORDER BY bonus DESC\n//values = []interface{}{11, 45, \"234\", \"tx2\", 5, \"beijing\", \"shanghai\", 35}\n\nrows, err := db.Query(cond, values...)\n\n\n// support builder.Raw in where \u0026 update\nwhere := map[string]interface{}{\"gmt_create \u003c\": builder.Raw(\"gmt_modified\")}\ncond, values, err := builder.BuildSelect(table, where, selectFields)\n// SELECT * FROM x WHERE gmt_create \u003c gmt_modified\n\nupdate = map[string]interface{}{\n    \"code\": builder.Raw(\"VALUES(code)\"), // mysql 8.x  builder.Raw(\"new.code\")\n    \"name\": builder.Raw(\"VALUES(name)\"), // mysql 8.x  builder.Raw(\"new.name\")\n}\ncond, values, err := builder.BuildInsertOnDuplicate(table, data, update)\n// INSERT INTO country (id, code, name) VALUES (?,?,?),(?,?,?),(?,?,?) \n// ON DUPLICATE KEY UPDATE code=VALUES(code),name=VALUES(name)\n```\n\nIn the `where` param, `in` operator is automatically added by value type(reflect.Slice).\n\n```go\nwhere := map[string]interface{}{\n\t\"city\": []string{\"beijing\", \"shanghai\"},\n}\n```\nthe same as\n```go\nwhere := map[string]interface{}{\n\t\"city in\": []string{\"beijing\", \"shanghai\"},\n}\n```\n\nBesides, the library provide a useful API for executing aggregate queries like count, sum, max, min, avg\n\n```go\nwhere := map[string]interface{}{\n    \"score \u003e \": 100,\n    \"city\": []interface{}{\"Beijing\", \"Shijiazhuang\", }\n}\n// AggregateSum, AggregateMax, AggregateMin, AggregateCount, AggregateAvg are supported\nresult, err := AggregateQuery(ctx, db, \"tableName\", where, AggregateSum(\"age\"))\nsumAge := result.Int64()\nresult, err = AggregateQuery(ctx, db, \"tableName\", where, AggregateCount(\"*\")) \nnumberOfRecords := result.Int64()\nresult, err = AggregateQuery(ctx, db, \"tableName\", where, AggregateAvg(\"score\"))\naverageScore := result.Float64()\n```\n\nmulti `or` condition can use multi `_or` prefix string mark \n``` go\nwhere := map[string]interface{}{\n    // location\n    \"_or_location\": []map[string]interface{}{{\n        \"subway\": \"beijing_15\", \n    }, {\n        \"district\": \"Chaoyang\", \n    }},\n    // functions\n    \"_or_functions\": []map[string]interface{}{{\n         \"has_gas\": true,\n    }, {\n        \"has_lift\": true,\n}}}\n\n// query = (((subway=?) OR (district=?)) AND ((has_gas=?) OR (has_lift=?)))\n// args = [\"beijing_15\", \"Chaoyang\", true, true]\n```\n\nIf you want to clear the value '0' in the where map, you can use builder.OmitEmpty\n\n``` go\nwhere := map[string]interface{}{\n\t\t\"score\": 0,\n\t\t\"age \u003e\": 35,\n\t}\nfinalWhere := builder.OmitEmpty(where, []string{\"score\", \"age\"})\n// finalWhere = map[string]interface{}{\"age \u003e\": 35}\n\n// support: Bool, Array, String, Float32, Float64, Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr, Map, Slice, Interface, Struct\n```\n\nFor complex queries, `NamedQuery` may be helpful:\n```go\ncond, vals, err := builder.NamedQuery(\"select * from tb where name={{name}} and id in (select uid from anothertable where score in {{m_score}})\", map[string]interface{}{\n\t\"name\": \"caibirdme\",\n\t\"m_score\": []float64{3.0, 5.8, 7.9},\n})\n\nassert.Equal(\"select * from tb where name=? and id in (select uid from anothertable where score in (?,?,?))\", cond)\nassert.Equal([]interface{}{\"caibirdme\", 3.0, 5.8, 7.9}, vals)\n```\nslice type can be expanded automatically according to its length. Thus, these SQLs are very convenient for DBA to review.  \n**For critical system, this is recommended**\n\nFor more detail, see [builder's doc](builder/README.md) or just use `godoc`\n\n\u003ch3 id=\"scanner\"\u003eScanner\u003c/h3\u003e\nFor each response from MySQL, you want to map it with your well-defined structure.\nScanner provides a straightforward API to do this, it's based on reflection:\n\n##### standard library\n```go\ntype Person struct {\n\tName string\n\tAge int\n}\n\nrows, err := db.Query(\"SELECT age as m_age, name from g_xxx where xxx\")\ndefer rows.Close()\n\nvar students []Person\n\nfor rows.Next() {\n\tvar student Person\n\trows.Scan(student.Age, student.Name)\n\tstudents = append(students, student)\n}\n```\n##### using scanner\n```go\ntype Person struct {\n\tName string `ddb:\"name\"`\n\tAge int `ddb:\"m_age\"`\n}\n\nrows, err := db.Query(\"SELECT age as m_age, name from g_xxx where xxx\")\ndefer rows.Close()\n\nvar students []Person\n\nscanner.Scan(rows, \u0026students)\n```\nTypes that implement the interface\n```go\ntype ByteUnmarshaler interface {\n\tUnmarshalByte(data []byte) error\n}\n```\nwill take over the corresponding unmarshal work.\n\n```go\ntype human struct {\n\tAge   int       `ddb:\"ag\"`\n\tExtra *extraInfo `ddb:\"ext\"`\n}\n\ntype extraInfo struct {\n\tHobbies     []string `json:\"hobbies\"`\n\tLuckyNumber int      `json:\"ln\"`\n}\n\nfunc (ext *extraInfo) UnmarshalByte(data []byte) error {\n\treturn json.Unmarshal(data, ext)\n}\n\n//if the type of ext column in a table is varchar(stored legal json string) or json(mysql5.7)\nvar student human\nerr := scanner.Scan(rows, \u0026student)\n// ...\n```\n\nThe extra tag of the struct will be used by scanner resolve data from response.The default tag name is `ddb:\"tagname\"`, but you can specify your own such as:\n\n``` go\nscanner.SetTagName(\"json\")\ntype Person struct {\n\tName string `json:\"name\"`\n\tAge int `json:\"m_age\"`\n}\n\n// ...\nvar student Person\nscanner.Scan(rows, \u0026student)\n```\n\n**scanner.SetTagName is a global setting and it can be invoked only once**\n\n#### ScanMap\n```go\nrows, _ := db.Query(\"select name, age as m_age from person\")\nresult, err := scanner.ScanMap(rows)\nfor _, record := range result {\n\tfmt.Println(record[\"name\"], record[\"m_age\"])\n}\n```\nScanMap scans data from rows and returns a `[]map[string]interface{}`  \nint, float, string type may be stored as []uint8 by MySQL driver. ScanMap copies those values into the map. If you're sure that there's no binary data type in your MySQL table(in most cases, this is true), you can use ScanMapDecode instead which will convert []uint8 to int, float64 or string\n\nFor more detail, see [scanner's doc](scanner/README.md)\n\nPS：\n\n* Don't forget close rows if you don't use ScanXXXClose\n* The second parameter of Scan must be a reference\n\n\u003ch3 id=\"tools\"\u003eTools\u003c/h3\u003e\nBesides APIs above, Gendry provides a [CLI tool](https://github.com/caibirdme/gforge) to help generating codes.\n\n\n\n\n\n","funding_links":[],"categories":["开源类库","Database","Go","数据库","Open source library","Uncategorized","Generators","Data Integration Frameworks","数据库  `go语言实现的数据库`"],"sub_categories":["数据库","SQL Query Builders","Advanced Console UIs","SQL查询生成器","Database","SQL 查询语句构建库"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdidi%2Fgendry","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdidi%2Fgendry","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdidi%2Fgendry/lists"}