├── .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 |
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+=""+d(e)+">"}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:/,end:/(\/\w+|\w+\/)>/,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 |
42 |
43 |
44 |
72 |
73 |
76 |
77 |
78 |
79 |
80 |
84 |
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 |
42 |
43 |
44 |
72 |
73 |
76 |
77 |
78 |
79 |
80 |
84 |
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 |
124 |
125 |
126 |
127 | s
is alias for clojure.spec.alpha
128 |
129 |
130 |
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 |
192 |
193 |
194 |
195 | gen
is alias for clojure.test.check.generators
196 |
197 |
198 |
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 | 1
244 | We send a map to eql/make-gen
to override some of the generator settings, any non
245 | defined keys will fallback to default implementation
246 |
247 |
248 | 2
249 | Select which generator to use, this is useful to generate only sub-parts if needed
250 |
251 |
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 |
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 |
42 |
43 |
44 |
72 |
73 |
76 |
77 |
78 |
79 |
80 |
84 |
85 |
90 |
91 |
92 |
93 |
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 |
164 |
165 |
166 |
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 |
172 |
173 |
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 |
192 |
193 |
194 |
195 |
Datomic Pull Syntax comparison
196 |
197 |
200 |
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 |
--------------------------------------------------------------------------------