{"id":13706904,"url":"https://github.com/krisajenkins/yesql","last_synced_at":"2025-04-09T00:29:11.119Z","repository":{"id":10791316,"uuid":"13061319","full_name":"krisajenkins/yesql","owner":"krisajenkins","description":"A Clojure library for using SQL.","archived":false,"fork":false,"pushed_at":"2024-08-13T03:48:44.000Z","size":319,"stargazers_count":1466,"open_issues_count":58,"forks_count":111,"subscribers_count":41,"default_branch":"master","last_synced_at":"2024-10-29T15:34:10.134Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/krisajenkins.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":"2013-09-24T10:29:23.000Z","updated_at":"2024-10-28T06:31:14.000Z","dependencies_parsed_at":"2024-11-19T01:10:12.841Z","dependency_job_id":"4ec0b864-7d80-4295-9a42-458ad8abbcf5","html_url":"https://github.com/krisajenkins/yesql","commit_stats":{"total_commits":283,"total_committers":26,"mean_commits":"10.884615384615385","dds":0.1307420494699647,"last_synced_commit":"cd6c60a92f2930b5c876e1a54b0564fe6d19ed6e"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krisajenkins%2Fyesql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krisajenkins%2Fyesql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krisajenkins%2Fyesql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/krisajenkins%2Fyesql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/krisajenkins","download_url":"https://codeload.github.com/krisajenkins/yesql/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247949049,"owners_count":21023265,"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-08-02T22:01:11.915Z","updated_at":"2025-04-09T00:29:11.093Z","avatar_url":"https://github.com/krisajenkins.png","language":"Clojure","funding_links":[],"categories":["Clojure","\u003ca name=\"Clojure\"\u003e\u003c/a\u003eClojure"],"sub_categories":[],"readme":"# Yesql - Clojure \u0026 SQL rethought.\n\nYesql is a Clojure library for _using_ SQL.\n\n## Status\n\nThawing. Assistance maintaining sought.\n\nTested with Clojure 1.7-1.12.0.\n\n[Kris Jenkins](https://twitter.com/krisajenkins) originally wrote and\nmaintained this project, and [Mike Schaeffer](https://mschaef.com)\nis taking on forward maintenance. Please contact Mike with issues,\nsuggestions, and questions.\n\nThe primary focus moving forward will be to keep versions and tests up\nto date, and potentially add a few new features where they are most\nlikely to be helpful. The goal is for yesql to continue as a stable\nand composable library for integrating SQL easily into Clojure\nprojects.\n\nYou might also consider using [hugsql](https://www.hugsql.org/) which is\nphilosophically similar and more actively developed.\n\n## Installation\n\nAdd this to your [Leiningen](https://github.com/technomancy/leiningen) `:dependencies`:\n\n[![Clojars Project](https://img.shields.io/clojars/v/yesql.svg)](https://clojars.org/yesql)\n\n### Driver\nPlus you'll want a database driver. Here are some examples (but double\ncheck, because there may be a newer version available):\n\n|Database|`:dependencies` Entry|\n|---|---|\n|Derby|`[org.apache.derby/derby \"10.16.1.1\"]`|\n|H2|`[com.h2database/h2 \"2.1.212\"]`|\n|HyperSQL|`[org.hsqldb/hsqldb \"2.6.1\"]`|\n|MySQL|`[mysql/mysql-connector-java \"8.0.29\"]`|\n|Oracle|`[com.oracle.database.jdbc/ojdbc10 \"19.14.0.0\"]]`|\n|PostgreSQL|`[org.postgresql/postgresql \"42.3.5\"]`|\n|SQLite|`[org.xerial/sqlite-jdbc \"3.36.0.3\"]`|\n\n(Any database with a JDBC driver should work. If you know of a driver\nthat's not listed here, please open a pull request to update this\nsection.)\n\n### Migrating From Previous Versions\n\nSee the [Migration Guide](https://github.com/krisajenkins/yesql/wiki/Migration).\n\n## Rationale\n\nYou're writing Clojure. You need to write some SQL.\n\nI think we're all agreed that this is a problem:\n\n``` clojure\n(query \"SELECT * FROM users WHERE country_code = ?\" \"GB\")\n```\n\nUnless these query strings are short, they quickly get hard to read\nand hard to rewrite. Plus the lack of indentation \u0026 syntax\nhighlighting is horrible.\n\nBut something like this **is not the solution**:\n\n``` clojure\n(select :*\n        (from :users)\n        (where (= :country_code \"GB\")))\n```\n\nClojure is a great language for writing DSLs, but we don't need a new\none. SQL is already a mature DSL.  And s-expressions are great, but\nhere they're not adding anything. This is parens-for-parens sake.\n(Don't agree? Wait until this extra syntax layer breaks down and you\nstart wrestling with a `(raw-sql)` function.)\n\nSo what's the solution? Keep the SQL as SQL. Have one file with your\nquery:\n\n``` sql\n-- name: users-by-country\nSELECT *\nFROM users\nWHERE country_code = :country_code\n```\n\n...and then read that file to turn it into a regular Clojure function:\n\n``` clojure\n(defqueries \"some/where/users_by_country.sql\"\n   {:connection db-spec})\n\n;;; A function with the name `users-by-country` has been created.\n;;; Let's use it:\n(users-by-country {:country_code \"GB\"})\n;=\u003e ({:name \"Kris\" :country_code \"GB\" ...} ...)\n```\n\nBy keeping the SQL and Clojure separate you get:\n\n- No syntactic surprises. Your database doesn't stick to the SQL\n  standard - none of them do - but Yesql doesn't care. You will\n  never spend time hunting for \"the equivalent sexp syntax\". You will\n  never need to fall back to a `(raw-sql \"some('funky'::SYNTAX)\")` function.\n- Better editor support. Your editor probably already has great SQL\n  support. By keeping the SQL as SQL, you get to use it.\n- Team interoperability. Your DBAs can read and write the SQL you\n  use in your Clojure project.\n- Easier performance tuning. Need to `EXPLAIN` that query plan? It's\n  much easier when your query is ordinary SQL.\n- Query reuse. Drop the same SQL files into other projects, because\n  they're just plain ol' SQL. Share them as a submodule.\n\n### When Should I Not Use Yesql?\n\nWhen you need your SQL to work with many different kinds of\ndatabase at once. If you want one complex query to be transparently\ntranslated into different dialects for MySQL, Oracle, Postgres etc.,\nthen you genuinely do need an abstraction layer on top of SQL.\n\n## Usage\n### One File, One Query\n\nCreate an SQL query. Note we can supply named parameters ([in\n`snake_case`](https://github.com/krisajenkins/yesql/issues/1))\nand a comment string:\n\n```sql\n-- Counts the users in a given country.\nSELECT count(*) AS count\nFROM user\nWHERE country_code = :country_code\n```\n\nMake sure it's on the classpath. For this example, it's in\n`src/some/where/`. Now we can use it in our Clojure program.\n\n```clojure\n(require '[yesql.core :refer [defquery]])\n\n; Define a database connection spec. (This is standard clojure.java.jdbc.)\n(def db-spec {:classname \"org.postgresql.Driver\"\n              :subprotocol \"postgresql\"\n              :subname \"//localhost:5432/demo\"\n              :user \"me\"})\n\n; Import the SQL query as a function.\n(defquery users-by-country \"some/where/users_by_country.sql\"\n   {:connection db-spec})\n```\n\nLo! The function has been created, with automatic, useful docstrings\nin the REPL:\n\n```clojure\n(clojure.repl/doc users-by-country)\n\n;=\u003e -------------------------\n;=\u003e user/users-by-country\n;=\u003e ([{:keys [country_code]}]\n;=\u003e  [{:keys [country_code]} {:keys [connection]}])\n;=\u003e\n;=\u003e   Counts the users in a given country.\n```\n\nNow we can use it:\n\n```clojure\n; Use it standalone.\n(users-by-country {:country_code \"GB\"})\n;=\u003e ({:count 58})\n\n; Use it in a clojure.java.jdbc transaction.\n(require '[clojure.java.jdbc :as jdbc])\n\n(jdbc/with-db-transaction [tx db-spec]\n   {:limeys (users-by-country {:country_code \"GB\"} {:connection tx})\n    :yanks  (users-by-country {:country_code \"US\"} {:connection tx})})\n```\n\n### One File, Many Queries\n\nAs an alternative to the above, you can have many SQL statements in a\nsingle SQL file. The file format is: `(\u003cname tag\u003e [docstring comments]\n\u003cthe query\u003e)*`, like so:\n\n``` sql\n-- name: users-by-country\n-- Counts the users in a given country.\nSELECT count(*) AS count\nFROM user\nWHERE country_code = :country_code\n\n-- name: user-count\n-- Counts all the users.\nSELECT count(*) AS count\nFROM user\n```\n\nThen read the file in like so:\n\n```clojure\n(require '[yesql.core :refer [defqueries]])\n(defqueries \"some/where/queryfile.sql\"\n   {:connection db-spec})\n```\n\n`defqueries` returns a sequence of the vars it binds, which can be\nuseful feedback while developing.\n\nAs with `defquery`, each function will have a docstring based on the\ncomments, and a parameter map based on the SQL parameters.\n\n### ? Parameters\n\nYesql supports named parameters, and `?`-style positional\nparameters. Here's an example:\n\n```sql\n-- name: young-users-by-country\nSELECT *\nFROM user\nWHERE (\n  country_code = ?\n  OR\n  country_code = ?\n)\nAND age \u003c :max_age\n```\n\nSupply the `?` parameters as a vector under the `:?` key, like so:\n\n```clojure\n(young-users-by-country {:? [\"GB\" \"US\"]\n                         :max_age 18})\n```\n\n#### Selectively import queries\n\nSimilarly to `defqueries`, `require-sql` lets you create a number of\nquery functions at a time, but with a syntax more like\n`clojure.core/require`.\n\nUsing the `queryfile.sql` from the previous example:\n\n```clojure\n(require '[yesql.core :refer [require-sql]])\n\n; Use :as to alias the entire namespace, and :refer to refer functions\n; into the current namespace. Use one or both.\n(require-sql [\"some/where/queryfile.sql\" :as user :refer [user-count])\n\n(user-count)\n;=\u003e ({:count 132})\n\n(user/users-by-country db-spec \"GB\")\n;=\u003e ({:count 58})\n```\n\n### IN-list Queries\n\nYesql supports `IN`-style queries. Define your query with a\nsingle-element in the `IN` list, like so:\n\n```sql\n-- name: find-users\n-- Find the users with the given ID(s).\nSELECT *\nFROM user\nWHERE user_id IN (:id)\nAND age \u003e :min_age\n```\n\nAnd then supply the `IN`-list as a vector, like so:\n\n```clojure\n(defqueries \"some/where/queryfile.sql\"\n   {:connection db-spec})\n\n(find-users {:id [1001 1003 1005]\n             :min_age 18})\n```\n\nThe query will be automatically expanded to `... IN (1001, 1003, 1005)\n...` under the hood, and work as expected.\n\nJust remember that some databases have a limit on the number of values\nin an `IN`-list, and Yesql makes no effort to circumvent such limits.\n\n### Row And Result Processors\n\nLike `clojure.java.jdbc`, Yesql accepts functions to pre-process each\nrow, and the final result, like so:\n\n```sql\n-- name: current-time\n-- Selects the current time, according to the database.\nSELECT sysdate\nFROM dual;\n```\n\n```clojure\n(defqueries \"/some/where/queryfile.sql\"\n  {:connection db-spec})\n\n;;; Without processors, this query returns a list with one element,\n;;;   containing a map with one key:\n(current-time)\n;=\u003e ({:sysdate #inst \"2014-09-30T07:30:06.764000000-00:00\"})\n\n;;; With processors we just get the value we want:\n(current-time {} {:result-set-fn first\n                  :row-fn :sysdate\n                  :identifiers identity})\n;=\u003e #inst \"2014-09-30T07:30:06.764000000-00:00\"\n```\n\nAs with `clojure.java.jdbc` the default `:result-set-fn` is `doall`,\nthe default `:row-fn` is `identity`, and the default `:identifiers` is\n`clojure.string/lower-case`.\n\n_A note of caution_: Remember you're often better off doing your\nprocessing directly in SQL. For example, if you're counting a million\nrows, you can do it with `{:result-set-fn count}` or\n`SELECT count(*) ...`. Both wil give the same answer, but the\nSQL-version will avoid sending a million rows over the wire to do it.\n\n### Insert/Update/Delete and More\n\nTo do `INSERT/UPDATE/DELETE` statements, you just need to add an `!`\nto the end of the function name, and Yesql will execute the function\nappropriately. For example:\n\n```sql\n-- name: save-person!\nUPDATE person\nSET name = :name\nWHERE id = :id\n```\n\n```clojure\n(save-person! {:id 1\n               :name \"Dave\"})\n;=\u003e 1\n```\n\nA `!`-tagged function will return the number of rows affected.\n\n`!` enables every statement type - not just `INSERT/UPDATE/DELETE` but\nalso `CREATE/DROP/ALTER/BEGIN/...` - anything your driver will\nsupport.\n\n### Insert, Returning Autogenerated Keys\n\nThere's one more variant: when you want to insert data and get back a\ndatabase-generated primary key, the driver requires a special call, so\nYesql needs to be specially-informed. You can do an \"insert returning\nautogenerated key\" with the `\u003c!` suffix, like so:\n\n```sql\n-- name: create-person\u003c!\nINSERT INTO person (name) VALUES (:name)\n```\n\n```clojure\n(create-person\u003c! {:name \"Dave\"})\n;=\u003e {:name \"Dave\" :id 5}\n```\n\nThe exact return value will depend on your database driver. For\nexample PostgreSQL returns the whole row, whereas Derby returns just\n`{:1 5M}`.\n\nThe `\u003c!` suffix is intended to mirror `core.async`, so it should be easy to remember.\n\n## Query Middlware (Experimental)\n\n***Note: This section covers an experimental feature that is under\ndevelopment. The interface is subject to review (by you!) and may\nor may not continue to be supported its current form. Please\ncheck it out and report back how it works and might be improved.***\n\nYesql now has support for middleware in a style modeled after\n[Ring](https://github.com/ring-clojure/ring/wiki/Concepts#middleware).\nYesql middleware are higher-order functions that wrap queries,\nallowing inspection and modification of both arguments and\nresults. The intent is for middlware to allowa single definition for\ncross cutting concerns that apply to all of the queries defined within\na file. Possible uses include query logging, caching, alternative\nsources for database connections, and various forms of result\npost-processing.\n\nMiddleware is specified via a new `:middleware` option to `defquery`,\n`defquery*`, and `defqueries`.\n\n```clojure\n(defquery current-time-query-middleware\n  \"yesql/sample_files/current_time.sql\"\n  {:middleware log-query-middleware})\n```\n\nThe middleware itself is a higher order function over a query\nfunction. (Note that the query is passed as a new value under\n`call-options`, keyed by `:query`.)\n\n```clojure\n(def log-query-middleware\n  (fn [ query-fn ]\n    (fn [args call-options]\n      (let [ query-name (get-in call-options [:query :name]) ]\n        (println [ :begin query-name ])\n        (let [ result (query-fn args call-options) ]\n          (println [ :end query-name])\n          result)))))\n```\n\nTo allow middleware to be paramaterized per-query, the query\ndefinition format has been extended to allow `info` attributes\nto be specified, binding EDN values to specific names.\n\n``` sql\n-- name: users-by-country\n-- info-value-1: { :edn :value-1 }\n-- info-value-2: { :edn :value-2 }\nSELECT *\nFROM users\nWHERE country_code = :country_code\n```\n\nThe `info` values are accessible within the middleware through the\n`:info` field of the query. (`(get-in call-options [:query :info :value-1])`)\n\n## Development \u0026 Testing\n\nYesql uses the marvellous\n[expectations library](http://jayfields.com/expectations/index.html)\nfor tests. It's like clojure.test, but has lighter-weight syntax and\nmuch better failure messages.\n\nCall `lein test` to run the test suite.\nCall `lein test-all` to run the tests against all (supported) versions of Clojure.\nCall `lein autoexpect` to automatically re-run the tests as source files change.\n\n## Other Languages\n\nYesql has inspired ports to other languages:\n\n|Language|Project|\n|---|---|\n|JavaScript|[jsyesql](https://github.com/fanatid/jsyesql)|\n|JavaScript|[Preql](https://github.com/NGPVAN/preql)|\n|JavaScript|[sqlt](https://github.com/eugeneware/sqlt)|\n|Python|[aiosql](https://github.com/nackjicholson/aiosql)|\n|Go|[DotSql](https://github.com/gchaincl/dotsql)|\n|Go|[goyesql](https://github.com/nleof/goyesql)|\n|C#|[JaSql](https://bitbucket.org/rick/jasql)|\n|Ruby|[yayql](https://github.com/gnarmis/yayql)|\n|Erlang|[eql](https://github.com/artemeff/eql)|\n|Clojure|[YeSPARQL](https://github.com/joelkuiper/yesparql)|\n|PHP|[YepSQL](https://github.com/LionsHead/YepSQL)|\n|Haskell|[YeshQL](https://hackage.haskell.org/package/yeshql)|\n\n## License\n\nCopyright © 2013-2016 Kris Jenkins\nCopyright © 2022-2024 Mike Schaeffer\n\nDistributed under the Eclipse Public License, the same as Clojure.\n\n## PS - Is Yesql An ORM?\n\nNo. There are no Objects here, only Values. Yesql is a VRM. This is\nbetter because it's pronounced, \"Vroom!\"\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkrisajenkins%2Fyesql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkrisajenkins%2Fyesql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkrisajenkins%2Fyesql/lists"}