{"id":17327044,"url":"https://github.com/jaypipes/sqlb","last_synced_at":"2025-03-22T19:33:12.132Z","repository":{"id":48601533,"uuid":"100320598","full_name":"jaypipes/sqlb","owner":"jaypipes","description":"A library for efficiently generating SQL expressions","archived":false,"fork":false,"pushed_at":"2024-09-29T13:40:14.000Z","size":774,"stargazers_count":4,"open_issues_count":9,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-10-16T14:18:39.632Z","etag":null,"topics":["golang","sql","sql-buffer","sql-builder","sql-construction"],"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/jaypipes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","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-08-15T00:20:08.000Z","updated_at":"2024-09-28T20:11:47.000Z","dependencies_parsed_at":"2024-09-17T17:12:12.003Z","dependency_job_id":"a82d51b4-aefc-428b-aa0d-d350de1c1469","html_url":"https://github.com/jaypipes/sqlb","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaypipes%2Fsqlb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaypipes%2Fsqlb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaypipes%2Fsqlb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaypipes%2Fsqlb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jaypipes","download_url":"https://codeload.github.com/jaypipes/sqlb/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221832482,"owners_count":16888258,"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":["golang","sql","sql-buffer","sql-builder","sql-construction"],"created_at":"2024-10-15T14:18:26.755Z","updated_at":"2024-10-28T13:35:03.590Z","avatar_url":"https://github.com/jaypipes.png","language":"Go","readme":"# sqlb\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/jaypipes/sqlb.svg)](https://pkg.go.dev/github.com/jaypipes/sqlb)\n[![Go Report Card](https://goreportcard.com/badge/github.com/jaypipes/sqlb)](https://goreportcard.com/report/github.com/jaypipes/sqlb)\n[![Build Status](https://github.com/jaypipes/sqlb/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/jaypipes/sqlb/actions)\n[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md)\n\n`sqlb` is a Go library designed for efficiently constructing SQL expressions in\na concise, readable fashion.\n\nInstead of hand-constructing strings containing raw SQL, users of the `sqlb`\nlibrary instead construct query expressions and the `sqlb` library does the\nwork of producing the raw strings that get sent to a SQL database.\n\n## Building SQL expressions, not strings\n\nIt's best to learn by example, so let's walk through a common way in which Go\napplications might typically work with an underlying SQL database and transform\nthis application to instead work with the `sqlb` library, showing the resulting\ngains in both code expressiveness, application speed and memory efficiency.\n\nOur example will be a simple blogging application.\n\nImagine we have the following set of tables in our database:\n\n```sql\nCREATE TABLE users (\n  id INT NOT NULL,\n  email VARCHAR(100) NOT NULL,\n  name VARCHAR(100) NOT NULL,\n  is_author CHAR(1) NOT NULL,\n  profile TEXT NULL,\n  created_on DATETIME NOT NULL,\n  updated_on DATETIME NOT NULL,\n  PRIMARY KEY (id),\n  UNIQUE INDEX (email)\n);\n\nCREATE TABLE articles (\n  id INT NOT NULL,\n  title VARCHAR(200) NOT NULL,\n  content TEXT NOT NULL,\n  created_by INT NOT NULL,\n  published_on DATETIME NULL,\n  PRIMARY KEY (id),\n  INDEX ix_title (title),\n  FOREIGN KEY fk_users (created_by) REFERENCES users (id)\n);\n```\n\nOur blogging application's default home page might return information about the\nlast ten articles published. It's reasonable to believe that the following SQL\nexpression might be used to grab this information from the database:\n\n```sql\nSELECT\n  articles.title,\n  articles.content,\n  articles.created_on\n  users.name,\nFROM articles\nJOIN users\n ON articles.created_by = users.id\nORDER BY articles.created_on DESC\nLIMIT 10\n```\n\nOur Go code for the server side of our application might look something like\nthis:\n\n```go\npackage main\n\nimport (\n    \"database/sql\"\n    \"fmt\"\n    \"log\"\n    \"net/http\"\n)\n\nconst (\n    DSN = \"root:password@/blogdb\"\n)\n\nvar db *sql.DB\n\ntype Article struct {\n    Title string\n    AuthorName string\n    PublishedOn string\n    Content string\n}\n\nfunc getArticles() []*Article {\n    qs := `\nSELECT\n  articles.title,\n  articles.content,\n  articles.created_on\n  users.name,\nFROM articles\nJOIN users\n ON articles.created_by = users.id\nORDER BY articles.created_on DESC\nLIMIT 10\n`\n    res := make([]*Article, 0)\n    rows, err := db.Query(qs)\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer rows.Close()\n    for rows.Next() {\n        a := \u0026Article{}\n        err := rows.Scan(\n            \u0026a.Title,\n            \u0026a.Content,\n            \u0026a.PublishedOn,\n            \u0026a.AuthorName,\n        )\n        if err != nil {\n            log.Fatal(err)\n        }\n        res = append(res, a)\n    }\n    if err := rows.Err(); err != nil {\n        log.Fatal(err)\n    }\n    return res\n}\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n    articleTemplate := `%s\n-----------------------------------------------------\nby %s on %s\n\n%s\n`\n    articles := getArticles()\n    for _, article := range articles {\n        fmt.Fprintf(w, articleTemplate, article.Title, article.AuthorName,\n                    article.PublishedOn, article.Content)\n    }\n}\n\nfunc main() {\n    if db, err := sql.Open(\"mysql\", DSN); err != nil {\n        log.Fatal(err)\n    }\n    http.HandleFunc(\"/\", handler)\n    http.ListenAndServe(\":8080\", nil)\n}\n```\n\n**Note**: Clearly, I'm not doing proper error handling and I'm hard-coding\nthings like the DSN that should be pulled from a configuration system in this\nexample code.\n\nThe above code works, but it's fragile in the face of inevitable change to the\napplication. What if we want to make the number of articles returned\nconfigurable? What if we want to allow users to list only articles by a\nparticular author? In both of these cases, we will need to modify the\n`getArticles()` function to modify the SQL query string that it constructs:\n\n```go\nfunc getArticles(numArticles int, byAuthor string) []*Articles {\n    // Our collection of query arguments\n    qargs := make([]interface{}, 0)\n    qs := `\nSELECT\n  articles.title,\n  articles.content,\n  articles.created_on\n  users.name,\nFROM articles\nJOIN users\n ON articles.created_by = users.id\n`\n    if byAuthor != \"\" {\n        qs += \"WHERE users.name = ? \"\n        qargs = append(qargs, byAuthor)\n    }\n    qs += `ORDER BY articles.created_on DESC\nLIMIT ?`\n    qargs = append(qargs, numArticles)\n    res := make([]*Article, 0)\n    rows, err := db.Query(qs, qargs...)\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer rows.Close()\n    for rows.Next() {\n        a := \u0026Article{}\n        err := rows.Scan(\n            \u0026a.Title,\n            \u0026a.Content,\n            \u0026a.PublishedOn,\n            \u0026a.AuthorName,\n        )\n        if err != nil {\n            log.Fatal(err)\n        }\n        res = append(res, a)\n    }\n    if err := rows.Err(); err != nil {\n        log.Fatal(err)\n    }\n    return res\n}\n```\n\nAs you can see above, the minor enhancements to our application of allowing a\nconfigurable number of articles and filtering by author have already begun to\nmake the `getArticles()` function unwieldy. The string being generated for our\nSQL `SELECT` statement is difficult to read and hides the *intent* of the query\nexpression in the ugliness of string concatenation.\n\nAdding more filtering capability brings more conditionals and more string\nconcatenation, leading to ever-increasing complexity and reduced code\nreadability.\n\n`sqlb` is designed to solve this problem.\n\n## Rewriting our application to use `sqlb`\n\nLet's rewrite our example application above to use the `sqlb` library instead\nof manually constructing SQL strings.\n\nWe start by initializing `sqlb`'s reflection system in our application's\n`main()` entrypoint:\n\n```go\nimport (\n    \"database/sql\"\n\n    \"github.com/jaypipes/sqlb\"\n)\n\n\nvar (\n    meta *sqlb.Meta\n    articles *sqlb.Table\n    users *sqlb.Table\n)\n\nfunc main() {\n    if db, err := sql.Open(\"mysql\", DSN); err != nil {\n        log.Fatal(err)\n    }\n    if meta, err := sqlb.Reflect(db); err != nil {\n        log.Fatal(err)\n    }\n    articles = meta.Table(\"articles\")\n    users = meta.Table(\"users\")\n}\n```\n\nThe `sqlb.Meta` struct is now populated with information about the database,\nincluding metadata about tables, columns, indexes, and relations. You use\n`sqlb.Meta` when constructing `sqlb` Query Expressions.\n\nWe've set two package-scoped variables called `articles` and `users` that refer\nto the \"articles\" and \"users\" database tables, respectively. We will refer to\nthese variables in our `getArticles()` function.\n\nLet's transform our original `getArticles()` function -- before we added\nsupport for a configurable number of articles and filtering by author -- to use\n`sqlb`:\n\n```go\n\nfunc getArticles() []*Article {\n    q := sqlb.Select(articles.C(\"title\"), articles.C(\"content\"),\n                     articles.C(\"created_by\"), users.C(\"name\"))\n    q.Join(users, sqlb.Equal(articles.C(\"author\"), users.C(\"id\")))\n    q.OrderBy(articles.C(\"created_by\").Desc())\n    q.Limit(10)\n\n    res := make([]*Article, 0)\n    rows, err := sqlb.Query(db, q)\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer rows.Close()\n    for rows.Next() {\n        a := \u0026Article{}\n        err := rows.Scan(\n            \u0026a.Title,\n            \u0026a.Content,\n            \u0026a.PublishedOn,\n            \u0026a.AuthorName,\n        )\n        if err != nil {\n            log.Fatal(err)\n        }\n        res = append(res, a)\n    }\n    if err := rows.Err(); err != nil {\n        log.Fatal(err)\n    }\n    return res\n}\n```\n\nThe above code ends up producing an identical SQL string as the original code\nwithout any of the string concatenation.\n\nLet's add in functionality to have a configurable number of returned articles\nand optionally filter for a specific author's articles.\n\n```go\nfunc getArticles(numArticles int, byAuthor string) []*Articles {\n    q := sqlb.Select(articles.C(\"title\"), articles.C(\"content\"),\n                     articles.C(\"created_by\"), users.C(\"name\"))\n    q.Join(users, sqlb.Equal(articles.C(\"author\"), users.C(\"id\")))\n    if byAuthor != \"\" {\n        q.Where(sqlb.Equal(users.C(\"name\"), byAuthor))\n    }\n    q.OrderBy(articles.C(\"created_by\").Desc())\n    q.Limit(numArticles)\n\n    res := make([]*Article, 0)\n    rows, err := sqlb.Query(db, q)\n    if err != nil {\n        log.Fatal(err)\n    }\n    for rows.Next() {\n        a := \u0026Article{}\n        err := rows.Scan(\n            \u0026a.Title,\n            \u0026a.Content,\n            \u0026a.PublishedOn,\n            \u0026a.AuthorName,\n        )\n        if err != nil {\n            log.Fatal(err)\n        }\n        res = append(res, a)\n    }\n    if err := rows.Err(); err != nil {\n        log.Fatal(err)\n    }\n    return res\n}\n```\n\nNo more manually constructing and reconstructing strings or tracking query\narguments. `sqlb` handles the SQL string construction for you as well as the\nslice of query arguments, allowing you to write custom query code in a more\nnatural and efficient manner.\n\n## License\n\n`sqlb` is licensed under the Apache license version 2. See the\n[COPYING](COPYING) file for more information.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaypipes%2Fsqlb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjaypipes%2Fsqlb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaypipes%2Fsqlb/lists"}