├── .circleci └── config.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.adoc ├── assets ├── eql_logo.svg └── eql_logo_github.png ├── deps.edn ├── docs-dev.yml ├── docs-src ├── antora.yml └── modules │ └── ROOT │ ├── nav.adoc │ └── pages │ ├── clojure-specs.adoc │ ├── library.adoc │ ├── specification.adoc │ └── what-is-eql.adoc ├── docs.yml ├── docs ├── .nojekyll ├── 404.html ├── CNAME ├── assets │ ├── css │ │ ├── site.css │ │ └── vendor │ │ │ └── docsearch.css │ ├── font │ │ ├── gotham-bold.woff │ │ ├── gotham-book.woff │ │ ├── gotham-light.woff │ │ ├── gotham-medium.woff │ │ ├── roboto-mono-latin-400.woff │ │ ├── roboto-mono-latin-400.woff2 │ │ ├── roboto-mono-latin-500.woff │ │ └── roboto-mono-latin-500.woff2 │ ├── img │ │ ├── back.svg │ │ ├── caret-down.svg │ │ ├── caution.svg │ │ ├── close.svg │ │ ├── edit.svg │ │ ├── favicon.ico │ │ ├── important.svg │ │ ├── logo.svg │ │ ├── menu.svg │ │ ├── note.svg │ │ ├── search-close.svg │ │ ├── search.svg │ │ ├── tip.svg │ │ └── warning.svg │ └── js │ │ ├── site.js │ │ └── vendor │ │ ├── docsearch.js │ │ ├── feedback.js │ │ ├── highlight.js │ │ ├── jquery.js │ │ └── mark.js ├── eql │ └── 1.0.0 │ │ ├── clojure-specs.html │ │ ├── library.html │ │ ├── specification.html │ │ └── what-is-eql.html ├── index.html └── sitemap.xml ├── project.clj ├── script └── release ├── src └── edn_query_language │ ├── core.cljc │ └── gen.cljc └── test └── edn_query_language └── core_test.cljc /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Clojure CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-clojure/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | - image: circleci/clojure:tools-deps 10 | 11 | working_directory: ~/repo 12 | 13 | environment: 14 | LEIN_ROOT: "true" 15 | JVM_OPTS: -Xmx3200m 16 | 17 | steps: 18 | - checkout 19 | 20 | # Download and cache dependencies 21 | - restore_cache: 22 | keys: 23 | - v1-dependencies-{{ checksum "deps.edn" }} 24 | 25 | - run: clojure -R:test -e "" 26 | 27 | - save_cache: 28 | paths: 29 | - ~/.m2 30 | key: v1-dependencies-{{ checksum "deps.edn" }} 31 | 32 | - run: clojure -A:test 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | pom.xml 3 | target 4 | .cpcache 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2021.07.18] 4 | - `merge-ast` support arities 0 and 1 to work as a reducing function 5 | 6 | ## [2021.02.28] 7 | - Add `update-child` helper 8 | - Add `update-recursive-depth` helper 9 | 10 | ## [1.0.2] 11 | - In the specs for AST, :query is now optional 12 | 13 | ## [1.0.0] 14 | - Generative features moved to `edn-query-language.core` to avoid requiring test.check for basic operations 15 | 16 | ## [0.0.10] 17 | - `ast->query` always returns a query, this is a bug fix but if you relied on the bad behvior this may be a breaking change, if that's the case replace your call to `ast->query` with `ast->expr` 18 | 19 | ## [0.0.9] 20 | - Add helper to mask queries. 21 | 22 | ## [0.0.8] 23 | - Make out of `query->ast1` nilable 24 | 25 | ## [0.0.7] 26 | - Fix specs for `query->ast1` and `ast->query` 27 | 28 | ## [0.0.6] 29 | - Removed `::eql/key` spec, that was a leftover from porting, `::eql/join-key` is the correct one to use. 30 | 31 | ## [0.0.5] 32 | - focus-subquery* is public 33 | - support removing specs from Clojurescript build 34 | 35 | ## [0.0.4] 36 | - Add `eql/query->shallow-ast` 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/eql_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /assets/eql_logo_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edn-query-language/eql/b24c96d9af23e508df3f04ba54910e996dfb7694/assets/eql_logo_github.png -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths 2 | ["src"] 3 | 4 | :deps 5 | {} 6 | 7 | :aliases 8 | {:provided 9 | {:extra-deps {org.clojure/clojure {:mvn/version "1.9.0"} 10 | org.clojure/clojurescript {:mvn/version "1.10.339"} 11 | org.clojure/test.check {:mvn/version "1.0.0"}}} 12 | 13 | :test 14 | {:extra-paths ["test"] 15 | :extra-deps {com.cognitect/test-runner {:git/url "https://github.com/cognitect-labs/test-runner.git" 16 | :sha "5fb4fc46ad0bf2e0ce45eba5b9117a2e89166479"}} 17 | :main-opts ["-m" "cognitect.test-runner"]}}} 18 | -------------------------------------------------------------------------------- /docs-dev.yml: -------------------------------------------------------------------------------- 1 | site: 2 | title: EDN Query Language 3 | url: http://127.0.0.1:8080 4 | start_page: eql::what-is-eql.adoc 5 | keys: 6 | google_analytics: UA-3833116-19 7 | content: 8 | sources: 9 | - url: . 10 | branches: HEAD 11 | start_path: docs-src 12 | ui: 13 | bundle: 14 | url: /Users/wilker.lucio/Development/third-part/docs-ui/build/ui-bundle.zip 15 | snapshot: true 16 | output_dir: assets 17 | output: 18 | dir: ./docs 19 | asciidoc: 20 | attributes: 21 | experimental: '' 22 | tabs: tabs 23 | toc: ~ 24 | xrefstyle: short 25 | -------------------------------------------------------------------------------- /docs-src/antora.yml: -------------------------------------------------------------------------------- 1 | name: eql 2 | title: EQL 3 | version: 1.0.0 4 | start_page: ROOT:what-is-eql.adoc 5 | nav: 6 | - modules/ROOT/nav.adoc 7 | -------------------------------------------------------------------------------- /docs-src/modules/ROOT/nav.adoc: -------------------------------------------------------------------------------- 1 | * xref:what-is-eql.adoc[What is EQL?] 2 | * xref:specification.adoc[Specification] 3 | * xref:clojure-specs.adoc[Clojure Specs] 4 | * xref:library.adoc[Library] 5 | -------------------------------------------------------------------------------- /docs-src/modules/ROOT/pages/clojure-specs.adoc: -------------------------------------------------------------------------------- 1 | = Clojure Specs 2 | 3 | Here you can find a formal definition for the query syntax, expressed as Clojure Specs. 4 | 5 | [source,clojure] 6 | ---- 7 | (s/def ::property keyword?) 8 | (s/def ::special-property #{'*}) 9 | (s/def ::ident-value (s/with-gen any? (default-gen ::gen-ident-value))) 10 | (s/def ::ident (s/with-gen (s/tuple ::property ::ident-value) (default-gen ::gen-ident))) 11 | (s/def ::join-key (s/or :prop ::property, :ident ::ident, :param-exp ::join-key-param-expr)) 12 | (s/def ::join (s/map-of ::join-key ::join-query, :count 1, :conform-keys true)) 13 | (s/def ::union (s/map-of ::property ::query, :min-count 1, :conform-keys true)) 14 | (s/def ::recursion-depth (s/with-gen nat-int? (default-gen ::gen-depth))) 15 | (s/def ::recursion (s/or :depth ::recursion-depth, :unbounded #{'...})) 16 | 17 | (s/def ::join-query 18 | (s/with-gen 19 | (s/or :query ::query 20 | :union ::union 21 | :recursion ::recursion) 22 | (default-gen ::gen-join-query))) 23 | 24 | (s/def ::params 25 | (s/with-gen map? (default-gen ::gen-params))) 26 | 27 | (s/def ::param-expr-key 28 | (s/with-gen 29 | (s/or :prop ::property 30 | :join ::join 31 | :ident ::ident) 32 | (default-gen ::gen-param-expr-key))) 33 | 34 | (s/def ::param-expr 35 | (s/with-gen 36 | (s/and seq? (s/cat :expr ::param-expr-key :params (s/? ::params))) 37 | (default-gen ::gen-param-expr))) 38 | 39 | (s/def ::join-key-param-key (s/or :prop ::property :ident ::ident)) 40 | 41 | (s/def ::join-key-param-expr 42 | (s/with-gen 43 | (s/and seq? (s/cat :expr ::join-key-param-key :params (s/? ::params))) 44 | (default-gen ::gen-join-key-param-expr))) 45 | 46 | (s/def ::mutation-key (s/with-gen symbol? (default-gen ::gen-mutation-key))) 47 | 48 | (s/def ::mutation-expr 49 | (s/with-gen 50 | (s/and seq? (s/cat :mutate-key ::mutation-key :params (s/? ::params))) 51 | (default-gen ::gen-mutation-expr))) 52 | 53 | (s/def ::mutation-join 54 | (s/map-of ::mutation-expr ::query :count 1 :conform-keys true)) 55 | 56 | (s/def ::mutation 57 | (s/or :mutation ::mutation-expr 58 | :mutation-join ::mutation-join)) 59 | 60 | (s/def ::query-expr 61 | (s/or :prop ::property 62 | :join ::join 63 | :ident ::ident 64 | :mutation ::mutation 65 | :param-exp ::param-expr 66 | :special ::special-property)) 67 | 68 | (s/def ::query 69 | (s/coll-of ::query-expr :kind vector? :gen (default-gen ::gen-query))) 70 | ---- 71 | -------------------------------------------------------------------------------- /docs-src/modules/ROOT/pages/library.adoc: -------------------------------------------------------------------------------- 1 | = Library 2 | 3 | The package `edn-query-language.core` provides a suite of specs to validate queries and 4 | ASTs. It also provides generators for the query and helper functions to common 5 | query operations. 6 | 7 | == Clojure Specs 8 | 9 | The EQL library provides specs to validate and generate queries. 10 | 11 | === Validation 12 | 13 | You can validate the query syntax using link:https://clojure.org/guides/spec[clojure.spec], here is an example: 14 | 15 | [source,clojure] 16 | ---- 17 | (s/valid? ::eql/query [:sample :query]) ; => true 18 | (s/valid? ::eql/query [#{:set}]) ; => false 19 | (s/valid? ::eql/query ['(call/op {})]) ; => true 20 | ---- 21 | 22 | NOTE: `s` is alias for `clojure.spec.alpha` 23 | 24 | You can use spec explain feature for more details: 25 | 26 | [source,clojure] 27 | ---- 28 | (s/explain ::eql/query [#{:set}]) 29 | ; In: [0] val: #{:set} fails spec: :edn-query-language.core/mutation-expr at: [:mutation :mutation] predicate: seq? 30 | ; In: [0] val: #{:set} fails spec: :edn-query-language.core/mutation-join at: [:mutation :mutation-join] predicate: map? 31 | ; In: [0] val: #{:set} fails spec: :edn-query-language.core/property at: [:prop] predicate: keyword? 32 | ; In: [0] val: #{:set} fails spec: :edn-query-language.core/join at: [:join] predicate: map? 33 | ; In: [0] val: #{:set} fails spec: :edn-query-language.core/ident at: [:ident] predicate: vector? 34 | ; In: [0] val: #{:set} fails spec: :edn-query-language.core/param-expr at: [:param-exp] predicate: seq? 35 | ; In: [0] val: #{:set} fails spec: :edn-query-language.core/special-property at: [:special] predicate: #{(quote *)} 36 | ---- 37 | 38 | I suggest you check the link:https://github.com/edn-query-language/eql/blob/master/src/edn_query_language/core.cljc#L133-L196[sources for the specs] for more details on parts that compose 39 | it, they will stay consistent and can be used to validate parts of the transaction as well. 40 | 41 | === Generation 42 | 43 | EQL also provides built-in generators, the main intended usage for it is to write generative 44 | tests for parser implementations. 45 | 46 | Basic example to generate random queries: 47 | 48 | [source,clojure] 49 | ---- 50 | (gen/sample (s/gen ::query) 10) 51 | => 52 | ([] 53 | [] 54 | [(:?./*_ {}) :z/ZH] 55 | [] 56 | [#:J{:w {:c/!V [#:YY{:u [:u1/X?! 57 | #:r94{:*+ [#:aG{:YA 2} :t!o/Ya1 :XL/HR #:!-Q{:b_ []}]} 58 | :OP/E]} 59 | :.qE/Nd-], 60 | :j./!T [[:p/h*y :f?1] 61 | #:s*{:-W []} 62 | (NG_ 63 | {[] #{}, [4] (0.5 :_ -3 -Ch), #{} #{}, #{-1 {##-Inf ?.1/e?A}} {}})], 64 | :z/s+ []}} 65 | :-_/_ 66 | :H/E 67 | :Y/xD] 68 | [:?7/w :iO/! (:r/!N {{-2.0 false} [], [] [], [:P7] [0 J1]})] 69 | [:+Bi/-K :!8*/r0 :?/Cio] 70 | [:*.-/R* :+BT/W :-l8/c :Ih/V [:RE/- "0>WwI`u"] :H/vT] 71 | [:z+8/g] 72 | []) 73 | ---- 74 | 75 | NOTE: `gen` is alias for `clojure.test.check.generators` 76 | 77 | Although fully random queries can be interesting to test some parser edge cases, in many 78 | situations you will may want to constraint how the query is generated, with this in mind 79 | EQL provides a way to enable this kind of customization. To get a sense of what you can 80 | customize link:https://github.com/edn-query-language/eql/blob/master/src/edn_query_language/core.cljc#L10-L121[you can take a look at the default implementation for each default generator], 81 | any of those keys can be tuned to constraint how the query is generated. 82 | 83 | To demonstrate how to use this, let's customize the generator to limit the properties it generates 84 | to a fixed set we pre defined: 85 | 86 | [source,clojure] 87 | ---- 88 | (gen/sample (eql/make-gen {::eql/gen-property ; <1> 89 | (fn [_] (gen/elements [:id :name :title :foo :bar]))} 90 | ::eql/gen-query) ; <2> 91 | 10) 92 | => 93 | ([] 94 | [] 95 | [] 96 | [[:X/q6 1] :name :title] 97 | [({:title [(L {#{} [], () [], #{-5} ()}) 98 | (:name {{#{} {}} :., {} {}}) 99 | {:name [:bar :title]}]} 100 | {[*+-] #{0.5625 #uuid"edf051fb-ab28-42d0-a941-152c4e87b060"}, 101 | #{#uuid"712e7415-5148-400b-99db-cfb79004700e" -1/2} (), 102 | {} (:F/le9 #uuid"5ad52713-d13a-4888-bd92-2d1541c0387b" "" true)}) 103 | {(:foo 104 | {[(2.0 false) z/NO] [I./j #uuid"eef64a1d-8055-4ae7-95be-06bdc4f9cefd"], {} [""]}) [:id 105 | ({:id [:name 106 | *]} 107 | {})]}] 108 | [:id :id] 109 | [{:foo [:name * [:mO/D MZ_/e0Z] :bar :foo]}] 110 | [] 111 | [:bar] 112 | [:foo]) 113 | ---- 114 | 115 | <1> We send a map to `eql/make-gen` to override some of the generator settings, any non 116 | defined keys will fallback to default implementation 117 | <2> Select which generator to use, this is useful to generate only sub-parts if needed 118 | 119 | One more example changing many definitions: 120 | 121 | [source,clojure] 122 | ---- 123 | (let [system (assoc generators 124 | ::gen-params 125 | (fn [_] (gen/map (gen/elements [:param :foo/param]) gen/string-ascii)) 126 | 127 | ::gen-property 128 | (fn [_] (gen/elements [:id :name :title :foo :bar :other :price :namespaced/value])) 129 | 130 | ::gen-ident-key 131 | (fn [_] (gen/elements [:user/by-id :other/by-id])) 132 | 133 | ::gen-ident-value 134 | (fn [_] gen/string-ascii) 135 | 136 | ::gen-mutation-key 137 | (fn [_] (gen/elements '[do-something create/this-thing operation.on/space])))] 138 | (gen/sample ((::gen-query system) system))) 139 | => 140 | ([] 141 | [{:other []}] 142 | [] 143 | [] 144 | [] 145 | [{:price [{[:user/by-id "!"] []} :title]} :id] 146 | [:bar {[:other/by-id "@"] [:foo :other :name]}] 147 | [:name :id] 148 | [:price :title :id :name] 149 | [:foo 150 | ({:bar [[:user/by-id ""] :price {:id [:other]} :other]} {}) 151 | :other 152 | :namespaced/value 153 | {:name [:name 154 | {:bar [:name 155 | :bar 156 | :namespaced/value 157 | ({[:user/by-id "AeA$;"] [:foo]} 158 | {:foo/param "_+y9ihY", :param "Y@p5Bd5B"}) 159 | :id 160 | :namespaced/value 161 | :name]}]} 162 | :id]) 163 | ---- 164 | 165 | If you wanna see an even more advanced usage, you can check link:https://github.com/wilkerlucio/pathom/blob/master/src/com/wsscode/pathom/connect/gen.cljc[Pathom connect generator], which 166 | uses the Pathom connect index to generate queries that are valid according to the user property graph. 167 | 168 | === Removing specs on Clojurescript 169 | 170 | If you are not using the specs provided by EQL you can free some build space by 171 | eliding then. To do that you need to set the Clojurescript compiler options with: 172 | 173 | [source,clojure] 174 | ---- 175 | {:closure-defines {edn-query-language.core.INCLUDE_SPECS false}} 176 | ---- 177 | 178 | == AST Encode/Decode 179 | 180 | To convert between query and AST, EQL provides the helper functions `eql/query->ast` and 181 | `eql/ast->query`. Here are some example usages: 182 | 183 | [source,clojure] 184 | ---- 185 | (eql/query->ast [:foo]) 186 | ; => {:type :root, :children [{:type :prop, :dispatch-key :foo, :key :foo}]} 187 | 188 | (eql/ast->query {:type :root, :children [{:type :prop, :dispatch-key :foo, :key :foo}]}) 189 | ; => [:foo] 190 | ---- 191 | 192 | == API Docs 193 | 194 | Check the complete API docs at link:https://cljdoc.org/d/edn-query-language/eql/CURRENT/api/edn-query-language.core[EQL cljdoc page]. 195 | -------------------------------------------------------------------------------- /docs-src/modules/ROOT/pages/specification.adoc: -------------------------------------------------------------------------------- 1 | = Specification 2 | 3 | The following sections explain the features provided by the EQL syntax and the 4 | common usages of it. Along with code examples from the transactions, after that, we 5 | are going to provide the AST data that's equivalent to the transaction, the AST is the 6 | recommended format to transform EQL transactions programmatically. The EQL format is 7 | great for users and interfaces, but to transform is easier if you don't have to deal 8 | with the syntax details/ambiguities. The EQL library provides functions to convert from and to 9 | AST from EQL. 10 | 11 | In the end of this section you will also find the Clojure Spec formal specification 12 | for the EQL syntax. 13 | 14 | == Query / Transactions 15 | 16 | An EQL transaction is represented by an EDN vector. 17 | 18 | Examples: 19 | 20 | [source,clojure] 21 | ---- 22 | [] ; empty transaction 23 | 24 | ; ast 25 | 26 | {:type :root, :children []} 27 | ---- 28 | 29 | A transaction that only contains reads is commonly called a `query`, but notice that 30 | at the syntax level, it has no difference. 31 | 32 | == Properties 33 | 34 | Properties in `EQL` are expressed as Clojure keywords; they can be simple or qualified 35 | keywords, and they express the property been requested. 36 | 37 | Examples: 38 | 39 | [source,clojure] 40 | ---- 41 | [:album/name :album/year] 42 | 43 | ; ast 44 | 45 | {:type :root 46 | :children [{:type :prop, :dispatch-key :album/name, :key :album/name} 47 | {:type :prop, :dispatch-key :album/year, :key :album/year}]} 48 | ---- 49 | 50 | == Joins 51 | 52 | Joins are used to describe nesting in the request transaction. They are represented as 53 | EDN maps, *always with a single entry*, the entry key is the property to join on, and the 54 | entry value is a sub-query to run. 55 | 56 | Examples: 57 | 58 | [source,clojure] 59 | ---- 60 | [{:favorite-albums 61 | [:album/name :album/year]}] 62 | 63 | ; ast 64 | 65 | {:type :root 66 | :children [{:type :join 67 | :dispatch-key :favorite-albums 68 | :key :favorite-albums 69 | :query [:album/name :album/year] 70 | :children [{:type :prop, :dispatch-key :album/name, :key :album/name} 71 | {:type :prop, :dispatch-key :album/year, :key :album/year}]}]} 72 | ---- 73 | 74 | Nested joins example: 75 | 76 | [source,clojure] 77 | ---- 78 | [{:favorite-albums 79 | [:album/name :album/year 80 | {:album/tracks 81 | [:track/name 82 | :track/duration]}]}] 83 | 84 | ; ast 85 | 86 | {:type :root 87 | :children 88 | [{:type :join 89 | :dispatch-key :favorite-albums 90 | :key :favorite-albums 91 | 92 | :query [:album/name 93 | :album/year 94 | {:album/tracks [:track/name :track/duration]}] 95 | 96 | :children [{:type :prop, :dispatch-key :album/name, :key :album/name} 97 | {:type :prop, :dispatch-key :album/year, :key :album/year} 98 | {:type :join 99 | :dispatch-key :album/tracks 100 | :key :album/tracks 101 | :query [:track/name :track/duration] 102 | :children [{:type :prop, :dispatch-key :track/name, :key :track/name} 103 | {:type :prop 104 | :dispatch-key :track/duration 105 | :key :track/duration}]}]}]} 106 | ---- 107 | 108 | == Idents 109 | 110 | Idents are represented by a vector with two elements, where the first is a keyword and 111 | the second can be anything. They are like link:http://blog.datomic.com/2014/02/datomic-lookup-refs.html[lookup refs on Datomic], 112 | in general, they can provide an address-like thing, and their use and semantic might 113 | vary from system to system. 114 | 115 | Examples: 116 | 117 | [source,clojure] 118 | ---- 119 | [[:customer/id 123]] 120 | 121 | ; ast 122 | 123 | {:type :root 124 | :children [{:type :prop, :dispatch-key :customer/id, :key [:customer/id 123]}]} 125 | ---- 126 | 127 | Note that this time in the AST the `:dispatch-key` and `:key` got different values this 128 | time, the `:dispatch-key` been just the `ident key` while the `:key` contains the 129 | full thing. 130 | 131 | It's common to use an ident as a join key to start a query for some entity: 132 | 133 | [source,clojure] 134 | ---- 135 | [{[:customer/id 123] 136 | [:customer/name :customer/email]}] 137 | 138 | ; ast 139 | 140 | {:type :root 141 | :children [{:type :join 142 | :dispatch-key :customer/id 143 | :key [:customer/id 123] 144 | :query [:customer/name :customer/email] 145 | :children [{:type :prop, :dispatch-key :customer/name, :key :customer/name} 146 | {:type :prop 147 | :dispatch-key :customer/email 148 | :key :customer/email}]}]} 149 | ---- 150 | 151 | == Parameters 152 | 153 | EQL properties, joins, and idents have support for parametrization. This allows the 154 | query to provide an extra dimension of information about the requested data. A parameter 155 | is expressed by wrapping the thing with an EDN list, like so: 156 | 157 | [source,clojure] 158 | ---- 159 | ; without params 160 | [:foo] 161 | 162 | ; with params 163 | [(:foo {:with "params"})] 164 | 165 | ; ast 166 | 167 | {:type :root 168 | :children [{:type :prop 169 | :dispatch-key :foo 170 | :key :foo 171 | :params {:with "params"} 172 | :meta {:line 1, :column 15}}]} 173 | ---- 174 | 175 | Note on the AST side it gets a new `:params` key. Params *must* always be maps, the 176 | map values can be anything. Here are more examples of parameterizing queries: 177 | 178 | [source,clojure] 179 | ---- 180 | ; ident with params 181 | 182 | [([:ident "value"] {:with "param"})] 183 | 184 | {:type :root 185 | :children [{:type :prop 186 | :dispatch-key :ident 187 | :key [:ident "value"] 188 | :params {:with "param"} 189 | :meta {:line 1, :column 15}}]} 190 | 191 | ; join with params wrap the key with the list 192 | 193 | [{(:join-key {:with "params"}) 194 | [:sub-query]}] 195 | 196 | {:type :root 197 | :children [{:type :join 198 | :dispatch-key :join-key 199 | :key :join-key 200 | :params {:with "params"} 201 | :meta {:line 1, :column 16} 202 | :query [:sub-query] 203 | :children [{:type :prop 204 | :dispatch-key :sub-query 205 | :key :sub-query}]}]} 206 | 207 | ; ident join with params 208 | 209 | [{([:ident "value"] {:with "params"}) 210 | [:sub-query]}] 211 | 212 | {:type :root 213 | :children [{:type :join 214 | :dispatch-key :ident 215 | :key [:ident "value"] 216 | :params {:with "params"} 217 | :meta {:line 1 :column 16} 218 | :query [:sub-query] 219 | :children [{:type :prop 220 | :dispatch-key :sub-query 221 | :key :sub-query}]}]} 222 | 223 | ; alternate syntax to add params on joins (wrap the entire map, AST result is the same) 224 | 225 | [({:join-key 226 | [:sub-query]} 227 | {:with "params"})] 228 | 229 | {:type :root 230 | :children [{:type :join 231 | :dispatch-key :join-key 232 | :key :join-key 233 | :params {:with "params"} 234 | :meta {:line 1, :column 16} 235 | :query [:sub-query] 236 | :children [{:type :prop 237 | :dispatch-key :sub-query 238 | :key :sub-query}]}]} 239 | ---- 240 | 241 | WARNING: You'll need to use quote and unquote in CLJ files for calls, otherwise the lists will be evaluated as Clojure calls. Quote is not necessary in EDN files. 242 | 243 | == Recursive Queries 244 | 245 | EQL supports the concept of recursive queries. For example, imagine the scenario of trying to load a structure 246 | like a file system, where folders recursively contain folders. 247 | 248 | [source,clojure] 249 | ---- 250 | ; this is an unbounded recursive query, use ... as the join value 251 | 252 | [:entry/name {:entry/folders ...}] 253 | 254 | {:type :root, 255 | :children 256 | [{:type :prop, :dispatch-key :entry/name, :key :entry/name} 257 | {:type :join, 258 | :dispatch-key :entry/folders, 259 | :key :entry/folders, 260 | :query ...}]} 261 | 262 | ; you can bound the recursion limit using a number as a sub-query 263 | 264 | [:entry/name {:entry/folders 3}] 265 | 266 | {:type :root, 267 | :children 268 | [{:type :prop, :dispatch-key :entry/name, :key :entry/name} 269 | {:type :join, 270 | :dispatch-key :entry/folders, 271 | :key :entry/folders, 272 | :query 3}]} 273 | ---- 274 | 275 | IMPORTANT: it's up to the parser to properly implement the semantics around unbounded vs 276 | bounded recursive queries. 277 | 278 | == Query Meta 279 | 280 | Metadata can be stored on a query. The AST will encode the metadata so that transformations to/from an AST can preserve it. 281 | 282 | [source,clojure] 283 | ---- 284 | (with-meta [] {:meta "data"}) 285 | 286 | ; ast 287 | 288 | {:type :root, :children [], :meta {:meta "data"}} 289 | ---- 290 | 291 | == Unions 292 | 293 | In EQL unions are used to specify polymorphic requirements, that means depending on some 294 | condition a different query might be chosen to fulfill the requirements. For example, 295 | a messaging app may have a single list, and each entry on the chat log can be a `message`, 296 | `audio` or `photo`, each having its own query requirement. Here it is in code: 297 | 298 | [source,clojure] 299 | ---- 300 | ; message query 301 | [:message/id :message/text :chat.entry/timestamp] 302 | 303 | ; audio query 304 | [:audio/id :audio/url :audio/duration :chat.entry/timestamp] 305 | 306 | ; photo query 307 | [:photo/id :photo/url :photo/width :photo/height :chat.entry/timestamp] 308 | 309 | ; list query 310 | [{:chat/entries ???}] ; what goes there? 311 | ---- 312 | 313 | Now to express this polymorphic requirement as the sub-query of the `:chat/entries` list 314 | we can use a map as the join value, and each entry on this map represents a possible 315 | sub-query. The way this information is used is up to the parser implementation; EQL only 316 | defines the syntax. Here are some examples of how it could be written: 317 | 318 | [source,clojure] 319 | ---- 320 | ; in this example, the selection is made by looking if the processed entry contains 321 | ; some value on the key used for its selection 322 | [{:chat/entries 323 | {:message/id [:message/id :message/text :chat.entry/timestamp] 324 | :audio/id [:audio/id :audio/url :audio/duration :chat.entry/timestamp] 325 | :photo/id [:photo/id :photo/url :photo/width :photo/height :chat.entry/timestamp]}}] 326 | 327 | ; in this case, we give a type name and use as the key, this usually requires some 328 | ; out of band configuration to know how to pull this data from each entry to use 329 | ; as the comparison 330 | [{:chat/entries 331 | {:entry.type/message [:message/id :message/text :chat.entry/timestamp] 332 | :entry.type/audio [:audio/id :audio/url :audio/duration :chat.entry/timestamp] 333 | :entry.type/photo [:photo/id :photo/url :photo/width :photo/height :chat.entry/timestamp]}}] 334 | 335 | ; ast for the first example 336 | 337 | {:type :root 338 | :children 339 | [{:type :join 340 | :dispatch-key :chat/entries 341 | :key :chat/entries 342 | :query {:message/id [:message/id :message/text :chat.entry/timestamp] 343 | :audio/id [:audio/id :audio/url :audio/duration :chat.entry/timestamp] 344 | :photo/id [:photo/id 345 | :photo/url 346 | :photo/width 347 | :photo/height 348 | :chat.entry/timestamp]} 349 | :children [{:type :union 350 | :query 351 | {:message/id [:message/id :message/text :chat.entry/timestamp] 352 | :audio/id [:audio/id :audio/url :audio/duration :chat.entry/timestamp] 353 | :photo/id [:photo/id 354 | :photo/url 355 | :photo/width 356 | :photo/height 357 | :chat.entry/timestamp]} 358 | :children 359 | [{:type :union-entry 360 | :union-key :message/id 361 | :query [:message/id :message/text :chat.entry/timestamp] 362 | :children [{:type :prop, :dispatch-key :message/id, :key :message/id} 363 | {:type :prop, :dispatch-key :message/text, :key :message/text} 364 | {:type :prop 365 | :dispatch-key :chat.entry/timestamp 366 | :key :chat.entry/timestamp}]} 367 | {:type :union-entry 368 | :union-key :audio/id 369 | :query [:audio/id :audio/url :audio/duration :chat.entry/timestamp] 370 | :children [{:type :prop, :dispatch-key :audio/id, :key :audio/id} 371 | {:type :prop, :dispatch-key :audio/url, :key :audio/url} 372 | {:type :prop 373 | :dispatch-key :audio/duration 374 | :key :audio/duration} 375 | {:type :prop 376 | :dispatch-key :chat.entry/timestamp 377 | :key :chat.entry/timestamp}]} 378 | {:type :union-entry 379 | :union-key :photo/id 380 | :query [:photo/id 381 | :photo/url 382 | :photo/width 383 | :photo/height 384 | :chat.entry/timestamp] 385 | :children [{:type :prop, :dispatch-key :photo/id, :key :photo/id} 386 | {:type :prop, :dispatch-key :photo/url, :key :photo/url} 387 | {:type :prop, :dispatch-key :photo/width, :key :photo/width} 388 | {:type :prop, :dispatch-key :photo/height, :key :photo/height} 389 | {:type :prop 390 | :dispatch-key :chat.entry/timestamp 391 | :key :chat.entry/timestamp}]}]}]}]} 392 | ---- 393 | 394 | == Mutations 395 | 396 | Mutations in EQL are used to represent operation calls, usually to do something that will 397 | cause a side effect. Mutations as data allows that operation to behave much like event 398 | sourcing, and can be transparently applied locally, across a network, onto an event bus, etc. 399 | 400 | A mutation is represented by a list of two elements; the first is the symbol 401 | that names the mutation, and the second is a map with input data. 402 | 403 | [source,clojure] 404 | ---- 405 | [(call.some/operation {:data "input"})] 406 | 407 | ; ast 408 | 409 | {:type :root 410 | :children 411 | [{:dispatch-key call.some/operation 412 | :key call.some/operation 413 | :params {:data "input"} 414 | :meta {:line 610, :column 17} 415 | :type :call}]} 416 | ---- 417 | 418 | NOTE: Mutations and parameters are very similar, their main difference 419 | is that one uses symbols as keys, and the other uses one of the read options (properties, 420 | idents, joins). 421 | 422 | The EQL notation does not technically limit the combination of expressions that contain 423 | both query and mutation elements; however, implementations of EQL processing may choose 424 | to make restrictions on these combinations in order to enforce particular semantics. 425 | 426 | === Mutation Joins 427 | 428 | A mutation may have a return value, and that return value can be a graph; therefore, it 429 | makes sense that EQL support the ability to describe what portion of the available returned 430 | graph should be returned. The support for mutation graph return values is done by combining 431 | the syntax of a join with the syntax of a mutation: 432 | 433 | [source,clojure] 434 | ---- 435 | [{(call.some/operation {:data "input"}) 436 | [:response :key-a :key-b]}] 437 | 438 | ; ast 439 | 440 | {:type :root 441 | :children 442 | [{:dispatch-key call.some/operation 443 | :key call.some/operation 444 | :params {:data "input"} 445 | :meta {:line 612 :column 18} 446 | :type :call 447 | :query [:response :key-a :key-b] 448 | :children [{:type :prop, :dispatch-key :response, :key :response} 449 | {:type :prop, :dispatch-key :key-a, :key :key-a} 450 | {:type :prop, :dispatch-key :key-b, :key :key-b}]}]} 451 | ---- 452 | -------------------------------------------------------------------------------- /docs-src/modules/ROOT/pages/what-is-eql.adoc: -------------------------------------------------------------------------------- 1 | image:https://raw.githubusercontent.com/edn-query-language/eql/main/assets/eql_logo.svg[] 2 | 3 | == What is EQL? 4 | 5 | EQL is a declarative way to make hierarchical (and possibly nested) selections of information about data requirements. 6 | 7 | EQL doesn't have its own language; it uses EDN to express the request, taking advantage of 8 | the rich set of primitives provided by it. 9 | 10 | == EQL for selections 11 | 12 | An easy way to get started is to think of a map and try to describe its shape. For example: 13 | 14 | [source,clojure] 15 | ---- 16 | {:album/name "Magical Mystery Tour" 17 | :album/year 1967} 18 | ---- 19 | 20 | By describing the shape, we mean describing the structure but without the values, the previous example can be described using EQL as: 21 | 22 | [source,clojure] 23 | ---- 24 | [:album/name :album/year] 25 | ---- 26 | 27 | Like using `select-keys` to specify which fields to extract from a map. Now let's see 28 | what it looks like for nested structures: 29 | 30 | [source,clojure] 31 | ---- 32 | {:album/name "Magical Mystery Tour" 33 | :album/artist {:artist/name "The Beatles"}} 34 | 35 | ; can be described as: 36 | 37 | [:album/name 38 | ; note the use of a map to express nesting 39 | {:album/artist 40 | [:artist/name]}] 41 | ---- 42 | 43 | It works the same to represent nested sequences: 44 | 45 | [source,clojure] 46 | ---- 47 | {:album/name "Magical Mystery Tour" 48 | :album/tracks [{:track/name "The Fool On The Hill"} 49 | {:track/name "All You Need Is Love"}]} 50 | 51 | ; can be described as: 52 | 53 | [:album/name 54 | {:album/tracks 55 | [:track/name]}] 56 | ---- 57 | 58 | TIP: Although with just EQL you can't know if a key value is expected to be a single item or a sequence, you 59 | can have this information setup out of band using Clojure specs. If you do so, you can instrospect the spec 60 | and detect that, this is not a feature of EQL in any way, just a suggested approach in case you need to know 61 | if the response of a key is a single item or a sequence. 62 | 63 | == EQL for operations 64 | 65 | EQL also supports `mutations`, which are like side effect calls to an API. Mutations can 66 | appear on EQL transaction, and they look like Clojure function calls, example: 67 | 68 | [source,clojure] 69 | ---- 70 | [(call-mutation {:data "value"})] 71 | ---- 72 | 73 | More details in xref:specification#_mutations[Mutations]. 74 | 75 | == Datomic Pull Syntax comparison 76 | 77 | On top of the link:https://docs.datomic.com/on-prem/pull.html[Datomic Pull Syntax] expression, EQL also supports: 78 | 79 | - xref:specification#_parameters[Parameters] 80 | - xref:specification#_mutations[Mutations] 81 | - xref:specification#_unions[Union Queries] 82 | 83 | Check the links on each for more details. 84 | 85 | The link:https://docs.datomic.com/on-prem/pull.html#attribute-with-options[attribute with options] feature 86 | from the Datomic Pull Syntax is not present in EQL; instead, we provide the parameterized 87 | attributes that can handle arbitrary data to go along the base property. 88 | 89 | == GraphQL comparison 90 | 91 | Similar to GraphQL, EQL works as a language to client libraries to communicate data requirements and operations, 92 | there is a good set of intersection in features between both, as: 93 | 94 | - a language to describe arbitrarily nested structures 95 | - support for mutations (operations to side effect the world) 96 | - support for parametrization 97 | - union queries for query branching (select query according to some custom definition based on the data) 98 | 99 | GraphQL has a type system in its definition, and it is required for a GraphQL system to work. EQL has 100 | no such thing, and it dictates only the syntax but not the semantics. Some features in 101 | GraphQL don't make sense in EQL, like fragments, since EQL is already a data format (EDN), 102 | it's easy to compose data as you would do in a regular Clojure program, for that reason 103 | many features are not necessary because EQL is a parseable data format with all the Clojure 104 | tools already available to operate on top of it. Also read the xref:library#_ast_encodedecode[AST Encode/Decode] 105 | section for more information on how to programmatically manipulate EQL data structures. 106 | -------------------------------------------------------------------------------- /docs.yml: -------------------------------------------------------------------------------- 1 | site: 2 | title: EDN Query Language 3 | url: http://edn-query-language.org 4 | start_page: eql::what-is-eql.adoc 5 | keys: 6 | google_analytics: UA-3833116-19 7 | content: 8 | sources: 9 | - url: git@github.com:edn-query-language/eql.git 10 | branches: HEAD 11 | start_path: docs-src 12 | ui: 13 | bundle: 14 | url: https://github.com/wilkerlucio/docs-ui/raw/wsscode-theme/build/ui-bundle.zip 15 | snapshot: true 16 | output_dir: assets 17 | runtime: 18 | fetch: true 19 | output: 20 | dir: ./docs 21 | asciidoc: 22 | attributes: 23 | experimental: '' 24 | tabs: tabs 25 | toc: ~ 26 | xrefstyle: short 27 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edn-query-language/eql/b24c96d9af23e508df3f04ba54910e996dfb7694/docs/.nojekyll -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | Page Not Found | EDN Query Language 17 | 18 | 19 | 20 | 21 | 22 |
23 | 36 |
37 |
38 |
39 |
40 |

Page Not Found

41 |
42 |

The page you're looking for does not exist. It may have been moved.

43 |
44 |
45 |

If you arrived on this page by clicking on a link, please notify the owner of the site that the link is broken. 46 | If you typed the URL of this page manually, please double check that you entered the address correctly.

47 |
48 |
49 |
50 |
51 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | edn-query-language.org -------------------------------------------------------------------------------- /docs/assets/css/vendor/docsearch.css: -------------------------------------------------------------------------------- 1 | .searchbox{display:inline-block;position:relative;width:200px;height:32px!important;white-space:nowrap;-webkit-box-sizing:border-box;box-sizing:border-box;visibility:visible!important}.searchbox .algolia-autocomplete{display:block;width:100%;height:100%}.searchbox__wrapper{width:100%;height:100%;z-index:999;position:relative}.searchbox__input{display:inline-block;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-transition:background .4s ease,-webkit-box-shadow .4s ease;transition:background .4s ease,-webkit-box-shadow .4s ease;transition:box-shadow .4s ease,background .4s ease;transition:box-shadow .4s ease,background .4s ease,-webkit-box-shadow .4s ease;border:0;border-radius:16px;-webkit-box-shadow:inset 0 0 0 1px #ccc;box-shadow:inset 0 0 0 1px #ccc;background:#fff!important;padding:0 26px 0 32px;width:100%;height:100%;vertical-align:middle;white-space:normal;font-size:12px;-webkit-appearance:none;-moz-appearance:none;appearance:none}.searchbox__input::-webkit-search-cancel-button,.searchbox__input::-webkit-search-decoration,.searchbox__input::-webkit-search-results-button,.searchbox__input::-webkit-search-results-decoration{display:none}.searchbox__input:hover{-webkit-box-shadow:inset 0 0 0 1px #b3b3b3;box-shadow:inset 0 0 0 1px #b3b3b3}.searchbox__input:active,.searchbox__input:focus{outline:0;-webkit-box-shadow:inset 0 0 0 1px #aaa;box-shadow:inset 0 0 0 1px #aaa;background:#fff}.searchbox__input::-webkit-input-placeholder{color:#aaa}.searchbox__input:-ms-input-placeholder{color:#aaa}.searchbox__input::-ms-input-placeholder{color:#aaa}.searchbox__input::-moz-placeholder{color:#aaa}.searchbox__input::placeholder{color:#aaa}.searchbox__submit{position:absolute;top:0;margin:0;border:0;border-radius:16px 0 0 16px;background-color:rgba(69,142,225,0);padding:0;width:32px;height:100%;vertical-align:middle;text-align:center;font-size:inherit;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;right:inherit;left:0}.searchbox__submit:before{display:inline-block;margin-right:-4px;height:100%;vertical-align:middle;content:""}.searchbox__submit:active,.searchbox__submit:hover{cursor:pointer}.searchbox__submit:focus{outline:0}.searchbox__submit svg{width:14px;height:14px;vertical-align:middle;fill:#6d7e96}.searchbox__reset{display:block;position:absolute;top:8px;right:8px;margin:0;border:0;background:none;cursor:pointer;padding:0;font-size:inherit;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;fill:rgba(0,0,0,.5)}.searchbox__reset.hide{display:none}.searchbox__reset:focus{outline:0}.searchbox__reset svg{display:block;margin:4px;width:8px;height:8px}.searchbox__input:valid~.searchbox__reset{display:block;-webkit-animation-name:sbx-reset-in;animation-name:sbx-reset-in;-webkit-animation-duration:.15s;animation-duration:.15s}@-webkit-keyframes sbx-reset-in{0%{-webkit-transform:translate3d(-20%,0,0);transform:translate3d(-20%,0,0);opacity:0}to{-webkit-transform:none;transform:none;opacity:1}}@keyframes sbx-reset-in{0%{-webkit-transform:translate3d(-20%,0,0);transform:translate3d(-20%,0,0);opacity:0}to{-webkit-transform:none;transform:none;opacity:1}}.algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu{right:0!important;left:inherit!important}.algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu:before{right:48px}.algolia-autocomplete.algolia-autocomplete-left .ds-dropdown-menu{left:0!important;right:inherit!important}.algolia-autocomplete.algolia-autocomplete-left .ds-dropdown-menu:before{left:48px}.algolia-autocomplete .ds-dropdown-menu{top:-6px;border-radius:4px;margin:6px 0 0;padding:0;text-align:left;height:auto;position:relative;background:transparent;border:none;z-index:999;max-width:600px;min-width:500px;-webkit-box-shadow:0 1px 0 0 rgba(0,0,0,.2),0 2px 3px 0 rgba(0,0,0,.1);box-shadow:0 1px 0 0 rgba(0,0,0,.2),0 2px 3px 0 rgba(0,0,0,.1)}.algolia-autocomplete .ds-dropdown-menu:before{display:block;position:absolute;content:"";width:14px;height:14px;background:#fff;z-index:1000;top:-7px;border-top:1px solid #d9d9d9;border-right:1px solid #d9d9d9;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);border-radius:2px}.algolia-autocomplete .ds-dropdown-menu .ds-suggestions{position:relative;z-index:1000;margin-top:8px}.algolia-autocomplete .ds-dropdown-menu .ds-suggestions a:hover{text-decoration:none}.algolia-autocomplete .ds-dropdown-menu .ds-suggestion{cursor:pointer}.algolia-autocomplete .ds-dropdown-menu .ds-suggestion.ds-cursor .algolia-docsearch-suggestion.suggestion-layout-simple,.algolia-autocomplete .ds-dropdown-menu .ds-suggestion.ds-cursor .algolia-docsearch-suggestion:not(.suggestion-layout-simple) .algolia-docsearch-suggestion--content{background-color:rgba(69,142,225,.05)}.algolia-autocomplete .ds-dropdown-menu [class^=ds-dataset-]{position:relative;border:1px solid #d9d9d9;background:#fff;border-radius:4px;overflow:auto;padding:0 8px 8px}.algolia-autocomplete .ds-dropdown-menu *{-webkit-box-sizing:border-box;box-sizing:border-box}.algolia-autocomplete .algolia-docsearch-suggestion{display:block;position:relative;padding:0 8px;background:#fff;color:#02060c;overflow:hidden}.algolia-autocomplete .algolia-docsearch-suggestion--highlight{color:#174d8c;background:rgba(143,187,237,.1);padding:.1em .05em}.algolia-autocomplete .algolia-docsearch-suggestion--category-header .algolia-docsearch-suggestion--category-header-lvl0 .algolia-docsearch-suggestion--highlight,.algolia-autocomplete .algolia-docsearch-suggestion--category-header .algolia-docsearch-suggestion--category-header-lvl1 .algolia-docsearch-suggestion--highlight,.algolia-autocomplete .algolia-docsearch-suggestion--text .algolia-docsearch-suggestion--highlight{padding:0 0 1px;background:inherit;-webkit-box-shadow:inset 0 -2px 0 0 rgba(69,142,225,.8);box-shadow:inset 0 -2px 0 0 rgba(69,142,225,.8);color:inherit}.algolia-autocomplete .algolia-docsearch-suggestion--content{display:block;float:right;width:70%;position:relative;padding:5.33333px 0 5.33333px 10.66667px;cursor:pointer}.algolia-autocomplete .algolia-docsearch-suggestion--content:before{content:"";position:absolute;display:block;top:0;height:100%;width:1px;background:#ddd;left:-1px}.algolia-autocomplete .algolia-docsearch-suggestion--category-header{position:relative;border-bottom:1px solid #ddd;display:none;margin-top:8px;padding:4px 0;font-size:1em;color:#33363d}.algolia-autocomplete .algolia-docsearch-suggestion--wrapper{width:100%;float:left;padding:8px 0 0}.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column{float:left;width:30%;text-align:right;position:relative;padding:5.33333px 10.66667px;color:#a4a7ae;font-size:.9em;word-wrap:break-word}.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column:before{content:"";position:absolute;display:block;top:0;height:100%;width:1px;background:#ddd;right:0}.algolia-autocomplete .algolia-docsearch-suggestion--subcategory-inline{display:none}.algolia-autocomplete .algolia-docsearch-suggestion--title{margin-bottom:4px;color:#02060c;font-size:.9em;font-weight:700}.algolia-autocomplete .algolia-docsearch-suggestion--text{display:block;line-height:1.2em;font-size:.85em;color:#63676d}.algolia-autocomplete .algolia-docsearch-suggestion--no-results{width:100%;padding:8px 0;text-align:center;font-size:1.2em}.algolia-autocomplete .algolia-docsearch-suggestion--no-results:before{display:none}.algolia-autocomplete .algolia-docsearch-suggestion code{padding:1px 5px;font-size:90%;border:none;color:#222;background-color:#ebebeb;border-radius:3px;font-family:Menlo,Monaco,Consolas,Courier New,monospace}.algolia-autocomplete .algolia-docsearch-suggestion code .algolia-docsearch-suggestion--highlight{background:none}.algolia-autocomplete .algolia-docsearch-suggestion.algolia-docsearch-suggestion__main .algolia-docsearch-suggestion--category-header,.algolia-autocomplete .algolia-docsearch-suggestion.algolia-docsearch-suggestion__secondary{display:block}@media (min-width:768px){.algolia-autocomplete .algolia-docsearch-suggestion .algolia-docsearch-suggestion--subcategory-column{display:block}}@media (max-width:768px){.algolia-autocomplete .algolia-docsearch-suggestion .algolia-docsearch-suggestion--subcategory-column{display:inline-block;width:auto;float:left;padding:0;color:#02060c;font-size:.9em;font-weight:700;text-align:left;opacity:.5}.algolia-autocomplete .algolia-docsearch-suggestion .algolia-docsearch-suggestion--subcategory-column:before{display:none}.algolia-autocomplete .algolia-docsearch-suggestion .algolia-docsearch-suggestion--subcategory-column:after{content:"|"}.algolia-autocomplete .algolia-docsearch-suggestion .algolia-docsearch-suggestion--content{display:inline-block;width:auto;text-align:left;float:left;padding:0}.algolia-autocomplete .algolia-docsearch-suggestion .algolia-docsearch-suggestion--content:before{display:none}}.algolia-autocomplete .suggestion-layout-simple.algolia-docsearch-suggestion{border-bottom:1px solid #eee;padding:8px;margin:0}.algolia-autocomplete .suggestion-layout-simple .algolia-docsearch-suggestion--content{width:100%;padding:0}.algolia-autocomplete .suggestion-layout-simple .algolia-docsearch-suggestion--content:before{display:none}.algolia-autocomplete .suggestion-layout-simple .algolia-docsearch-suggestion--category-header{margin:0;padding:0;display:block;width:100%;border:none}.algolia-autocomplete .suggestion-layout-simple .algolia-docsearch-suggestion--category-header-lvl0,.algolia-autocomplete .suggestion-layout-simple .algolia-docsearch-suggestion--category-header-lvl1{opacity:.6;font-size:.85em}.algolia-autocomplete .suggestion-layout-simple .algolia-docsearch-suggestion--category-header-lvl1:before{background-image:url('data:image/svg+xml;utf8,');content:"";width:10px;height:10px;display:inline-block}.algolia-autocomplete .suggestion-layout-simple .algolia-docsearch-suggestion--wrapper{width:100%;float:left;margin:0;padding:0}.algolia-autocomplete .suggestion-layout-simple .algolia-docsearch-suggestion--duplicate-content,.algolia-autocomplete .suggestion-layout-simple .algolia-docsearch-suggestion--subcategory-inline{display:none!important}.algolia-autocomplete .suggestion-layout-simple .algolia-docsearch-suggestion--title{margin:0;color:#458ee1;font-size:.9em;font-weight:400}.algolia-autocomplete .suggestion-layout-simple .algolia-docsearch-suggestion--title:before{content:"#";font-weight:700;color:#458ee1;display:inline-block}.algolia-autocomplete .suggestion-layout-simple .algolia-docsearch-suggestion--text{margin:4px 0 0;display:block;line-height:1.4em;padding:5.33333px 8px;background:#f8f8f8;font-size:.85em;opacity:.8}.algolia-autocomplete .suggestion-layout-simple .algolia-docsearch-suggestion--text .algolia-docsearch-suggestion--highlight{color:#3f4145;font-weight:700;-webkit-box-shadow:none;box-shadow:none}.algolia-autocomplete .algolia-docsearch-footer{width:134px;height:20px;z-index:2000;margin-top:10.66667px;float:right;font-size:0;line-height:0}.algolia-autocomplete .algolia-docsearch-footer--logo{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='168' height='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cpath d='M78.988.938h16.594a2.968 2.968 0 0 1 2.966 2.966V20.5a2.967 2.967 0 0 1-2.966 2.964H78.988a2.967 2.967 0 0 1-2.966-2.964V3.897A2.961 2.961 0 0 1 78.988.938zm41.937 17.866c-4.386.02-4.386-3.54-4.386-4.106l-.007-13.336 2.675-.424v13.254c0 .322 0 2.358 1.718 2.364v2.248zm-10.846-2.18c.821 0 1.43-.047 1.855-.129v-2.719a6.334 6.334 0 0 0-1.574-.199 5.7 5.7 0 0 0-.897.069 2.699 2.699 0 0 0-.814.24c-.24.116-.439.28-.582.491-.15.212-.219.335-.219.656 0 .628.219.991.616 1.23s.938.362 1.615.362zm-.233-9.7c.883 0 1.629.109 2.231.328.602.218 1.088.525 1.444.915.363.396.609.922.76 1.483.157.56.232 1.175.232 1.85v6.874a32.5 32.5 0 0 1-1.868.314c-.834.123-1.772.185-2.813.185-.69 0-1.327-.069-1.895-.198a4.001 4.001 0 0 1-1.471-.636 3.085 3.085 0 0 1-.951-1.134c-.226-.465-.343-1.12-.343-1.803 0-.656.13-1.073.384-1.525a3.24 3.24 0 0 1 1.047-1.106c.445-.287.95-.492 1.532-.615a8.8 8.8 0 0 1 1.82-.185 8.404 8.404 0 0 1 1.972.24v-.438c0-.307-.035-.6-.11-.874a1.88 1.88 0 0 0-.384-.73 1.784 1.784 0 0 0-.724-.493 3.164 3.164 0 0 0-1.143-.205c-.616 0-1.177.075-1.69.164a7.735 7.735 0 0 0-1.26.307l-.321-2.192c.335-.117.834-.233 1.478-.349a10.98 10.98 0 0 1 2.073-.178zm52.842 9.626c.822 0 1.43-.048 1.854-.13V13.7a6.347 6.347 0 0 0-1.574-.199c-.294 0-.595.021-.896.069a2.7 2.7 0 0 0-.814.24 1.46 1.46 0 0 0-.582.491c-.15.212-.218.335-.218.656 0 .628.218.991.615 1.23.404.245.938.362 1.615.362zm-.226-9.694c.883 0 1.629.108 2.231.327.602.219 1.088.526 1.444.915.355.39.609.923.759 1.483a6.8 6.8 0 0 1 .233 1.852v6.873c-.41.088-1.034.19-1.868.314-.834.123-1.772.184-2.813.184-.69 0-1.327-.068-1.895-.198a4.001 4.001 0 0 1-1.471-.635 3.085 3.085 0 0 1-.951-1.134c-.226-.465-.343-1.12-.343-1.804 0-.656.13-1.073.384-1.524.26-.45.608-.82 1.047-1.107.445-.286.95-.491 1.532-.614a8.803 8.803 0 0 1 2.751-.13c.329.034.671.096 1.04.185v-.437a3.3 3.3 0 0 0-.109-.875 1.873 1.873 0 0 0-.384-.731 1.784 1.784 0 0 0-.724-.492 3.165 3.165 0 0 0-1.143-.205c-.616 0-1.177.075-1.69.164a7.75 7.75 0 0 0-1.26.307l-.321-2.193c.335-.116.834-.232 1.478-.348a11.633 11.633 0 0 1 2.073-.177zm-8.034-1.271a1.626 1.626 0 0 1-1.628-1.62c0-.895.725-1.62 1.628-1.62.904 0 1.63.725 1.63 1.62 0 .895-.733 1.62-1.63 1.62zm1.348 13.22h-2.689V7.27l2.69-.423v11.956zm-4.714 0c-4.386.02-4.386-3.54-4.386-4.107l-.008-13.336 2.676-.424v13.254c0 .322 0 2.358 1.718 2.364v2.248zm-8.698-5.903c0-1.156-.253-2.119-.746-2.788-.493-.677-1.183-1.01-2.067-1.01-.882 0-1.574.333-2.065 1.01-.493.676-.733 1.632-.733 2.788 0 1.168.246 1.953.74 2.63.492.683 1.183 1.018 2.066 1.018.882 0 1.574-.342 2.067-1.019.492-.683.738-1.46.738-2.63zm2.737-.007c0 .902-.13 1.584-.397 2.33a5.52 5.52 0 0 1-1.128 1.906 4.986 4.986 0 0 1-1.752 1.223c-.685.286-1.739.45-2.265.45-.528-.006-1.574-.157-2.252-.45a5.096 5.096 0 0 1-1.744-1.223c-.487-.527-.863-1.162-1.137-1.906a6.345 6.345 0 0 1-.41-2.33c0-.902.123-1.77.397-2.508a5.554 5.554 0 0 1 1.15-1.892 5.133 5.133 0 0 1 1.75-1.216c.679-.287 1.425-.423 2.232-.423.808 0 1.553.142 2.237.423a4.88 4.88 0 0 1 1.753 1.216 5.644 5.644 0 0 1 1.135 1.892c.287.738.431 1.606.431 2.508zm-20.138 0c0 1.12.246 2.363.738 2.882.493.52 1.13.78 1.91.78.424 0 .828-.062 1.204-.178.377-.116.677-.253.917-.417V9.33a10.476 10.476 0 0 0-1.766-.226c-.971-.028-1.71.37-2.23 1.004-.513.636-.773 1.75-.773 2.788zm7.438 5.274c0 1.824-.466 3.156-1.404 4.004-.936.846-2.367 1.27-4.296 1.27-.705 0-2.17-.137-3.34-.396l.431-2.118c.98.205 2.272.26 2.95.26 1.074 0 1.84-.219 2.299-.656.459-.437.684-1.086.684-1.948v-.437a8.07 8.07 0 0 1-1.047.397c-.43.13-.93.198-1.492.198-.739 0-1.41-.116-2.018-.349a4.206 4.206 0 0 1-1.567-1.025c-.431-.45-.774-1.017-1.013-1.694-.24-.677-.363-1.885-.363-2.773 0-.834.13-1.88.384-2.577.26-.696.629-1.298 1.129-1.796.493-.498 1.095-.881 1.8-1.162a6.605 6.605 0 0 1 2.428-.457c.87 0 1.67.109 2.45.24.78.129 1.444.265 1.985.415V18.17z' fill='%235468FF'/%3E%3Cpath d='M6.972 6.677v1.627c-.712-.446-1.52-.67-2.425-.67-.585 0-1.045.13-1.38.391a1.24 1.24 0 0 0-.502 1.03c0 .425.164.765.494 1.02.33.256.835.532 1.516.83.447.192.795.356 1.045.495.25.138.537.332.862.582.324.25.563.548.718.894.154.345.23.741.23 1.188 0 .947-.334 1.691-1.004 2.234-.67.542-1.537.814-2.601.814-1.18 0-2.16-.229-2.936-.686v-1.708c.84.628 1.814.942 2.92.942.585 0 1.048-.136 1.388-.407.34-.271.51-.646.51-1.125 0-.287-.1-.55-.302-.79-.203-.24-.42-.42-.655-.542-.234-.123-.585-.29-1.053-.503a61.27 61.27 0 0 1-.582-.271 13.67 13.67 0 0 1-.55-.287 4.275 4.275 0 0 1-.567-.351 6.92 6.92 0 0 1-.455-.4c-.18-.17-.31-.34-.39-.51-.08-.17-.155-.37-.224-.598a2.553 2.553 0 0 1-.104-.742c0-.915.333-1.638.998-2.17.664-.532 1.523-.798 2.576-.798.968 0 1.793.17 2.473.51zm7.468 5.696v-.287c-.022-.607-.187-1.088-.495-1.444-.309-.357-.75-.535-1.324-.535-.532 0-.99.194-1.373.583-.382.388-.622.949-.717 1.683h3.909zm1.005 2.792v1.404c-.596.34-1.383.51-2.362.51-1.255 0-2.255-.377-3-1.132-.744-.755-1.116-1.744-1.116-2.968 0-1.297.34-2.316 1.021-3.055.68-.74 1.548-1.11 2.6-1.11 1.033 0 1.852.323 2.458.966.606.644.91 1.572.91 2.784 0 .33-.033.676-.096 1.038h-5.314c.107.702.405 1.239.894 1.611.49.372 1.106.558 1.85.558.862 0 1.58-.202 2.155-.606zm6.605-1.77h-1.212c-.596 0-1.045.116-1.349.35-.303.234-.454.532-.454.894 0 .372.117.664.35.877.235.213.575.32 1.022.32.51 0 .912-.142 1.204-.424.293-.281.44-.651.44-1.108v-.91zm-4.068-2.554V9.325c.627-.361 1.457-.542 2.489-.542 2.116 0 3.175 1.026 3.175 3.08V17h-1.548v-.957c-.415.68-1.143 1.02-2.186 1.02-.766 0-1.38-.22-1.843-.661-.462-.442-.694-1.003-.694-1.684 0-.776.293-1.38.878-1.81.585-.431 1.404-.647 2.457-.647h1.34V11.8c0-.554-.133-.971-.399-1.253-.266-.282-.707-.423-1.324-.423a4.07 4.07 0 0 0-2.345.718zm9.333-1.93v1.42c.394-1 1.101-1.5 2.123-1.5.148 0 .313.016.494.048v1.531a1.885 1.885 0 0 0-.75-.143c-.542 0-.989.24-1.34.718-.351.479-.527 1.048-.527 1.707V17h-1.563V8.91h1.563zm5.01 4.084c.022.82.272 1.492.75 2.019.479.526 1.15.79 2.01.79.639 0 1.235-.176 1.788-.527v1.404c-.521.319-1.186.479-1.995.479-1.265 0-2.276-.4-3.031-1.197-.755-.798-1.133-1.792-1.133-2.984 0-1.16.38-2.151 1.14-2.975.761-.825 1.79-1.237 3.088-1.237.702 0 1.346.149 1.93.447v1.436a3.242 3.242 0 0 0-1.77-.495c-.84 0-1.513.266-2.019.798-.505.532-.758 1.213-.758 2.042zM40.24 5.72v4.579c.458-1 1.293-1.5 2.505-1.5.787 0 1.42.245 1.899.734.479.49.718 1.17.718 2.042V17h-1.564v-5.106c0-.553-.14-.98-.422-1.284-.282-.303-.652-.455-1.11-.455-.531 0-1.002.202-1.411.606-.41.405-.615 1.022-.615 1.851V17h-1.563V5.72h1.563zm14.966 10.02c.596 0 1.096-.253 1.5-.758.404-.506.606-1.157.606-1.955 0-.915-.202-1.62-.606-2.114-.404-.495-.92-.742-1.548-.742-.553 0-1.05.224-1.491.67-.442.447-.662 1.133-.662 2.058 0 .958.212 1.67.638 2.138.425.469.946.703 1.563.703zM53.004 5.72v4.42c.574-.894 1.388-1.341 2.44-1.341 1.022 0 1.857.383 2.506 1.149.649.766.973 1.781.973 3.047 0 1.138-.309 2.109-.925 2.912-.617.803-1.463 1.205-2.537 1.205-1.075 0-1.894-.447-2.457-1.34V17h-1.58V5.72h1.58zm9.908 11.104l-3.223-7.913h1.739l1.005 2.632 1.26 3.415c.096-.32.48-1.458 1.15-3.415l.909-2.632h1.66l-2.92 7.866c-.777 2.074-1.963 3.11-3.559 3.11a2.92 2.92 0 0 1-.734-.079v-1.34c.17.042.351.064.543.064 1.032 0 1.755-.57 2.17-1.708z' fill='%235D6494'/%3E%3Cpath d='M89.632 5.967v-.772a.978.978 0 0 0-.978-.977h-2.28a.978.978 0 0 0-.978.977v.793c0 .088.082.15.171.13a7.127 7.127 0 0 1 1.984-.28c.65 0 1.295.088 1.917.259.082.02.164-.04.164-.13m-6.248 1.01l-.39-.389a.977.977 0 0 0-1.382 0l-.465.465a.973.973 0 0 0 0 1.38l.383.383c.062.061.15.047.205-.014.226-.307.472-.601.746-.874.281-.28.568-.526.883-.751.068-.042.075-.137.02-.2m4.16 2.453v3.341c0 .096.104.165.192.117l2.97-1.537c.068-.034.089-.117.055-.184a3.695 3.695 0 0 0-3.08-1.866c-.068 0-.136.054-.136.13m0 8.048a4.489 4.489 0 0 1-4.49-4.482 4.488 4.488 0 0 1 4.49-4.482 4.488 4.488 0 0 1 4.489 4.482 4.484 4.484 0 0 1-4.49 4.482m0-10.85a6.363 6.363 0 1 0 0 12.729 6.37 6.37 0 0 0 6.372-6.368 6.358 6.358 0 0 0-6.371-6.36' fill='%23FFF'/%3E%3C/g%3E%3C/svg%3E");background-repeat:no-repeat;background-position:50%;background-size:100%;overflow:hidden;text-indent:-9000px;padding:0!important;width:100%;height:100%;display:block}.algolia-autocomplete .ds-dropdown-menu{border:1px solid #d9d9d9;border-radius:0;-webkit-box-shadow:none;box-shadow:none;margin-top:.75rem;max-width:none;width:100%}.algolia-autocomplete .ds-dropdown-menu [class^=ds-dataset-]{border:0;border-radius:0}.algolia-autocomplete .ds-dropdown-menu .ds-suggestions{overflow:auto;max-height:calc(100vh - 7.375rem);margin:0 -.5rem;padding:.5rem}.algolia-autocomplete .ds-dropdown-menu .ds-suggestions::-webkit-scrollbar{width:.25rem}.algolia-autocomplete .ds-dropdown-menu .ds-suggestions::-webkit-scrollbar-thumb{background-color:#c1c1c1} -------------------------------------------------------------------------------- /docs/assets/font/gotham-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edn-query-language/eql/b24c96d9af23e508df3f04ba54910e996dfb7694/docs/assets/font/gotham-bold.woff -------------------------------------------------------------------------------- /docs/assets/font/gotham-book.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edn-query-language/eql/b24c96d9af23e508df3f04ba54910e996dfb7694/docs/assets/font/gotham-book.woff -------------------------------------------------------------------------------- /docs/assets/font/gotham-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edn-query-language/eql/b24c96d9af23e508df3f04ba54910e996dfb7694/docs/assets/font/gotham-light.woff -------------------------------------------------------------------------------- /docs/assets/font/gotham-medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edn-query-language/eql/b24c96d9af23e508df3f04ba54910e996dfb7694/docs/assets/font/gotham-medium.woff -------------------------------------------------------------------------------- /docs/assets/font/roboto-mono-latin-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edn-query-language/eql/b24c96d9af23e508df3f04ba54910e996dfb7694/docs/assets/font/roboto-mono-latin-400.woff -------------------------------------------------------------------------------- /docs/assets/font/roboto-mono-latin-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edn-query-language/eql/b24c96d9af23e508df3f04ba54910e996dfb7694/docs/assets/font/roboto-mono-latin-400.woff2 -------------------------------------------------------------------------------- /docs/assets/font/roboto-mono-latin-500.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edn-query-language/eql/b24c96d9af23e508df3f04ba54910e996dfb7694/docs/assets/font/roboto-mono-latin-500.woff -------------------------------------------------------------------------------- /docs/assets/font/roboto-mono-latin-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edn-query-language/eql/b24c96d9af23e508df3f04ba54910e996dfb7694/docs/assets/font/roboto-mono-latin-500.woff2 -------------------------------------------------------------------------------- /docs/assets/img/back.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/img/caret-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/img/caution.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/img/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/img/edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edn-query-language/eql/b24c96d9af23e508df3f04ba54910e996dfb7694/docs/assets/img/favicon.ico -------------------------------------------------------------------------------- /docs/assets/img/important.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/img/menu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/img/note.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/img/search-close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/img/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/img/tip.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/img/warning.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/js/site.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";var t=document.querySelector("nav.nav"),o={};if(o.element=t&&t.querySelector(".nav-menu")){var e,n,i,c=o.element.querySelector(".is-current-page");o.element.addEventListener("mousedown",function(e){1 h2[id]",c=e=document.querySelector("article.doc"),n=f((c||document).querySelectorAll(o))).length){var o,c,d,i,r={},a=n.reduce(function(e,t){var n=f(t.childNodes).reduce(function(e,t){return"A"!==t.nodeName&&e.appendChild(t.cloneNode(!0)),e},document.createElement("a"));r[n.href="#"+t.id]=n;var o=document.createElement("li");return o.appendChild(n),e.appendChild(o),e},document.createElement("ul"));(i=t&&t.querySelector(".toc-menu"))||((i=document.createElement("div")).className="toc-menu");var l=document.createElement("h3");l.textContent="On This Page",i.appendChild(l),i.appendChild(a),t&&window.addEventListener("load",function(){m(),window.addEventListener("scroll",m)});var s=e.querySelector("h1.page ~ :not(.labels)");if(s){var u=document.createElement("aside");u.className="toc embedded",u.appendChild(i.cloneNode(!0)),e.insertBefore(u,s)}}else t.parentNode.removeChild(t);function m(){var t;if(n.some(function(e){if(!(Math.floor(e.getBoundingClientRect().top)<=0))return!0;t="#"+e.id}),t){if(t!==d){d&&r[d].classList.remove("is-active");var e=r[t];e.classList.add("is-active"),i.scrollHeight>i.offsetHeight&&(i.scrollTop=Math.max(0,e.offsetTop+e.offsetHeight-i.offsetHeight)),d=t}}else d&&(r[d].classList.remove("is-active"),d=void 0)}function f(e){return[].slice.call(e)}}(); 3 | !function(){"use strict";"MozAppearance"in document.body.style&&Array.prototype.slice.call(document.querySelectorAll("main [id]")).forEach(function(e){if(e.firstChild&&~window.getComputedStyle(e).display.indexOf("inline")){var t=document.createElement("a");t.id=e.id,e.removeAttribute("id"),e.parentNode.insertBefore(t,e)}})}(); 4 | !function(){"use strict";document.addEventListener("DOMContentLoaded",function(){var e=document.querySelector(".navbar-burger");e&&e.addEventListener("click",function(t){t.stopPropagation(),e.classList.toggle("is-active"),document.getElementById(e.dataset.target).classList.toggle("is-active"),document.documentElement.classList.toggle("is-clipped--navbar")})})}(); 5 | !function(){"use strict";var r=window.location.hash;function l(t,a){return Array.prototype.slice.call((a||document).querySelectorAll(t))}l(".doc .tabset").forEach(function(s){var c,n,t=s.querySelector(".tabs");t&&(l("li",t).forEach(function(t,a){var i=(t.querySelector("a[id]")||t).id;if(i){var e=function(a,t){return l(".tab-pane",t).find(function(t){return t.getAttribute("aria-labelledby")===a})}(i,s);a||(n={tab:t,pane:e}),!c&&r==="#"+i&&(c=!0)?(t.classList.add("is-active"),e&&e.classList.add("is-active")):a||(t.classList.remove("is-active"),e&&e.classList.remove("is-active")),t.addEventListener("click",function(t){var a=this.tab,i=this.pane;l(".tabs li, .tab-pane",this.tabset).forEach(function(t){t===a||t===i?t.classList.add("is-active"):t.classList.remove("is-active")}),t.preventDefault()}.bind({tabset:s,tab:t,pane:e}))}}),!c&&n&&(n.tab.classList.add("is-active"),n.pane&&n.pane.classList.add("is-active")));s.classList.remove("is-loading")})}(); -------------------------------------------------------------------------------- /docs/assets/js/vendor/feedback.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";Math.max(window.screen.availHeight,window.screen.availWidth)<769||window.addEventListener("load",function(){var e=document.getElementById("feedback-script").dataset,c=document.createElement("script");c.src="https://issues.couchbase.com/s/df1d14c9d77c1ad1c4ef2db3d8d7da3f-T/-6ikjte/73013/c4d560fe7f608200148fbd40cb0f35ae/2.0.23/_/download/batch/com.atlassian.jira.collector.plugin.jira-issue-collector-plugin:issuecollector/com.atlassian.jira.collector.plugin.jira-issue-collector-plugin:issuecollector.js?locale=en-US&collectorId="+e.collectorId,document.body.appendChild(c)})}(); -------------------------------------------------------------------------------- /docs/assets/js/vendor/highlight.js: -------------------------------------------------------------------------------- 1 | !function i(s,l,o){function c(n,e){if(!l[n]){if(!s[n]){var a="function"==typeof require&&require;if(!e&&a)return a(n,!0);if(u)return u(n,!0);var t=new Error("Cannot find module '"+n+"'");throw t.code="MODULE_NOT_FOUND",t}var r=l[n]={exports:{}};s[n][0].call(r.exports,function(e){return c(s[n][1][e]||e)},r,r.exports,i,s,l,o)}return l[n].exports}for(var u="function"==typeof require&&require,e=0;e]+>|\t|)+|(?:\n)))/gm,v="",N={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};function _(e){return e.replace(/&/g,"&").replace(//g,">")}function d(e){return e.nodeName.toLowerCase()}function y(e,n){var a=e&&e.exec(n);return a&&0===a.index}function u(e){return n.test(e)}function f(e){var n,a={},t=Array.prototype.slice.call(arguments,1);for(n in e)a[n]=e[n];return t.forEach(function(e){for(n in e)a[n]=e[n]}),a}function m(e){var r=[];return function e(n,a){for(var t=n.firstChild;t;t=t.nextSibling)3===t.nodeType?a+=t.nodeValue.length:1===t.nodeType&&(r.push({event:"start",offset:a,node:t}),a=e(t,a),d(t).match(/br|hr|img|input/)||r.push({event:"stop",offset:a,node:t}));return a}(e,0),r}function w(s){function l(e){return e&&e.source||e}function o(e,n){return new RegExp(l(e),"m"+(s.case_insensitive?"i":"")+(n?"g":""))}!function n(a,e){if(!a.compiled){if(a.compiled=!0,a.keywords=a.keywords||a.beginKeywords,a.keywords){function t(a,e){s.case_insensitive&&(e=e.toLowerCase()),e.split(" ").forEach(function(e){var n=e.split("|");r[n[0]]=[a,n[1]?Number(n[1]):1]})}var r={};"string"==typeof a.keywords?t("keyword",a.keywords):c(a.keywords).forEach(function(e){t(e,a.keywords[e])}),a.keywords=r}a.lexemesRe=o(a.lexemes||/\w+/,!0),e&&(a.beginKeywords&&(a.begin="\\b("+a.beginKeywords.split(" ").join("|")+")\\b"),a.begin||(a.begin=/\B|\b/),a.beginRe=o(a.begin),a.endSameAsBegin&&(a.end=a.begin),a.end||a.endsWithParent||(a.end=/\B|\b/),a.end&&(a.endRe=o(a.end)),a.terminator_end=l(a.end)||"",a.endsWithParent&&e.terminator_end&&(a.terminator_end+=(a.end?"|":"")+e.terminator_end)),a.illegal&&(a.illegalRe=o(a.illegal)),null==a.relevance&&(a.relevance=1),a.contains||(a.contains=[]),a.contains=Array.prototype.concat.apply([],a.contains.map(function(e){return function(n){return n.variants&&!n.cached_variants&&(n.cached_variants=n.variants.map(function(e){return f(n,{variants:null},e)})),n.cached_variants||n.endsWithParent&&[f(n)]||[n]}("self"===e?a:e)})),a.contains.forEach(function(e){n(e,a)}),a.starts&&n(a.starts,e);var i=a.contains.map(function(e){return e.beginKeywords?"\\.?("+e.begin+")\\.?":e.begin}).concat([a.terminator_end,a.illegal]).map(l).filter(Boolean);a.terminators=i.length?o(i.join("|"),!0):{exec:function(){return null}}}}(s)}function M(e,n,i,a){function l(e,n,a,t){var r='')+n+(a?"":v)}function s(){d+=null!=u.subLanguage?function(){var e="string"==typeof u.subLanguage;if(e&&!E[u.subLanguage])return _(f);var n=e?M(u.subLanguage,f,!0,g[u.subLanguage]):R(f,u.subLanguage.length?u.subLanguage:void 0);return 0")+'"');return f+=n,n.length||1}var c=O(e);if(!c)throw new Error('Unknown language: "'+e+'"');w(c);var r,u=a||c,g={},d="";for(r=u;r!==c;r=r.parent)r.className&&(d=l(r.className,"",!0)+d);var f="",m=0;try{for(var p,b,h=0;u.terminators.lastIndex=h,p=u.terminators.exec(n);)b=t(n.substring(h,p.index),p[0]),h=p.index+b;for(t(n.substr(h)),r=u;r.parent;r=r.parent)r.className&&(d+=v);return{relevance:m,value:d,language:e,top:u}}catch(e){if(e.message&&-1!==e.message.indexOf("Illegal"))return{relevance:0,value:_(n)};throw e}}function R(a,e){e=e||N.languages||c(E);var t={relevance:0,value:_(a)},r=t;return e.filter(O).filter(s).forEach(function(e){var n=M(e,a,!1);n.language=e,n.relevance>r.relevance&&(r=n),n.relevance>t.relevance&&(r=t,t=n)}),r.language&&(t.second_best=r),t}function p(e){return N.tabReplace||N.useBR?e.replace(a,function(e,n){return N.useBR&&"\n"===e?"
":N.tabReplace?n.replace(/\t/g,N.tabReplace):""}):e}function t(e){var n,a,t,r,i,s=function(e){var n,a,t,r,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",a=o.exec(i))return O(a[1])?a[1]:"no-highlight";for(n=0,t=(i=i.split(/\s+/)).length;n/g,"\n"):n=e,i=n.textContent,t=s?M(s,i,!0):R(i),(a=m(n)).length&&((r=document.createElementNS("http://www.w3.org/1999/xhtml","div")).innerHTML=t.value,t.value=function(e,n,a){var t=0,r="",i=[];function s(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function o(e){r+=""}function c(e){("start"===e.event?l:o)(e.node)}for(;e.length||n.length;){var u=s();if(r+=_(a.substring(t,u[0].offset)),t=u[0].offset,u===e){for(i.reverse().forEach(o);c(u.splice(0,1)[0]),(u=s())===e&&u.length&&u[0].offset===t;);i.reverse().forEach(l)}else"start"===u[0].event?i.push(u[0].node):i.pop(),c(u.splice(0,1)[0])}return r+_(a.substr(t))}(a,m(r),i)),t.value=p(t.value),e.innerHTML=t.value,e.className=function(e,n,a){var t=n?l[n]:a,r=[e.trim()];return e.match(/\bhljs\b/)||r.push("hljs"),-1===e.indexOf(t)&&r.push(t),r.join(" ").trim()}(e.className,s,t.language),e.result={language:t.language,re:t.relevance},t.second_best&&(e.second_best={language:t.second_best.language,re:t.second_best.relevance}))}function i(){if(!i.called){i.called=!0;var e=document.querySelectorAll("pre code");g.forEach.call(e,t)}}function O(e){return e=(e||"").toLowerCase(),E[e]||E[l[e]]}function s(e){var n=O(e);return n&&!n.disableAutodetect}return r.highlight=M,r.highlightAuto=R,r.fixMarkup=p,r.highlightBlock=t,r.configure=function(e){N=f(N,e)},r.initHighlighting=i,r.initHighlightingOnLoad=function(){addEventListener("DOMContentLoaded",i,!1),addEventListener("load",i,!1)},r.registerLanguage=function(n,e){var a=E[n]=e(r);a.aliases&&a.aliases.forEach(function(e){l[e]=n})},r.listLanguages=function(){return c(E)},r.getLanguage=O,r.autoDetection=s,r.inherit=f,r.IDENT_RE="[a-zA-Z]\\w*",r.UNDERSCORE_IDENT_RE="[a-zA-Z_]\\w*",r.NUMBER_RE="\\b\\d+(\\.\\d+)?",r.C_NUMBER_RE="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",r.BINARY_NUMBER_RE="\\b(0b[01]+)",r.RE_STARTERS_RE="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",r.BACKSLASH_ESCAPE={begin:"\\\\[\\s\\S]",relevance:0},r.APOS_STRING_MODE={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[r.BACKSLASH_ESCAPE]},r.QUOTE_STRING_MODE={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[r.BACKSLASH_ESCAPE]},r.PHRASAL_WORDS_MODE={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},r.COMMENT=function(e,n,a){var t=r.inherit({className:"comment",begin:e,end:n,contains:[]},a||{});return t.contains.push(r.PHRASAL_WORDS_MODE),t.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0}),t},r.C_LINE_COMMENT_MODE=r.COMMENT("//","$"),r.C_BLOCK_COMMENT_MODE=r.COMMENT("/\\*","\\*/"),r.HASH_COMMENT_MODE=r.COMMENT("#","$"),r.NUMBER_MODE={className:"number",begin:r.NUMBER_RE,relevance:0},r.C_NUMBER_MODE={className:"number",begin:r.C_NUMBER_RE,relevance:0},r.BINARY_NUMBER_MODE={className:"number",begin:r.BINARY_NUMBER_RE,relevance:0},r.CSS_NUMBER_MODE={className:"number",begin:r.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},r.REGEXP_MODE={className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[r.BACKSLASH_ESCAPE,{begin:/\[/,end:/\]/,relevance:0,contains:[r.BACKSLASH_ESCAPE]}]},r.TITLE_MODE={className:"title",begin:r.IDENT_RE,relevance:0},r.UNDERSCORE_TITLE_MODE={className:"title",begin:r.UNDERSCORE_IDENT_RE,relevance:0},r.METHOD_GUARD={begin:"\\.\\s*"+r.UNDERSCORE_IDENT_RE,relevance:0},r},r="object"==typeof window&&window||"object"==typeof self&&self,void 0!==a?t(a):r&&(r.hljs=t({}),"function"==typeof define&&define.amd&&define([],function(){return r.hljs}))},{}],2:[function(e,n,a){n.exports=function(e){return{aliases:["adoc"],contains:[e.COMMENT("^/{4,}\\n","\\n/{4,}$",{relevance:10}),e.COMMENT("^//","$",{relevance:0}),{className:"title",begin:"^\\.\\w.*$"},{begin:"^[=\\*]{4,}\\n",end:"\\n^[=\\*]{4,}$",relevance:10},{className:"section",relevance:10,variants:[{begin:"^(={1,5}) .+?( \\1)?$"},{begin:"^[^\\[\\]\\n]+?\\n[=\\-~\\^\\+]{2,}$"}]},{className:"meta",begin:"^:.+?:",end:"\\s",excludeEnd:!0,relevance:10},{className:"meta",begin:"^\\[.+?\\]$",relevance:0},{className:"quote",begin:"^_{4,}\\n",end:"\\n_{4,}$",relevance:10},{className:"code",begin:"^[\\-\\.]{4,}\\n",end:"\\n[\\-\\.]{4,}$",relevance:10},{begin:"^\\+{4,}\\n",end:"\\n\\+{4,}$",contains:[{begin:"<",end:">",subLanguage:"xml",relevance:0}],relevance:10},{className:"bullet",begin:"^(\\*+|\\-+|\\.+|[^\\n]+?::)\\s+"},{className:"symbol",begin:"^(NOTE|TIP|IMPORTANT|WARNING|CAUTION):\\s+",relevance:10},{className:"strong",begin:"\\B\\*(?![\\*\\s])",end:"(\\n{2}|\\*)",contains:[{begin:"\\\\*\\w",relevance:0}]},{className:"emphasis",begin:"\\B'(?!['\\s])",end:"(\\n{2}|')",contains:[{begin:"\\\\'\\w",relevance:0}],relevance:0},{className:"emphasis",begin:"_(?![_\\s])",end:"(\\n{2}|_)",relevance:0},{className:"string",variants:[{begin:"``.+?''"},{begin:"`.+?'"}]},{className:"code",begin:"(`.+?`|\\+.+?\\+)",relevance:0},{className:"code",begin:"^[ \\t]",end:"$",relevance:0},{begin:"^'{3,}[ \\t]*$",relevance:10},{begin:"(link:)?(http|https|ftp|file|irc|image:?):\\S+\\[.*?\\]",returnBegin:!0,contains:[{begin:"(link|image:?):",relevance:0},{className:"link",begin:"\\w",end:"[^\\[]+",relevance:0},{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0,relevance:0}],relevance:10}]}}},{}],3:[function(e,n,a){n.exports=function(e){var n={className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{(.*?)}/}]},a={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,n,{className:"variable",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]}]};return{aliases:["sh","zsh"],lexemes:/\b-?[a-z\._]+\b/,keywords:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[{className:"meta",begin:/^#![^\n]+sh\s*$/,relevance:10},{className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0},e.HASH_COMMENT_MODE,a,{className:"string",begin:/'/,end:/'/},n]}}},{}],4:[function(e,n,a){n.exports=function(e){var n="a-zA-Z_\\-!.?+*=<>&#'",a="["+n+"]["+n+"0-9/;:]*",t={begin:a,relevance:0},r={className:"number",begin:"[-+]?\\d+(\\.\\d+)?",relevance:0},i=e.inherit(e.QUOTE_STRING_MODE,{illegal:null}),s=e.COMMENT(";","$",{relevance:0}),l={className:"literal",begin:/\b(true|false|nil)\b/},o={begin:"[\\[\\{]",end:"[\\]\\}]"},c={className:"comment",begin:"\\^"+a},u=e.COMMENT("\\^\\{","\\}"),g={className:"symbol",begin:"[:]{1,2}"+a},d={begin:"\\(",end:"\\)"},f={endsWithParent:!0,relevance:0},m={keywords:{"builtin-name":"def defonce cond apply if-not if-let if not not= = < > <= >= == + / * - rem quot neg? pos? delay? symbol? keyword? true? false? integer? empty? coll? list? set? ifn? fn? associative? sequential? sorted? counted? reversible? number? decimal? class? distinct? isa? float? rational? reduced? ratio? odd? even? char? seq? vector? string? map? nil? contains? zero? instance? not-every? not-any? libspec? -> ->> .. . inc compare do dotimes mapcat take remove take-while drop letfn drop-last take-last drop-while while intern condp case reduced cycle split-at split-with repeat replicate iterate range merge zipmap declare line-seq sort comparator sort-by dorun doall nthnext nthrest partition eval doseq await await-for let agent atom send send-off release-pending-sends add-watch mapv filterv remove-watch agent-error restart-agent set-error-handler error-handler set-error-mode! error-mode shutdown-agents quote var fn loop recur throw try monitor-enter monitor-exit defmacro defn defn- macroexpand macroexpand-1 for dosync and or when when-not when-let comp juxt partial sequence memoize constantly complement identity assert peek pop doto proxy defstruct first rest cons defprotocol cast coll deftype defrecord last butlast sigs reify second ffirst fnext nfirst nnext defmulti defmethod meta with-meta ns in-ns create-ns import refer keys select-keys vals key val rseq name namespace promise into transient persistent! conj! assoc! dissoc! pop! disj! use class type num float double short byte boolean bigint biginteger bigdec print-method print-dup throw-if printf format load compile get-in update-in pr pr-on newline flush read slurp read-line subvec with-open memfn time re-find re-groups rand-int rand mod locking assert-valid-fdecl alias resolve ref deref refset swap! reset! set-validator! compare-and-set! alter-meta! reset-meta! commute get-validator alter ref-set ref-history-count ref-min-history ref-max-history ensure sync io! new next conj set! to-array future future-call into-array aset gen-class reduce map filter find empty hash-map hash-set sorted-map sorted-map-by sorted-set sorted-set-by vec vector seq flatten reverse assoc dissoc list disj get union difference intersection extend extend-type extend-protocol int nth delay count concat chunk chunk-buffer chunk-append chunk-first chunk-rest max min dec unchecked-inc-int unchecked-inc unchecked-dec-inc unchecked-dec unchecked-negate unchecked-add-int unchecked-add unchecked-subtract-int unchecked-subtract chunk-next chunk-cons chunked-seq? prn vary-meta lazy-seq spread list* str find-keyword keyword symbol gensym force rationalize"},lexemes:a,className:"name",begin:a,starts:f},p=[d,i,c,u,s,g,o,r,l,t];return d.contains=[e.COMMENT("comment",""),m,f],f.contains=p,o.contains=p,u.contains=[o],{aliases:["clj"],illegal:/\S/,contains:[d,i,c,u,s,g,o,r,l]}}},{}],5:[function(e,n,a){n.exports=function(e){var n="[A-Za-z$_][0-9A-Za-z$_]*",a={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},t={className:"number",variants:[{begin:"\\b(0[bB][01]+)"},{begin:"\\b(0[oO][0-7]+)"},{begin:e.C_NUMBER_RE}],relevance:0},r={className:"subst",begin:"\\$\\{",end:"\\}",keywords:a,contains:[]},i={className:"string",begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE,r]};r.contains=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,i,t,e.REGEXP_MODE];var s=r.contains.concat([e.C_BLOCK_COMMENT_MODE,e.C_LINE_COMMENT_MODE]);return{aliases:["js","jsx"],keywords:a,contains:[{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},{className:"meta",begin:/^#!/,end:/$/},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t,{begin:/[{,]\s*/,relevance:0,contains:[{begin:n+"\\s*:",returnBegin:!0,relevance:0,contains:[{className:"attr",begin:n,relevance:0}]}]},{begin:"("+e.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.REGEXP_MODE,{className:"function",begin:"(\\(.*?\\)|"+n+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:n},{begin:/\(\s*\)/},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:a,contains:s}]}]},{begin://,subLanguage:"xml",contains:[{begin:/<\w+\s*\/>/,skip:!0},{begin:/<\w+/,end:/(\/\w+|\w+\/)>/,skip:!0,contains:[{begin:/<\w+\s*\/>/,skip:!0},"self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:n}),{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:s}],illegal:/\[|%/},{begin:/\$[(.]/},e.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0}],illegal:/#(?!!)/}}},{}],6:[function(e,n,a){n.exports=function(e){var n={literal:"true false null"},a=[e.QUOTE_STRING_MODE,e.C_NUMBER_MODE],t={end:",",endsWithParent:!0,excludeEnd:!0,contains:a,keywords:n},r={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE],illegal:"\\n"},e.inherit(t,{begin:/:/})],illegal:"\\S"},i={begin:"\\[",end:"\\]",contains:[e.inherit(t)],illegal:"\\S"};return a.splice(a.length,0,r,i),{contains:a,keywords:n,illegal:"\\S"}}},{}],7:[function(n,e,a){!function(){"use strict";var e=n("highlight.js/lib/highlight");e.registerLanguage("asciidoc",n("highlight.js/lib/languages/asciidoc")),e.registerLanguage("bash",n("highlight.js/lib/languages/bash")),e.registerLanguage("clojure",n("highlight.js/lib/languages/clojure")),e.registerLanguage("javascript",n("highlight.js/lib/languages/javascript")),e.registerLanguage("json",n("highlight.js/lib/languages/json")),e.registerLanguage("graphql",function(e){return{aliases:["gql"],keywords:{keyword:"query mutation subscription|10 type input schema directive interface union scalar fragment|10 enum on ...",literal:"true false null"},contains:[e.HASH_COMMENT_MODE,e.QUOTE_STRING_MODE,e.NUMBER_MODE,{className:"type",begin:"[^\\w][A-Z][a-z]",end:"\\W",excludeEnd:!0},{className:"literal",begin:"[^\\w][A-Z][A-Z]",end:"\\W",excludeEnd:!0},{className:"variable",begin:"\\$",end:"\\W",excludeEnd:!0},{className:"keyword",begin:"[.]{2}",end:"\\."},{className:"meta",begin:"@",end:"\\W",excludeEnd:!0}],illegal:/([;<']|BEGIN)/}}),e.initHighlighting()}()},{"highlight.js/lib/highlight":1,"highlight.js/lib/languages/asciidoc":2,"highlight.js/lib/languages/bash":3,"highlight.js/lib/languages/clojure":4,"highlight.js/lib/languages/javascript":5,"highlight.js/lib/languages/json":6}]},{},[7]); -------------------------------------------------------------------------------- /docs/assets/js/vendor/mark.js: -------------------------------------------------------------------------------- 1 | !function o(a,s,c){function u(t,e){if(!s[t]){if(!a[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(l)return l(t,!0);var r=new Error("Cannot find module '"+t+"'");throw r.code="MODULE_NOT_FOUND",r}var i=s[t]={exports:{}};a[t][0].call(i.exports,function(e){return u(a[t][1][e]||e)},i,i.exports,o,a,s,c)}return s[t].exports}for(var l="function"==typeof require&&require,e=0;e?@[\\]^_`{|}~¡¿")))+"]*"+e+"[^"+o+"]*)";case"exactly":return"(^|\\s"+o+")("+e+")(?=$|\\s"+o+")"}}},{key:"getSeparatedKeywords",value:function(e){var t=this,n=[];return e.forEach(function(e){t.opt.separateWordSearch?e.split(" ").forEach(function(e){e.trim()&&-1===n.indexOf(e)&&n.push(e)}):e.trim()&&-1===n.indexOf(e)&&n.push(e)}),{keywords:n.sort(function(e,t){return t.length-e.length}),length:n.length}}},{key:"isNumeric",value:function(e){return Number(parseFloat(e))==e}},{key:"checkRanges",value:function(e){var i=this;if(!Array.isArray(e)||"[object Object]"!==Object.prototype.toString.call(e[0]))return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(e),[];var o=[],a=0;return e.sort(function(e,t){return e.start-t.start}).forEach(function(e){var t=i.callNoMatchOnInvalidRanges(e,a),n=t.start,r=t.end;t.valid&&(e.start=n,e.length=r-n,o.push(e),a=r)}),o}},{key:"callNoMatchOnInvalidRanges",value:function(e,t){var n=void 0,r=void 0,i=!1;return e&&void 0!==e.start?(r=(n=parseInt(e.start,10))+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&0c){if(!l(e.node))return!1;var r=c-e.start,i=(u>e.end?e.end:u)-e.start,o=s.value.substr(0,e.start),a=s.value.substr(i+e.start);if(e.node=f.wrapRangeInTextNode(e.node,r,i),s.value=o+a,s.nodes.forEach(function(e,t){n<=t&&(0e.end))return!1;c=e.end}return!0})}},{key:"wrapMatches",value:function(i,e,o,a,t){var s=this,c=0===e?0:e+1;this.getTextNodes(function(e){e.nodes.forEach(function(e){e=e.node;for(var t=void 0;null!==(t=i.exec(e.textContent))&&""!==t[c];)if(o(t[c],e)){var n=t.index;if(0!==c)for(var r=1;r 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | Clojure Specs | EDN Query Language 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 41 |
42 |
43 | 73 | 76 |
77 |
78 | 79 | 85 | 90 |
91 |
92 |

Clojure Specs

93 |
94 |

Here you can find a formal definition for the query syntax, expressed as Clojure Specs.

95 |
96 |
97 |
98 |
(s/def ::property keyword?)
 99 | (s/def ::special-property #{'*})
100 | (s/def ::ident-value (s/with-gen any? (default-gen ::gen-ident-value)))
101 | (s/def ::ident (s/with-gen (s/tuple ::property ::ident-value) (default-gen ::gen-ident)))
102 | (s/def ::join-key (s/or :prop ::property, :ident ::ident, :param-exp ::join-key-param-expr))
103 | (s/def ::join (s/map-of ::join-key ::join-query, :count 1, :conform-keys true))
104 | (s/def ::union (s/map-of ::property ::query, :min-count 1, :conform-keys true))
105 | (s/def ::recursion-depth (s/with-gen nat-int? (default-gen ::gen-depth)))
106 | (s/def ::recursion (s/or :depth ::recursion-depth, :unbounded #{'...}))
107 | 
108 | (s/def ::join-query
109 |   (s/with-gen
110 |     (s/or :query ::query
111 |           :union ::union
112 |           :recursion ::recursion)
113 |     (default-gen ::gen-join-query)))
114 | 
115 | (s/def ::params
116 |   (s/with-gen map? (default-gen ::gen-params)))
117 | 
118 | (s/def ::param-expr-key
119 |   (s/with-gen
120 |     (s/or :prop ::property
121 |           :join ::join
122 |           :ident ::ident)
123 |     (default-gen ::gen-param-expr-key)))
124 | 
125 | (s/def ::param-expr
126 |   (s/with-gen
127 |     (s/and seq? (s/cat :expr ::param-expr-key :params (s/? ::params)))
128 |     (default-gen ::gen-param-expr)))
129 | 
130 | (s/def ::join-key-param-key (s/or :prop ::property :ident ::ident))
131 | 
132 | (s/def ::join-key-param-expr
133 |   (s/with-gen
134 |     (s/and seq? (s/cat :expr ::join-key-param-key :params (s/? ::params)))
135 |     (default-gen ::gen-join-key-param-expr)))
136 | 
137 | (s/def ::mutation-key (s/with-gen symbol? (default-gen ::gen-mutation-key)))
138 | 
139 | (s/def ::mutation-expr
140 |   (s/with-gen
141 |     (s/and seq? (s/cat :mutate-key ::mutation-key :params (s/? ::params)))
142 |     (default-gen ::gen-mutation-expr)))
143 | 
144 | (s/def ::mutation-join
145 |   (s/map-of ::mutation-expr ::query :count 1 :conform-keys true))
146 | 
147 | (s/def ::mutation
148 |   (s/or :mutation ::mutation-expr
149 |         :mutation-join ::mutation-join))
150 | 
151 | (s/def ::query-expr
152 |   (s/or :prop ::property
153 |         :join ::join
154 |         :ident ::ident
155 |         :mutation ::mutation
156 |         :param-exp ::param-expr
157 |         :special ::special-property))
158 | 
159 | (s/def ::query
160 |   (s/coll-of ::query-expr :kind vector? :gen (default-gen ::gen-query)))
161 |
162 |
163 |
164 |
165 |
166 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /docs/eql/1.0.0/library.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | Library | EDN Query Language 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 41 |
42 |
43 | 73 | 76 |
77 |
78 | 79 | 85 | 90 |
91 |
92 |

Library

93 |
94 |
95 |
96 |

The package edn-query-language.core provides a suite of specs to validate queries and 97 | ASTs. It also provides generators for the query and helper functions to common 98 | query operations.

99 |
100 |
101 |
102 |
103 |

Clojure Specs

104 |
105 |
106 |

The EQL library provides specs to validate and generate queries.

107 |
108 |
109 |

Validation

110 |
111 |

You can validate the query syntax using clojure.spec, here is an example:

112 |
113 |
114 |
115 |
(s/valid? ::eql/query [:sample :query]) ; => true
116 | (s/valid? ::eql/query [#{:set}]) ; => false
117 | (s/valid? ::eql/query ['(call/op {})]) ; => true
118 |
119 |
120 |
121 | 122 | 123 | 126 | 129 | 130 |
124 | 125 | 127 | s is alias for clojure.spec.alpha 128 |
131 |
132 |
133 |

You can use spec explain feature for more details:

134 |
135 |
136 |
137 |
(s/explain ::eql/query [#{:set}])
138 | ; In: [0] val: #{:set} fails spec: :edn-query-language.core/mutation-expr at: [:mutation :mutation] predicate: seq?
139 | ; In: [0] val: #{:set} fails spec: :edn-query-language.core/mutation-join at: [:mutation :mutation-join] predicate: map?
140 | ; In: [0] val: #{:set} fails spec: :edn-query-language.core/property at: [:prop] predicate: keyword?
141 | ; In: [0] val: #{:set} fails spec: :edn-query-language.core/join at: [:join] predicate: map?
142 | ; In: [0] val: #{:set} fails spec: :edn-query-language.core/ident at: [:ident] predicate: vector?
143 | ; In: [0] val: #{:set} fails spec: :edn-query-language.core/param-expr at: [:param-exp] predicate: seq?
144 | ; In: [0] val: #{:set} fails spec: :edn-query-language.core/special-property at: [:special] predicate: #{(quote *)}
145 |
146 |
147 |
148 |

I suggest you check the sources for the specs for more details on parts that compose 149 | it, they will stay consistent and can be used to validate parts of the transaction as well.

150 |
151 |
152 |
153 |

Generation

154 |
155 |

EQL also provides built-in generators, the main intended usage for it is to write generative 156 | tests for parser implementations.

157 |
158 |
159 |

Basic example to generate random queries:

160 |
161 |
162 |
163 |
(gen/sample (s/gen ::query) 10)
164 | =>
165 | ([]
166 |  []
167 |  [(:?./*_ {}) :z/ZH]
168 |  []
169 |  [#:J{:w {:c/!V [#:YY{:u [:u1/X?!
170 |                           #:r94{:*+ [#:aG{:YA 2} :t!o/Ya1 :XL/HR #:!-Q{:b_ []}]}
171 |                           :OP/E]}
172 |                  :.qE/Nd-],
173 |           :j./!T [[:p/h*y :f?1]
174 |                   #:s*{:-W []}
175 |                   (NG_
176 |                    {[] #{}, [4] (0.5 :_ -3 -Ch), #{} #{}, #{-1 {##-Inf ?.1/e?A}} {}})],
177 |           :z/s+ []}}
178 |   :-_/_
179 |   :H/E
180 |   :Y/xD]
181 |  [:?7/w :iO/! (:r/!N {{-2.0 false} [], [] [], [:P7] [0 J1]})]
182 |  [:+Bi/-K :!8*/r0 :?/Cio]
183 |  [:*.-/R* :+BT/W :-l8/c :Ih/V [:RE/- "0>WwI`u"] :H/vT]
184 |  [:z+8/g]
185 |  [])
186 |
187 |
188 |
189 | 190 | 191 | 194 | 197 | 198 |
192 | 193 | 195 | gen is alias for clojure.test.check.generators 196 |
199 |
200 |
201 |

Although fully random queries can be interesting to test some parser edge cases, in many 202 | situations you will may want to constraint how the query is generated, with this in mind 203 | EQL provides a way to enable this kind of customization. To get a sense of what you can 204 | customize you can take a look at the default implementation for each default generator, 205 | any of those keys can be tuned to constraint how the query is generated.

206 |
207 |
208 |

To demonstrate how to use this, let’s customize the generator to limit the properties it generates 209 | to a fixed set we pre defined:

210 |
211 |
212 |
213 |
(gen/sample (eql/make-gen {::eql/gen-property ; (1)
214 |                        (fn [_] (gen/elements [:id :name :title :foo :bar]))}
215 |               ::eql/gen-query) ; (2)
216 |   10)
217 | =>
218 | ([]
219 |  []
220 |  []
221 |  [[:X/q6 1] :name :title]
222 |  [({:title [(L {#{} [], () [], #{-5} ()})
223 |             (:name {{#{} {}} :., {} {}})
224 |             {:name [:bar :title]}]}
225 |    {[*+-] #{0.5625 #uuid"edf051fb-ab28-42d0-a941-152c4e87b060"},
226 |     #{#uuid"712e7415-5148-400b-99db-cfb79004700e" -1/2} (),
227 |     {} (:F/le9 #uuid"5ad52713-d13a-4888-bd92-2d1541c0387b" "" true)})
228 |   {(:foo
229 |     {[(2.0 false) z/NO] [I./j #uuid"eef64a1d-8055-4ae7-95be-06bdc4f9cefd"], {} [""]}) [:id
230 |                                                                                        ({:id [:name
231 |                                                                                               *]}
232 |                                                                                         {})]}]
233 |  [:id :id]
234 |  [{:foo [:name * [:mO/D MZ_/e0Z] :bar :foo]}]
235 |  []
236 |  [:bar]
237 |  [:foo])
238 |
239 |
240 |
241 | 242 | 243 | 244 | 246 | 247 | 248 | 249 | 250 | 251 |
1We send a map to eql/make-gen to override some of the generator settings, any non 245 | defined keys will fallback to default implementation
2Select which generator to use, this is useful to generate only sub-parts if needed
252 |
253 |
254 |

One more example changing many definitions:

255 |
256 |
257 |
258 |
(let [system (assoc generators
259 |                ::gen-params
260 |                (fn [_] (gen/map (gen/elements [:param :foo/param]) gen/string-ascii))
261 | 
262 |                ::gen-property
263 |                (fn [_] (gen/elements [:id :name :title :foo :bar :other :price :namespaced/value]))
264 | 
265 |                ::gen-ident-key
266 |                (fn [_] (gen/elements [:user/by-id :other/by-id]))
267 | 
268 |                ::gen-ident-value
269 |                (fn [_] gen/string-ascii)
270 | 
271 |                ::gen-mutation-key
272 |                (fn [_] (gen/elements '[do-something create/this-thing operation.on/space])))]
273 |   (gen/sample ((::gen-query system) system)))
274 | =>
275 | ([]
276 |  [{:other []}]
277 |  []
278 |  []
279 |  []
280 |  [{:price [{[:user/by-id "!"] []} :title]} :id]
281 |  [:bar {[:other/by-id "@"] [:foo :other :name]}]
282 |  [:name :id]
283 |  [:price :title :id :name]
284 |  [:foo
285 |   ({:bar [[:user/by-id ""] :price {:id [:other]} :other]} {})
286 |   :other
287 |   :namespaced/value
288 |   {:name [:name
289 |           {:bar [:name
290 |                  :bar
291 |                  :namespaced/value
292 |                  ({[:user/by-id "AeA$;"] [:foo]}
293 |                   {:foo/param "_+y9ihY", :param "Y@p5Bd5B"})
294 |                  :id
295 |                  :namespaced/value
296 |                  :name]}]}
297 |   :id])
298 |
299 |
300 |
301 |

If you wanna see an even more advanced usage, you can check Pathom connect generator, which 302 | uses the Pathom connect index to generate queries that are valid according to the user property graph.

303 |
304 |
305 |
306 |

Removing specs on Clojurescript

307 |
308 |

If you are not using the specs provided by EQL you can free some build space by 309 | eliding then. To do that you need to set the Clojurescript compiler options with:

310 |
311 |
312 |
313 |
{:closure-defines {edn-query-language.core.INCLUDE_SPECS false}}
314 |
315 |
316 |
317 |
318 |
319 |
320 |

AST Encode/Decode

321 |
322 |
323 |

To convert between query and AST, EQL provides the helper functions eql/query→ast and 324 | eql/ast→query. Here are some example usages:

325 |
326 |
327 |
328 |
(eql/query->ast [:foo])
329 | ; => {:type :root, :children [{:type :prop, :dispatch-key :foo, :key :foo}]}
330 | 
331 | (eql/ast->query {:type :root, :children [{:type :prop, :dispatch-key :foo, :key :foo}]})
332 | ; => [:foo]
333 |
334 |
335 |
336 |
337 |
338 |

API Docs

339 |
340 |
341 |

Check the complete API docs at EQL cljdoc page.

342 |
343 |
344 |
345 |
346 |
347 |
348 | 355 | 356 | 357 | 358 | 359 | -------------------------------------------------------------------------------- /docs/eql/1.0.0/what-is-eql.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | Untitled | EDN Query Language 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 41 |
42 |
43 | 73 | 76 |
77 |
78 | 79 | 85 | 90 |
91 |
92 |
93 |

eql logo

94 |
95 |
96 |

What is EQL?

97 |
98 |
99 |

EQL is a declarative way to make hierarchical (and possibly nested) selections of information about data requirements.

100 |
101 |
102 |

EQL doesn’t have its own language; it uses EDN to express the request, taking advantage of 103 | the rich set of primitives provided by it.

104 |
105 |
106 |
107 |
108 |

EQL for selections

109 |
110 |
111 |

An easy way to get started is to think of a map and try to describe its shape. For example:

112 |
113 |
114 |
115 |
{:album/name "Magical Mystery Tour"
116 |  :album/year 1967}
117 |
118 |
119 |
120 |

By describing the shape, we mean describing the structure but without the values, the previous example can be described using EQL as:

121 |
122 |
123 |
124 |
[:album/name :album/year]
125 |
126 |
127 |
128 |

Like using select-keys to specify which fields to extract from a map. Now let’s see 129 | what it looks like for nested structures:

130 |
131 |
132 |
133 |
{:album/name   "Magical Mystery Tour"
134 |  :album/artist {:artist/name "The Beatles"}}
135 | 
136 | ; can be described as:
137 | 
138 | [:album/name
139 |  ; note the use of a map to express nesting
140 |  {:album/artist
141 |   [:artist/name]}]
142 |
143 |
144 |
145 |

It works the same to represent nested sequences:

146 |
147 |
148 |
149 |
{:album/name   "Magical Mystery Tour"
150 |  :album/tracks [{:track/name "The Fool On The Hill"}
151 |                 {:track/name "All You Need Is Love"}]}
152 | 
153 | ; can be described as:
154 | 
155 | [:album/name
156 |  {:album/tracks
157 |   [:track/name]}]
158 |
159 |
160 |
161 | 162 | 163 | 166 | 172 | 173 |
164 | 165 | 167 | Although with just EQL you can’t know if a key value is expected to be a single item or a sequence, you 168 | can have this information setup out of band using Clojure specs. If you do so, you can instrospect the spec 169 | and detect that, this is not a feature of EQL in any way, just a suggested approach in case you need to know 170 | if the response of a key is a single item or a sequence. 171 |
174 |
175 |
176 |
177 |
178 |

EQL for operations

179 |
180 |
181 |

EQL also supports mutations, which are like side effect calls to an API. Mutations can 182 | appear on EQL transaction, and they look like Clojure function calls, example:

183 |
184 |
185 |
186 |
[(call-mutation {:data "value"})]
187 |
188 |
189 |
190 |

More details in Mutations.

191 |
192 |
193 |
194 |
195 |

Datomic Pull Syntax comparison

196 |
197 |
198 |

On top of the Datomic Pull Syntax expression, EQL also supports:

199 |
200 |
201 | 212 |
213 |
214 |

Check the links on each for more details.

215 |
216 |
217 |

The attribute with options feature 218 | from the Datomic Pull Syntax is not present in EQL; instead, we provide the parameterized 219 | attributes that can handle arbitrary data to go along the base property.

220 |
221 |
222 |
223 |
224 |

GraphQL comparison

225 |
226 |
227 |

Similar to GraphQL, EQL works as a language to client libraries to communicate data requirements and operations, 228 | there is a good set of intersection in features between both, as:

229 |
230 |
231 |
    232 |
  • 233 |

    a language to describe arbitrarily nested structures

    234 |
  • 235 |
  • 236 |

    support for mutations (operations to side effect the world)

    237 |
  • 238 |
  • 239 |

    support for parametrization

    240 |
  • 241 |
  • 242 |

    union queries for query branching (select query according to some custom definition based on the data)

    243 |
  • 244 |
245 |
246 |
247 |

GraphQL has a type system in its definition, and it is required for a GraphQL system to work. EQL has 248 | no such thing, and it dictates only the syntax but not the semantics. Some features in 249 | GraphQL don’t make sense in EQL, like fragments, since EQL is already a data format (EDN), 250 | it’s easy to compose data as you would do in a regular Clojure program, for that reason 251 | many features are not necessary because EQL is a parseable data format with all the Clojure 252 | tools already available to operate on top of it. Also read the AST Encode/Decode 253 | section for more information on how to programmatically manipulate EQL data structures.

254 |
255 |
256 |
257 |
258 |
259 |
260 | 267 | 268 | 269 | 270 | 271 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Redirect Notice 8 |

Redirect Notice

9 |

The page you requested has been relocated to http://edn-query-language.org/eql/1.0.0/what-is-eql.html.

-------------------------------------------------------------------------------- /docs/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | http://edn-query-language.org/eql/1.0.0/clojure-specs.html 5 | 2021-12-01T11:59:08.713Z 6 | 7 | 8 | http://edn-query-language.org/eql/1.0.0/library.html 9 | 2021-12-01T11:59:08.713Z 10 | 11 | 12 | http://edn-query-language.org/eql/1.0.0/specification.html 13 | 2021-12-01T11:59:08.713Z 14 | 15 | 16 | http://edn-query-language.org/eql/1.0.0/what-is-eql.html 17 | 2021-12-01T11:59:08.713Z 18 | 19 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject edn-query-language/eql "2021.07.18" 2 | :description "EDN Query Language support library" 3 | :url "https://github.com/edn-query-language/eql" 4 | :license {:name "MIT" :url "https://opensource.org/licenses/MIT"} 5 | 6 | :plugins [[lein-tools-deps "0.4.1"]] 7 | :middleware [lein-tools-deps.plugin/resolve-dependencies-with-deps-edn] 8 | :lein-tools-deps/config {:config-files [:install :user :project]} 9 | 10 | :jar-exclusions [#"public/.*" #"\.DS_Store"]) 11 | -------------------------------------------------------------------------------- /script/release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | lein vcs tag v 4 | git push --follow-tags 5 | lein deploy clojars 6 | -------------------------------------------------------------------------------- /src/edn_query_language/core.cljc: -------------------------------------------------------------------------------- 1 | (ns edn-query-language.core 2 | (:refer-clojure :exclude [ident?]) 3 | (:require [clojure.spec.alpha :as s])) 4 | 5 | (def ^:dynamic *shallow-conversion* 6 | "Dynamic var. When bound to true in the current thread, calls to query->ast will not go past the 7 | first level of children. This is useful when you just want the AST for one layer of a query." 8 | false) 9 | 10 | #?(:clj (def INCLUDE_SPECS true) 11 | :cljs (goog-define INCLUDE_SPECS true)) 12 | 13 | (when INCLUDE_SPECS 14 | ; tag::specs[] 15 | (s/def ::property keyword?) 16 | (s/def ::special-property #{'*}) 17 | (s/def ::ident-value any?) 18 | (s/def ::ident (s/tuple ::property ::ident-value)) 19 | (s/def ::join-key (s/or :prop ::property, :ident ::ident, :param-exp ::join-key-param-expr)) 20 | (s/def ::join (s/map-of ::join-key ::join-query, :count 1, :conform-keys true)) 21 | (s/def ::union (s/map-of ::property ::query, :min-count 1, :conform-keys true)) 22 | (s/def ::recursion-depth nat-int?) 23 | (s/def ::recursion (s/or :depth ::recursion-depth, :unbounded #{'...})) 24 | 25 | (s/def ::join-query 26 | (s/or :query ::query 27 | :union ::union 28 | :recursion ::recursion)) 29 | 30 | (s/def ::params 31 | map?) 32 | 33 | (s/def ::param-expr-key 34 | (s/or :prop ::property 35 | :join ::join 36 | :ident ::ident)) 37 | 38 | (s/def ::param-expr 39 | (s/and seq? (s/cat :expr ::param-expr-key :params (s/? ::params)))) 40 | 41 | (s/def ::join-key-param-key (s/or :prop ::property :ident ::ident)) 42 | 43 | (s/def ::join-key-param-expr 44 | (s/and seq? (s/cat :expr ::join-key-param-key :params (s/? ::params)))) 45 | 46 | (s/def ::mutation-key symbol?) 47 | 48 | (s/def ::mutation-expr 49 | (s/and seq? (s/cat :mutate-key ::mutation-key :params (s/? ::params)))) 50 | 51 | (s/def ::mutation-join 52 | (s/map-of ::mutation-expr ::query :count 1 :conform-keys true)) 53 | 54 | (s/def ::mutation 55 | (s/or :mutation ::mutation-expr 56 | :mutation-join ::mutation-join)) 57 | 58 | (s/def ::query-expr 59 | (s/or :prop ::property 60 | :join ::join 61 | :ident ::ident 62 | :mutation ::mutation 63 | :param-exp ::param-expr 64 | :special ::special-property)) 65 | 66 | (s/def ::query 67 | (s/coll-of ::query-expr :kind vector?)) 68 | ; end::specs[] 69 | 70 | ;; ast specs 71 | 72 | (s/def :edn-query-language.ast/query ::join-query) 73 | (s/def :edn-query-language.ast/key (s/or :prop ::property :ident ::ident :sym symbol?)) 74 | (s/def :edn-query-language.ast/dispatch-key (s/or :prop ::property :sym symbol?)) 75 | (s/def :edn-query-language.ast/union-key ::property) 76 | 77 | (s/def :edn-query-language.ast/children 78 | (s/coll-of :edn-query-language.ast/node)) 79 | 80 | (s/def :edn-query-language.ast/root 81 | (s/and (s/keys :req-un [:edn-query-language.ast/type :edn-query-language.ast/children]) 82 | #(= :root (:type %)) 83 | (fn [x] (every? (comp #(contains? #{:prop :join :call nil} %) :type) (:children x))))) 84 | 85 | (defmulti node-type :type) 86 | 87 | (defmethod node-type nil [_] 88 | (s/keys :req-un [:edn-query-language.ast/key :edn-query-language.ast/dispatch-key])) 89 | 90 | (defmethod node-type :prop [_] 91 | (s/keys :req-un [:edn-query-language.ast/type :edn-query-language.ast/key :edn-query-language.ast/dispatch-key])) 92 | 93 | (defmethod node-type :join [_] 94 | (s/and (s/keys :req-un [:edn-query-language.ast/type :edn-query-language.ast/key :edn-query-language.ast/dispatch-key] :opt-un [:edn-query-language.ast/children :edn-query-language.ast/query]) 95 | #(if (-> % :query first (= :recursion)) % (if (contains? % :children) % false)) 96 | (fn [x] (every? (comp #(contains? #{:prop :join :union :call nil} %) :type) (:children x))))) 97 | 98 | (defmethod node-type :union [_] 99 | (s/and (s/keys :req-un [:edn-query-language.ast/type :edn-query-language.ast/children] :opt-un [:edn-query-language.ast/query]) 100 | #(every? (comp #{:union-entry} :type) (:children %)))) 101 | 102 | (defmethod node-type :union-entry [_] 103 | (s/and (s/keys :req-un [:edn-query-language.ast/type :edn-query-language.ast/union-key :edn-query-language.ast/children] 104 | :opt-un [:edn-query-language.ast/query]) 105 | (fn [x] (every? (comp #(contains? #{:prop :join :call nil} %) :type) (:children x))))) 106 | 107 | (defmethod node-type :call [_] 108 | (s/and (s/keys 109 | :req-un [:edn-query-language.ast/type :edn-query-language.ast/key :edn-query-language.ast/dispatch-key ::params] 110 | :opt-un [:edn-query-language.ast/query :edn-query-language.ast/children]) 111 | (fn [x] (every? (comp #(contains? #{:prop :join :call nil} %) :type) (:children x))))) 112 | 113 | (defmethod node-type :root [_] 114 | (s/spec :edn-query-language.ast/root)) 115 | 116 | (s/def :edn-query-language.ast/type (set (keys (methods node-type)))) 117 | (s/def :edn-query-language.ast/node (s/multi-spec node-type :type))) 118 | 119 | ;; library 120 | 121 | (declare expr->ast) 122 | 123 | (defn- mark-meta [source target] 124 | (cond-> target 125 | (meta source) (assoc :meta (meta source)))) 126 | 127 | (defn symbol->ast [k] 128 | {:dispatch-key k 129 | :key k}) 130 | 131 | (defn keyword->ast [k] 132 | {:type :prop 133 | :dispatch-key k 134 | :key k}) 135 | 136 | (defn union-entry->ast [[k v]] 137 | (let [component (-> v meta :component)] 138 | (merge 139 | {:type :union-entry 140 | :union-key k 141 | :query v 142 | :children (into [] (map expr->ast) v)} 143 | (when-not (nil? component) 144 | {:component component})))) 145 | 146 | (defn union->ast [m] 147 | {:type :union 148 | :query m 149 | :children (into [] (map union-entry->ast) m)}) 150 | 151 | (defn call->ast [[f args :as call]] 152 | (if (= 'quote f) 153 | (assoc (expr->ast args) :target (or (-> call meta :target) :remote)) 154 | (let [ast (update-in (expr->ast f) [:params] merge (or args {}))] 155 | (cond-> (mark-meta call ast) 156 | (symbol? (:dispatch-key ast)) (assoc :type :call))))) 157 | 158 | (defn query->ast 159 | "Convert a query to its AST representation." 160 | [query] 161 | (let [component (-> query meta :component)] 162 | (merge 163 | (mark-meta query 164 | {:type :root 165 | :children (into [] (map expr->ast) query)}) 166 | (when-not (nil? component) 167 | {:component component})))) 168 | 169 | (defn query->ast1 170 | "Call query->ast and return the first children." 171 | [query-expr] 172 | (-> (query->ast query-expr) :children first)) 173 | 174 | (defn join->ast [join] 175 | (let [query-root? (-> join meta :query-root) 176 | [k v] (first join) 177 | ast (expr->ast k) 178 | type (if (= :call (:type ast)) :call :join) 179 | component (-> v meta :component)] 180 | (merge ast 181 | (mark-meta join {:type type :query v}) 182 | (when-not (nil? component) 183 | {:component component}) 184 | (when query-root? 185 | {:query-root true}) 186 | (when-not (or (number? v) (= '... v) *shallow-conversion*) 187 | (cond 188 | (vector? v) {:children (into [] (map expr->ast) v)} 189 | (map? v) {:children [(union->ast v)]} 190 | :else (throw 191 | (ex-info (str "Invalid join, " join) 192 | {:type :error/invalid-join}))))))) 193 | 194 | (defn ident->ast [[k id :as ref]] 195 | {:type :prop 196 | :dispatch-key k 197 | :key ref}) 198 | 199 | (defn expr->ast 200 | "Given a query expression convert it into an AST." 201 | [x] 202 | (cond 203 | (symbol? x) (symbol->ast x) 204 | (keyword? x) (keyword->ast x) 205 | (map? x) (join->ast x) 206 | (vector? x) (ident->ast x) 207 | (seq? x) (call->ast x) 208 | :else (throw 209 | (ex-info (str "Invalid expression " x) 210 | {:type :error/invalid-expression})))) 211 | 212 | (defn wrap-expr [root? expr] 213 | (if root? 214 | (with-meta 215 | (cond-> expr (keyword? expr) list) 216 | {:query-root true}) 217 | expr)) 218 | 219 | (defn parameterize [expr params] 220 | (if-not (empty? params) 221 | (list expr params) 222 | (list expr))) 223 | 224 | (defn ast->expr 225 | "Given a query expression AST convert it back into a query expression." 226 | ([ast] 227 | (ast->expr ast false)) 228 | ([{:keys [type component] ast-meta :meta :as ast} unparse?] 229 | (if (= :root type) 230 | (cond-> (into (with-meta [] ast-meta) (map #(ast->expr % unparse?)) (:children ast)) 231 | (not (nil? component)) (vary-meta assoc :component component)) 232 | (let [{:keys [key query query-root params]} ast] 233 | (wrap-expr query-root 234 | (if (and params (not= :call type)) 235 | (let [expr (ast->expr (dissoc ast :params) unparse?)] 236 | (parameterize expr params)) 237 | (let [key (if (= :call type) (parameterize key params) key)] 238 | (if (or (= :join type) 239 | (and (= :call type) (:children ast))) 240 | (if (and (not= '... query) (not (number? query)) 241 | (or (true? unparse?) 242 | (= :call type))) 243 | (let [{:keys [children]} ast 244 | query-meta (meta query)] 245 | (if (and (== 1 (count children)) 246 | (= :union (:type (first children)))) ;; UNION 247 | (with-meta 248 | {key (into (cond-> (with-meta {} ast-meta) 249 | component (vary-meta assoc :component component)) 250 | (map (fn [{:keys [union-key children component]}] 251 | [union-key 252 | (cond-> (into [] (map #(ast->expr % unparse?)) children) 253 | (not (nil? component)) (vary-meta assoc :component component))])) 254 | (:children (first children)))} 255 | ast-meta) 256 | (with-meta 257 | {key (cond-> (into (with-meta [] query-meta) (map #(ast->expr % unparse?)) children) 258 | (not (nil? component)) (vary-meta assoc :component component))} 259 | ast-meta))) 260 | (with-meta {key query} ast-meta)) 261 | key)))))))) 262 | 263 | (defn ast->query [query-ast] 264 | "Given an AST convert it back into a query expression." 265 | (as-> (ast->expr query-ast true) <> 266 | (if (vector? <>) 267 | <> 268 | [<>]))) 269 | 270 | (defn ident? 271 | "Check if x is a EQL ident." 272 | [x] 273 | (and (vector? x) 274 | (keyword? (first x)) 275 | (= 2 (count x)))) 276 | 277 | ;; query processing helpers 278 | 279 | (declare focus-subquery*) 280 | 281 | (defn focus-subquery-union* 282 | [query-ast sub-ast] 283 | (let [s-index (into {} (map #(vector (:union-key %) %)) (:children sub-ast))] 284 | (assoc query-ast 285 | :children 286 | (reduce 287 | (fn [children {:keys [union-key] :as union-entry}] 288 | (if-let [sub (get s-index union-key)] 289 | (conj children (focus-subquery* union-entry sub)) 290 | (conj children union-entry))) 291 | [] 292 | (:children query-ast))))) 293 | 294 | (defn focus-subquery* 295 | "Internal implementation of focus-subquery, you can use this function directly if 296 | you want to send AST in and get AST out (instead of query in / query out)." 297 | [query-ast sub-ast] 298 | (let [q-index (into {} (map #(vector (:key %) %)) (:children query-ast))] 299 | (assoc query-ast 300 | :children 301 | (reduce 302 | (fn [children {:keys [key type] :as focus}] 303 | (if-let [source (get q-index key)] 304 | (cond 305 | (= :join type (:type source)) 306 | (conj children (focus-subquery* source focus)) 307 | 308 | (= :union type (:type source)) 309 | (conj children (focus-subquery-union* source focus)) 310 | 311 | :else 312 | (conj children source)) 313 | children)) 314 | [] 315 | (:children sub-ast))))) 316 | 317 | (defn focus-subquery 318 | "Given a query, focus it along the specified query expression. 319 | 320 | Examples: 321 | (focus-query [:foo :bar :baz] [:foo]) 322 | => [:foo] 323 | 324 | (fulcro.client.primitives/focus-query [{:foo [:bar :baz]} :woz] [{:foo [:bar]} :woz]) 325 | => [{:foo [:bar]} :woz]" 326 | [query sub-query] 327 | (let [query-ast (query->ast query) 328 | sub-ast (query->ast sub-query)] 329 | (ast->expr (focus-subquery* query-ast sub-ast) true))) 330 | 331 | (defn transduce-children 332 | "Recursivelly transduce children on the AST, you can use this to apply filter/transformations 333 | on a whole AST. Each iteration of the transducer will get a single AST node to process. 334 | 335 | ``` 336 | (->> [:a {:b [:c :d]} :e] 337 | (p/query->ast) 338 | (p/transduce-children (remove (comp #{:a :c} :key))) 339 | (p/ast->query)) 340 | ; => [{:b [:d]} :e] 341 | ```" 342 | [xform {:keys [children] :as node}] 343 | (cond-> node 344 | (seq children) 345 | (update :children 346 | (fn [children] 347 | (into [] (comp xform (map #(transduce-children xform %))) children))))) 348 | 349 | (defn union-children? 350 | "Given an AST point, check if the children is a union query type." 351 | [ast] 352 | (= :union (some-> ast :children first :type))) 353 | 354 | (defn update-property-param 355 | "Add property param, eg: 356 | 357 | ``` 358 | (p/update-property-param :keyword assoc :foo \"bar\") => (:keyword {:foo \"bar\"}) 359 | (p/update-property-param '(:keyword {:param \"prev\"}) assoc :foo \"bar\") => (:keyword {:foo \"bar\" :param \"prev\"}) 360 | ``` 361 | " 362 | [x f & args] 363 | (if (seq? x) 364 | (let [[k p] x] 365 | (list k (apply f p args))) 366 | 367 | (list x (apply f {} args)))) 368 | 369 | (defn merge-asts 370 | "Merges two ast's." 371 | ([] {:type :root 372 | :children []}) 373 | ([q] q) 374 | ([qa qb] 375 | (reduce (fn [ast {:keys [key type params] :as item-b}] 376 | (if-let [[idx item] (->> ast :children 377 | (keep-indexed #(if (-> %2 :key (= key)) [%1 %2])) 378 | first)] 379 | (cond 380 | (or (= :join (:type item) type) 381 | (= :prop (:type item) type)) 382 | (if (= (:params item) params) 383 | (update-in ast [:children idx] merge-asts item-b) 384 | (reduced nil)) 385 | 386 | (and (= :prop (:type item)) 387 | (= :join type)) 388 | (assoc-in ast [:children idx] item-b) 389 | 390 | (= :call type) 391 | (reduced nil) 392 | 393 | :else ast) 394 | (update ast :children conj item-b))) 395 | qa 396 | (:children qb)))) 397 | 398 | (defn merge-queries 399 | "Merges two queries" 400 | [qa qb] 401 | (some-> (merge-asts (query->ast qa) (query->ast qb)) 402 | (ast->query))) 403 | 404 | (defn mask-query* [{:keys [children] :as source-ast} mask-ast] 405 | (reduce 406 | (fn [ast {mask-children :children 407 | :keys [key] 408 | :as mask-node}] 409 | (if-let [source-node (->> children (filter (comp #{key} :key)) first)] 410 | (if (and (seq (:children source-node)) (seq mask-children)) 411 | (update ast :children conj (mask-query* source-node mask-node)) 412 | (update ast :children conj source-node)) 413 | ast)) 414 | (assoc source-ast :children []) 415 | (:children mask-ast))) 416 | 417 | (defn update-child 418 | "Given an AST, find the child with a given key and run update against it." 419 | [ast key & args] 420 | (if-let [idx (some->> (:children ast) 421 | (map-indexed vector) 422 | (filter (comp #{key} :key second)) 423 | ffirst)] 424 | (apply update-in ast [:children idx] args) 425 | ast)) 426 | 427 | (defn update-recursive-depth 428 | "Given an AST, find the child with a given key and run update against it." 429 | [ast key & args] 430 | (if-let [idx (some->> (:children ast) 431 | (map-indexed vector) 432 | (filter (comp #(and (= key (:key %)) 433 | (pos-int? (:query %))) second)) 434 | ffirst)] 435 | (apply update-in ast [:children idx :query] args) 436 | ast)) 437 | 438 | (defn mask-query 439 | "Given a source EQL query, use a mask EQL query to filter which elements to pick from 440 | the source. Params will be maintaned from the source, params in mask are ignored." 441 | [source mask] 442 | (let [source-ast (query->ast source) 443 | mask-ast (query->ast mask)] 444 | (ast->query (mask-query* source-ast mask-ast)))) 445 | 446 | (defn normalize-query-variables 447 | "Converts ident values and param values to ::p/var." 448 | [query] 449 | (->> (query->ast query) 450 | (transduce-children 451 | (map (fn [x] 452 | (cond-> x 453 | (ident? (:key x)) 454 | (assoc :key [(first (:key x)) ::var]) 455 | 456 | (:params x) 457 | (update :params #(into {} (map (fn [[k _]] [k ::var])) %)))))) 458 | (ast->query))) 459 | 460 | (defn query-id 461 | "Generates a consistent hash from the query. The query first goes to a process to remove any 462 | variables from idents and params, then we get the Clojure hash of it. You can use this to save 463 | information about a query that can be used to correlate with the query later." 464 | [query] 465 | (hash (normalize-query-variables query))) 466 | 467 | (defn query->shallow-ast 468 | "Like query->ast, but does not follow joins. Useful for efficiently getting just the top-level entries in 469 | a large query." 470 | [query] 471 | (binding [*shallow-conversion* true] 472 | (query->ast query))) 473 | 474 | (when INCLUDE_SPECS 475 | (s/fdef query->ast 476 | :args (s/cat :query (s/nilable ::query)) 477 | :ret :edn-query-language.ast/root) 478 | 479 | (s/fdef query->ast1 480 | :args (s/cat :query ::query) 481 | :ret (s/nilable :edn-query-language.ast/node)) 482 | 483 | (s/fdef ast->query 484 | :args (s/cat :ast :edn-query-language.ast/node) 485 | :ret ::query) 486 | 487 | (s/fdef ident? 488 | :args (s/cat :x any?) 489 | :ret boolean?) 490 | 491 | (s/fdef focus-subquery 492 | :args (s/cat :query ::query :sub-query ::query) 493 | :ret ::query) 494 | 495 | (s/fdef transduce-children 496 | :args (s/cat :xform fn? :node :edn-query-language.ast/node) 497 | :ret :edn-query-language.ast/node) 498 | 499 | (s/fdef union-children? 500 | :args (s/cat :ast :edn-query-language.ast/node) 501 | :ret boolean?) 502 | 503 | (s/fdef update-property-param 504 | :args (s/cat :x (s/or :property ::property 505 | :expr ::param-expr) 506 | :f fn? 507 | :args (s/* any?)) 508 | :ret ::param-expr) 509 | 510 | (s/fdef merge-asts 511 | :args (s/or 512 | :init (s/cat) 513 | :completion (s/cat :q :edn-query-language.ast/node) 514 | :step (s/cat :qa :edn-query-language.ast/node, :qb :edn-query-language.ast/node)) 515 | :ret (s/nilable :edn-query-language.ast/node)) 516 | 517 | (s/fdef merge-queries 518 | :args (s/cat :qa (s/nilable ::query), :qb (s/nilable ::query)) 519 | :ret (s/nilable ::query))) 520 | -------------------------------------------------------------------------------- /src/edn_query_language/gen.cljc: -------------------------------------------------------------------------------- 1 | (ns edn-query-language.gen 2 | (:require 3 | [clojure.test.check] 4 | [clojure.test.check.generators :as gen #?@(:cljs [:include-macros true])] 5 | [clojure.test.check.properties] 6 | [clojure.spec.alpha :as s] 7 | [edn-query-language.core :as eql])) 8 | 9 | (when eql/INCLUDE_SPECS 10 | ;; query specs 11 | 12 | (def generators 13 | {::gen-max-depth 14 | 4 15 | 16 | ::gen-property 17 | (fn gen-property [_] gen/keyword-ns) 18 | 19 | ::gen-special-property 20 | (fn gen-special-property [_] (gen/return '*)) 21 | 22 | ::gen-ident-key 23 | (fn gen-ident-key [_] gen/keyword-ns) 24 | 25 | ::gen-ident-value 26 | (fn gen-ident-value [_] 27 | (gen/frequency [[15 gen/simple-type-printable] 28 | [1 (gen/return '_)]])) 29 | 30 | ::gen-ident 31 | (fn gen-ident [{::keys [gen-ident-key gen-ident-value] :as env}] 32 | (gen/tuple 33 | (gen-ident-key env) 34 | (gen-ident-value env))) 35 | 36 | ::gen-params 37 | (fn gen-params [_] (gen/map gen/any-printable gen/any-printable)) 38 | 39 | ::gen-join-key 40 | (fn gen-join-key [{::keys [gen-property gen-ident gen-join-key-param-expr] :as env}] 41 | (gen/frequency [[10 (gen-property env)] 42 | [3 (gen-ident env)] 43 | [1 (gen-join-key-param-expr env)]])) 44 | 45 | ::gen-join-key-param-key 46 | (fn gen-join-key-param-key [{::keys [gen-property gen-ident] :as env}] 47 | (gen/one-of [(gen-property env) (gen-ident env)])) 48 | 49 | ::gen-join-key-param-expr 50 | (fn gen-join-key-param-expr [{::keys [gen-join-key-param-key gen-params] :as env}] 51 | (gen/let [q (gen-join-key-param-key env) 52 | p (gen-params env)] 53 | (list q p))) 54 | 55 | ::gen-join 56 | (fn gen-join [{::keys [gen-join-key gen-join-query] :as env}] 57 | (gen/map (gen-join-key env) (gen-join-query env) {:num-elements 1})) 58 | 59 | ::gen-join-query 60 | (fn gen-join-query [{::keys [gen-query gen-union gen-recursion] :as env}] 61 | (gen/frequency [[10 (gen-query env)] 62 | [2 (gen-union env)] 63 | [1 (gen-recursion env)]])) 64 | 65 | ::gen-union-key 66 | (fn gen-union-key [_] gen/keyword-ns) 67 | 68 | ::gen-union 69 | (fn gen-union [{::keys [gen-union-key gen-query] :as env}] 70 | (gen/map (gen-union-key env) (gen-query env) {:min-elements 1})) 71 | 72 | ::gen-depth 73 | (fn gen-depth [_] (gen/large-integer* {:min 1 :max 5})) 74 | 75 | ::gen-recursion 76 | (fn gen-recursion [{::keys [gen-depth] :as env}] 77 | (gen/one-of [(gen-depth env) (gen/return '...)])) 78 | 79 | ::gen-param-expr-key 80 | (fn gen-param-expr-key [{::keys [gen-property gen-join gen-ident] :as env}] 81 | (gen/frequency [[20 (gen-property env)] 82 | [8 (gen-join env)] 83 | [4 (gen-ident env)]])) 84 | 85 | ::gen-param-expr 86 | (fn gen-param-expr [{::keys [gen-param-expr-key gen-params] :as env}] 87 | (gen/let [q (gen-param-expr-key env) 88 | p (gen-params env)] 89 | (list q p))) 90 | 91 | ::gen-query-expr 92 | (fn gen-query-expr [{::keys [gen-property gen-join gen-ident gen-param-expr gen-special-property gen-mutation] 93 | :as env}] 94 | (gen/frequency [[20 (gen-property env)] 95 | [6 (gen-join env)] 96 | [1 (gen-ident env)] 97 | [2 (gen-param-expr env)] 98 | [1 (gen-mutation env)] 99 | [1 (gen-special-property env)]])) 100 | 101 | ::gen-query 102 | (fn gen-query [{::keys [gen-property gen-query-expr gen-max-depth] :as env}] 103 | (if (> gen-max-depth 0) 104 | (gen/vector (gen-query-expr (update env ::gen-max-depth dec))) 105 | (gen/vector-distinct (gen-property env)))) 106 | 107 | ::gen-mutation-key 108 | (fn gen-mutation-key [_] gen/symbol) 109 | 110 | ::gen-mutation-expr 111 | (fn gen-mutation-expr [{::keys [gen-mutation-key gen-params] :as env}] 112 | (gen/let [key (gen-mutation-key env) 113 | val (gen-params env)] 114 | (list key val))) 115 | 116 | ::gen-mutation-join 117 | (fn mutation-join [{::keys [gen-mutation-expr gen-query] :as env}] 118 | (gen/map (gen-mutation-expr env) (gen-query env) {:num-elements 1})) 119 | 120 | ::gen-mutation 121 | (fn gen-mutation [{::keys [gen-mutation-expr gen-mutation-join] :as env}] 122 | (gen/frequency [[5 (gen-mutation-expr env)] 123 | [1 (gen-mutation-join env)]]))}) 124 | 125 | (defn default-gen [name] 126 | #((get generators name) generators)) 127 | 128 | (defn make-gen 129 | [env name] 130 | (let [env (merge generators env) 131 | gen (get env name)] 132 | (assert gen (str "No generator available for " name)) 133 | ((get env name) env)))) 134 | -------------------------------------------------------------------------------- /test/edn_query_language/core_test.cljc: -------------------------------------------------------------------------------- 1 | (ns edn-query-language.core-test 2 | (:require [clojure.spec.alpha :as s] 3 | [clojure.spec.test.alpha :as s.test] 4 | [clojure.test :refer [deftest is testing]] 5 | [clojure.test.check :as tc] 6 | [clojure.test.check.clojure-test :as test] 7 | [clojure.test.check.generators :as gen] 8 | [clojure.test.check.properties :as props] 9 | [edn-query-language.core :as eql] 10 | [edn-query-language.gen :as eql-gen])) 11 | 12 | (s.test/instrument) 13 | 14 | ;; spec tests 15 | 16 | (defn valid-queries-props [] 17 | (props/for-all [query (eql-gen/make-gen {} ::eql-gen/gen-query)] 18 | (s/valid? ::eql/query query))) 19 | 20 | (test/defspec generator-makes-valid-queries {:max-size 12 :num-tests 50} (valid-queries-props)) 21 | 22 | (comment 23 | (tc/quick-check 50 (valid-queries-props) :max-size 12)) 24 | 25 | ;; lib tests 26 | 27 | (defn remove-meta [x] 28 | (eql/transduce-children (map #(dissoc % :meta)) x)) 29 | 30 | (defn tquery->ast [query] 31 | (remove-meta (eql/query->ast query))) 32 | 33 | (deftest test-query->ast 34 | (testing "empty query" 35 | (is (= (tquery->ast []) 36 | {:type :root, :children []}))) 37 | 38 | (testing "single property" 39 | (is (= (tquery->ast [:a]) 40 | {:type :root, :children [{:type :prop, :dispatch-key :a, :key :a}]}))) 41 | 42 | (testing "multiple properties" 43 | (is (= (tquery->ast [:a :b]) 44 | {:type :root, 45 | :children [{:type :prop, :dispatch-key :a, :key :a} 46 | {:type :prop, :dispatch-key :b, :key :b}]}))) 47 | 48 | (testing "blank join" 49 | (is (= (tquery->ast [{:a []}]) 50 | {:type :root, 51 | :children [{:type :join, :dispatch-key :a, :key :a, :query [], :children []}]}))) 52 | 53 | (testing "simple join" 54 | (is (= (tquery->ast [{:a [:b]}]) 55 | {:type :root, 56 | :children [{:type :join, 57 | :dispatch-key :a, 58 | :key :a, 59 | :query [:b], 60 | :children [{:type :prop, :dispatch-key :b, :key :b}]}]}))) 61 | 62 | (testing "param expression" 63 | (is (= (tquery->ast ['(:a {:foo "bar"})]) 64 | {:type :root, 65 | :children [{:type :prop, 66 | :dispatch-key :a, 67 | :key :a, 68 | :params {:foo "bar"},}]}))) 69 | 70 | (testing "param join" 71 | (is (= (tquery->ast ['({:a [:sub]} {:foo "bar"})]) 72 | {:type :root, 73 | :children [{:type :join, 74 | :dispatch-key :a, 75 | :key :a, 76 | :query [:sub], 77 | :children [{:type :prop, :dispatch-key :sub, :key :sub}], 78 | :params {:foo "bar"},}]}))) 79 | 80 | (testing "param join 2" 81 | (is (= (tquery->ast [{'(:a {:foo "bar"}) [:sub]}]) 82 | {:type :root 83 | :children [{:children [{:dispatch-key :sub 84 | :key :sub 85 | :type :prop}] 86 | :dispatch-key :a 87 | :key :a 88 | :params {:foo "bar"} 89 | :query [:sub] 90 | :type :join}]}))) 91 | 92 | (testing "union query" 93 | (is (= (tquery->ast [{:foo {:a [:b] 94 | :c [:d]}}]) 95 | {:type :root, 96 | :children [{:type :join, 97 | :dispatch-key :foo, 98 | :key :foo, 99 | :query {:a [:b], :c [:d]}, 100 | :children [{:type :union, 101 | :query {:a [:b], :c [:d]}, 102 | :children [{:type :union-entry, 103 | :union-key :a, 104 | :query [:b], 105 | :children [{:type :prop, :dispatch-key :b, :key :b}]} 106 | {:type :union-entry, 107 | :union-key :c, 108 | :query [:d], 109 | :children [{:type :prop, :dispatch-key :d, :key :d}]}]}]}]}))) 110 | 111 | (testing "unbounded recursion" 112 | (is (= (tquery->ast '[{:item [:a :b {:parent ...}]}]) 113 | '{:type :root, 114 | :children [{:type :join, 115 | :dispatch-key :item, 116 | :key :item, 117 | :query [:a :b {:parent ...}], 118 | :children [{:type :prop, :dispatch-key :a, :key :a} 119 | {:type :prop, :dispatch-key :b, :key :b} 120 | {:type :join, :dispatch-key :parent, :key :parent, :query ...}]}]}))) 121 | 122 | (testing "bounded recursion" 123 | (is (= (tquery->ast '[{:item [:a :b {:parent 5}]}]) 124 | '{:type :root, 125 | :children [{:type :join, 126 | :dispatch-key :item, 127 | :key :item, 128 | :query [:a :b {:parent 5}], 129 | :children [{:type :prop, :dispatch-key :a, :key :a} 130 | {:type :prop, :dispatch-key :b, :key :b} 131 | {:type :join, :dispatch-key :parent, :key :parent, :query 5}]}]}))) 132 | 133 | (testing "mutation expression" 134 | (is (= (tquery->ast ['(a {})]) 135 | '{:type :root, 136 | :children [{:dispatch-key a, 137 | :key a, 138 | :params {}, 139 | :type :call}]}))) 140 | 141 | (testing "mutation join expression" 142 | (is (= (tquery->ast [{'(a {}) [:sub-query]}]) 143 | '{:type :root, 144 | :children [{:dispatch-key a, 145 | :key a, 146 | :params {}, 147 | :type :call, 148 | :query [:sub-query], 149 | :children [{:type :prop, :dispatch-key :sub-query, :key :sub-query}]}]})))) 150 | 151 | (defn query<->ast-props [] 152 | (props/for-all [query (eql-gen/make-gen {::eql-gen/gen-params 153 | (fn [_] 154 | (gen/map gen/keyword gen/string-alphanumeric))} 155 | ::eql-gen/gen-query)] 156 | (let [ast (-> query 157 | eql/query->ast 158 | eql/ast->query 159 | eql/query->ast)] 160 | (= ast (-> ast 161 | eql/ast->query 162 | eql/query->ast))))) 163 | 164 | (test/defspec query-ast-roundtrip {:max-size 12 :num-tests 100} (query<->ast-props)) 165 | 166 | (comment 167 | (tc/quick-check 100 (query<->ast-props) :max-size 12)) 168 | 169 | (deftest test-ast->query 170 | (is (= (eql/ast->query {:type :prop 171 | :key :foo 172 | :dispatch-key :foo}) 173 | [:foo])) 174 | 175 | (is (= (eql/ast->query {:type :root 176 | :children [{:type :prop 177 | :dispatch-key :foo 178 | :key :foo}]}) 179 | [:foo]))) 180 | 181 | (deftest test-focus-subquery 182 | (is (= (eql/focus-subquery [] []) 183 | [])) 184 | (is (= (eql/focus-subquery [:a :b :c] []) 185 | [])) 186 | (is (= (eql/focus-subquery [:a :b :c] [:d]) 187 | [])) 188 | (is (= (eql/focus-subquery [:a :b :c] [:a]) 189 | [:a])) 190 | (is (= (eql/focus-subquery [:a :b :c] [:a :b]) 191 | [:a :b])) 192 | (is (= (eql/focus-subquery [:a {:b [:d]}] [:a :b]) 193 | [:a {:b [:d]}])) 194 | (is (= (eql/focus-subquery [:a {:b [:c :d]}] [:a {:b [:c]}]) 195 | [:a {:b [:c]}])) 196 | (is (= (eql/focus-subquery [:a '({:b [:c :d]} {:param "value"})] [:a {:b [:c]}]) 197 | [:a '({:b [:c]} {:param "value"})])) 198 | 199 | ; in union case, keys absent from focus will be pulled anyway, given ones will focus 200 | (is (= (eql/focus-subquery [:a {:b {:c [:d :e] 201 | :f [:g :h]}}] 202 | [:a {:b {:f [:g]}}]) 203 | [:a {:b {:c [:d :e] :f [:g]}}]))) 204 | 205 | (defn transduce-query [xform query] 206 | (->> query eql/query->ast 207 | (eql/transduce-children xform) 208 | eql/ast->query)) 209 | 210 | (deftest test-tranduce-children 211 | (is (= (transduce-query 212 | (comp (filter (comp #{:a :c} :key)) 213 | (map #(assoc % :params {:n 42}))) 214 | [:a :b :c :d]) 215 | '[(:a {:n 42}) (:c {:n 42})]))) 216 | 217 | (deftest test-merge-queries 218 | (is (= (eql/merge-queries nil nil) 219 | [])) 220 | 221 | (is (= (eql/merge-queries [:a] nil) 222 | [:a])) 223 | 224 | (is (= (eql/merge-queries [] []) 225 | [])) 226 | 227 | (is (= (eql/merge-queries [:a] []) 228 | [:a])) 229 | 230 | (is (= (eql/merge-queries [:a] [:a]) 231 | [:a])) 232 | 233 | (is (= (eql/merge-queries [:a] [:b]) 234 | [:a :b])) 235 | 236 | (is (= (eql/merge-queries [:a] [:b :c :d]) 237 | [:a :b :c :d])) 238 | 239 | (is (= (eql/merge-queries [[:u/id 1]] [[:u/id 2]]) 240 | [[:u/id 1] [:u/id 2]])) 241 | 242 | (is (= (eql/merge-queries [{:user [:name]}] [{:user [:email]}]) 243 | [{:user [:name :email]}])) 244 | 245 | (is (= (eql/merge-queries [:a] [{:a [:x]}]) 246 | [{:a [:x]}])) 247 | 248 | (is (= (eql/merge-queries [{:a [:x]}] [:a]) 249 | [{:a [:x]}])) 250 | 251 | (testing "don't merge queries with different params" 252 | (is (= (eql/merge-queries ['({:user [:name]} {:login "u1"})] 253 | ['({:user [:email]} {:login "u2"})]) 254 | nil))) 255 | 256 | (testing "don't merge queries with different params" 257 | (is (= (eql/merge-queries ['(:user {:login "u1"})] 258 | ['(:user {:login "u2"})]) 259 | nil))) 260 | 261 | (testing "merge when params are same" 262 | (is (= (eql/merge-queries ['({:user [:name]} {:login "u1"})] 263 | ['({:user [:email]} {:login "u1"})]) 264 | ['({:user [:name :email]} {:login "u1"})]))) 265 | 266 | (testing "calls can't be merged when same name occurs" 267 | (is (= (eql/merge-queries ['(hello {:login "u1"})] 268 | ['(hello {:bla "2"})]) 269 | nil))) 270 | 271 | (testing "even when parameters are the same" 272 | (is (= (eql/merge-queries ['(hello {:login "u1"})] 273 | ['(hello {:login "u1"})]) 274 | nil)))) 275 | 276 | (deftest test-update-child 277 | (is (= (eql/update-child {:children [{:dispatch-key :id :key :id :type :prop} 278 | {:dispatch-key :parent :key :parent :query 3 :type :join}] 279 | :type :root} 280 | :parent update :query dec) 281 | {:children [{:dispatch-key :id :key :id :type :prop} 282 | {:dispatch-key :parent :key :parent :query 2 :type :join}] 283 | :type :root}))) 284 | 285 | (deftest update-recursive-depth-test 286 | (is (= (eql/update-recursive-depth 287 | {:children [{:dispatch-key :id :key :id :type :prop} 288 | {:dispatch-key :parent :key :parent :query 3 :type :join}] 289 | :type :root} 290 | :parent dec) 291 | {:children [{:dispatch-key :id :key :id :type :prop} 292 | {:dispatch-key :parent :key :parent :query 2 :type :join}] 293 | :type :root}))) 294 | 295 | (deftest test-mask-query 296 | (is (= (eql/mask-query [] []) 297 | [])) 298 | (is (= (eql/mask-query [:foo :bar] []) 299 | [])) 300 | (is (= (eql/mask-query [:foo :bar] [:foo]) 301 | [:foo])) 302 | (is (= (eql/mask-query [:bar :foo] [:foo]) 303 | [:foo])) 304 | (is (= (eql/mask-query [:foo {:bar [:inside]}] [:foo]) 305 | [:foo])) 306 | (is (= (eql/mask-query ['(:foo {:bla "meh"}) :bar] [:foo]) 307 | ['(:foo {:bla "meh"})])) 308 | (is (= (eql/mask-query [:foo {:bar [:inside :more]}] [:foo :bar]) 309 | [:foo {:bar [:inside :more]}])) 310 | (is (= (eql/mask-query [:foo {:bar [:inside :more]}] [:foo {:bar [:inside]}]) 311 | [:foo {:bar [:inside]}]))) 312 | 313 | (deftest test-normalize-query-variables 314 | (testing "blank query" 315 | (is (= (eql/normalize-query-variables []) 316 | []))) 317 | 318 | (testing "simple query" 319 | (is (= (eql/normalize-query-variables [:a :b :c]) 320 | [:a :b :c]))) 321 | 322 | (testing "normalize ident values" 323 | (is (= (eql/normalize-query-variables [[:foo "bar"]]) 324 | [[:foo ::eql/var]]))) 325 | 326 | (testing "normalize params" 327 | (is (= (eql/normalize-query-variables ['(:foo {:x 1 :y 2})]) 328 | ['(:foo {:x ::eql/var :y ::eql/var})]))) 329 | 330 | (testing "all together" 331 | (is (= (eql/normalize-query-variables '[:a :b {[:join "val"] [{(:c {:page 10}) [:d]}]}]) 332 | '[:a :b 333 | {[:join ::eql/var] 334 | [({:c [:d]} 335 | {:page ::eql/var})]}])))) 336 | 337 | (deftest test-query-id 338 | (is (= (eql/query-id '[:a :b {[:join "val"] [{(:c {:page 10}) [:d]}]}]) 339 | -61421281))) 340 | 341 | 342 | (deftest shallow-conversion 343 | (testing "requesting shallow conversion will only convert the first layer of a query" 344 | (let [ast (eql/query->shallow-ast [:x 345 | {:y [{:z [:a]}]} 346 | {[:table 1] [:z {:other [:m :n]}]} 347 | {:ujoin {:u1 [:x] :u2 [:y]}}])] 348 | (is (= {:type :root, 349 | :children [{:type :prop, :dispatch-key :x, :key :x} 350 | {:type :join, :dispatch-key :y, :key :y, :query [{:z [:a]}]} 351 | {:type :join, :dispatch-key :table, :key [:table 1], :query [:z {:other [:m :n]}]} 352 | {:type :join, :dispatch-key :ujoin, :key :ujoin, :query {:u1 [:x], :u2 [:y]}}]} 353 | ast))))) 354 | 355 | 356 | (deftest merge-asts-as-reduce-function 357 | (testing 358 | "init - when called with arity zero, it returns an empty ast" 359 | (is (= {:type :root 360 | :children []} 361 | (transduce (map identity) 362 | eql/merge-asts 363 | [])))) 364 | (testing 365 | "completion - when called with arity one, it should return its argument" 366 | (is (= {:children [{:dispatch-key :a 367 | :key :a 368 | :type :prop}] 369 | :type :root} 370 | (transduce (map identity) 371 | eql/merge-asts 372 | [(eql/query->ast [:a])])))) 373 | (testing 374 | "step - the old arity 2. Should compose both nodes into a new node" 375 | (is (= {:children [{:dispatch-key :a 376 | :key :a 377 | :type :prop} 378 | {:dispatch-key :b 379 | :key :b 380 | :type :prop}] 381 | :type :root} 382 | (transduce (map identity) 383 | eql/merge-asts 384 | [(eql/query->ast [:a]) 385 | (eql/query->ast [:b])]))))) 386 | --------------------------------------------------------------------------------