{"id":16971546,"url":"https://github.com/thomastjdev/nim_sqlbuilder","last_synced_at":"2026-03-01T03:31:51.701Z","repository":{"id":87657533,"uuid":"153813567","full_name":"ThomasTJdev/nim_sqlbuilder","owner":"ThomasTJdev","description":"SQL builder for Nim queries","archived":false,"fork":false,"pushed_at":"2025-03-09T06:09:24.000Z","size":221,"stargazers_count":11,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-19T07:58:24.022Z","etag":null,"topics":["nim","postgres","sql","sqlbuilder"],"latest_commit_sha":null,"homepage":"","language":"Nim","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/ThomasTJdev.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,"zenodo":null}},"created_at":"2018-10-19T16:43:39.000Z","updated_at":"2025-04-02T04:47:01.000Z","dependencies_parsed_at":"2025-01-05T08:17:25.413Z","dependency_job_id":"6adddc48-8562-459b-ab20-79f935e77994","html_url":"https://github.com/ThomasTJdev/nim_sqlbuilder","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/ThomasTJdev/nim_sqlbuilder","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ThomasTJdev%2Fnim_sqlbuilder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ThomasTJdev%2Fnim_sqlbuilder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ThomasTJdev%2Fnim_sqlbuilder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ThomasTJdev%2Fnim_sqlbuilder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ThomasTJdev","download_url":"https://codeload.github.com/ThomasTJdev/nim_sqlbuilder/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ThomasTJdev%2Fnim_sqlbuilder/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29959363,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-01T01:47:18.291Z","status":"online","status_checked_at":"2026-03-01T02:00:07.437Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["nim","postgres","sql","sqlbuilder"],"created_at":"2024-10-14T00:52:24.751Z","updated_at":"2026-03-01T03:31:51.676Z","avatar_url":"https://github.com/ThomasTJdev.png","language":"Nim","readme":"**⚠️ Not actively maintained - moved to https://github.com/ThomasTJdev/nim_sqlquery ⚠️**\n\n\n# SQL builder\n\nSQL builder for ``INSERT``, ``UPDATE``, ``SELECT`` and ``DELETE`` queries.\nThe builder will check for NULL values and build a query with them.\n\nAfter Nim's update to 0.19.0, the check for NULL values was removed\ndue to the removal of ``nil``.\n\nThis library allows the user, to insert NULL values into queries and\nease the creating of queries.\n\n\n# TOC\n\n- General\n  - [Importing and use](#importing)\n  - [Macro generated queries](#macro-generated-queries)\n  - [NULL values](#null-values)\n- Main examples\n  - [Examples (INSERT)](#examples-insert)\n  - [Examples (UPDATE)](#examples-update)\n  - [Examples (SELECT)](#examples-select)\n- Utilities\n  - [Custom args](#custom-args)\n  - [Dynamic selection of columns](#dynamic-selection-of-columns)\n  - [Query calls for the lazy](#query-calls-for-the-lazy)\n  - [Convert result to types](#convert-result-to-types)\n- [Examples](#examples)\n\n\n# Importing\n\n## Import all\n```nim\nimport sqlbuilder\n```\n\n## Import only the SELECT builder\n```nim\nimport sqlbuilder/select\n```\n\n## Import and set global soft delete marker\n```nim\nconst tablesWithDeleteMarkerInit = [\"table_with_deletemarker\"]\ninclude src/sqlbuilder_include\n```\n\n## Import all but with legacy softdelete fix\n```nim\nimport src/sqlbuilder/sqlbuilderpkg/insert\nexport insert\n\nimport src/sqlbuilder/sqlbuilderpkg/update\nexport update\n\nimport src/sqlbuilder/sqlbuilderpkg/delete\nexport delete\n\nimport src/sqlbuilder/sqlbuilderpkg/utils\nexport utils\n\n# This enables the softdelete columns for the legacy selector\nconst tablesWithDeleteMarker = [\"tasks\", \"persons\"]\n# Notice the include instead of import\ninclude src/sqlbuilderpkg/select\n```\n\n\n\n# Macro generated queries\n\nThe library supports generating some queries with a macro which can improve the\nperformance due to query being generated on compile time.\n\n\n\n# NULL values\n\nAfter Nim's update to 0.19.0, the check for NULL values was removed\ndue to the removal of `nil`.\n\nYou can use `NULL` values in different ways. See the examples.\n\n## Inline in query\n\n```nim\nsqlSelect(\n      table     = \"tasks\",\n      tableAs   = \"t\",\n      select    = @[\"id\", \"name\", \"description\", \"created\", \"updated\", \"completed\"],\n      where     = @[\"id =\", \"name != NULL\", \"description = NULL\"],\n      useDeleteMarker = false\n    )\ncheck querycompare(test, sql(\"SELECT id, name, description, created, updated, completed FROM tasks AS t WHERE id = ? AND name != NULL AND description = NULL \"))\n```\n\n```nim\nsqlUpdate(\n      \"table\",\n      [\"name\", \"age\", \"info = NULL\"],\n      [\"id =\"],\n    )\ncheck querycompare(q, sql(\"UPDATE table SET name = ?, age = ?, info = NULL WHERE id = ?\"))\n```\n\n## Custom args\n\n### A NULL value\n\nThe global ``const dbNullVal`` represents a NULL value. Use ``dbNullVal``\nin your args if you need to insert/update to a NULL value.\n\n### Insert value or NULL\n\nThe global ``proc dbValOrNull()`` will check, if it contains a value\nor is empty. If it contains a value, the value will be used in the args,\notherwise a NULL value (``dbNullVal``) will be used.\n\n``dbValOrNull()`` accepts all types due to `value: auto`.\n\n### Auto NULL-values\n\nThere are two generators, which can generate the `NULL` values for you.\n\n* `genArgs` does only set a field to `NULL` if `dbNullVal`/`dbValOrNull()` is passed.\n* `genArgsSetNull` sets empty field (`\"\"` / `c.len() == 0`) to `NULL`.\n\n\n\n\n\n\n\n\n# Examples (INSERT)\n\n## Insert default\n\n```nim\ntest = sqlInsert(\"my-table\", [\"name\", \"age\"])\ncheck querycompare(test, sql(\"INSERT INTO my-table (name, age) VALUES (?, ?)\"))\n```\n\n```nim\ntest = sqlInsert(\"my-table\", [\"name\", \"age = NULL\"])\ncheck querycompare(test, sql(\"INSERT INTO my-table (name, age) VALUES (?, NULL)\"))\n```\n\n```nim\nlet vals = @[\"thomas\", \"\"]\ntest = sqlInsert(\"my-table\", [\"name\", \"age\"], vals)\ncheck querycompare(test, sql(\"INSERT INTO my-table (name, age) VALUES (?, NULL)\"))\n```\n\n```nim\ntest = sqlInsertMacro(\"my-table\", [\"name\", \"age = NULL\"])\ncheck querycompare(test, sql(\"INSERT INTO my-table (name, age) VALUES (?, NULL)\"))\n```\n\n```nim\n let a = genArgs(\"em@em.com\", dbNullVal)\n exec(db, sqlInsert(\"myTable\", [\"email\", \"age\"], a.query), a.args)\n # ==\u003e INSERT INTO myTable (email) VALUES (?)\n```\n\n\n# Examples (UPDATE)\n\n```nim\nlet q = sqlUpdate(\n      \"table\",\n      [\"name\", \"age\", \"info\"],\n      [\"id =\"],\n    )\ncheck querycompare(q, sql(\"UPDATE table SET name = ?, age = ?, info = ? WHERE id = ?\"))\n```\n\n```nim\nlet q = sqlUpdate(\n      \"table\",\n      [\"name\", \"age\", \"info = NULL\"],\n      [\"id =\"],\n    )\ncheck querycompare(q, sql(\"UPDATE table SET name = ?, age = ?, info = NULL WHERE id = ?\"))\n```\n\n```nim\nlet q = sqlUpdate(\n      \"table\",\n      [\"name = NULL\", \"age\", \"info = NULL\"],\n      [\"id =\", \"epoch \u003e\", \"parent IS NULL\", \"name IS NOT NULL\", \"age != 22\", \"age !=\"],\n    )\ncheck querycompare(q, sql(\"UPDATE table SET name = NULL, age = ?, info = NULL WHERE id = ? AND epoch \u003e ? AND parent IS NULL AND name IS NOT NULL AND age != 22 AND age != ?\"))\n```\n\n```nim\nlet q = sqlUpdate(\n      \"table\",\n      [\"parents = ARRAY_APPEND(id, ?)\", \"age = ARRAY_REMOVE(id, ?)\", \"info = NULL\"],\n      [\"last_name NOT IN ('Anderson', 'Johnson', 'Smith')\"],\n    )\ncheck querycompare(q, sql(\"UPDATE table SET parents = ARRAY_APPEND(id, ?), age = ARRAY_REMOVE(id, ?), info = NULL WHERE last_name NOT IN ('Anderson', 'Johnson', 'Smith')\"))\n```\n\n```nim\n  # sqlUpdate or sqlUpdateMacro\n  let q = sqlUpdate(\n      \"table\",\n      [\"name = NULL\", \"age\", \"info = NULL\"],\n      [\"id =\", \"epoch \u003e\", \"parent IS NULL\", \"name IS NOT NULL\", \"age != 22\", \"age !=\"],\n    )\n  # ==\u003e UPDATE table SET name = NULL, age = ?, info = NULL WHERE id = ? AND epoch \u003e ? AND parent IS NULL AND name IS NOT NULL AND age != 22 AND age != ?\n```\n\n```nim\nlet a2 = genArgsSetNull(\"hje\", \"\")\nlet q = sqlUpdate(\"my-table\", [\"name\", \"age\"], [\"id\"], a2.query)\ncheck querycompare(q, sql(\"UPDATE my-table SET name = ?, age = NULL WHERE id = ?\"))\n```\n\n\n\n# Examples (SELECT)\n\n## Example on builder\n```nim\ntest = sqlSelect(\n  table     = \"tasks\",\n  tableAs   = \"t\",\n  select    = @[\"id\", \"name\"],\n  where     = @[\"id =\"],\n  joinargs  = @[(table: \"projects\", tableAs: \"\", on: @[\"projects.id = t.project_id\", \"projects.status = 1\"])],\n  jointype  = INNER\n)\ncheck querycompare(test, sql(\"SELECT id, name FROM tasks AS t INNER JOIN projects ON (projects.id = t.project_id AND projects.status = 1) WHERE id = ? \"))\n```\n\n```nim\ntest = sqlSelect(\n  table     = \"tasksitems\",\n  tableAs   = \"tasks\",\n  select    = @[\n      \"tasks.id\",\n      \"tasks.name\",\n      \"tasks.status\",\n      \"tasks.created\",\n      \"his.id\",\n      \"his.name\",\n      \"his.status\",\n      \"his.created\",\n      \"projects.id\",\n      \"projects.name\",\n      \"person.id\",\n      \"person.name\",\n      \"person.email\"\n    ],\n  where     = @[\n      \"projects.id =\",\n      \"tasks.status \u003e\"\n    ],\n  joinargs  = @[\n      (table: \"history\", tableAs: \"his\", on: @[\"his.id = tasks.hid\", \"his.status = 1\"]),\n      (table: \"projects\", tableAs: \"\", on: @[\"projects.id = tasks.project_id\", \"projects.status = 1\"]),\n      (table: \"person\", tableAs: \"\", on: @[\"person.id = tasks.person_id\"])\n    ],\n  whereInField = \"tasks.id\",\n  whereInValue = @[\"1\", \"2\", \"3\"],\n  customSQL = \"ORDER BY tasks.created DESC\",\n  tablesWithDeleteMarker = tableWithDeleteMarker\n)\ncheck querycompare(test, (sql(\"\"\"\n    SELECT\n      tasks.id,\n      tasks.name,\n      tasks.status,\n      tasks.created,\n      his.id,\n      his.name,\n      his.status,\n      his.created,\n      projects.id,\n      projects.name,\n      person.id,\n      person.name,\n      person.email\n    FROM\n      tasksitems AS tasks\n    LEFT JOIN history AS his ON\n      (his.id = tasks.hid AND his.status = 1 AND his.is_deleted IS NULL)\n    LEFT JOIN projects ON\n      (projects.id = tasks.project_id AND projects.status = 1)\n    LEFT JOIN person ON\n      (person.id = tasks.person_id)\n    WHERE\n          projects.id = ?\n      AND tasks.status \u003e ?\n      AND tasks.id in (1,2,3)\n      AND tasks.is_deleted IS NULL\n    ORDER BY\n      tasks.created DESC\n  \"\"\")))\n```\n\n\n## Convert legacy\n\nThe legacy SELECT builder is deprecated and will be removed in the future. It\nis commented out in the source code, and a converter has been added to convert\nthe legacy query to the new builder.\n\nThat means, you don't have to worry, but you should definitely convert your\nlegacy queries to the new builder.\n\n```nim\n# Legacy builder\ntest = sqlSelect(\"tasks AS t\", [\"t.id\", \"t.name\", \"p.id\"], [\"project AS p ON p.id = t.project_id\"], [\"t.id =\"], \"\", \"\", \"\")\n\ncheck querycompare(test, sql(\"\"\"\n    SELECT\n      t.id,\n      t.name,\n      p.id\n    FROM\n      tasks AS t\n    LEFT JOIN project AS p ON\n      (p.id = t.project_id)\n    WHERE\n      t.id = ?\n  \"\"\"))\n```\n\n\n\n# Custom args\n## Update string \u0026 int\n\n### Version 1\n*Required if NULL values could be expected*\n```nim\n let a = genArgs(\"em@em.com\", 20, \"John\")\n exec(db, sqlUpdate(\"myTable\", [\"email\", \"age\"], [\"name\"], a.query), a.args)\n # ==\u003e string, int\n # ==\u003e UPDATE myTable SET email = ?, age = ? WHERE name = ?\n```\n\n\n### Version 2\n```nim\n exec(db, sqlUpdate(\"myTable\", [\"email\", \"age\"], [\"name\"]), \"em@em.com\", 20, \"John\")\n # ==\u003e string, int\n # ==\u003e UPDATE myTable SET email = ?, age = ? WHERE name = ?\n```\n\n\n### Version 3\n```nim\n let a = genArgsSetNull(\"em@em.com\", \"\", \"John\")\n exec(db, sqlUpdate(\"myTable\", [\"email\", \"age\"], [\"name\"], a.query), a.args)\n # ==\u003e string, NULL\n # ==\u003e UPDATE myTable SET email = ?, age = NULL WHERE name = ?\n```\n\n\n## Update NULL \u0026 int\n\n```nim\n let a = genArgs(\"\", 20, \"John\")\n exec(db, sqlUpdate(\"myTable\", [\"email\", \"age\"], [\"name\"], a.query), a.args)\n # ==\u003e NULL, int\n # ==\u003e UPDATE myTable SET email = NULL, age = ? WHERE name = ?\n```\n\n\n## Update string \u0026 NULL\n\n```nim\n a = genArgs(\"aa@aa.aa\", dbNullVal, \"John\")\n exec(db, sqlUpdate(\"myTable\", [\"email\", \"age\"], [\"name\"], a.query), a.args)\n # ==\u003e string, NULL\n # ==\u003e UPDATE myTable SET email = ?, age = NULL WHERE name = ?\n```\n\n\n## Error: Update string \u0026 NULL into an integer column\n\nAn empty string, \"\", will be inserted into the database as NULL.\nEmpty string cannot be used for an INTEGER column. You therefore\nneed to use the ``dbValOrNull()`` or ``dbNullVal`` for ``int-values``.\n\nThis is due to, that the library does not know you DB-architecture, so it\nis your responsibility to respect the columns.\n\n```nim\n a = genArgs(\"aa@aa.aa\", \"\", \"John\")\n exec(db, sqlUpdate(\"myTable\", [\"email\", \"age\"], [\"name\"], a.query), a.args)\n # ==\u003e string, ERROR\n # ==\u003e UPDATE myTable SET email = ?, age = ? WHERE name = ?\n # ==\u003e To insert a NULL into a int-field, it is required to use dbValOrNull()\n #     or dbNullVal, it is only possible to pass and empty string.\n```\n\n\n## Update NULL \u0026 NULL\n\n```nim\n let cc = \"\"\n a = genArgs(dbValOrNull(cc), dbValOrNull(cc), \"John\")\n exec(db, sqlUpdate(\"myTable\", [\"email\", \"age\"], [\"name\"], a.query), a.args)\n # ==\u003e NULL, NULL\n # ==\u003e UPDATE myTable SET email = NULL, age = NULL WHERE name = ?\n```\n\n\n\n## Update unknow value - maybe NULL\n\n```nim\n a = genArgs(dbValOrNull(var1), dbValOrNull(var2), \"John\")\n exec(db, sqlUpdate(\"myTable\", [\"email\", \"age\"], [\"name\"], a.query), a.args)\n # ==\u003e AUTO or NULL, AUTO or NULL, string\n```\n\n\n\n# Dynamic selection of columns\nSelect which columns to include.\n\nLets say, that you are importing data from a spreadsheet with static\ncolumns, and you need to update your DB with the new values.\n\nIf the spreadsheet contains an empty field, you can update your DB with the\nNULL value. But sometimes the spreadsheet only contains 5 out of the 10\nstatic columns. Now you don't know the values of the last 5 columns,\nwhich will result in updating the DB with NULL values.\n\nThe template `genArgsColumns()` allows you to use the same query, but  selecting\nonly the columns which shall be updated. When importing your spreadsheet, check\nif the column exists (bool), and pass that as the `use: bool` param. If\nthe column does not exists, it will be skipped in the query.\n\n## Insert \u0026 Delete\n```nim\n let (s, a) = genArgsColumns((true, \"name\", \"Thomas\"), (true, \"age\", 30), (false, \"nim\", \"never\"))\n # We are using the column `name` and `age` and ignoring the column `nim`.\n\n echo $a.args\n # ==\u003e Args: @[\"Thomas\", \"30\"]\n\n let a1 = sqlInsert(\"my-table\", s, a.query)\n # ==\u003e INSERT INTO my-table (name, age) VALUES (?, ?)\n\n let a2 = sqlDelete(\"my-table\", s, a.query)\n # ==\u003e DELETE FROM my-table WHERE name = ? AND age = ?\n```\n\n## Update \u0026 Select\n```nim\n let (s, a) = genArgsColumns((true, \"name\", \"Thomas\"), (true, \"age\", 30), (false, \"nim\", \"\"), (true, \"\", \"154\"))\n # We are using the column `name` and `age` and ignoring the column `nim`. We\n # are using the value `154` as our identifier, therefor the column is not\n # specified\n\n echo $a.args\n # ==\u003e Args: @[\"Thomas\", \"30\", \"154\"]\n\n let a3 = sqlUpdate(\"my-table\", s, [\"id\"], a.query)\n # ==\u003e UPDATE my-table SET name = ?, age = ? WHERE id = ?\n\n let a4 = sqlSelect(\"my-table\", s, [\"\"], [\"id =\"], \"\", \"\", \"\", a.query)\n # ==\u003e SELECT name, age FROM my-table WHERE id = ?\n```\n\n\n\n# Query calls for the lazy\n\nThese are procs to catch DB errors and return a default value to move on.\nThis should only be used if:\n  - It is not critical data\n  - You can live with a default value in case of an error\n  - You have no other way to catch the error\n  - You are to lazy to write the try-except procs yourself\n\n!! These are not available if you use external libraries, e.g. `waterpark`,\n!! since they rely on default`DbConn`.\n\n## Import\n\n```nim\nimport sqlbuilder/query_calls\n```\n\n## Procs\n\n```nim\nproc getValueTry*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): string =\n```\n____\n\n\n```nim\nproc getAllRowsTry*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): seq[Row] =\n```\n\n____\n\n\n```nim\nproc getRowTry*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): Row =\n```\n\n____\n\n\n```nim\nproc tryExecTry*(db: DbConn, query: SqlQuery; args: varargs[string, `$`]): bool =\n```\n\n____\n\n\n```nim\nproc execTry*(db: DbConn, query: SqlQuery; args: varargs[string, `$`]) =\n```\n\n____\n\n\n```nim\nproc execAffectedRowsTry*(db: DbConn, query: SqlQuery; args: varargs[string, `$`]): int64 =\n```\n\n____\n\n```nim\niterator fastRowsTry*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): Row =\n```\n\n____\n\n\n# Convert result to types\n\nThe `totypes` module contains procs to convert the result to types.\n\n```nim\ntype\n  Person = ref object\n    id: int\n    username: string\n    age: int\n    secretIdent: string\n    is_nimmer: bool\n\nlet\n  columns = @[\"name AS username\",\"id\",\"ident AS secretIdent\"]\n  val = db.getRow(sql(\"SELECT \" \u0026 columns.join(\",\") \u0026 \" FROM my_table WHERE id = 1\"))\n  res = sqlToTypeAs(Person, columns, val)\n```\n\n```nim\ntype\n  Person = ref object\n    id: int\n    name: string\n    age: int\n    ident: string\n    is_nimmer: bool\n\nlet\n  columns = @[\"name\",\"id\",\"ident\"]\n  val = db.getRow(sql(\"SELECT \" \u0026 columns.join(\",\") \u0026 \" FROM my_table WHERE id = 1\"))\n  res = sqlToType(Person, columns, val)\n```\n\n\n# Examples\n\nSee the test files in `tests/` for more examples.\n\n\n\n# Credit\nInspiration for builder: [Nim Forum](https://github.com/nim-lang/nimforum)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthomastjdev%2Fnim_sqlbuilder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthomastjdev%2Fnim_sqlbuilder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthomastjdev%2Fnim_sqlbuilder/lists"}