{"id":13412129,"url":"https://github.com/nullism/bqb","last_synced_at":"2025-03-14T18:30:21.561Z","repository":{"id":42389575,"uuid":"391424717","full_name":"nullism/bqb","owner":"nullism","description":"BQB is a lightweight and easy to use query builder that works with sqlite, mysql, mariadb, postgres, and others. ","archived":false,"fork":false,"pushed_at":"2024-04-29T21:21:21.000Z","size":155,"stargazers_count":136,"open_issues_count":0,"forks_count":10,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-07-31T20:49:53.021Z","etag":null,"topics":["go","golang","mysql","postgresql","query-builder","sql","sqlite"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nullism.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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":"2021-07-31T17:41:45.000Z","updated_at":"2024-07-05T14:27:07.000Z","dependencies_parsed_at":"2023-10-13T14:04:50.192Z","dependency_job_id":"9f3b732a-4c3f-4d55-974e-05019f8f916d","html_url":"https://github.com/nullism/bqb","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nullism%2Fbqb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nullism%2Fbqb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nullism%2Fbqb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nullism%2Fbqb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nullism","download_url":"https://codeload.github.com/nullism/bqb/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243624988,"owners_count":20321208,"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","golang","mysql","postgresql","query-builder","sql","sqlite"],"created_at":"2024-07-30T20:01:21.328Z","updated_at":"2025-03-14T18:30:21.555Z","avatar_url":"https://github.com/nullism.png","language":"Go","readme":"# Basic Query Builder\n\n[![Tests Status](https://github.com/nullism/bqb/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/nullism/bqb/actions/workflows/tests.yml) [![GoDoc](https://godoc.org/github.com/nullism/bqb?status.svg)](https://godoc.org/github.com/nullism/bqb) [![code coverage](coverage.svg)](https://github.com/nullism/bqb/actions/workflows/tests.yml) [![Go Report Card](https://img.shields.io/badge/go%20report-A+-brightgreen.svg?style=flat)](https://goreportcard.com/report/github.com/nullism/bqb) [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go)\n\n# Compatibility\n\nThis has been tested with sqlite, PostGres, and MySQL, using `database/sql`, `pq`, `pgx`, and `sqlx`.\nBy the nature of how it works it should be fully compatible with any DB interface and database that uses `?` or `$` parameter syntax.\n\n_Note: Go `v1.20+` is required for BQB `\u003e= v1.4.0`. Go `v1.17+` is required for BQB `\u003c= v1.3.0`._\n\n# Why\n\n1. Simple, lightweight, and fast\n2. Supports any and all syntax by the nature of how it works\n3. Doesn't require learning special syntax or operators\n4. 100% test coverage\n\n# Examples\n\n## Basic\n\n```golang\nq := bqb.New(\"SELECT * FROM places WHERE id = ?\", 1234)\nsql, params, err := q.ToSql()\n```\n\nProduces\n\n```sql\nSELECT * FROM places WHERE id = ?\n```\n\n```\nPARAMS: [1234]\n```\n\n### Escaping `?`\n\nUse the double question mark `??` value to escape the `?` in Postgres queries.\nFor example:\n\n```golang\nq := bqb.New(\"SELECT * FROM places WHERE json_obj_column ?? 'key'\")\nsql, params, err := q.ToPgsql()\n```\n\nThis query uses the `?` operator for jsonb types in Postgres to test an object\nfor the presence of a key. It should not be interpreted as an escaped value by\nbqb.\n\n```sql\nSELECT * FROM places WHERE json_obj_column ? 'key'\n```\n\n```\nPARAMS: []\n```\n\n## Postgres - ToPgsql()\n\nJust call the `ToPgsql()` method instead of `ToSql()` to convert the query to Postgres syntax\n\n```golang\nq := bqb.New(\"DELETE FROM users\").\n    Space(\"WHERE id = ? OR name IN (?)\", 7, []string{\"delete\", \"remove\"}).\n    Space(\"LIMIT ?\", 5)\nsql, params, err := q.ToPgsql()\n```\n\nProduces\n\n```sql\nDELETE FROM users WHERE id = $1 OR name IN ($2, $3) LIMIT $4\n```\n\n```\nPARAMS: [7, \"delete\", \"remove\", 5]\n```\n\n## Raw - ToRaw()\n\n_Obvious warning: You should not use this for user input_\n\nThe `ToRaw()` call returns a string with the values filled in rather than parameterized\n\n```golang\nq := New(\"a = ?, b = ?, c = ?\", \"my a\", 1234, nil)\nsql, err := q.ToRaw()\n```\n\nProduces\n\n```\na = 'my a', b = 1234, c = NULL\n```\n\n## Types\n\n```golang\nq := bqb.New(\n    \"int:? string:? []int:? []string:? Query:? JsonMap:? nil:? []intf:?\",\n    1, \"2\", []int{3, 3}, []string{\"4\", \"4\"}, bqb.New(\"5\"), bqb.JsonMap{\"6\": 6}, nil, []interface{}{\"a\",1,true},\n)\nsql, _ := q.ToRaw()\n```\n\nProduces\n\n```\nint:1 string:'2' []int:3,3 []string:'4','4' Query:5 JsonMap:'{\"6\":6}' nil:NULL []intf:'a',1,true\n```\n\n### driver.Valuer\n\nThe [driver.Valuer](https://pkg.go.dev/database/sql/driver#Valuer) interface is supported for types that are able to convert\nthemselves to a sql driver value. See [examples/main.go:valuer](./examples/main.go#L102).\n\n```\nq := bqb.New(\"?\", valuer)\n```\n\n### Embedder\n\nBQB provides an Embedder interface for directly replacing `?` with a string returned by the `RawValue` method on the Embedder implementation.\n\nThis can be useful for changing sort direction or embedding table and column names. See [examples/main.go:embedder](./examples/main.go#L122) for an example.\n\n_Note: Since this is a raw value, special attention should be paid to ensure user-input is checked and sanitized._\n\n### Folded\n\nThe `Folded` type and corresponding `ToFolded` generic function will prevent spreading of `[]string`, `[]int`, or `[]any`. For example, `bqb.New(\"?\", []string{\"a\",\"b\"})` will become `(\"?,?\", \"a\", \"b\")` by default.\n\n```go\nstrns := []string{\"a\", \"b\"}\nq := bqb.New(\"Folded: ? - Default: ?\", bqb.ToFolded(strns), strns)\n// sql = Folded: ? - Default: ?,?\n// params = { []string{\"a\", \"b\"}, \"a\", \"b\" }\n```\n\n## Query IN\n\nArguments of type `[]string`,`[]*string`, `[]int`,`[]*int`, and `[]any` / `[]interface{}` are automatically expanded.\n\n```golang\n    q := bqb.New(\n        \"strs:(?) *strs:(?) ints:(?) *ints:(?) intfs:(?)\",\n        []string{\"a\", \"b\"}, []*string{}, []int{1, 2}, []*int{}, []any{3, true},\n    )\n    sql, params, _ := q.ToSql()\n```\n\nProduces\n\n```\nSQL: strs:(?,?) *strs:(?) ints:(?,?) *ints:(?) intfs:(?,?)\nPARAMS: [a b \u003cnil\u003e 1 2 \u003cnil\u003e 3 true]\n```\n\n## Json Arguments\n\nThere are two helper structs, `JsonMap` and `JsonList` to make JSON conversion a little simpler.\n\n```golang\n\nsql, err := bqb.New(\n    \"INSERT INTO my_table (json_map, json_list) VALUES (?, ?)\",\n    bqb.JsonMap{\"a\": 1, \"b\": []string{\"a\",\"b\",\"c\"}},\n    bqb.JsonList{\"string\",1,true,nil},\n).ToRaw()\n```\n\nProduces\n\n```sql\nINSERT INTO my_table (json_map, json_list)\nVALUES ('{\"a\": 1, \"b\": [\"a\",\"b\",\"c\"]}', '[\"string\",1,true,null]')\n```\n\n## Query Building\n\nSince queries are built in an additive way by reference rather than value, it's easy to mutate a query without\nhaving to reassign the result.\n\n### Basic Example\n\n```golang\nsel := bqb.New(\"SELECT\")\n\n...\n\n// later\nsel.Space(\"id\")\n\n...\n\n// even later\nsel.Comma(\"age\").Comma(\"email\")\n```\n\nProduces\n\n```sql\nSELECT id,age,email\n```\n\n### Advanced Example\n\nThe `Optional(string)` function returns a query that resolves to an empty string if no query parts have\nbeen added via methods on the query instance, and joins with a space to the next query part.\nFor example `q := Optional(\"SELECT\")` will resolve to an empty string unless parts have been added by one of the methods,\ne.g `q.Space(\"* FROM my_table\")` would make `q.ToSql()` resolve to `SELECT * FROM my_table`.\n\n```golang\n\nsel := bqb.Optional(\"SELECT\")\n\nif getName {\n    sel.Comma(\"name\")\n}\n\nif getId {\n    sel.Comma(\"id\")\n}\n\nif !getName \u0026\u0026 !getId {\n    sel.Comma(\"*\")\n}\n\nfrom := bqb.New(\"FROM my_table\")\n\nwhere := bqb.Optional(\"WHERE\")\n\nif filterAdult {\n    adultCond := bqb.New(\"name = ?\", \"adult\")\n    if ageCheck {\n        adultCond.And(\"age \u003e ?\", 20)\n    }\n    where.And(\"(?)\", adultCond)\n}\n\nif filterChild {\n    where.Or(\"(name = ? AND age \u003c ?)\", \"youth\", 21)\n}\n\nq := bqb.New(\"? ? ?\", sel, from, where).Space(\"LIMIT ?\", 10)\n```\n\nAssuming all values are true, the query would look like:\n\n```sql\nSELECT name,id FROM my_table WHERE (name = 'adult' AND age \u003e 20) OR (name = 'youth' AND age \u003c 21) LIMIT 10\n```\n\nIf `getName` and `getId` are false, the query would be\n\n```sql\nSELECT * FROM my_table WHERE (name = 'adult' AND age \u003e 20) OR (name = 'youth' AND age \u003c 21) LIMIT 10\n```\n\nIf `filterAdult` is `false`, the query would be:\n\n```sql\nSELECT name,id FROM my_table WHERE (name = 'youth' AND age \u003c 21) LIMIT 10\n```\n\nIf all values are `false`, the query would be:\n\n```sql\nSELECT * FROM my_table LIMIT 10\n```\n\n## Methods\n\nMethods on the bqb `Query` struct follow the same pattern.\n\nAll query-modifying methods take a string (the query text) and variable length interface (the query args).\n\nFor example `q.And(\"abc\")` will add `AND abc` to the query.\n\nTake the following\n\n```golang\nq := bqb.Optional(\"WHERE\")\nq.Empty() // returns true\nq.Len() // returns 0\nq.Space(\"1 = 2\") // query is now WHERE 1 = 2\nq.Empty() // returns false\nq.Len() // returns 1\nq.And(\"b\") // query is now WHERE 1 = 2 AND b\nq.Or(\"c\") // query is now WHERE 1 = 2 AND b OR c\nq.Concat(\"d\") // query is now WHERE 1 = 2 AND b OR cd\nq.Comma(\"e\") // query is now WHERE 1 = 2 AND b OR cd,e\nq.Join(\"+\", \"f\") // query is now WHERE 1 = 2 AND b OR cd,e+f\n```\n\nValid `args` include `string`, `int`, `floatN`, `*Query`, `[]int`, `Embedder`, `Embedded`, `driver.Valuer` or `[]string`.\n\n# Frequently Asked Questions\n\n## Is there more documentation?\n\nIt's not really necessary because the API is so tiny and public methods are documented in code,\nsee [godoc](https://godoc.org/github.com/nullism/bqb).\nHowever, you can check out the [tests](./query_test.go) and [examples](./examples/main.go) to see the variety of usages.\n\n## Why not just use a string builder?\n\nBqb provides several benefits over a string builder:\n\nFor example let's say we use the string builder way to build the following:\n\n```golang\nvar params []interface{}\nvar whereParts []string\nq := \"SELECT * FROM my_table \"\nif filterAge {\n    params = append(params, 21)\n    whereParts = append(whereParts, fmt.Sprintf(\"age \u003e $%d \", len(params)))\n}\n\nif filterBobs {\n    params = append(params, \"Bob%\")\n    whereParts = append(whereParts, fmt.Sprintf(\"name LIKE $%d \", len(params)))\n}\n\nif len(whereParts) \u003e 0 {\n    q += \"WHERE \" + strings.Join(whereParts, \" AND \") + \" \"\n}\n\nif limit != nil {\n    params = append(params, limit)\n    q += fmt.Sprintf(\"LIMIT $%d\", len(params))\n}\n\n// SELECT * FROM my_table WHERE age \u003e $1 AND name LIKE $2 LIMIT $3\n```\n\nSome problems with that approach\n\n1. You must perform a string join for the various parts of the where clause\n2. You must remember to include a trailing or leading space for each clause\n3. You have to keep track of parameter count (for Postgres anyway)\n4. It's kind of ugly\n\nThe same logic can be achieved with `bqb` a bit more cleanly\n\n```golang\nq := bqb.New(\"SELECT * FROM my_table\")\nwhere := bqb.Optional(\"WHERE\")\nif filterAge {\n    where.And(\"age \u003e ?\", 21)\n}\n\nif filterBobs {\n    where.And(\"name LIKE ?\", \"Bob%\")\n}\n\nq.Space(\"?\", where)\n\nif limit != nil {\n    q.Space(\"LIMIT ?\", limit)\n}\n\n// SELECT * FROM my_table WHERE age \u003e $1 AND name LIKE $2 LIMIT $3\n```\n\nBoth methods will allow you to remain close to the SQL, however the `bqb` approach will\n\n1. Easily adapt to MySQL or Postgres without changing parameters\n2. Hide the \"WHERE\" clause if both `filterBobs` and `filterAge` are false\n\n## Why not use a full query builder?\n\nTake the following _typical_ query example:\n\n```golang\nq := qb.Select(\"*\").From(\"users\").Where(qb.And{qb.Eq{\"name\": \"Ed\"}, qb.Gt{\"age\": 21}})\n```\n\nVs the bqb way:\n\n```golang\nq := bqb.New(\"SELECT * FROM users WHERE name = ? AND age \u003e ?\", \"ed\", 21)\n```\n\n## Okay, so a simple query it might make sense to use something like `bqb`, but what about grouped queries?\n\nA query builder can handle this in multiple ways, a fairly common pattern might be:\n\n```golang\nq := qb.Select(\"name\").From(\"users\")\n\nand := qb.And{}\n\nif checkAge {\n    and = append(and, qb.Gt{\"age\": 21})\n}\n\nif checkName {\n    or := qb.Or{qb.Eq{\"name\":\"trusted\"}}\n    if nullNameOkay {\n        or = append(or, qb.Is{\"name\": nil})\n    }\n    and = append(and, or)\n}\n\nq = q.Where(and)\n\n// SELECT name FROM users WHERE age \u003e 21 AND (name = 'trusted' OR name IS NULL)\n```\n\nContrast that with the `bqb` approach:\n\n```golang\n\nq := bqb.New(\"SELECT name FROM users\")\n\nwhere := bqb.Optional(\"WHERE\")\n\nif checkAge {\n    where.And(\"age \u003e ?\", 21)\n}\n\nif checkName {\n    or := bqb.New(\"name = ?\", \"trusted\")\n    if nullNameOkay {\n        or.Or(\"name IS ?\", nil)\n    }\n    where.And(\"(?)\", or)\n}\n\nq.Space(\"?\", where)\n\n// SELECT name FROM users WHERE age \u003e 21 AND (name = 'trusted' OR name IS NULL)\n```\n\nIt seems to be a matter of taste as to which method appears cleaner.\n","funding_links":[],"categories":["Database","Uncategorized","Data Integration Frameworks","Generators","数据库"],"sub_categories":["SQL Query Builders","SQL查询生成器"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnullism%2Fbqb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnullism%2Fbqb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnullism%2Fbqb/lists"}