{"id":37121972,"url":"https://github.com/rrgmc/litsql","last_synced_at":"2026-01-14T14:02:54.119Z","repository":{"id":242556286,"uuid":"808628072","full_name":"rrgmc/litsql","owner":"rrgmc","description":"Golang literal SQL query builder as a replacement for raw SQL strings","archived":false,"fork":false,"pushed_at":"2024-06-22T13:20:28.000Z","size":347,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-07-09T08:41:37.327Z","etag":null,"topics":["golang-library","sql-builder","sql-query"],"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/rrgmc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"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":"2024-05-31T13:17:16.000Z","updated_at":"2024-06-28T02:36:05.000Z","dependencies_parsed_at":"2024-06-19T03:05:19.287Z","dependency_job_id":"a5e06fa3-5d3e-4f76-ac78-97602d4ba975","html_url":"https://github.com/rrgmc/litsql","commit_stats":null,"previous_names":["rrgmc/litsql"],"tags_count":86,"template":false,"template_full_name":null,"purl":"pkg:github/rrgmc/litsql","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rrgmc%2Flitsql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rrgmc%2Flitsql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rrgmc%2Flitsql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rrgmc%2Flitsql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rrgmc","download_url":"https://codeload.github.com/rrgmc/litsql/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rrgmc%2Flitsql/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28422403,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T13:30:50.153Z","status":"ssl_error","status_checked_at":"2026-01-14T13:29:08.907Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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-library","sql-builder","sql-query"],"created_at":"2026-01-14T14:02:53.400Z","updated_at":"2026-01-14T14:02:54.097Z","avatar_url":"https://github.com/rrgmc.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# litsql - Literal SQL query builder\n\n[![Test Status](https://github.com/rrgmc/litsql/actions/workflows/go.yml/badge.svg)](https://github.com/rrgmc/litsql/actions/workflows/go.yml) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/rrgmc/litsql) [![Go Reference](https://pkg.go.dev/badge/github.com/rrgmc/litsql.svg)](https://pkg.go.dev/github.com/rrgmc/litsql) [![Go Report Card](https://goreportcard.com/badge/github.com/rrgmc/litsql)](https://goreportcard.com/report/github.com/rrgmc/litsql) ![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/rrgmc/litsql) [![Coverage Status](https://coveralls.io/repos/github/rrgmc/litsql/badge.svg)](https://coveralls.io/github/rrgmc/litsql)\n\n`litsql` is a Golang string concatenation library **disguised as an SQL query builder**.\n\nOk, it really is an SQL query builder, but it aims to be an **easier-to-use replacement for raw SQL strings**.\n\nEach `litsql` statement **must** be directly related to an SQL output, **including whitespace** (backed by whitespace tests), \nwhich must be obvious to the user of the library. The output will be exactly the passed values.\n\n```go\nfunc ExampleSelect_literalSimple() {\n    // SELECT\n    q := psql.Select(\n        // u.id, u.name\n        sm.Columns(\"u.id\", \"u.name\"),\n        // , u.created_at, u.updated_at\n        sm.Columns(\"u.created_at\", \"u.updated_at\"),\n        // FROM users AS u\n        sm.From(\"users AS u\"),\n        // WHERE u.age \u003e $1\n        sm.WhereClause(\"u.age \u003e ?\", 40),\n        // WHERE u.city_id = $2\n        sm.WhereClause(\"u.city_id = ?\", sq.NamedArg(\"city_id\")),\n        // AND u.deleted_at IS NOT NULL\n        sm.Where(\"u.deleted_at IS NOT NULL\"),\n        // ORDER BY u.name ASC, u.age DESC\n        sm.OrderBy(\"u.name ASC\", \"u.age DESC\"),\n    )\n    qs, args, err := q.Build(\n        sq.WithParseArgs(sq.MapArgValues{\n            \"city_id\": 66,\n        }),\n    )\n    if err != nil {\n        panic(err)\n    }\n    fmt.Println(qs)\n    fmt.Println(\"===\")\n    fmt.Println(args)\n\n    // Output:\n    // SELECT u.id, u.name, u.created_at, u.updated_at\n    // FROM users AS u\n    // WHERE u.age \u003e $1 AND u.city_id = $2 AND u.deleted_at IS NOT NULL\n    // ORDER BY u.name ASC, u.age DESC\n    // ===\n    // [40 66]\n}\n```\n\nThis library will do:\n\n * ensure clause ordering\n * enforce some kind of code structure\n * be type-safe without using `any` too much\n * guarantee whitespace. **extra whitespace is considered a bug**\n * output correct argument characters for each database dialect\n\nThis library won't do:\n\n * prevent invalid SQL from being output\n * quoting\n * execute queries in databases\n * provide helper expressions to build things like \"IsEQ()\", \"Not(expression)\", \"LT(value)\". These are expected to be written as strings\n * be an ORM (**never**)\n\nThe different SQL dialects uses code generation to add/remove things that are dialect specific.\n\n## Installation\n\n```shell\ngo get -u github.com/rrgmc/litsql\n```\n\n## Reference\n\nThis library is heavily inspired by the excellent [Bob Go SQL Access Toolkit](https://bob.stephenafamo.com/). Its base\nideas and some of its implementations where used to build this library.\n\nThe biggest difference is that `Bob` is not only a query builder, but an ORM, so the query builder part must be\nmuch more complex to be able to tackle multiple jobs. It encourages using Go to code SQL expressions, which this \nlibrary heavily discourages.\n\n## Dialects\n\n* [PostgreSQL](https://pkg.go.dev/github.com/rrgmc/litsql/dialect/psql)\n* [MySQL](https://pkg.go.dev/github.com/rrgmc/litsql/dialect/mysql)\n* [SQLite](https://pkg.go.dev/github.com/rrgmc/litsql/dialect/sqlite)\n\n## Related\n\n* [libsql-db](https://github.com/rrgmc/litsql-db): wrappers for running `litsql` queries directly in databases.\n\n## Examples\n\n```go\nfunc ExampleSelect_literalJoin() {\n    // SELECT\n    q := psql.Select(\n        // orders.id as order_id, orders.date\n        sm.Columns(\"orders.id AS order_id\", \"orders.date\"),\n        // u.id AS user_id, u.name as user_name\n        sm.Columns(\"u.id AS user_id\", \"u.name AS user_name\"),\n        // FROM orders\n        sm.From(\"orders\"),\n        // INNER JOIN users AS u ON orders.user_id = u.id\n        sm.InnerJoin(\"users AS u\").On(\"orders.user_id = u.id\"),\n        // WHERE u.age \u003e $1\n        sm.WhereClause(\"u.age ?\",\n            // example to use either IS NULL or a comparison\n            expr.IfElse(true, // some condition\n                expr.Clause(\"\u003e ?\", 32),\n                expr.String(\"IS NULL\"))),\n        // AND u.deleted_at IS NOT NULL\n        sm.Where(\"u.deleted_at IS NOT NULL\"),\n        // ORDER BY order.date DESC, u.name ASC\n        sm.OrderBy(\"orders.date DESC\", \"u.name ASC\"),\n    )\n    qs, args, err := q.Build()\n    if err != nil {\n        panic(err)\n    }\n    fmt.Println(qs)\n    fmt.Println(\"===\")\n    fmt.Println(args)\n\n    // Output:\n    // SELECT orders.id AS order_id, orders.date, u.id AS user_id, u.name AS user_name\n    // FROM orders\n    // INNER JOIN users AS u ON orders.user_id = u.id\n    // WHERE u.age \u003e $1 AND u.deleted_at IS NOT NULL\n    // ORDER BY orders.date DESC, u.name ASC\n    // ===\n    // [32]\n}\n```\n\n\n```go\nfunc ExampleSelect_literalWith() {\n    q := psql.Select(\n        // WITH regional_sales AS (\n        sm.With(\"regional_sales\").As(\n            // SELECT\n            psql.Select(\n                // region, SUM(amount) AS total_sales\n                sm.Columns(\"region\", \"SUM(amount) AS total_sales\"),\n                // FROM orders\n                sm.From(\"orders\"),\n                // GROUP BY region\n                sm.GroupBy(\"region\"),\n            ),\n        ),\n        // ), top_regions AS (\n        sm.With(\"top_regions\").As(\n            // SELECT\n            psql.Select(\n                // region\n                sm.Columns(\"region\"),\n                // FROM regional_sales\n                sm.From(\"regional_sales\"),\n                // WHERE total_sales \u003e (SELECT SUM(total_sales)/10 FROM regional_sales)\n                sm.WhereClause(\"total_sales \u003e ?\",\n                    psql.Select(\n                        sm.Columns(\"SUM(total_sales)/10\"),\n                        sm.From(\"regional_sales\"),\n                    ),\n                ),\n            ),\n        ),\n        // )\n        // SELECT\n        // region, product, SUM(quantity) AS product_units, SUM(amount) AS product_sales\n        sm.Columns(\"region\", \"product\", \"SUM(quantity) AS product_units\", \"SUM(amount) AS product_sales\"),\n        // FROM orders\n        sm.From(\"orders\"),\n        // WHERE region IN (SELECT region FROM top_regions)\n        sm.WhereClause(\"region IN ?\",\n            psql.Select(\n                sm.Columns(\"region\"),\n                sm.From(\"top_regions\"),\n            ),\n        ),\n        // GROUP BY region, product\n        sm.GroupBy(\"region\", \"product\"),\n    )\n    qs, _, err := q.Build()\n    if err != nil {\n        panic(err)\n    }\n    fmt.Println(qs)\n\n    // Output:\n    // WITH regional_sales AS (\n    //   SELECT region, SUM(amount) AS total_sales\n    //   FROM orders\n    //   GROUP BY region\n    // ),\n    // top_regions AS (\n    //   SELECT region\n    //   FROM regional_sales\n    //   WHERE total_sales \u003e (\n    //     SELECT SUM(total_sales)/10\n    //     FROM regional_sales\n    //   )\n    // )\n    // SELECT region, product, SUM(quantity) AS product_units, SUM(amount) AS product_sales\n    // FROM orders\n    // WHERE region IN (\n    //   SELECT region\n    //   FROM top_regions\n    // )\n    // GROUP BY region, product\n}\n```\n\n## Tasks\n\n#### Dynamic query building\n\n```go\ntype userFilter struct {\n    Name string\n}\nfilter := userFilter{\n    Name: \"john\",\n}\nquery := psql.Select(\n    sm.Columns(\"id\", \"name\"),\n    sm.From(\"users\"),\n)\nif filter.Name != \"\" {\n    query.Apply(\n        sm.WhereClause(\"name = ?\", filter.Name),\n    )\n}\n```\n\n#### Select from subselect\n\n```go\nquery := psql.Select(\n    sm.Columns(\"id\", \"name\", \"age\"),\n    sm.FromQuery(psql.Select(\n        sm.Columns(\"id\", \"name\", \"age\"),\n        sm.From(\"users\"),\n        sm.Where(\"age \u003e 10\"),\n    )),\n)\n```\n\n```sql\nSELECT id, name, age\nFROM (\n  SELECT id, name, age\n  FROM users\n  WHERE age \u003e 10\n)\n```\n\n#### WHERE value IN\n\n```go\nquery := psql.Select(\n    sm.Columns(\"id\", \"name\", \"age\"),\n    sm.From(\"users\"),\n    sm.WhereClause(\"age IN (?)\", expr.In([]any{15, 30, 45})),\n)\n```\n\n```sql\nSELECT id, name, age\nFROM users\nWHERE age IN ($1, $2, $3)\n```\n\n```\n--------------- QUERY ARGS ---------------\n([]interface {}) (len=3 cap=3) {\n (int) 15,\n (int) 30,\n (int) 45\n}\n```\n\n#### WHERE value IN using named arguments\n\n```go\nquery := psql.Select(\n    sm.Columns(\"id\", \"name\", \"age\"),\n    sm.From(\"users\"),\n    sm.WhereClause(\"age IN (?)\", expr.In([]any{\n        sq.NamedArg(\"first\"),\n        sq.NamedArg(\"second\"),\n        sq.NamedArg(\"third\"),\n    })),\n)\nqs, args, err := query.Build(\n    sq.WithParseArgs(sq.MapArgValues{\n        \"first\":  15,\n        \"second\": 30,\n        \"third\":  45,\n    }),\n)\n```\n\n```sql\nSELECT id, name, age\nFROM users\nWHERE age IN ($1, $2, $3)\n```\n\n```\n+++++++++++++++ PARSED ARGS +++++++++++++++\n([]interface {}) (len=3 cap=4) {\n (int) 15,\n (int) 30,\n (int) 45\n}\n```\n\n#### WHERE value IN subselect\n\n```go\nquery := psql.Select(\n    sm.Columns(\"id\", \"name\", \"age\"),\n    sm.From(\"users\"),\n    sm.WhereClause(\"region IN ?\",\n        psql.Select(\n            sm.Columns(\"region\"),\n            sm.From(\"top_regions\"),\n        ),\n    ),\n)\n```\n\n```sql\nSELECT id, name, age\nFROM users\nWHERE region IN (\n  SELECT region\n  FROM top_regions\n)\n```\n\n#### Expression function to generate using custom code\n\n```go\nquery := psql.Select(\n    sm.Columns(\"id\", \"name\", \"age\"),\n    sm.From(\"users\"),\n    sm.WhereClause(\"age \u003e ?\",\n        expr.Func(func() (litsql.Expression, error) {\n            r := rand.Intn(3)\n            switch r {\n            case 0:\n                return expr.Arg(20), nil\n            case 1:\n                return expr.Arg(30), nil\n            default:\n                return expr.Arg(50), nil\n            }\n        }),\n    ),\n)\n```\n\n```sql\nSELECT id, name, age\nFROM users\nWHERE age \u003e $1\n```\n\n```\n--------------- QUERY ARGS ---------------\n([]interface {}) (len=1 cap=1) {\n (int) 30\n}\n```\n\n#### Add clauses in inline callback\n\n```go\nquery := psql.Select(\n    sm.Columns(\"id\", \"name\", \"age\"),\n    sm.From(\"users\"),\n    sm.Apply(func(a psql.SelectModApply) {\n        a.Apply(\n            sm.Where(\"age \u003e 10\"),\n        )\n    }),\n)\n```\n\n```sql\nSELECT id, name, age\nFROM users\nWHERE age \u003e 10\n```\n\n#### Use IS NULL or a condition depending on a flag\n\n```go\nv := any(32)\nquery := psql.Select(\n    sm.Columns(\"id\", \"name\", \"age\"),\n    sm.From(\"users\"),\n    sm.WhereClause(\"u.age ?\",\n        expr.IfElse(v != nil,\n            expr.Clause(\"\u003e ?\", 32),\n            expr.String(\"IS NULL\"))),\n)\n```\n\n```sql\nSELECT id, name, age\nFROM users\nWHERE u.age \u003e $1\n```\n\n```\n--------------- QUERY ARGS ---------------\n([]interface {}) (len=1 cap=1) {\n (int) 32\n}\n```\n\n#### OR expression\n\n```go\nquery := psql.Select(\n    sm.Columns(\"id\", \"name\", \"age\"),\n    sm.From(\"users\"),\n    sm.WhereExpr(\n        expr.OrExpr(\n            expr.Clause(\"(age \u003e ? AND city_id = ?)\", 10, 12),\n            expr.Clause(\"(age \u003c ? AND city_id = ?)\", 10, 15),\n        ),\n    ),\n)\n```\n\n```sql\nSELECT id, name, age\nFROM users\nWHERE (age \u003e $1 AND city_id = $2) OR (age \u003c $3 AND city_id = $4)\n```\n\n```\n([]interface {}) (len=4 cap=4) {\n (int) 10,\n (int) 12,\n (int) 10,\n (int) 15\n}\n```\n\n#### UNION\n\n```go\nquery := psql.Select(\n    sm.Columns(\"id\", \"name\", \"age\"),\n    sm.From(\"users\"),\n    sm.Where(\"age \u003c 10\"),\n    sm.Union(psql.Select(\n        sm.Columns(\"id\", \"name\", \"age\"),\n        sm.From(\"users\"),\n        sm.Where(\"age \u003e 50\"),\n    )),\n)\n```\n\n```sql\nSELECT id, name, age\nFROM users\nWHERE age \u003c 10\nUNION (\n  SELECT id, name, age\n  FROM users\n  WHERE age \u003e 50\n)\n```\n\n#### Full raw query (the query and parameters will be returned as-is)\n\n```go\nquery := psql.SelectRaw(\"select * from users where user_id = $1\", 55)\n```\n\n```sql\nselect * from users where user_id = $1\n```\n\n```\n([]interface {}) (len=1 cap=1) {\n (int) 55\n}\n```\n\n#### Full raw query (with clause processing)\n\n```go\nquery := psql.SelectRawExpr(expr.Clause(\"select * from users where user_id = ?\", 55))\n```\n\n```sql\nselect * from users where user_id = $1\n```\n\n```\n--------------- QUERY ARGS ---------------\n([]interface {}) (len=1 cap=1) {\n (int) 55\n}\n```\n\n#### Prepared statements\n\nWhen using prepared statements, the use of named arguments is required, as it would be impossible to know which\nargument maps to each value.\n\n```go\nquery := psql.Select(\n    sm.Columns(\"film_id\", \"title\", \"length\"),\n    sm.From(\"film\"),\n    sm.WhereClause(\"length \u003e ?\", sq.NamedArg(\"length\")),\n    sm.LimitArgNamed(\"limit\"),\n)\n\nqueryStr, args, err := query.Build()\nif err != nil {\n    return err\n}\n\nprepq, err := db.PrepareContext(ctx, queryStr)\nif err != nil {\n    return err\n}\n\npargs, err := sq.ParseArgs(args, map[string]any{\n    \"length\": 100,\n    \"limit\":  10,\n})\nif err != nil {\n    return err\n}\n\nrows, err := prepq.QueryContext(ctx, pargs...)\nif err != nil {\n    return err\n}\ndefer rows.Close()\n\nfor rows.Next() {\n    var id, length int\n    var title string\n    if err := rows.Scan(\u0026id, \u0026title, \u0026length); err != nil {\n        return err\n    }\n    fmt.Println(id, title, length)\n}\n\nif rows.Err() != nil {\n    return rows.Err()\n}\n```\n\n## Author\n\nRangel Reale (rangelreale@gmail.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frrgmc%2Flitsql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frrgmc%2Flitsql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frrgmc%2Flitsql/lists"}