{"id":32181114,"url":"https://github.com/gered/views.sql","last_synced_at":"2025-10-21T22:32:24.518Z","repository":{"id":57713691,"uuid":"59430183","full_name":"gered/views.sql","owner":"gered","description":"SQL plugin for the views library. Allows for plain ol' SQL strings to be used.","archived":false,"fork":false,"pushed_at":"2022-01-12T22:39:41.000Z","size":25,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-07-25T06:37:43.261Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Clojure","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/gered.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}},"created_at":"2016-05-22T19:26:40.000Z","updated_at":"2022-01-12T21:27:01.000Z","dependencies_parsed_at":"2022-09-18T06:55:24.133Z","dependency_job_id":null,"html_url":"https://github.com/gered/views.sql","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/gered/views.sql","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gered%2Fviews.sql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gered%2Fviews.sql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gered%2Fviews.sql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gered%2Fviews.sql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gered","download_url":"https://codeload.github.com/gered/views.sql/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gered%2Fviews.sql/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280347948,"owners_count":26315364,"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","status":"online","status_checked_at":"2025-10-21T02:00:06.614Z","response_time":58,"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":[],"created_at":"2025-10-21T22:31:02.294Z","updated_at":"2025-10-21T22:32:24.507Z","avatar_url":"https://github.com/gered.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# views.sql\n\nSQL plugin for the [views][1] library. Allows for views to be created \nwhich retrieve data via [clojure.java.jdbc][2] using SQL queries \nprovided as plain ol' strings (as opposed to [views.honeysql][3] which \nuses HoneySQL-format SQL queries). Provides an alternate \n`execute!`-like function to execute INSERT/UPDATE/DELETE queries and \nadd appropriate hints to the view system at the same time to trigger \nview refreshes.\n\n[1]: https://github.com/gered/views\n[2]: https://github.com/clojure/java.jdbc\n[3]: https://github.com/gered/views.honeysql\n\nviews.sql interops well with views.honeysql when both types of views \nare included within the same system.\n\n\n## Leiningen\n\n[![](https://clojars.org/net.gered/views.sql/latest-version.svg)](https://clojars.org/net.gered/views.sql)\n\n## Creating SQL Views\n\n```clj\n(require '[views.core :as views]\n         '[views.sql.view :as vsql])\n\n(def db ... )            ; a standard JDBC database connection map\n(def view-system ... )   ; pre-initialized view system\n\n\n; view functions. these are just functions that return SQL in a \"sql vector\" format.\n; (which is the exact same format as you pass SQL in to clojure.java.jdbc/query, etc)\n\n(defn my-view-sql []\n  [\"SELECT * FROM foo\"])\n\n(defn people-by-type-sql [type]\n  [\"SELECT first_name, last_name FROM people WHERE type = ?\" type])\n\n\n; add 2 views, :my-view and :people-by-type, to the view system\n\n(views/add-views!\n  view-system\n  [(vsql/view :my-view db my-view-sql)\n   (vsql/view :people-by-type db people-by-type-sql)])\n```\n\nThe calls to `views.sql.view/view` return instances of a `SQLView` \nview. The \"view functions\" which contain the actual SQL queries are \ncalled in two instances:\n\n* When the view's data is being refreshed. The view function is called\nto get the SQL to be run via `clojure.java.jdbc/query` using the `db`\nconnection that was provided to the view.\n* Whenever hints are being checked for relevancy against the view when\nthe view system is determining whether the view needs to be refreshed\nor not.\n\nNote also that the view functions can take any number of parameters\nwhich are provided during view subscription:\n\n```clj\n(require '[views.core :refer [subscribe! -\u003eview-sig]])\n\n(subscribe! view-system (-\u003eview-sig :my-namespace :my-view []) 123 nil)\n(subscribe! view-system (-\u003eview-sig :my-namespace :people-by-type [\"student\"]) 123 nil)\n```\n\n### Extra Features and Options\n\nYou can use clojure.java.jdbc's `:row-fn` and `:result-set-fn` (see\n[here][4] and [here][5] for more info on what these options are) with\nSQL views:\n\n```clj\n(vsql/view :foobar-view db foobar-view-sql {:row-fn my-row-fn \n                                            :result-set-fn my-result-set-fn})\n```\n\n[4]: http://clojure-doc.org/articles/ecosystem/java_jdbc/using_sql.html#processing-each-row-lazily\n[5]: http://clojure-doc.org/articles/ecosystem/java_jdbc/using_sql.html#processing-a-result-set-lazily\n\nAdditionally the `db` argument can be a function that accepts a\nnamespace and returns a standard database connection map.\n\n```clj\n(defn db-selector [namespace]\n  (case namespace\n    :foo foo-db\n    :bar bar-db\n    default-db))\n\n(vsql/view :people-by-type db-selector people-by-type-sql)\n```\n\nIn this case, `db-selector` would be called only when the view data is\nbeing refreshed (it is not used during hint relevancy checks). The\nnamespace that would be passed in is taken from the view\nsubscription(s) for which the view is being refresh for (so it could\nbe anything, even `nil`... whatever was provided as the namespace at\nthe time subscriptions are created).\n\n\n## Running INSERT/UPDATE/DELETE Queries\n\nInstead of using clojure.java.jdbc's `execute!` or `query!`, you\nshould instead use `views.sql.core/vexec!`:\n\n```clj\n(require '[views.sql.core :refer [vexec!]])\n\n(vexec! view-system db\n        [\"INSERT INTO people (type, first_name, last_name) VALUES (?, ?, ?)\"\n         \"student\" \"Foo\" \"Bar\"])\n```\n\nThis will both, execute the SQL query and also analyze it to determine\nwhat hints need to be added to the view system and then add them.\n\nWith the above `vexec!` call the hints that would be added to the view\nsystem would trigger view refreshes for anyone subscribed to any SQL\nviews in the system that use a SELECT query to retrieve data from the\n\"people\" table (either using another simple SELECT, or JOINing it with\nother tables as part of a larger query, a sub-SELECT, etc).\n\n### Transactions\n\nIf you need to run some SQL queries within a transaction, you should\nuse `views.sql.core/with-view-transaction` instead of \nclojure.java.jdbc's `with-db-transaction`. It basically works exactly\nthe same:\n\n```clj\n(require '[views.sql.core :refer [with-view-transaction]])\n\n(with-view-transaction\n  view-system            ; need to pass in the view-system atom\n  [dt db]\n  (let [user-id (vexec! view-system dt \n                        [\"INSERT INTO users (username) \n                          VALUES (?) \n                          RETURNING user_id\"\n                         \"fbar\"])]\n    (vexec! view-system dt\n            [\"INSERT INTO people (type, first_name, last_name, user_id) \n              VALUES (?, ?, ?, ?)\"\n             \"student\" \"Foo\" \"Bar\" user-id])))\n```\n\nThe hints generated by any `vexec!` calls within a transaction are\ncollected in a list and only at the end of the (successful) transaction\nare they added to the view system.\n\n### Namespaces\n\nNamespaces can be specified in an additional options map as the last\nargument to `vexec!`. If you don't provide this, then a `nil` namespace\nis used for the hints sent to the view system.\n\n```clj\n(vexec! view-system db\n        [\"INSERT INTO people (type, first_name, last_name) VALUES (?, ?, ?)\"\n         \"student\" \"Foo\" \"Bar\"]\n        {:namespace :my-namespace)\n```\n\n\n## Hints\n\nHints for the view system are automatically determined from the SQL\nqueries being used in the view functions and from `vexec!` calls by\nanalyzing the SQL and figuring out what tables are being queried from\nor changed. All you need to do is write the SQL.\n\nThe hints themselves are simply SQL table names represented as \nkeywords, e.g. `:people` for the \"people\" table. Hints are considered\nrelevant to a SQL view if the list of tables being queried from in the\nview's SELECT statement have at least some matches against the hints\nbeing compared against.\n\nThis SQL query analysis is done via [JSqlParser][6]. The tables list\nobtained from the analysis is cached by views.sql so as to keep\nperformance as fast as possible.\n\n[6]: https://github.com/JSQLParser/JSqlParser\n\n\u003e Hints generated by views.sql are compatible with the hints generated\n\u003e by [views.honeysql][7], so you can easily mix-and-match these views\n\u003e within the same system and get view refreshes triggered as you would\n\u003e expect for both types of views.\n\n[7]: https://github.com/gered/views.honeysql\n\n### When a SQL Query Cannot Be Analyzed\n\nWhile [JSqlParser][8] is quite a capable library and is able to\ncorrectly parse many queries without any problems, it is unfortunately\nnot perfect (sad, but true). You may eventually run into some SQL query\nthat it cannot parse and an exception gets thrown during a view refresh\nor a call to `vexec!`.\n\n[8]: https://github.com/JSQLParser/JSqlParser\n\nYou can work around this by manually specifying hints appropriate to\nthe problematic SQL query in your view definition or `vexec!` call.\nDoing this will skip the use of JSqlParser entirely for the problem\nquery.\n\n```clj\n; manually specifying the hints: the list of tables this query uses\n(vsql/view :people-by-type db people-by-type-sql [:people])\n\n\n; and also for vexec! ...\n(vexec! view-system db\n        [\"INSERT INTO people (type, first_name, last_name) VALUES (?, ?, ?)\"\n         \"student\" \"Foo\" \"Bar\"]\n        [:people])\n```\n\n\u003e Be careful when doing this! If you later update your SQL query, make\n\u003e sure you also update the list of hints since they are not being\n\u003e determined automatically.\n\nJSqlParser is actively maintained however, so the number of cases where\nmanual hints need to be provided should decrease in the future. Usually\nyou will only run into this if you're using something outside of ANSI\nSQL (e.g. vendor-specific extensions, but even some of these are \nsupported).\n\nAnother alternative you may wish to consider is using \n[views.honeysql][9] for these problematic queries. Due to HoneySQL\nrepresenting SQL queries as Clojure data structures, when it comes time\nto analyzing queries to get the hints it doesn't run into the same SQL\nparsing problems that JSqlParser sometimes does.\n\n[9]: https://github.com/gered/views.honeysql\n\n#### Use of the RETURNING Clause with Problematic SQL Queries\n\nThis only pertains to `vexec!` of course, but if you need to run an \nINSERT/UPDATE/DELETE query which JSqlParser cannot parse and are forced\nto manually specify hints, you will also need to provide the \n`:returning?` option if the query uses a RETURNING clause.\n\nWhen you are forced to manually specify hints, it is because JSqlParser\nwas unable to parse the query. This also means we cannot rely on it's\nautomatic detection of a RETURNING clause so we need to help out \n`vexec!` by providing this information:\n\n```clj\n(vexec! view-system db\n        [\"INSERT INTO mytable (field1, field2, field3) \n          VALUES ('abc', 'xyz', 123) \n          RETURNING *\"]\n        [:mytable]\n        {:returning? true})\n```\n\n\u003e `vexec!` uses `clojure.java.jdbc/query` to run queries that have a \n\u003e RETURNING claus, and `clojure.java.jdbc/execute!` to run those \n\u003e without. Thus the need for `vexec!` to know about the presence of a \n\u003e RETURNING clause.\n\n\n## License\n\nCopyright © 2022 Gered King\n\nDistributed under the MIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgered%2Fviews.sql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgered%2Fviews.sql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgered%2Fviews.sql/lists"}