{"id":32179703,"url":"https://github.com/sixthnormal/pullql","last_synced_at":"2026-02-18T21:02:56.406Z","repository":{"id":57713572,"uuid":"148643523","full_name":"sixthnormal/pullql","owner":"sixthnormal","description":"A GraphQL-like query language for DataScript, optimized for execution across many entities at once.","archived":false,"fork":false,"pushed_at":"2020-01-28T13:31:13.000Z","size":39,"stargazers_count":63,"open_issues_count":0,"forks_count":3,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-10-21T21:24:59.170Z","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":"epl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sixthnormal.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":"2018-09-13T13:41:35.000Z","updated_at":"2025-10-04T19:05:07.000Z","dependencies_parsed_at":"2022-09-14T16:41:36.256Z","dependency_job_id":null,"html_url":"https://github.com/sixthnormal/pullql","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sixthnormal/pullql","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sixthnormal%2Fpullql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sixthnormal%2Fpullql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sixthnormal%2Fpullql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sixthnormal%2Fpullql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sixthnormal","download_url":"https://codeload.github.com/sixthnormal/pullql/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sixthnormal%2Fpullql/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29596125,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T20:59:56.587Z","status":"ssl_error","status_checked_at":"2026-02-18T20:58:41.434Z","response_time":162,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6: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":[],"created_at":"2025-10-21T21:13:00.733Z","updated_at":"2026-02-18T21:02:56.394Z","avatar_url":"https://github.com/sixthnormal.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PullQL\n\n[![Clojars Project](https://img.shields.io/clojars/v/com.sixthnormal/pullql.svg)](https://clojars.org/com.sixthnormal/pullql)\n[![cljdoc badge](https://cljdoc.org/badge/com.sixthnormal/pullql)](https://cljdoc.org/d/com.sixthnormal/pullql/CURRENT)\n\nDeclarative query languages like [GraphQL](https://graphql.org/) and\n[Datomic Pull](https://docs.datomic.com/on-prem/pull.html) are popular\nmeans of decoupling a normalized data model from its inherently\nhierarchical display in a typical web UI. PullQL is an alternative\nquery language for DataScript, that is optimized for this use case.\n\nPullQL extends Datomic pull queries in a few ways:\n\n- [x] Selections\n- [x] Multiple top-level queries via aliases\n- [x] Derived attributes\n- [x] Usable in Clojure and ClojureScript environments\n\nPullQL *should* work on Datomic databases as well.\n\n# Usage\n\nAll of the examples assume a given DataScript schema and some initial data.\n\n``` clojure\n(require '[datascript.core :as d])\n(require '[pullql.core :refer [pull-all]])\n\n(def schema\n  {:human/name      {}\n   :human/starships {:db/valueType   :db.type/ref\n                     :db/cardinality :db.cardinality/many}\n   :ship/name       {}\n   :ship/class      {}})\n\n(def data\n [{:human/name      \"Naomi Nagata\"\n   :human/starships [{:db/id -1 :ship/name \"Roci\" :ship/class :ship.class/fighter}\n                     {:ship/name \"Anubis\" :ship/class :ship.class/science-vessel}]}\n  {:human/name      \"Amos Burton\"\n   :human/starships [-1]}])\n\n(def db\n  (-\u003e (d/empty-db schema)\n      (d/db-with data)))\n```\n\n## Basics\n\nThe simplest possible PullQL query asks for one or more attributes across an entire database.\n\n``` clojure\n(pull-all db '[:human/name])\n\n;; =\u003e [#:human{:name \"Naomi Nagata\"} \n;;     #:human{:name \"Amos Burton\"}]\n```\n\n``` clojure\n(pull-all db '[:ship/name :ship/class])\n\n;; =\u003e [#:ship{:name \"Anubis\", :class :ship.class/science-vessel} \n;;     #:ship{:name \"Roci\", :class :ship.class/fighter}]\n```\n\nPulling a reference such as `:human/starships` resolves to a sequence of related entity ids.\n\n``` clojure\n(pull-all db '[:human/name :human/starships])\n\n;; =\u003e [#:human{:name \"Naomi Nagata\", :starships (3 2)}\n;;     #:human{:name \"Amos Burton\", :starships (2)}]\n```\n\nReferenced entities can be pulled recursively.\n\n``` clojure\n(pull-all db '[:human/name\n               {:human/starships [:ship/name\n                                  :ship/class]}])\n\n;; =\u003e [#:human{:name      \"Naomi Nagata\",\n;;             :starships (#:ship{:name  \"Anubis\",\n;;                                :class :ship.class/science-vessel}\n;;                         #:ship{:name  \"Roci\",\n;;                                :class :ship.class/fighter})}\n;;      #:human{:name \"Amos Burton\",\n;;              :starships (#:ship{:name \"Roci\", \n;;\t                           :class :ship.class/fighter})}]\n```\n\nJust like in Datomic and DataScript, references can be traversed in both directions. An underscore such as is in `:human/_starships` names the reverse relation.\n\n``` clojure\n(pull-all db '[:ship/name :human/_starships])\n\n;; =\u003e [{:ship/name \"Anubis\", :human/_starships 1}\n;;     {:ship/name \"Roci\", :human/_starships 4}]\n```\n\n## Selections\n\nMost of the time we are interested in pulling only a subset of entities in the database. This is expressed by adding selective clauses to a PullQL query.\n\n``` clojure\n;; Pull ships for a specific human.\n(pull-all db '[[:human/name \"Naomi Nagata\"] \n               {:human/starships [:ship/name :ship/class]}])\n\n;; =\u003e [#:human{:name \"Naomi Nagata\", \n;;             :starships ({:ship/name \"Anubis\", \n;;                          :ship/class :ship.class/science-vessel}\n;;                         {:ship/name \"Roci\",\n;;                          :ship/class :ship.class/fighter})}]\n\n;; Pull all humans, but include only ships of the fighter class.\n(pull-all db '[:human/name\n               {:human/starships [:ship/name\n                                  [:ship/class :ship.class/fighter]]}])\n\n;; =\u003e [#:human{:name \"Naomi Nagata\", \n;;             :starships (#:ship{:name \"Roci\", :class :ship.class/fighter})}\n;;     #:human{:name \"Amos Burton\", \n;;             :starships (#:ship{:name \"Roci\", :class :ship.class/fighter})}]\n```\n\nWe can use the wildcard symbol `_` whenever the precise value of an attribute is not relevant, as long as the entity is guaranteed to have *some* value associated for it.\n\n``` clojure\n(pull-all db '[[:constellation/name _]\n               {:constellation/scenario [:scenario/name]}])\n\n(pull-all db '[:human/name\n               {:human/starships [:ship/name\n                                  [:ship/class _]]}])\n```\n\n## Aliases\n\nMultiple pull expressions can be sent as a single query using aliases.\n\n``` clojure\n(pull-all db '{:ship-detail [[:ship/name \"Roci\"] :ship/class]\n               :all-classes [[:ship/class _]]})\n\t       \n;; =\u003e {:ship-detail [#:ship{:name \"Roci\", :class :ship.class/fighter}], \n;;     :all-classes [#:ship{:class :ship.class/science-vessel}\n;;                   #:ship{:class :ship.class/fighter}]}\n```\n\n## Derived Attributes\n\nDerived attributes are computed on-the-fly when needed, rather than being stored\npermanently. A similar implementation is explained in [1]. \n\nDerived attributes must be annotated as `:db/valueType :db.type/derived` in the schema. \n\n``` clojure\n(def schema \n  {:human/name      {}\n   :human/starships {:db/valueType   :db.type/ref\n                     :db/cardinality :db.cardinality/many}\n\t\t     \n   :ship/name       {}\n   :ship/class      {}\n   :ship/price      {:db/derived true}})\n```\n\nImplementations for any derived attributes must be provided via a function or multimethod. This function will be called with the name of the derived attribute, the current database value, and sets of entities and values to filter for. The function is expected to return datoms that will be incorporated into the result set, just as if they were stored in the database.\n\nMost derivations follow the same schema: query a few existing (materialized) entities, compute some function of each one, and wrap the results in datoms. The `derive-from-query` helper encapsulates this pattern.\n\n``` clojure\n(require '[pullql.core :refer [derive-from-query]])\n\n(defn read [attr db eids values]\n  (case attr\n    :ship/price (derive-from-query :ship/price\n                                   [:db/id :ship/class]\n                                   (fn [ship]\n                                     (case (:ship/class ship)\n                                       :ship.class/fighter        1000\n                                       :ship.class/science-vessel 5000))\n                                   db eids values)\n    []))\n```\n\nWith the `read` function defined, we can start using derived attributes in queries. Note how the `read` function must be passed to `pull-all` now.\n\n``` clojure\n(pull-all db '[:ship/name :ship/price] read)\n\n;; =\u003e [#:ship{:name \"Anubis\", :price 5000} \n;;     #:ship{:name \"Roci\", :price 1000}]\n\n(pull-all db '[{:human/starships [:ship/name :ship/price]}] read)\n\n;; =\u003e [#:human{:starships (#:ship{:name \"Anubis\", :price 5000}\n;;                         #:ship{:name \"Roci\", :price 1000})}\n;;     #:human{:starships (#:ship{:name \"Roci\", :price 1000})}]\n```\n\nSelective clauses can be put on derived attributes as well.\n\n``` clojure\n(pull-all db '[:ship/name [:ship/price 1000]] read)\n\n;; =\u003e [#:ship{:name \"Roci\", :price 1000}]\n```\n\n## Sources\n\nA previous iteration of this language is described in detail in [0].\n\n- [0][https://www.nikolasgoebel.com/2018/06/26/a-query-language.html]\n- [1][http://www.nikolasgoebel.com/2018/03/25/derived-attributes-datascript.html]\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsixthnormal%2Fpullql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsixthnormal%2Fpullql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsixthnormal%2Fpullql/lists"}