├── .github └── workflows │ ├── build.yml │ ├── format.yml │ └── publish.yml ├── .gitignore ├── .prettierignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── scripts ├── generate-index.js └── publish.sh └── spec ├── .gitkeep ├── 01-overview.md ├── 02-syntax.md ├── 03-execution.md ├── 04-data-types.md ├── 05-equality-comparison.md ├── 06-simple-expressions.md ├── 07-compound-expressions.md ├── 08-traversal-operators.md ├── 09-operators.md ├── 10-precedence-associativity.md ├── 11-functions.md ├── 12-custom-functions.md ├── 12-pipe-functions.md ├── 13-vendor-functions.md ├── 14-extensions.md └── GROQ.md /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | # Trigger the workflow on push or pull request, 5 | # but only for the main branch 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | spec: 15 | name: Spec 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Check out Git repository 20 | uses: actions/checkout@v2 21 | 22 | - name: Set up Node.js 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: 16 26 | 27 | - name: Install Node.js dependencies 28 | run: npm ci 29 | 30 | - name: Build 31 | run: npm run build 32 | -------------------------------------------------------------------------------- /.github/workflows/format.yml: -------------------------------------------------------------------------------- 1 | name: Format 2 | 3 | on: 4 | # Trigger the workflow on push or pull request, 5 | # but only for the main branch 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | format: 15 | name: Format 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Check out Git repository 20 | uses: actions/checkout@v2 21 | 22 | - name: Set up Node.js 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: 16 26 | 27 | - name: Install Node.js dependencies 28 | run: npm ci 29 | 30 | - name: Check formating 31 | run: npm run format:check 32 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to website 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - GROQ-* 9 | 10 | jobs: 11 | scheduled: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Check out this repo 15 | uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 0 18 | - name: Set up Node 19 | uses: actions/setup-node@v2 20 | with: {node-version: '14'} 21 | - name: Install NPM dependencies 22 | run: npm i 23 | - name: Add gh-pages worktree 24 | run: git fetch origin gh-pages && git worktree add ./gh-pages gh-pages 25 | - name: Generate HTML 26 | run: sh scripts/publish.sh 27 | - name: Commit and push if it changed 28 | working-directory: gh-pages 29 | run: |- 30 | git config user.name "Automated" 31 | git config user.email "actions@users.noreply.github.com" 32 | git add -A 33 | git commit -m "Update HTML" || exit 0 34 | git push 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | .DS_Store 4 | .idea 5 | /out 6 | /gh-pages 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # This is a bit annoying. There's _one_ code block where we want to 2 | # avoid auto-formatting JSON, but spec-md doesn't support inline 3 | # <-- prettier-ignore -->. Luckily this file is quite small 4 | # so let's just disable Prettier for now. 5 | spec/01-overview.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Sanity Inc (https://www.sanity.io) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GROQ specification 2 | 3 | 👉🏻 **Published versions of the spec can be viewed [here](https://spec.groq.dev)**. 4 | 5 | This is the specification for GROQ (Graph-Relational Object Queries), a query language and execution engine made at [Sanity.io](https://www.sanity.io), for filtering and projecting JSON documents. 6 | The work started in 2015, and the development of this open standard started in 2019. 7 | Read the [announcement blog post](https://www.sanity.io/blog/we-re-open-sourcing-groq-a-query-language-for-json-documents) to understand more about the specification process and see the [getting started guide](https://www.sanity.io/docs/how-queries-work) to learn more about the language itself. 8 | 9 | Go to [GROQ Arcade](https://groq.dev) to try out GROQ with any JSON data today! 10 | 11 | ## Tools and resources for GROQ 12 | 13 | * [Syntax highlighting in VS Code](https://github.com/sanity-io/vscode-sanity). 14 | * [Syntax highlighting in Sublime Text](https://github.com/alevroub/groq-syntax-highlighting). 15 | * [GROQ tagged template literal](https://www.npmjs.com/package/groq), for writing GROQ queries in JavaScript files. 16 | * [groq-cli](https://github.com/sanity-io/groq-cli), a command-line tool for running GROQ on files and URLs. 17 | * [groq-js](https://github.com/sanity-io/groq-js), a JavaScript implementation of GROQ. 18 | * [Go GROQ library](https://github.com/sanity-io/go-groq), a Go implementation of the GROQ parser. 19 | * [groqfmt](https://github.com/sanity-io/groqfmt), a command-line formatter for GROQ, written in Go. 20 | 21 | ## Development of the specification 22 | 23 | The specification is written using [spec-md](https://spec-md.com), a Markdown variant optimized for writing specifications. 24 | The source is located under the `spec/`-directory which is then converted into HTML and presented at . 25 | To ensure that implementations are compatible we write test cases in the [GROQ test suite](https://github.com/sanity-io/groq-test-suite). 26 | 27 | The specification follows the versioning scheme of **GROQ-X.revisionY** where X (major) and Y (revision) are numbers: 28 | 29 | - The first version is GROQ-1.revision0. 30 | - Later revisions are always backwards compatible with earlier revisions in the same major version. 31 | - Revisions can include everything from minor clarifications to whole new functionality. 32 | - Major versions are used to introduce breaking changes. 33 | 34 | ## License 35 | 36 | The specification is made available under the Open Web Foundation Final Specification Agreement (OWFa 1.0). 37 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "groq-spec", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "groq-spec", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "date-fns": "^2.19.0", 13 | "prettier": "^2.6.2", 14 | "rimraf": "^3.0.2", 15 | "spec-md": "^3.0.2" 16 | } 17 | }, 18 | "node_modules/balanced-match": { 19 | "version": "1.0.2", 20 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 21 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 22 | "dev": true 23 | }, 24 | "node_modules/brace-expansion": { 25 | "version": "1.1.11", 26 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 27 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 28 | "dev": true, 29 | "dependencies": { 30 | "balanced-match": "^1.0.0", 31 | "concat-map": "0.0.1" 32 | } 33 | }, 34 | "node_modules/clipboard": { 35 | "version": "2.0.8", 36 | "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz", 37 | "integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==", 38 | "dev": true, 39 | "optional": true, 40 | "dependencies": { 41 | "good-listener": "^1.2.2", 42 | "select": "^1.1.2", 43 | "tiny-emitter": "^2.0.0" 44 | } 45 | }, 46 | "node_modules/concat-map": { 47 | "version": "0.0.1", 48 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 49 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 50 | "dev": true 51 | }, 52 | "node_modules/date-fns": { 53 | "version": "2.19.0", 54 | "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.19.0.tgz", 55 | "integrity": "sha512-X3bf2iTPgCAQp9wvjOQytnf5vO5rESYRXlPIVcgSbtT5OTScPcsf9eZU+B/YIkKAtYr5WeCii58BgATrNitlWg==", 56 | "dev": true, 57 | "engines": { 58 | "node": ">=0.11" 59 | }, 60 | "funding": { 61 | "type": "opencollective", 62 | "url": "https://opencollective.com/date-fns" 63 | } 64 | }, 65 | "node_modules/delegate": { 66 | "version": "3.2.0", 67 | "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", 68 | "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", 69 | "dev": true, 70 | "optional": true 71 | }, 72 | "node_modules/fs.realpath": { 73 | "version": "1.0.0", 74 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 75 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 76 | "dev": true 77 | }, 78 | "node_modules/glob": { 79 | "version": "7.1.6", 80 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 81 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 82 | "dev": true, 83 | "dependencies": { 84 | "fs.realpath": "^1.0.0", 85 | "inflight": "^1.0.4", 86 | "inherits": "2", 87 | "minimatch": "^3.0.4", 88 | "once": "^1.3.0", 89 | "path-is-absolute": "^1.0.0" 90 | }, 91 | "engines": { 92 | "node": "*" 93 | }, 94 | "funding": { 95 | "url": "https://github.com/sponsors/isaacs" 96 | } 97 | }, 98 | "node_modules/good-listener": { 99 | "version": "1.2.2", 100 | "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", 101 | "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", 102 | "dev": true, 103 | "optional": true, 104 | "dependencies": { 105 | "delegate": "^3.1.2" 106 | } 107 | }, 108 | "node_modules/inflight": { 109 | "version": "1.0.6", 110 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 111 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 112 | "dev": true, 113 | "dependencies": { 114 | "once": "^1.3.0", 115 | "wrappy": "1" 116 | } 117 | }, 118 | "node_modules/inherits": { 119 | "version": "2.0.4", 120 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 121 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 122 | "dev": true 123 | }, 124 | "node_modules/minimatch": { 125 | "version": "3.0.4", 126 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 127 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 128 | "dev": true, 129 | "dependencies": { 130 | "brace-expansion": "^1.1.7" 131 | }, 132 | "engines": { 133 | "node": "*" 134 | } 135 | }, 136 | "node_modules/once": { 137 | "version": "1.4.0", 138 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 139 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 140 | "dev": true, 141 | "dependencies": { 142 | "wrappy": "1" 143 | } 144 | }, 145 | "node_modules/path-is-absolute": { 146 | "version": "1.0.1", 147 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 148 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 149 | "dev": true, 150 | "engines": { 151 | "node": ">=0.10.0" 152 | } 153 | }, 154 | "node_modules/prettier": { 155 | "version": "2.6.2", 156 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", 157 | "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", 158 | "dev": true, 159 | "bin": { 160 | "prettier": "bin-prettier.js" 161 | }, 162 | "engines": { 163 | "node": ">=10.13.0" 164 | }, 165 | "funding": { 166 | "url": "https://github.com/prettier/prettier?sponsor=1" 167 | } 168 | }, 169 | "node_modules/prismjs": { 170 | "version": "1.23.0", 171 | "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz", 172 | "integrity": "sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==", 173 | "dev": true, 174 | "optionalDependencies": { 175 | "clipboard": "^2.0.0" 176 | } 177 | }, 178 | "node_modules/rimraf": { 179 | "version": "3.0.2", 180 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 181 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 182 | "dev": true, 183 | "dependencies": { 184 | "glob": "^7.1.3" 185 | }, 186 | "bin": { 187 | "rimraf": "bin.js" 188 | }, 189 | "funding": { 190 | "url": "https://github.com/sponsors/isaacs" 191 | } 192 | }, 193 | "node_modules/select": { 194 | "version": "1.1.2", 195 | "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", 196 | "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=", 197 | "dev": true, 198 | "optional": true 199 | }, 200 | "node_modules/spec-md": { 201 | "version": "3.0.2", 202 | "resolved": "https://registry.npmjs.org/spec-md/-/spec-md-3.0.2.tgz", 203 | "integrity": "sha512-UP5FZWwWrNuMBowubdPUgC4oKhBJCYUGdCMh4GR1KGuSLL4Hzo0fNI/jKGiLaKMxzO9EzAD9sTAZWRfTaBA/+g==", 204 | "dev": true, 205 | "dependencies": { 206 | "prismjs": "^1.23.0" 207 | }, 208 | "bin": { 209 | "spec-md": "bin/spec-md" 210 | }, 211 | "engines": { 212 | "node": ">=10.14.2" 213 | } 214 | }, 215 | "node_modules/tiny-emitter": { 216 | "version": "2.1.0", 217 | "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", 218 | "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", 219 | "dev": true, 220 | "optional": true 221 | }, 222 | "node_modules/wrappy": { 223 | "version": "1.0.2", 224 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 225 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 226 | "dev": true 227 | } 228 | }, 229 | "dependencies": { 230 | "balanced-match": { 231 | "version": "1.0.2", 232 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 233 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 234 | "dev": true 235 | }, 236 | "brace-expansion": { 237 | "version": "1.1.11", 238 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 239 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 240 | "dev": true, 241 | "requires": { 242 | "balanced-match": "^1.0.0", 243 | "concat-map": "0.0.1" 244 | } 245 | }, 246 | "clipboard": { 247 | "version": "2.0.8", 248 | "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz", 249 | "integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==", 250 | "dev": true, 251 | "optional": true, 252 | "requires": { 253 | "good-listener": "^1.2.2", 254 | "select": "^1.1.2", 255 | "tiny-emitter": "^2.0.0" 256 | } 257 | }, 258 | "concat-map": { 259 | "version": "0.0.1", 260 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 261 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 262 | "dev": true 263 | }, 264 | "date-fns": { 265 | "version": "2.19.0", 266 | "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.19.0.tgz", 267 | "integrity": "sha512-X3bf2iTPgCAQp9wvjOQytnf5vO5rESYRXlPIVcgSbtT5OTScPcsf9eZU+B/YIkKAtYr5WeCii58BgATrNitlWg==", 268 | "dev": true 269 | }, 270 | "delegate": { 271 | "version": "3.2.0", 272 | "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", 273 | "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", 274 | "dev": true, 275 | "optional": true 276 | }, 277 | "fs.realpath": { 278 | "version": "1.0.0", 279 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 280 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 281 | "dev": true 282 | }, 283 | "glob": { 284 | "version": "7.1.6", 285 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 286 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 287 | "dev": true, 288 | "requires": { 289 | "fs.realpath": "^1.0.0", 290 | "inflight": "^1.0.4", 291 | "inherits": "2", 292 | "minimatch": "^3.0.4", 293 | "once": "^1.3.0", 294 | "path-is-absolute": "^1.0.0" 295 | } 296 | }, 297 | "good-listener": { 298 | "version": "1.2.2", 299 | "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", 300 | "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", 301 | "dev": true, 302 | "optional": true, 303 | "requires": { 304 | "delegate": "^3.1.2" 305 | } 306 | }, 307 | "inflight": { 308 | "version": "1.0.6", 309 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 310 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 311 | "dev": true, 312 | "requires": { 313 | "once": "^1.3.0", 314 | "wrappy": "1" 315 | } 316 | }, 317 | "inherits": { 318 | "version": "2.0.4", 319 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 320 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 321 | "dev": true 322 | }, 323 | "minimatch": { 324 | "version": "3.0.4", 325 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 326 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 327 | "dev": true, 328 | "requires": { 329 | "brace-expansion": "^1.1.7" 330 | } 331 | }, 332 | "once": { 333 | "version": "1.4.0", 334 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 335 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 336 | "dev": true, 337 | "requires": { 338 | "wrappy": "1" 339 | } 340 | }, 341 | "path-is-absolute": { 342 | "version": "1.0.1", 343 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 344 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 345 | "dev": true 346 | }, 347 | "prettier": { 348 | "version": "2.6.2", 349 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", 350 | "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", 351 | "dev": true 352 | }, 353 | "prismjs": { 354 | "version": "1.23.0", 355 | "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz", 356 | "integrity": "sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==", 357 | "dev": true, 358 | "requires": { 359 | "clipboard": "^2.0.0" 360 | } 361 | }, 362 | "rimraf": { 363 | "version": "3.0.2", 364 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 365 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 366 | "dev": true, 367 | "requires": { 368 | "glob": "^7.1.3" 369 | } 370 | }, 371 | "select": { 372 | "version": "1.1.2", 373 | "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", 374 | "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=", 375 | "dev": true, 376 | "optional": true 377 | }, 378 | "spec-md": { 379 | "version": "3.0.2", 380 | "resolved": "https://registry.npmjs.org/spec-md/-/spec-md-3.0.2.tgz", 381 | "integrity": "sha512-UP5FZWwWrNuMBowubdPUgC4oKhBJCYUGdCMh4GR1KGuSLL4Hzo0fNI/jKGiLaKMxzO9EzAD9sTAZWRfTaBA/+g==", 382 | "dev": true, 383 | "requires": { 384 | "prismjs": "^1.23.0" 385 | } 386 | }, 387 | "tiny-emitter": { 388 | "version": "2.1.0", 389 | "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", 390 | "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", 391 | "dev": true, 392 | "optional": true 393 | }, 394 | "wrappy": { 395 | "version": "1.0.2", 396 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 397 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 398 | "dev": true 399 | } 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "groq-spec", 3 | "version": "1.0.0", 4 | "description": "Specification for GROQ - Graph Oriented Query Language", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "spec-md spec/GROQ.md > /dev/null", 8 | "clean": "rimraf out", 9 | "format": "prettier -w spec scripts", 10 | "format:check": "prettier -c spec scripts", 11 | "build": "mkdir -p out && spec-md spec/GROQ.md > out/index.html" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/sanity-io/GROQ.git" 16 | }, 17 | "keywords": [], 18 | "author": "", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/sanity-io/GROQ/issues" 22 | }, 23 | "homepage": "https://github.com/sanity-io/GROQ#readme", 24 | "devDependencies": { 25 | "date-fns": "^2.19.0", 26 | "prettier": "^2.6.2", 27 | "rimraf": "^3.0.2", 28 | "spec-md": "^3.0.2" 29 | }, 30 | "prettier": { 31 | "semi": false, 32 | "printWidth": 100, 33 | "bracketSpacing": false, 34 | "singleQuote": true 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /scripts/generate-index.js: -------------------------------------------------------------------------------- 1 | const formatDate = require('date-fns/format') 2 | const fs = require('fs') 3 | 4 | function render(versions) { 5 | const rows = versions.map(({slug, name, date, variant}) => { 6 | return ` 7 | ${variant ? `${variant}` : ''} 8 | ${name} 9 | ${formatDate(date, 'MMM d, yyyy')} 10 | ` 11 | }) 12 | 13 | return ` 14 | 15 | GROQ Specification Versions 16 | 17 | 47 | 48 | 49 |

GROQ

50 | 51 | ${rows.join('')} 52 |
53 | 54 | ` 55 | } 56 | 57 | async function main() { 58 | const versions = [ 59 | { 60 | slug: 'draft', 61 | name: 'Working Draft', 62 | date: new Date(), 63 | variant: 'Prerelease', 64 | }, 65 | ] 66 | 67 | const input = fs.readFileSync(0).toString().trim() 68 | const lines = input.length > 0 ? input.split('\n') : [] 69 | let isFirst = true 70 | 71 | for (const line of lines) { 72 | const [date, version] = line.split(' ') 73 | versions.push({ 74 | slug: version, 75 | name: version, 76 | date: Number(date) * 1000, 77 | variant: isFirst ? 'Latest release' : '', 78 | }) 79 | isFirst = false 80 | } 81 | 82 | console.log(render(versions)) 83 | } 84 | 85 | main() 86 | -------------------------------------------------------------------------------- /scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Inspired by: 4 | # https://github.com/graphql/graphql-spec/blob/main/publish.sh 5 | 6 | PUBLISH_DIR="gh-pages" 7 | 8 | echo "Building spec" 9 | npm run build > /dev/null 2>&1 10 | 11 | mkdir -p "$PUBLISH_DIR" 12 | 13 | # Replace /draft with this build. 14 | echo "Publishing to: /draft" 15 | rm -rf "$PUBLISH_DIR/draft" 16 | cp -R out/ "$PUBLISH_DIR/draft" 17 | 18 | CURRENT_VERSION=$(git tag --points-at "$GITHUB_SHA" | grep -E 'GROQ-[0-9].*') 19 | 20 | # If this is a tagged commit, publish to a permalink and index. 21 | if [ -n "$CURRENT_VERSION" ]; then 22 | echo "Publishing to: /$CURRENT_VERSION" 23 | cp -R out/ "$PUBLISH_DIR/$CURRENT_VERSION" 24 | fi 25 | 26 | # Update index 27 | echo "Updating index" 28 | git tag -l --format='%(creatordate:unix) %(refname:lstrip=2)' | sort -r | 29 | node scripts/generate-index.js > "$PUBLISH_DIR/index.html" 30 | 31 | echo "Done" 32 | -------------------------------------------------------------------------------- /spec/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanity-io/GROQ/5fd3c5d2b734ee5c8fdf7e19a2be07bc016c5297/spec/.gitkeep -------------------------------------------------------------------------------- /spec/01-overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | GROQ (Graph-Relational Object Queries) is a declarative language designed to query collections of largely schema-less JSON documents. Its primary design goals are expressive filtering, joining of several documents into a single response, and shaping the response to fit the client application. 4 | 5 | The idea behind GROQ is to be able to describe exactly what information your application needs, potentially joining together information from several sets of documents, then stitching together a very specific response with only the exact fields you need. 6 | 7 | A query in GROQ typically starts with `*`. This asterisk represents every document in your dataset. It is typically followed by a {filter} in brackets. The {filter} take {terms}, {operators} and {functions}. A {projection} is wrapped in curly braces and describe the data as we want it returned. 8 | 9 | Given these JSON documents: 10 | 11 | ```json 12 | { "id": 1, "name": "Peter"} 13 | { "id": 2, "name": "Gamora"} 14 | { "id": 3, "name": "Drax"} 15 | { "id": 4, "name": "Groot"} 16 | { "id": 5, "name": "Rocket"} 17 | ``` 18 | 19 | The following query: 20 | 21 | ```example 22 | *[id > 2]{name} 23 | ``` 24 | 25 | Will result in the following JSON document: 26 | 27 | ```json 28 | [ 29 | { "name": "Drax"}, 30 | { "name": "Groot"}, 31 | { "name": "Rocket"} 32 | ] 33 | ``` 34 | -------------------------------------------------------------------------------- /spec/02-syntax.md: -------------------------------------------------------------------------------- 1 | # Syntax 2 | 3 | A GROQ query is a string consisting of Unicode characters. The encoding of the query string is implementation-defined, but UTF-8 is the preferred choice. 4 | 5 | SourceCharacter : "any Unicode character" 6 | 7 | A query consist of a single {Expression} optionally preceded by a list of {FuncDecl}, with {WhiteSpace} and {Comment} allowed anywhere with no effect on the interpretation. 8 | 9 | Query : FuncDecl\* Expression 10 | 11 | ## JSON Superset 12 | 13 | GROQ's syntax is a superset of JSON, so any valid JSON value is a valid GROQ expression (that simply returns the given value). Below are a few examples of JSON values: 14 | 15 | ```json 16 | "Hi! 👋" 17 | ``` 18 | 19 | ```json 20 | ["An", "array", "of", "strings"] 21 | ``` 22 | 23 | ```json 24 | { 25 | "array": ["string", 3.14, true, null], 26 | "boolean": true, 27 | "number": 3.14, 28 | "null": null, 29 | "object": {"key": "value"}, 30 | "string": "Hi! 👋" 31 | } 32 | ``` 33 | 34 | ## White Space 35 | 36 | Whitespace is not significant in GROQ, except for acting as a token separator and comment terminator. Any sequence of the following characters is considered whitespace. 37 | 38 | WhiteSpace : 39 | 40 | - "Tab U+0009" 41 | - "Newline U+000A" 42 | - "Vertical tab U+000B" 43 | - "Form feed U+000C" 44 | - "Carriage return U+000D" 45 | - "Space U+0020" 46 | - "Next line U+0085" 47 | - "Non-breaking space U+00A0" 48 | 49 | Whitespace inside a string literal is interpreted as-is. 50 | 51 | ## Comments 52 | 53 | Comment : // CommentChar\* 54 | 55 | CommentChar : SourceCharacter but not "Newline U+000A" 56 | 57 | Comments serve as query documentation, and are ignored by the parser. They start with `//` and run to the end of the line: 58 | 59 | ```example 60 | { 61 | // Comments can be on a separate line 62 | "key": "value" // Or at the end of a line 63 | } 64 | ``` 65 | 66 | Comments cannot start inside a string literal. 67 | 68 | ```example 69 | { 70 | "key // This isn't a comment": "value" 71 | } 72 | ``` 73 | 74 | ## Identifier 75 | 76 | Identifiers are used to name entities such as parameters, attributes and functions. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter. 77 | 78 | Identifier : `/[A-Za-z\_][A-Za-z_0-9]\*/` 79 | 80 | ## Digits 81 | 82 | GROQ uses decimal digits (0-9) and hexadecimal digits (0-9, a-f) in various places. 83 | 84 | Digit : one of 0 1 2 3 4 5 6 7 8 9 85 | 86 | HexDigit : 87 | 88 | - Digit 89 | - HexLetter 90 | 91 | HexLetter : one of a `A` b `B` c `C` d `D` e `E` f `F` 92 | 93 | ## Expression 94 | 95 | An {Expression} is either a literal (e.g. `15`), a simple expression (e.g. `@`), or a compound expression (e.g. `*[name == "Michael"]`) or an operator call (e.g. `name == "Michael"`). The syntax and semantics of the different expressions are documented in their respective sections. 96 | 97 | Expression : 98 | 99 | - Literal 100 | - SimpleExpression 101 | - CompoundExpression 102 | - OperatorCall 103 | 104 | Literal : 105 | 106 | - Null 107 | - Boolean 108 | - Number 109 | - String 110 | - Array 111 | - Object 112 | 113 | SimpleExpression : 114 | 115 | - This 116 | - ThisAttribute 117 | - Everything 118 | - Parent 119 | - Parameter 120 | - FuncCall 121 | 122 | CompoundExpression : 123 | 124 | - Parenthesis 125 | - TraversalExpression 126 | - PipeFuncCall 127 | 128 | OperatorCall : 129 | 130 | - And 131 | - Or 132 | - Not 133 | - Equality 134 | - Comparison 135 | - In 136 | - Match 137 | - Asc 138 | - Desc 139 | - UnaryPlus 140 | - UnaryMinus 141 | - Plus 142 | - Minus 143 | - Star 144 | - Slash 145 | - Percent 146 | - StarStar 147 | 148 | ## Selector 149 | 150 | A selector is a subset of an expression used to search for fields inside a document. 151 | They have their own evaluation logic (see {EvaluateSelector()}) which is used by certain functions. 152 | 153 | Selector : 154 | 155 | - ThisAttribute 156 | - SelectorGroup 157 | - SelectorTuple 158 | - Selector AttributeAccess 159 | - Selector ArrayPostfix 160 | - Selector Filter 161 | - Selector `.` SelectorGroup 162 | - Selector `.` SelectorTuple 163 | 164 | SelectorGroup: 165 | 166 | - `(` Selector `)` 167 | 168 | SelectorTuple: 169 | 170 | - `(` Selector SelectorTuplePart+ `)` 171 | 172 | SelectorTuplePart : 173 | 174 | - `,` Selector 175 | -------------------------------------------------------------------------------- /spec/03-execution.md: -------------------------------------------------------------------------------- 1 | # Execution 2 | 3 | ## Overview 4 | 5 | Note: The following sub-section is a non-normative overview of the execution model. See the rest of the section for the exact semantics. 6 | 7 | A GROQ query is executed inside a query context, which contains the dataset and parameters, and returns a result. Typically the result is serialized to JSON. During the execution of a query different parts of the query are evaluated in different scopes. Each scope has a *this *value and can be nested. Simple attributes like `name` always refers to an attribute on the *this *value. 8 | 9 | ```example 10 | *[_type == "person"]{name, friends[country == "NO"]} 11 | ``` 12 | 13 | In the preceding example we have several scopes: 14 | 15 | - The first filter (`[_type == "person"]`) creates a new scope for every document in the dataset. An equivalent scope is created inside the projection (`{name, …}`). 16 | - The country filter (`[country == "NO"]`) creates a new scope for each element in the `friends` array. 17 | 18 | The parent expression (`^`) let's you refer to parent scopes, and this enables what is typically solved with joins in many databases. 19 | 20 | ```example 21 | *[_type == "person"]{ 22 | id, 23 | name, 24 | "children": *[_type == "person" && parentId == ^.id] 25 | } 26 | ``` 27 | 28 | While executing the inner filter (`[_type == "person" && parentId == ^.id]`) the expression `^.id` returns the `id` attribute of the parent scope's _this_ value. The parent scope is here the scope created by the projection (`{id, name, …}`). 29 | 30 | It's possible for a query to be _invalid_. This can happen when you e.g. use an unknown function or call a function with incorrect number of arguments. 31 | 32 | ## Mode 33 | 34 | Queries can be executed in two different modes: _normal_ and _delta_. 35 | Delta mode is intended to be used in case where a _change_ has been done to a document. 36 | In this mode you have the additional functionality of accessing the attributes before and after the change, and comparing the differences between them (using the functions in the `delta`-namespace). 37 | 38 | ## Query context 39 | 40 | A query context consists of: 41 | 42 | - the dataset 43 | - parameter values (map from {string} to {value}) 44 | - the mode: either "normal" or "delta" 45 | - custom function definitions (map from {string} to {function}) 46 | 47 | If the mode is "delta" then the query context also has: 48 | 49 | - a before object (which is {null} if this was a create-operation). 50 | - an after object (which is {null} if this was a delete-operation). 51 | 52 | ## Scope 53 | 54 | A scope consists of: 55 | 56 | - a this value 57 | - an optional parent scope 58 | - a query context 59 | - a map of parameters 60 | 61 | A root scope can be constructed from a query context, and a nested scope can be constructed from an existing scope. 62 | 63 | NewNestedScope(value, scope): 64 | 65 | - Let {newScope} be a new scope. 66 | - Set the this value of {newScope} to {value}. 67 | - Set the parent scope of {newScope} to {scope}. 68 | - Set the query context of {newScope} to the query context of {scope}. 69 | - Set the parameters of {newScope} to the parameter of {scope}. 70 | - Return {newScope}. 71 | 72 | NewRootScope(context): 73 | 74 | - Let {newScope} be a new scope. 75 | - Set the this value of {newScope} to {null}. 76 | - Set the parent scope of {newScope} to {null}. 77 | - Set the query context of {newScope} to {context}. 78 | - Set the parameters to the parameters given to the query request. 79 | - Return {newScope}. 80 | 81 | ## Expression validation 82 | 83 | An expression can be validated. This will only check that it's on a valid form, and will not execute anything. If an expression type does not have an explicitly defined validator in this specification, it has an implicit validator which runs {Validate} on all its child expressions. 84 | 85 | Validate(expr): 86 | 87 | - Let {validator} be the validator of {expr}. 88 | - Execute the {validator}. 89 | 90 | ## Query validation 91 | 92 | A query is validated by first validating the custom functions and then [validating the expression](#sec-Expression-validation). 93 | Custom functions are validated by validating the function bodies. 94 | 95 | ## Expression evaluation 96 | 97 | An expression is evaluated in a scope. You must successfully validate an expression before you attempt to evaluate it. Every expression type has their own evaluator function in their respective section in this specification (e.g. the evaluator of {ParenthesisExpression} is {EvaluateParenthesis()}). 98 | 99 | Evaluate(expr, scope): 100 | 101 | - Let {evaluator} be the evaluator of {expr}. 102 | - Return the result of {evaluator(scope)}. 103 | 104 | ## Constant expression evaluation 105 | 106 | Some expressions can be evaluated into a constant value. This is used for validation and to disambiguate between different syntactically ambiguous expressions. 107 | 108 | ConstantEvaluate(expr): 109 | 110 | - If {expr} is one of: {Literal}, {Parenthesis}, {Plus}, {Minus}, {Star}, {Slash}, {Percent}, {StarStar}, {UnaryPlus}, {UnaryMinus}. 111 | - Let {evaluator} be the evaluator of {expr}. 112 | - Let {result} be the result of executing {evaluator}, but using {ConstantEvaluate()} in-place of every {Evaluate()}. 113 | - Return the {result}. 114 | - Otherwise: Report an error. 115 | 116 | ## Score evaluation 117 | 118 | When evaluating {score}, a predicate returning `true` should have its score computed as 1.0, and all other values should receive a score of 0.0. All results involved in scoring start with a score of 1.0. The scores are evaluated once per result, and then added together. For example: 119 | 120 | ```example 121 | * | score(a > 1) 122 | ``` 123 | 124 | should assign a score of 2.0 to any document where `a > 1`, and a score of 1.0 to any non-matching document. 125 | 126 | For logical expressions, the score is the sum of the clauses of the expression evaluates to `true`, otherwise 0.0. In other words: 127 | 128 | - `true && false` receives the score 0.0. 129 | - `true && true` receives the score 2.0. 130 | - `true || true` receives the score 2.0. 131 | - `true || false` receives the score 1.0. 132 | 133 | The scoring function for `match` is left as an implementation detail and not covered by this specification. For example, an implementation may choose to use a TF/IDF or similar text scoring function that uses the text corpus and language configuration for the given field to compute a text score. 134 | 135 | A boosted predicate simply adds the boost value to the score if the predicate matches. For example, `boost(a > 1, 10)` would result in a score of 11 for any expression matching `a > 1`. 136 | 137 | ## Selector evaluation 138 | 139 | A selector (see {Selector}) is evaluated in a scope with a value and returns a list of _key paths_. 140 | A key path uniquely identifies a value by the attribute names and array indices used to access the value. 141 | For instance, the key path `.users[1].name` refers to the `"Bob"`-value in `{"users":[{"name":"Alice"},{"name":"Bob"}]}`. 142 | 143 | EvaluateSelector(selector, value, scope): 144 | 145 | - If {selector} is a {SelectorGroup}: 146 | - Let {inner} be the inner selector. 147 | - Return {EvaluateSelector(inner, value)}. 148 | - Let {result} be an empty list of key paths. 149 | - If {selector} is a {SelectorTuple}: 150 | - For each selector {inner} in the tuple: 151 | - Let {innerResult} be the result of {EvaluateSelector(inner, value, scope)}. 152 | - Concatenate {innerResult} to {result}. 153 | - If {selector} is a {ThisAttibute}: 154 | - If {value} is an object which has the given attribute: 155 | - Let {keyPath} be a new key path consisting of the attribute name. 156 | - Append {keyPath} to {result}. 157 | - If {selector} starts with a {Selector}: 158 | - Let {baseSelector} be the selector. 159 | - Let {base} be the result of {EvaluateSelector(baseSelector, value, scope)}. 160 | - For each {keyPath} in {base}: 161 | - Let {innerValue} be the value at {keyPath} in {value}. 162 | - If {selector} ends with a {ArrayPostfix}: 163 | - If {innerValue} is an array: 164 | - For each {item} in {innerValue}: 165 | - Let {nestedKeyPath} be the result of combining {keyPath} with the array index. 166 | - Append {nestedKeyPath} to {result}. 167 | - If {selector} ends with a {AttributeAccess}: 168 | - If {innerValue} is an object which has the given attribute: 169 | - Let {nestedKeyPath} be the result of combining {keyPath} with the attribute name. 170 | - Append {nestedKeyPath} to {result}. 171 | - If {selector} ends with a {Filter}: 172 | - If {innerValue} is an array: 173 | - For each {item} of {innerValue}: 174 | - Let {nestedScope} be the result of {NewNestedScope(value, scope)}. 175 | - Let {matched} be the result of {Evaluate(expr, nestedScope)}. 176 | - If {matched} is {true}: 177 | - Let {nestedKeyPath} be the result of combining {keyPath} with the array index. 178 | - Append {nestedKeyPath} to {result}. 179 | - If {selector} ends with a {SelectorGroup}: 180 | - Let {inner} be that selector. 181 | - Let {innerResult} be the result of {EvaluateSelector(inner, innerValue, scope)}. 182 | - For each {nestedKeyPath} in {innerResult}: 183 | - Let {combinedKeyPath} be the result of combining {keyPath} with {nestedKeyPath}. 184 | - Append {combinedKeyPath} to {result}. 185 | - If {selector} ends with a {SelectorTuple}: 186 | - For each selector {inner} in the tuple: 187 | - Let {innerResult} be the result of {EvaluateSelector(inner, innerValue, scope)}. 188 | - For each {nestedKeyPath} in {innerResult}: 189 | - Let {combinedKeyPath} be the result of combining {keyPath} with {nestedKeyPath}. 190 | - Append {combinedKeyPath} to {result}. 191 | - Return {result}. 192 | 193 | ## Traversal execution 194 | 195 | When working with JSON values you often need to access attributes in deeply nested arrays/objects. 196 | In JavaScript this is solved by using helper functions such as `map`, `filter` and `flatMap`. 197 | GROQ provides terse syntax for accomplishing the same functionality. 198 | 199 | ```example 200 | // The following GROQ: 201 | *[_type == "user"]._id 202 | 203 | // is equivalent to the following JavaScript: 204 | data.filter(u => u._type == "user").map(u => u._id) 205 | ``` 206 | 207 | The following expressions are implemented as a _traversal_: 208 | 209 | - `user.name`: {AttributeAccess}. 210 | - `image->`: {Dereference}. 211 | - `users[0]`: {ElementAccess}. 212 | - `users[0...5]`: {Slice}. 213 | - `users[type == "admin"]`: {Filter}. 214 | - `users[]`: {ArrayPostfix}. 215 | - `user{name}`: {Projection}. 216 | 217 | When these traversals are combined (e.g. `user.roles[0].permissions[priority > 2]{filter}`) it triggers a separate traversal logic. 218 | 219 | Informally the traversal logic is based on a few principles: 220 | 221 | - Traversal semantics are always statically known. 222 | The runtime value of an expression never impacts the overall interpretation of a traversal. 223 | - We categorize traversals into four types (plain, array, array source, array target) based on how they work on arrays. 224 | `.user.name` is considered a _plain_ traversal because it statically only deals with plain values. 225 | `[_type == "user"]` is considered an _array_ traversal because it works on arrays. 226 | - Placing a plain traversal next to an array traversals (`[_type == "user"].name.firstName`) will execute the plain traversal _for each element_ of the array. 227 | 228 | Formally the semantics are specified as follows: 229 | 230 | - Each traversal has a _traverse function_ which describes how it will traverse a value. 231 | This function takes a value and a scope as parameters. 232 | - There's a set of traversal combination functions which specifies how two traversals can be combined. 233 | This explains exactly how `.user.name` is mapped over each element of an array. 234 | - {TraversalPlain}, {TraversalArray}, {TraversalArraySource}, {TraversalArrayTarget} specifies the exact rules for how multiple traversals are combined together. 235 | - The {TraversalExpression} node is an {Expression} for the full set of traversal operators. 236 | This kicks off the whole traversal semantics. 237 | 238 | ### Combining traversal 239 | 240 | Multiple traversals are combined in four different ways: 241 | 242 | - _Joined_: 243 | In `.user.name` we want to execute the first traversal (`.user`), and then apply the second traversal (`.name`) on the result. 244 | This is specified by the {EvaluateTraversalJoin()} function. 245 | - _Mapped_: 246 | In `[_type == "user"].id` we want to execute the first traversal (`[_type == "user"]`), and then apply the second traversal (`.id`) for each element of the array. 247 | This is specified by the {EvaluateTraversalMap()} function. 248 | - _Flat-mapped_: 249 | In `[_type == "user"].names[]` we want to execute the first traversal (`[_type == "user"]`), and then apply the second traversal (`.names[]`) for each element of the array, and then flatten the result. 250 | This is specified by the {EvaluateTraversalFlatMap()} function. 251 | - _Inner mapped_: 252 | In `{name,type}[type == "admin"]` we want to execute the first traversal (`{name,type}`) for each element of the array, and then apply the second traversal (`[type == "admin"]`) on the full array. 253 | This is specified by the {EvaluateTraversalInnerMap()} function. 254 | 255 | Unless otherwise specified, any two traversal are combined using the {EvaluateTraversalJoin()} function. 256 | 257 | EvaluateTraversalJoin(base, scope): 258 | 259 | - Let {traverse} be the traverse function of the first node. 260 | - Let {nextTraverse} be the traverse function of the last node. 261 | - Let {result} to be the result of {traverse(base, scope)}. 262 | - Set {result} to be the result of {nextTraverse(result, scope)}. 263 | - Return {result}. 264 | 265 | EvaluateTraversalMap(base, scope): 266 | 267 | - Let {traverse} be the traverse function of the first node. 268 | - Let {nextTraverse} be the traverse function of the last node. 269 | - Set {base} to be the result of {traverse(base, scope)}. 270 | - If {base} is not an array: 271 | - Return {null}. 272 | - Let {result} be an empty array. 273 | - For each {value} in {base}: 274 | - Let {elem} be the result of {nextTraverse(value, scope)}. 275 | - Append {elem} to {result}. 276 | - Return {result}. 277 | 278 | EvaluateTraversalFlatMap(base, scope): 279 | 280 | - Let {traverse} be the traverse function of the first node. 281 | - Let {nextTraverse} be the traverse function of the last node. 282 | - Set {base} to be the result of {traverse(base, scope)}. 283 | - If {base} is not an array: 284 | - Return {null}. 285 | - Let {result} be an empty array. 286 | - For each {value} in {base}: 287 | - Let {elem} be the result of {nextTraverse(value, scope)}. 288 | - If {elem} is an array: 289 | - Concatenate {elem} to {result}. 290 | - Return {result}. 291 | 292 | EvaluateTraversalInnerMap(base, scope): 293 | 294 | - Let {traverse} be the traverse function of the first node. 295 | - Let {nextTraverse} be the traverse function of the last node. 296 | - If {base} is not an array: 297 | - Return {null}. 298 | - Let {result} be an empty array. 299 | - For each {value} in {base}: 300 | - Let {elem} be the result of {traverse(value, scope)}. 301 | - Append {elem} to {result}. 302 | - Set {result} to be the result of {nextResult(base, scope)}. 303 | - Return {result}. 304 | 305 | ### Plain traversal 306 | 307 | A plain traversal is a traversal which works on and returns unknown types. 308 | 309 | - `.user.name` 310 | - `.users[0].name` 311 | - `.image->{url}` 312 | 313 | The following are _not_ considered plain traversals: 314 | 315 | - `[_type == "user"].name` (because it works on an array) 316 | - `.users[]` (because it returns an array) 317 | 318 | BasicTraversalPlain : 319 | 320 | - AttributeAccess 321 | - Dereference 322 | 323 | TraversalPlain : 324 | 325 | - BasicTraversalPlain 326 | - BasicTraversalPlain TraversalPlain 327 | - BasicTraversalPlain TraversalArraySource 328 | - Projection 329 | - Projection TraversalPlain 330 | 331 | ### Array traversals 332 | 333 | An array traversal is a traversal which statically is known to works on and return an array: 334 | 335 | - `[_type == "user"].id` 336 | - `[_type == "user"].names[]` 337 | - `{name,type}[type == "admin"]` 338 | 339 | The following are _not_ considered array traversals: 340 | 341 | - `[_type == "user"].roles[0]` (because it returns an unknown type) 342 | - `.names[]` (because it works on a non-array) 343 | 344 | BasicTraversalArray : 345 | 346 | - Slice 347 | - Filter 348 | - ArrayPostfix 349 | 350 | TraversalArray : 351 | 352 | - BasicTraversalArray 353 | - BasicTraversalArray TraversalArray 354 | - ElementAccess TraversalArray 355 | - ElementAccess TraversalArrayTarget 356 | - BasicTraversalArray TraversalPlain "(Evaluated using EvaluateTraversalMap)" 357 | - BasicTraversalArray TraversalArrayTarget "(Evaluated using EvaluateTraversalFlatMap)" 358 | - Projection TraversalArray "(Evaluated using EvaluateTraversalInnerMap)" 359 | 360 | ### Array source traversals 361 | 362 | An array source traversal is a traversal which statically is known to work on an array, but returns an unknown type: 363 | 364 | - `[0].user.name` 365 | - `[_type == "user"].roles[0]` 366 | - `{name,type}[0]` 367 | 368 | The following are _not_ considered array source traversals: 369 | 370 | - `[_type == "user"].id` (because it returns an array) 371 | - `.user.name` (because it doesn't work on an array) 372 | 373 | TraversalArraySource : 374 | 375 | - ElementAccess 376 | - ElementAccess TraversalArraySource 377 | - ElementAccess TraversalPlain 378 | - BasicTraversalArray TraversalArraySource 379 | - Projection TraversalArraySource "(Evaluated using EvaluateTraversalInnerMap)" 380 | 381 | ### Array target traversals 382 | 383 | An array target traversal is a traversal which statically is known to return on an array, but works on an unknown type: 384 | 385 | - `user.roles[dataset == "production"]` 386 | - `{name,type}[]` 387 | 388 | The following are _not_ considered array source traversals: 389 | 390 | - `[_type == "user"].id` (because it also works on an array) 391 | - `.user.name` (because it doesn't work on an array) 392 | 393 | TraversalArrayTarget : 394 | 395 | - BasicTraversalPlain TraversalArray 396 | - BasicTraversalPlain TraversalArrayTarget 397 | - Projection TraversalArrayTarget 398 | 399 | ## Query execution 400 | 401 | To execute a query you must first construct a query context, and then evaluate the query expression inside a root scope. 402 | 403 | ExecuteQuery(query, context): 404 | 405 | - Let {scope} be the result of {NewRootScope(context)}. 406 | - Let {expr} be the expression of {query}. 407 | - Let {result} be the result of {Evalute(expr, scope)}. 408 | - Return {result}. 409 | -------------------------------------------------------------------------------- /spec/04-data-types.md: -------------------------------------------------------------------------------- 1 | # Data types 2 | 3 | ## Null 4 | 5 | An unknown value, expressed as {null}. This follows the SQL definition of null, which differs from the typical definition of "no value" in programming languages, and implies among other things that `1 + null` yields {null} (1 plus an unknown number yields an unknown number). 6 | 7 | Null : null 8 | 9 | ## Boolean 10 | 11 | Logical truth values, i.e. {true} and {false}. 12 | 13 | Boolean : 14 | 15 | - true 16 | - false 17 | 18 | ## Number 19 | 20 | Signed 64-bit double-precision floating point numbers, e.g. `3.14`, following the [IEEE 754 standard](https://en.wikipedia.org/wiki/Double-precision_floating-point_format). These have a magnitude of roughly 10⁻³⁰⁷ to 10³⁰⁸, and can represent 15 significant figures with exact precision - beyond this, significant figures are rounded to 53-bit precision. The special IEEE 754 values of {Infinity} and {NaN} are not supported, and are coerced to {null}. 21 | 22 | Number : 23 | 24 | - Integer 25 | - Decimal 26 | - ScientificNotation 27 | 28 | Sign : one of + - 29 | 30 | Integer : Sign? Digit+ 31 | 32 | Decimal : Sign? Digit+ Fractional 33 | 34 | ScientificNotation : Sign? Digit+ Fractional? ExponentMarker Sign? Digit+ 35 | 36 | Fractional : . Digit+ 37 | 38 | ExponentMarker : one of `e` `E` 39 | 40 | ## String 41 | 42 | A string stores an UTF-8 encoded list of characters. 43 | 44 | The syntax of a string literal is a subset of JSON with the following extensions: 45 | 46 | - Any control characters (including newlines) are allowed to appear inside a string. 47 | - Extended support for referring to Unicode characters above 16-bit: `"\u{1F600}"`. 48 | 49 | String : 50 | 51 | - `"` DoubleStringCharacter\* `"` 52 | - `'` SingleStringCharacter\* `'` 53 | 54 | DoubleStringCharacter : 55 | 56 | - SourceCharacter but not one of `"`, `\` 57 | - `\` EscapeSequence 58 | 59 | SingleStringCharacter : 60 | 61 | - SourceCharacter but not one of `'`, `\` 62 | - `\` EscapeSequence 63 | 64 | EscapeSequence : 65 | 66 | - SingleEscapeSequence 67 | - UnicodeEscapeSequence 68 | 69 | SingleEscapeSequence : one of ' `"` `\` `/` b f n r t 70 | 71 | UnicodeEscapeSequence : 72 | 73 | - u HexDigit HexDigit HexDigit HexDigit 74 | - u{ HexDigit+ } 75 | 76 | Escape sequences are interpreted as follows: 77 | 78 | - `\'` represents U+0027. 79 | - `\"` represents U+0022. 80 | - `\\` represents U+005C. 81 | - `\/` represents U+002F. 82 | - `\b` represents U+0008. 83 | - `\f` represents U+000C. 84 | - `\n` represents U+000A. 85 | - `\r` represents U+000D. 86 | - `\t` represents U+0009. 87 | - `\uXXXX` represents the Unicode code point U+XXXX. 88 | - `\uXXXX\uYYYY`, where XXXX is a high surrogate (W1, 0xD800–0xDBFF) and YYYY is a low surrogate (W2, 0xDC00–0xDFFF) is interpreted as a UTF-16 surrogate pair and encoded into a single code point. 89 | 90 | It's a syntactical error when a Unicode escape sequence represents an invalid Unicode code point. 91 | 92 | ## Array 93 | 94 | An ordered collection of values, e.g. `[1, 2, 3]`. Can contain any combination of other types, including other arrays and mixed types. An element inside an array literal can be preceded by `...` which causes it to be flattened into the array. 95 | 96 | Array : [ ArrayElements? `,`? ] 97 | 98 | ArrayElements : 99 | 100 | - ArrayElement 101 | - ArrayElements , ArrayElement 102 | 103 | ArrayElement : `...`? Expression 104 | 105 | EvaluateArray(scope): 106 | 107 | 1. Let {result} be a new empty array. 108 | 2. For each {ArrayElement}: 109 | 3. Let {elementNode} be the {Expression} of the {ArrayElement}. 110 | 4. Let {element} be the result of {Evaluate(elementNode, scope)}. 111 | 5. If the {ArrayElement} contains {...}: 112 | 1. If {element} is an array: 113 | 1. Concatenate {element} to {result}. 114 | 6. Otherwise: 115 | 1. Append {element} to {result}. 116 | 7. Return {result}. 117 | 118 | ## Object 119 | 120 | An unordered collection of key/value pairs (referred to as attributes) with unique keys, e.g. `{"a": 1, "b": 2}`. Keys must be strings, while values can be any combination of other types, including other objects. If duplicate keys are specified, the last key is used. 121 | 122 | The values of an object literal can use the full power of expressions: 123 | 124 | ```example 125 | *[_type == "rect"]{"area": width * height} 126 | ``` 127 | 128 | Note: A {Projection} expression is just an expression with an object literal to the right of it. 129 | 130 | Object literal supports syntactical sugar when the attribute name and value is equivalent: 131 | 132 | ```example 133 | // These two are equivalent 134 | *[_type == "person"]{name} 135 | *[_type == "person"]{"name": name} 136 | ``` 137 | 138 | Object : { ObjectAttributes? `,`? } 139 | 140 | ObjectAttributes : 141 | 142 | - ObjectAttribute 143 | - ObjectAttributes , ObjectAttribute 144 | 145 | ObjectAttribute : 146 | 147 | - String : Expression 148 | - Expression 149 | - `...` Expression? 150 | 151 | EvaluateObject(scope): 152 | 153 | - Let {result} be a new empty object. 154 | - For each {ObjectAttribute}: 155 | - If the {ObjectAttribute} contains `...`: 156 | - If the {ObjectAttribute} contains an {Expression}: 157 | - Let {baseNode} be the {Expression}. 158 | - Let {base} be the result of {Evaluate(baseNode, scope)}. 159 | - Otherwise: 160 | - Let {base} be the this value of {scope}. 161 | - For each {name} and {value} of {base}: 162 | - Set the attribute {name} to {value} in {result}. 163 | - Otherwise: 164 | - Let {valueNode} be the {Expression} of the {ObjectAttribute}. 165 | - Let {value} be the result of {Evaluate(valueNode, scope)}. 166 | - If the {ObjectAttribute} contains a {String}: 167 | - Let {name} be the string value of the {String}. 168 | - Otherwise: 169 | - Let {name} be the result of {DetermineName(valueNode)}. 170 | - Set the attribute {name} to {value} in {result}. 171 | - Return {result}. 172 | 173 | DetermineName(node): 174 | 175 | - If {node} is an {ThisAttribute}: 176 | - Return the string value of the {Identifier} of {node}. 177 | - If {node} is a {ArrayPostfix}, {Dereference}, {ElementAccess}, {Filter}, {Map}, {Projection}, {SelectorGroup}, or {Slice}: 178 | - Let {base} be the first {Expression} of {expr}. 179 | - Return the result of {DetermineName(base)}. 180 | 181 | ValidateObject(): 182 | 183 | - For each {ObjectAttribute}: 184 | - If the {ObjectAttribute} does not contain a {String}: 185 | - Let {expr} be the {Expression}. 186 | - Execute {ValidateObjectAttribute(expr)}. 187 | 188 | ValidateObjectAttribute(expr): 189 | 190 | - If {node} is an {ThisAttribute}: 191 | - Stop. 192 | - If {node} is a {Projection}, {ElementAccess}, {Slice}, or {Filter}: 193 | - Let {base} be the first {Expression} of {expr}. 194 | - Execute {ValidateObjectAttribute(base)}. 195 | - Otherwise: 196 | - Report an error. 197 | 198 | ## Pair 199 | 200 | A pair of values, e.g. `"a" => 1`. Pairs can contain any combination of other types, including other pairs, and are mainly used internally with e.g. projection conditionals and`select()`. 201 | 202 | In serialized JSON, pairs are represented as a string on the form `fst => snd` where `fst` and `snd` are the serialized JSON for the first and the second expression. 203 | 204 | Pair : Expression `=>` Expression 205 | 206 | EvaluatePair(scope): 207 | 208 | - Let {firstNode} be the first {Expression}. 209 | - Let {secondNode} be the second {Expression}. 210 | - Let {result} be a new pair. 211 | - Set the first value of {result} to the result of {Evaluate(firstNode, scope)}. 212 | - Set the second value of {result} to the result of {Evaluate(secondNode, scope)}. 213 | - Return {result}. 214 | 215 | ## Range 216 | 217 | An interval containing all values that are ordered between the start and end values. The starting value is always included, while the end may be either included or excluded. A right-inclusive range is expressed as two values separated by `..`, e.g. `1..3`, while a right-exclusive range is separated by `...`, e.g. `1...3`. 218 | 219 | Ranges can have endpoints of any comparable data type, but both endpoints must be of the same type (except integers and floats which can be used interchangeably). Ranges with incompatible or invalid endpoints types will yield `null`. 220 | 221 | Ranges are mainly used internally, e.g. with the `in` operator and array slice access operator. The endpoints may have context-dependent semantics, e.g. in array slices the range `[2..-1]` will cover the range from the third array element to the last element, while the same range is considered empty when used with `in`. For more details, see the documentation for the relevant operators. 222 | 223 | In serialized JSON, ranges are represented as a string on the form `start..end` (for inclusive ranges) and `start...end` (for exclusive ranges) where `start` and `end` are the serialized JSON for the start and the end expression. 224 | 225 | Range : 226 | 227 | - InclusiveRange 228 | - ExclusiveRange 229 | 230 | InclusiveRange : Expression `..` Expression 231 | 232 | ExclusiveRange : Expression `...` Expression 233 | 234 | EvaluateRange(scope): 235 | 236 | - Let {startNode} be the first {Expression}. 237 | - Let {endNode} be the second {Expression}. 238 | - Let {start} be the result of {Evaluate(startNode, scope)}. 239 | - Let {end} be the result of {Evaluate(endNode, scope)}. 240 | - If {PartialCompare(start, end)} is {null}: 241 | - Return {null}. 242 | - Let {result} be a new range. 243 | - Set the start value of {result} to {start}. 244 | - Set the end value of {result} to {end}. 245 | - Mark the range as inclusive or exclusive. 246 | - Return {result}. 247 | 248 | ## Datetime 249 | 250 | A datetime is a combination of a Gregorian-calendar date and a time in UTC. It's stored in millisecond precision, but an implementation can choose to support even finer granularity. Datetimes support date/time arithmetic. Only valid date/time combinations can be represented. 251 | 252 | Datetimes cannot be constructed from literals, but must be constructed with the {dateTime} function. 253 | 254 | In serialized JSON, datetimes are represented as a string with using [RFC 3339](https://tools.ietf.org/html/rfc3339) timestamp format, e.g. `2006-01-02T15:04:05Z` using the following rules: 255 | 256 | 1. If there is no millisecond information in the datetime, format it without any fractional digits: `2006-01-02T15:04:05Z` 257 | 2. If there is millisecond information in the datetime, format it with 3 fractional digits: `2006-01-02T15:04:05.508Z` 258 | 3. If the datetime contains even finer granularity, it's implementation dependent how the additional fractional digits are formatted. 259 | -------------------------------------------------------------------------------- /spec/05-equality-comparison.md: -------------------------------------------------------------------------------- 1 | # Equality and comparison 2 | 3 | GROQ provides trivial equality and comparison between numbers, strings and booleans. Other types are considered inequal or incomparable to each other. Incomparability between values are represented by operators returning {null} (e.g. `2 > "1"` is {null}). 4 | 5 | ## Equality 6 | 7 | Simple values such as numbers, strings, booleans and {null} are equal when they contain the same data. All other values are considered inequal to each other (e.g. `[] != []`). 8 | 9 | Note: In GROQ `1 == null` returns `false` (which is different from e.g. SQL). 10 | 11 | Equal(a, b): 12 | 13 | - If both {a} and {b} is {null}: 14 | - Return {true}. 15 | - Let {cmp} be the result of {PartialCompare(a, b)}. 16 | - If {cmp} is {Equal}: 17 | - Return {true}. 18 | - Otherwise: 19 | - Return {false}. 20 | 21 | ## Partial comparison 22 | 23 | A partial comparison between two values return either {Greater}, {Equal}, {Less} or {null}. {null} represents that the values are incomparable to each other. This is used by the comparison operators (<, <=, >, >=). 24 | 25 | PartialCompare(a, b): 26 | 27 | - If the type of {a} is different from the type of {b}: 28 | - Return {null}. 29 | - If {a} is a datetime, consider the datetimes as absolute points in time in the UTC time zone: 30 | - If a < b: 31 | - Return {Less}. 32 | - If a > b: 33 | - Return {Greater}. 34 | - If a = b: 35 | - Return {Equal}. 36 | - If {a} is a number: 37 | - If a < b: 38 | - Return {Less}. 39 | - If a > b: 40 | - Return {Greater}. 41 | - If a = b: 42 | - Return {Equal}. 43 | - If {a} is a string: 44 | - For each Unicode code point ({aCodePoint}, {bCodePoint}) in {a} and {b}: 45 | - If {aCodePoint} < {bCodePoint}: 46 | - Return {Less}. 47 | - If {aCodePoint} > {bCodePoint}: \* Return {Greater}. 48 | - If {a} is shorter than {b}: 49 | - Return {Less}. 50 | - If {a} is longer than {b}: 51 | - Return {Greater}. 52 | - Return {Equal}. 53 | - If {a} is a boolean: 54 | - Return the comparison between {a} and {b} with {false} < {true}. 55 | - Return {null}. 56 | 57 | ## Total comparison 58 | 59 | A total comparison between two values return either {Greater}, {Equal} or {Less}. It provides a consistent ordering of values of different types (for string, numbers and boolean) and considers all other types to be equal to each other. This is used by the {order()} function. 60 | 61 | TypeOrder(val): 62 | 63 | - If {val} is a datetime: 64 | - Return 1. 65 | - If {val} is a number: 66 | - Return 2. 67 | - If {val} is a string: 68 | - Return 3. 69 | - If {val} is a boolean: 70 | - Return 4. 71 | - Return 5. 72 | 73 | TotalCompare(a, b): 74 | 75 | - Let {aTypeOrder} be the result of {TypeOrder(a)}. 76 | - Let {bTypeOrder} be the result of {TypeOrder(b)}. 77 | - If {aTypeOrder} != {bTypeOrder}: 78 | - Return the result of {PartialCompare(aTypeOrder, bTypeOrder)}. 79 | - Let {result} be the result of {PartialCompare(a, b)}. 80 | - If {result} is {null}: 81 | - Return {Equal}. 82 | - Otherwise: 83 | - Return {result}. 84 | -------------------------------------------------------------------------------- /spec/06-simple-expressions.md: -------------------------------------------------------------------------------- 1 | # Simple expressions 2 | 3 | ## This expression 4 | 5 | A this expression returns the this value of the current scope. 6 | 7 | ```example 8 | *[_id == "doc"][0].numbers[@ >= 10] 9 | ~ 10 | ``` 11 | 12 | This : `@` 13 | 14 | EvaluateThis(scope): 15 | 16 | - Return the this value of {scope}. 17 | 18 | ## This attribute expression 19 | 20 | A *this attribute expression *returns an attribute from the this value of the current scope. 21 | 22 | ```example 23 | *[_id == "document"][name == "Michael Bluth"] 24 | ~~~ ~~~~ 25 | ``` 26 | 27 | ThisAttribute : Identifier 28 | 29 | EvaluateThisAttribute(scope): 30 | 31 | - Let {base} be the this value of {scope}. 32 | - Let {name} be the string value of the {Identifier}. 33 | - If {base} is not an object, return {null}. 34 | - If {base} does not contain an attribute {name}, return {null}. 35 | - Return the value of the attribute {name} in {base}. 36 | 37 | ## Everything expression 38 | 39 | An everything expression returns the full dataset. 40 | 41 | ```example 42 | *[_type == "person"] 43 | ~ 44 | ``` 45 | 46 | Everything : `*` 47 | 48 | EvaluateEverything(scope): 49 | 50 | - Let {context} be the query context of {scope}. 51 | - Return the dataset of {context}. 52 | 53 | ## Parent expression 54 | 55 | A parent expression returns a this value for an upper scope. 56 | 57 | ```example 58 | // Find all people who have a cool friend 59 | *[_type == "person" && *[_id == ^.friend._ref][0].isCool] 60 | ~ 61 | ``` 62 | 63 | Parent : 64 | 65 | - `^` 66 | - `^.` Parent 67 | 68 | EvaluateParent(scope): 69 | 70 | - Let {level} be the number of `^` in the parent expression. 71 | - Let {currentScope} be {scope}. 72 | - While {level} is greater than zero: 73 | - Set {currentScope} to the parent of {currentScope}. 74 | - If {currentScope} is now {null}, return {null}. 75 | - Decrease {level} by one. 76 | - Return the this value of {currentScope}. 77 | 78 | ## Parameter expression 79 | 80 | A parameter expression returns the value of a parameter 81 | 82 | ```example 83 | *[_type == $type] 84 | ~~~~~ 85 | ``` 86 | 87 | Parameter : `$` Identifier 88 | 89 | EvaluateParameter(scope): 90 | 91 | - Let {name} be the string value of the {Identifier}. 92 | - Return the value of the parameter in {scope}. 93 | 94 | ValidateParameter(): 95 | 96 | - Let {name} be the string value of the {Identifier}. 97 | - If the parameter doesn't exist in the current validation context: 98 | - Report an error. 99 | 100 | ## Function call expression 101 | 102 | GROQ comes with a set of built-in functions which provides additional features. See the ["Functions"](#sec-Functions) for available functions and their namespaces. 103 | 104 | Custom GROQ functions can be defined to extend or override the built-in function set. See [Custom functions](12-custom-functions.md) for details. 105 | 106 | ```example 107 | *{"score": round(score, 2)} 108 | ~~~~~~~~~~~~~~~ 109 | 110 | *{"description": global::lower(description)} 111 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 112 | ``` 113 | 114 | FuncCall : FuncNamespace? FuncIdentifier ( FuncCallArgs ) 115 | 116 | FuncNamespace : Identifier "::" 117 | 118 | FuncIdentifier : Identifier 119 | 120 | FuncCallArgs : 121 | 122 | - Expression 123 | - FuncCallArgs , Expression 124 | 125 | EvaluateFuncCall(scope): 126 | 127 | - Let {namespace} be the string value of the {FuncNamespace}. 128 | - Let {name} be the string value of the {FuncIdentifier}. 129 | - Let {args} be an empty array. 130 | - For each {Expression} in {FuncCallArgs}: 131 | - Let {argumentNode} be the {Expression}. 132 | - Append {argumentNode} to {args}. 133 | - If the query context of {scope} has a function defined with the given {name} and {namespace}: 134 | - Let {funcBody} be body of the custom function. 135 | - Let {context} be the query context of {scope}. 136 | - Let {newScope} be the result of {NewRootScope(context)}. 137 | - For each {param} in the parameter list of the custom function: 138 | - Let {argNode} be next {Expression} in {FuncCallArgs}. 139 | - Let {arg} be the result of {Evaluate(argNode, scope)}. 140 | - Set the parameter named {param} in {newScope} to {arg}. 141 | - Return the result of {Evaluate(funcBody, newScope)} 142 | - Otherwise: 143 | - Let {func} be the function defined under the name {name} in either {namespace} namespace if provided, or the `global` namespace. 144 | - Return the result of {func(args, scope)}. 145 | 146 | ValidateFuncCall(): 147 | 148 | - Let {name} be the string value of the {FuncIdentifier}. 149 | - Let {namespace} be the string value of the {FuncNamespace}. 150 | - Let {args} be an array of the {Expression}s in {FuncCallArgs}. 151 | - If there's a custom function defined with the given {name} and {namespace}: 152 | - For each {arg} in {args}: 153 | - Execute {Validate(arg)}. 154 | - Stop. 155 | - If there is no namespace named {namespace}: 156 | - Stop and report an error. 157 | - If there is no function named {name} defined in either {namespace} namespace if provided, or the `global` namespace: 158 | - Stop and report an error. 159 | - Let {validator} be the validator for the function under the name {name}. 160 | - Execute {validator(args)}. 161 | -------------------------------------------------------------------------------- /spec/07-compound-expressions.md: -------------------------------------------------------------------------------- 1 | # Compound expressions 2 | 3 | ## Parenthesis expression 4 | 5 | A parenthesis expression allows you to add parenthesis around another expression to control precedence of operators. 6 | 7 | ```example 8 | (1 + 2) * 3 9 | ~~~~~~~ 10 | ``` 11 | 12 | Parenthesis : ( Expression ) 13 | 14 | EvaluateParenthesis(scope): 15 | 16 | - Let {innerNode} be the {Expression}. 17 | - Let {result} be the result of {Evaluate(innerNode, scope)}. 18 | - Return {result}. 19 | 20 | ## Traversal expression 21 | 22 | A traversal expressions starts a traversal. 23 | 24 | ```example 25 | users.foo.bar[0].sources[]->name 26 | ``` 27 | 28 | When the left-hand side is an {Everything}, {Array}, or {PipeFuncCall} this is interpreted as if there was an additional explicit {ArrayPostfix} traversal. 29 | E.g. `*._id` is interpreted as `*[]._id` and therefore returns an array of IDs. 30 | 31 | TraversalExpression : Expression Traversal 32 | 33 | Traversal : 34 | 35 | - TraversalPlain 36 | - TraversalArray 37 | - TraversalArraySource 38 | - TraversalArrayTarget 39 | 40 | EvaluateTraversalExpression(scope): 41 | 42 | - Let {node} be the {Expression}. 43 | - Let {traversalNode} be the {Traversal}. 44 | - Let {base} be the result of {Evaluate(node, scope)}. 45 | - If {node} is one of {Everything}, {Array}, {PipeFuncCall}: 46 | - Let {traverse} be the traversal function for the combination {ArrayPostfix} and {traversalNode}. 47 | - Otherwise: 48 | - Let {traverse} be the traverse function of {traversalNode}. 49 | - Return {traverse(base, scope)}. 50 | 51 | ## Pipe function call expression 52 | 53 | GROQ comes with a set of built-in pipe functions which provides additional features. Pipe functions always accepts an array on the left-hand side and returns another array, and the syntax is optimized for being able to chain it together with other compound expressions. See the ["Pipe functions"](#sec-Pipe-functions) for available functions. 54 | 55 | ```example 56 | *[_type == "person"] | order(name) | {age} 57 | ~~~~~~~~~~~~~ 58 | ``` 59 | 60 | PipeFuncCall : Expression `|` FuncCall 61 | 62 | EvaluatePipeFuncCall(scope): 63 | 64 | - Let {baseNode} be the first Expression. 65 | - Let {base} be the result of {Evaluate(baseNode, scope)}. 66 | - If {base} is not an array: 67 | - Return {null}. 68 | - Let {name} be the string value of the {Identifier} of the {FuncCall}. 69 | - Let {args} be an empty array. 70 | - For each {Expression} in the {FuncCallArgs} of the {FuncCall}. 71 | - Let {argumentNode} be the {Expression}. 72 | - Append {argumentNode} to {args}. 73 | - Let {func} be the pipe function defined under the name {name}. 74 | - Return the result of {func(base, args, scope)}. 75 | 76 | ValidatePipeFuncCall(): 77 | 78 | - Let {base} be the first {Expression}. 79 | - Execute {Validate(base)}. 80 | - Let {name} be the string value of the {Identifier} of the {FuncCall}. 81 | - If there is no pipe function named {name}: 82 | - Stop and report an error. 83 | - Let {args} be an array of the {Expression}s in the {FuncCallArgs} of the {FuncCall}. 84 | - Let {validator} be the validator for the pipe function under the name {name}. 85 | - Execute {validator(args)}. 86 | -------------------------------------------------------------------------------- /spec/08-traversal-operators.md: -------------------------------------------------------------------------------- 1 | # Traversal operators 2 | 3 | ## Attribute access traversal 4 | 5 | An attribute access returns an attribute of an object. 6 | 7 | ```example 8 | person.name 9 | ~~~~~ 10 | 11 | person["Full Name"] 12 | ~~~~~~~~~~~~~ 13 | ``` 14 | 15 | AttributeAccess : 16 | 17 | - `.` Identifier 18 | - `[` Expression `]` 19 | 20 | Note: {Filter}, {ElementAccess}, {AttributeAccess} are syntactically ambiguous. See ["Disambiguating square bracket traversal"](#sec-Disambiguating-square-bracket-traversal) for how to disambiguate between them. 21 | 22 | EvaluateAttributeAccess(base, scope): 23 | 24 | - If {base} is not an object, return {null}. 25 | - Let {name} be the string value of {String} or {Identifier}. 26 | - If {base} does not contain an attribute {name}, return {null}. 27 | - Return the value of the attribute {name} in {base}. 28 | 29 | ## Element access traversal 30 | 31 | An element access returns an element stored in an array. The array is 0-indexed and a negative index accesses the array from the end (i.e. an index of -1 returns the last element; -2 refers to the second last element). 32 | 33 | ElementAccess : `[` Expression `]` 34 | 35 | Note: {Filter}, {ElementAccess}, {AttributeAccess} are syntactically ambiguous. See ["Disambiguating square bracket traversal"](#sec-Disambiguating-square-bracket-traversal) for how to disambiguate between them. 36 | 37 | EvaluateElementAccess(base, scope): 38 | 39 | - If {base} is not an array, return {null}. 40 | - Let {idxNode} be the second {Expression}. 41 | - Let {idx} be the result of {Evaluate(idxNode, scope)}. 42 | This value is guaranteed to be an integer due to the validation. 43 | - If {idx} is negative, add the length of {base} to {idx}. 44 | - If {idx} is still negative, return {null}. 45 | - If {idx} is equal to or greater than the length of {base}, return {null}. 46 | - Return the value stored at position {idx} in {base}. 47 | 48 | ValidateElementAccess(): 49 | 50 | - Let {idxNode} be the second {Expression}. 51 | - Let {idx} be the result of {ConstantEvaluate(idxNode)}. 52 | This value is guaranteed to be a number due to square bracket disambiguation. 53 | - If {idx} is not an integer: Report an error. 54 | 55 | ## Slice traversal 56 | 57 | A slice returns a slice of an array. 58 | 59 | ```example 60 | people[0..10] 61 | ~~~~~~~ 62 | ``` 63 | 64 | Slice : `[` Range `]` 65 | 66 | EvaluateSlice(base, scope): 67 | 68 | - Let {base} be the result of {Evaluate(baseNode, scope)}. 69 | - If {base} is not an array, return {null}. 70 | - Process the left index: 71 | - Let {leftNode} be the left value of the {Range}. 72 | - Let {left} be the result of {Evaluate(leftNode, scope)}. 73 | This value is guaranteed to be an integer due to the validation. 74 | - If {left} is negative, add the length of {base} to {left}. 75 | - Clamp {left} between 0 and (the length of {base} minus 1). 76 | - Process the right index: 77 | - Let {rightNode} be the right value of the {Range}. 78 | - Let {right} be the result of {Evaluate(rightNode, scope)}. 79 | This value is guaranteed to be an integer due to the validation. 80 | - If {right} is negative, add the length of {base} to {right}. 81 | - If the {Range} is exclusive, subtract one from {right}. 82 | - Clamp {right} between 0 and (the length of {base} minus 1). 83 | - Let {result} be an array containing the elements of {base} from position {left} up to and including position {right}. 84 | - Return {result}. 85 | 86 | ValidateSlice(): 87 | 88 | - Let {leftNode} be the left value of the {Range}. 89 | - Let {leftValue} be the result of {ConstantEvaluate(leftNode)}. 90 | - If {leftValue} is not an integer: Report an error. 91 | - Let {rightNode} be the right value of the {Range}. 92 | - Let {rightValue} be the result of {ConstantEvaluate(rightNode)}. 93 | - If {rightValue} is not an integer: Report an error. 94 | 95 | ## Filter traversal 96 | 97 | A filter returns an array filtered another expression. 98 | 99 | ```example 100 | *[_type == "person"] 101 | ~~~~~~~~~~~~~~~~~~~ 102 | ``` 103 | 104 | Filter : `[` Expression `]` 105 | 106 | Note: {Filter}, {ElementAccess}, {AttributeAccess} are syntactically ambiguous. See ["Disambiguating square bracket traversal"](#sec-Disambiguating-square-backet-traversal) for how to disambiguate between them. 107 | 108 | EvaluateFilter(base, scope): 109 | 110 | - If {base} is not an array, return {base}. 111 | - Let {filterNode} be the second {Expression}. 112 | - Let {result} be a new empty array. 113 | - For each element {value} in {baseValue}: 114 | - Let {elementScope} be the result of {NewNestedScope(value, scope)}. 115 | - Let {matched} be the result of {Evaluate(filterNode, elementScope)}. 116 | - If {matched} is {true}, append {value} to {result}. 117 | - Return {result}. 118 | 119 | ## Array postfix traversal 120 | 121 | An array postfix coerces the value into an array. 122 | 123 | ArrayPostfix : `[` `]` 124 | 125 | EvaluateArrayPostfix(base, scope): 126 | 127 | - If {base} is not an array, return {null}. 128 | - Return {base}. 129 | 130 | ## Projection traversal 131 | 132 | A projection operator returns a new object. 133 | 134 | ```example 135 | *[_type == "person"]{name, "isLegal": age >= 18} 136 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 137 | ``` 138 | 139 | Projection : `|`? Object 140 | 141 | EvaluateProjection(base, scope): 142 | 143 | - Let {objNode} be the {Object}. 144 | - If {base} is not an object: 145 | - Return {null}. 146 | - Let {elementScope} be the result of {NewNestedScope(base, scope)}. 147 | - Let {result} be the result of {Evaluate(objNode, elementScope)}. 148 | - Return {result}. 149 | 150 | ## Dereference traversal 151 | 152 | Dereference : `->` Identifier? 153 | 154 | EvaluateDereference(base, scope): 155 | 156 | - If {base} is not an object: 157 | - Return {null}. 158 | - If {base} does not have an attribute `_ref`: 159 | - Return {null}. 160 | - Let {ref} be the value of the attribute `_ref` in {base}. 161 | - If {ref} is not a string: 162 | - Return {null}. 163 | - Let {dataset} be the dataset of the query context of {scope}. 164 | - If {dataset} is not an array: 165 | - Return {null}. 166 | - Let {result} be {null}. 167 | - For each {document} in {dataset}: 168 | - If {document} is an object and has an attribute `_id`: 169 | - Let {id} be the value of the attribute `_id` in {document}. 170 | - If {Equal(ref, id)} is {true}: 171 | - Set {result} to {document}. 172 | - Stop the loop. 173 | - If the dereference expression contains a {Identifier}: 174 | - Let {name} be the string value of the {Identifier}. 175 | - If {result} is an object and contains an attribute {name}: 176 | - Return the {value} of the attribute {name} in {result}. 177 | - Otherwise: 178 | - Return {null}. 179 | - Return {result}. 180 | 181 | ## Disambiguating square bracket traversal 182 | 183 | {Filter}, {ElementAccess} and {AttributeAccess} are syntactically ambiguous, and the following algorithm is used to disambiguate between them. 184 | 185 | SquareBracketTraversal : `[` Expression `]` 186 | 187 | DisambiguateSquareBracketTraversal(): 188 | 189 | - Let {valueNode} be the {Expression}. 190 | - Let {value} be the result of {ConstantEvaluate(valueNode)}. 191 | - If {value} is a string: Interpret it as an {AttributeAccess} traversal. 192 | - If {value} is a number: Interpret it as an {ElementAccess} traversal. 193 | - Otherwise: Interpret it as a {Filter} traversal. 194 | -------------------------------------------------------------------------------- /spec/09-operators.md: -------------------------------------------------------------------------------- 1 | # Operators 2 | 3 | ## And operator 4 | 5 | And : Expression `&&` Expression 6 | 7 | EvaluateAnd(scope): 8 | 9 | - Let {leftNode} be the first {Expression}. 10 | - Let {left} be the result of {Evaluate(leftNode, scope)}. 11 | - Let {rightNode} be the last {Expression}. 12 | - Let {right} be the result of {Evaluate(rightNode, scope)}. 13 | - If {left} or {right} is {false}: 14 | - Return {false}. 15 | - If {left} or {right} is not a boolean: 16 | - Return {null}. 17 | - Return {true}. 18 | 19 | ## Or operator 20 | 21 | Or : Expression `||` Expression 22 | 23 | EvaluateOr(scope): 24 | 25 | - Let {leftNode} be the first {Expression}. 26 | - Let {left} be the result of {Evaluate(leftNode, scope)}. 27 | - Let {rightNode} be the last {Expression}. 28 | - Let {right} be the result of {Evaluate(rightNode, scope)}. 29 | - If {left} or {right} is {true}: 30 | - Return {true}. 31 | - If {left} or {right} is not a boolean: 32 | - Return {null}. 33 | - Return {false}. 34 | 35 | ## Not operator 36 | 37 | Not : `!` Expression 38 | 39 | EvaluateNot(scope): 40 | 41 | - Let {valueNode} be the {Expression}. 42 | - Let {value} be the result of {Evaluate(valueNode, scope)}. 43 | - If {value} is {false}: 44 | - Return {true}. 45 | - If {value} is {true}: 46 | - Return {false}. 47 | - Return {null}. 48 | 49 | ## Equality operators 50 | 51 | Equality : Expression EqualityOperator Expression 52 | 53 | EqualityOperator : one of `==`, `!=` 54 | 55 | EvaluateEquality(scope): 56 | 57 | - Let {leftNode} be the first {Expression}. 58 | - Let {left} be the result of {Evaluate(leftNode, scope)}. 59 | - Let {rightNode} be the last {Expression}. 60 | - Let {right} be the result of {Evaluate(rightNode, scope)}. 61 | - Let {result} be the result of {Equal(left, right)}. 62 | - If the operator is `!=`: 63 | - If {result} is {true}: 64 | - Return {false}. 65 | - If {result} is {false}: 66 | - Return {true}. 67 | - Return {result}. 68 | 69 | ## Comparison operators 70 | 71 | Comparison : Expression ComparisonOperator Expression 72 | 73 | ComparisonOperator : one of `<`, `<=`, `>`, `>=` 74 | 75 | EvaluateComparison(scope): 76 | 77 | - Let {leftNode} be the first {Expression}. 78 | - Let {left} be the result of {Evaluate(leftNode, scope)}. 79 | - Let {rightNode} be the last {Expression}. 80 | - Let {right} be the result of {Evaluate(rightNode, scope)}. 81 | - Let {cmp} be the result of {PartialCompare(left, right)}. 82 | - If {cmp} is {null}: 83 | - Return {null}. 84 | - If {cmp} is {Less} and the operator is {<} or {<=}: 85 | - Return {true}. 86 | - If {cmp} is {Greater} and the operator is {>} or {>=}: 87 | - Return {true}. 88 | - If {cmp} is {Equal} and the operator is {<=} or {>=}: 89 | - Return {true}. 90 | - Return {false}. 91 | 92 | ## In operator 93 | 94 | In : 95 | 96 | - Expression in Expression 97 | - Expression in Range 98 | 99 | EvaluateIn(scope): 100 | 101 | - Let {leftNode} be the first {Expression}. 102 | - Let {left} be the result of {Evaluate(leftNode, scope)}. 103 | - If the right-hand side is a {Range}: 104 | - Let {lowerNode} be the start node of the range. 105 | - Let {lower} be the result of {Evaluate(lowerNode, scope)}. 106 | - Let {upperNode} be the end node of the range. 107 | - Let {upper} be the result of {Evaluate(upperNode, scope)}. 108 | - Let {leftCmp} be the result of {PartialCompare(left, lower)}. 109 | - Let {rightCmp} be the result of {PartialCompare(left, upper)}. 110 | - If {leftCmp} or {rightCmp} is {null}: 111 | - Return {null}. 112 | - If {leftCmp} is {Less}: 113 | - Return {false}. 114 | - If {rightCmp} is {Greater}: 115 | - Return {false}. 116 | - If the range is exclusive and {rightCmp} is {Equal}: 117 | - Return {false}. 118 | - Return {true}. 119 | - Let {rightNode} be the last {Expression}. 120 | - Let {right} be the result of {Evaluate(rightNode, scope)}. 121 | - If {right} is an array: 122 | - For each {value} in {right}: 123 | - If {Equal(left, value)} is {true}: 124 | - Return {true}. 125 | - Return {false}. 126 | - Return {null}. 127 | 128 | ## Match operator 129 | 130 | The match operator is defined in terms of _patterns_ and _tokens_: It returns true when any of patterns matches all of the tokens. The exact way of tokenizing text and interpreting patterns is left as an implementation detail. 131 | 132 | Match : Expression match Expression 133 | 134 | EvaluateMatch(scope): 135 | 136 | - Let {leftNode} be the first {Expression}. 137 | - Let {left} be the result of {Evaluate(leftNode, scope)}. 138 | - Let {rightNode} be the last {Expression}. 139 | - Let {right} be the result of {Evaluate(rightNode, scope)}. 140 | - Let {tokens} be an empty array. 141 | - If {left} is a string: 142 | - Concatenate {MatchTokenize(left)} to {tokens}. 143 | - If {left} is an array: 144 | - For each {value} in {left}: 145 | - If {value} is a string: 146 | - Concatenate {MatchTokenize(value)} to {tokens}. 147 | - Let {patterns} be an empty array. 148 | - If {right} is a string: 149 | - Append {MatchAnalyzePattern(right)} to {patterns}. 150 | - If {right} is an array: 151 | - For each {value} in {right}: 152 | - If {value} is a string: 153 | - Append {MatchAnalyzePattern(value)} to {patterns}. 154 | - Otherwise: \* Return {false}. 155 | - If {patterns} is empty: 156 | - Return {false}. 157 | - For each {pattern} in {patterns}: 158 | - If {pattern} does not matches {tokens}: 159 | - Return {false}. 160 | - Return {true}. 161 | 162 | MatchTokenize(value): 163 | 164 | - Return an array of tokens. 165 | 166 | MatchAnalyzePattern(value): 167 | 168 | - Return a pattern for the given string. 169 | 170 | ## Asc operator 171 | 172 | The asc operator is used by the {order()} function to signal that you want ascending sorting. Evaluating it in any other context is not allowed. 173 | 174 | Asc : Expression `asc` 175 | 176 | ValidateAsc(): 177 | 178 | - Report an error. 179 | 180 | ## Desc operator 181 | 182 | The desc operator is used by the {order()} function to signal that you want descending sorting. Evaluating it in any other context is not allowed. 183 | 184 | Desc : Expression `desc` 185 | 186 | ValidateDesc(): 187 | 188 | - Report an error. 189 | 190 | ## Unary plus operator 191 | 192 | UnaryPlus : `+` Expression 193 | 194 | EvaluateUnaryPlus(scope): 195 | 196 | - Let {valueNode} be the {Expression}. 197 | - Let {value} be the result of {Evaluate(valueNode, scope)}. 198 | - If {value} is a number: 199 | - Return {value}. 200 | - Return {null}. 201 | 202 | ## Unary minus operator 203 | 204 | UnaryMinus : `-` Expression 205 | 206 | EvaluateUnaryMinus(scope): 207 | 208 | - Let {valueNode} be the {Expression}. 209 | - Let {value} be the result of {Evaluate(valueNode, scope)}. 210 | - If {value} is a number: 211 | - Return {value} with opposite sign. 212 | - Return {null}. 213 | 214 | ## Binary plus operator 215 | 216 | Plus : Expression `+` Expression 217 | 218 | EvaluatePlus(scope): 219 | 220 | - Let {leftNode} be the first {Expression}. 221 | - Let {left} be the result of {Evaluate(leftNode, scope)}. 222 | - Let {rightNode} be the last {Expression}. 223 | - Let {right} be the result of {Evaluate(rightNode, scope)}. 224 | - If both {left} and {right} are strings: 225 | - Return the string concatenation of {left} and {right}. 226 | - If both {left} and {right} are numbers: 227 | - Return the addition of {left} and {right}. 228 | - If both {left} and {right} are arrays: 229 | - Return the concatenation of {left} and {right}. 230 | - If both {left} and {right} are objects: 231 | - Return the merged object of {left} and {right}. For duplicate fields the value from {right} takes precedence. 232 | - If {left} is a datetime and {right} is a number: 233 | - Return a new datetime that adds (or subtracts, if negative) {right} as a number of seconds to {left}. 234 | - Return {null}. 235 | 236 | ## Binary minus operator 237 | 238 | Minus : Expression `-` Expression 239 | 240 | EvaluateMinus(scope): 241 | 242 | - Let {leftNode} be the first {Expression}. 243 | - Let {left} be the result of {Evaluate(leftNode, scope)}. 244 | - Let {rightNode} be the last {Expression}. 245 | - Let {right} be the result of {Evaluate(rightNode, scope)}. 246 | - If both {left} and {right} are numbers: 247 | - Return the subtraction of {left} from {right}. 248 | - If both {left} and {right} are datetimes: 249 | - Return the difference, in seconds, between {left} from {right}. 250 | - If {left} is a datetime and {right} is a number: 251 | - Return a new datetime being {left} minus {right} as seconds. 252 | - Return {null}. 253 | 254 | ## Binary star operator 255 | 256 | Star : Expression `*` [lookahead != `*`] Expression 257 | 258 | EvaluateStar(scope): 259 | 260 | - Let {leftNode} be the first {Expression}. 261 | - Let {left} be the result of {Evaluate(leftNode, scope)}. 262 | - Let {rightNode} be the last {Expression}. 263 | - Let {right} be the result of {Evaluate(rightNode, scope)}. 264 | - If both {left} and {right} are numbers: 265 | - Return the multiplication of {left} and {right}. 266 | - Return {null}. 267 | 268 | Note: A binary star operator may not have a `*` immediately following it since this may cause ambiguity. 269 | 270 | ## Binary slash operator 271 | 272 | Slash : Expression `/` Expression 273 | 274 | EvaluateSlash(scope): 275 | 276 | - Let {leftNode} be the first {Expression}. 277 | - Let {left} be the result of {Evaluate(leftNode, scope)}. 278 | - Let {rightNode} be the last {Expression}. 279 | - Let {right} be the result of {Evaluate(rightNode, scope)}. 280 | - If both {left} and {right} are numbers: 281 | - Return the division of {left} by {right}. 282 | - Return {null}. 283 | 284 | ## Binary percent operator 285 | 286 | Percent : Expression `%` Expression 287 | 288 | EvaluatePercent(scope): 289 | 290 | - Let {leftNode} be the first {Expression}. 291 | - Let {left} be the result of {Evaluate(leftNode, scope)}. 292 | - Let {rightNode} be the last {Expression}. 293 | - Let {right} be the result of {Evaluate(rightNode, scope)}. 294 | - If both {left} and {right} are numbers: 295 | - Return the remainder of {left} after division by {right}. 296 | - Return {null}. 297 | 298 | ## Binary double star operator 299 | 300 | StarStar : Expression `**` Expression 301 | 302 | EvaluateStarStar(scope): 303 | 304 | - Let {leftNode} be the first {Expression}. 305 | - Let {left} be the result of {Evaluate(leftNode, scope)}. 306 | - Let {rightNode} be the last {Expression}. 307 | - Let {right} be the result of {Evaluate(rightNode, scope)}. 308 | - If both {left} and right are numbers: 309 | - Return the exponentiation of {left} to the power of {right}. 310 | - Return {null}. 311 | -------------------------------------------------------------------------------- /spec/10-precedence-associativity.md: -------------------------------------------------------------------------------- 1 | # Precedence and associativity 2 | 3 | In this specification the various expressions and operators are defined in ambiguously in terms on precedence and associativity. The table below describes the precedence levels used to determine the correct unambiguous interpretation. 4 | 5 | From highest to lowest: 6 | 7 | - Level 11: [Compound expressions](#sec-Compound-expressions). 8 | - Level 10, prefix: `+`, `!`. 9 | - Level 9, right-associative: [](#sec-Binary-double-star-operator) `**`. 10 | - Level 8, prefix: `-`. 11 | - Level 7, left-associative: Multiplicatives (`*`, `/`, `%`). 12 | - Level 6, left-associative: Additives (`+`, `-`). 13 | - Level 5, non-associative: Ranges (`..`, `...`). 14 | - Level 4, non-associative: Comparisons (`==`, `!=`, `>`, `>=`,`<`, `<=`, `in`, `match`). 15 | - Level 4, postfix: Ordering (`asc`, `desc`). 16 | - Level 3, left-associative: `&&`. 17 | - Level 2, left-associative: `||`. 18 | - Level 1, non-associative: `=>`. 19 | -------------------------------------------------------------------------------- /spec/11-functions.md: -------------------------------------------------------------------------------- 1 | # Functions 2 | 3 | Functions provide additional functionality to GROQ queries. They are invoked through a [Function call expression](#sec-Function-call-expression). Note that function arguments are not evaluated eagerly, and it's up to the function to decide which scope the arguments are evaluated it. As such, all functions below take an array of nodes. 4 | 5 | An implementation may provide additional functions, but should be aware that this can cause problems when interoperating with future versions of GROQ. 6 | 7 | Functions are namespaced which allows to group functions by logical scope. A function may be associated with multiple namespaces and behave differently. When a function is called without a namespace, it is by default associated with a "**global**" namespace. 8 | 9 | ## Global namespace 10 | 11 | ### global::after() 12 | 13 | The after function, in delta mode, returns the attributes after the change. 14 | 15 | global_after(args, scope): 16 | 17 | - Return the after object of the query context of {scope}. 18 | 19 | global_after_validate(args, scope): 20 | 21 | - If the length of {args} is not 0: 22 | - Report an error. 23 | - If the mode of the query context of {scope} is not "delta": 24 | - Report an error. 25 | 26 | ### global::before() 27 | 28 | The before function, in delta mode, returns the attributes before the change. 29 | 30 | global_before(args, scope): 31 | 32 | - Return the before object of the query context of {scope}. 33 | 34 | global_before_validate(args, scope): 35 | 36 | - If the length of {args} is not 0: 37 | - Report an error. 38 | - If the mode of the query context of {scope} is not "delta": 39 | - Report an error. 40 | 41 | ### global::coalesce() 42 | 43 | The coalesce function returns the first value of the arguments which is not {null}. 44 | 45 | global_coalesce(args, scope): 46 | 47 | - For each {arg} in {args}: 48 | - Let {value} be the result of {Evaluate(arg, scope)}. 49 | - If {value} is not {null}: 50 | - Return {value}. 51 | - Return {null}. 52 | 53 | ### global::count() 54 | 55 | The count function returns the length of an array. 56 | 57 | global_count(args, scope): 58 | 59 | - Let {baseNode} be the first element of {args}. 60 | - Let {base} be the result of {Evaluate(baseNode, scope)}. 61 | - If {base} is an array: 62 | - Return the length of {base}. 63 | - Otherwise: 64 | - Return {null}. 65 | 66 | global_count_validate(args): 67 | 68 | - If the length of {args} is not 1: 69 | - Report an error. 70 | 71 | ### global::dateTime() 72 | 73 | The `dateTime` function takes a string or another datatime value, returning a datetime value. This function is idempotent. 74 | 75 | global_dateTime(args, scope): 76 | 77 | - Let {baseNode} be the first element of {args}. 78 | - Let {base} be the result of {Evaluate(baseNode, scope)}. 79 | - If {base} is a string: 80 | - Try to parse {base} as a datetime using the [RFC 3339](https://tools.ietf.org/html/rfc3339) timestamp format. 81 | - If the input is a valid datetime: 82 | - Return the datetime. 83 | - If {base} is a datetime value: 84 | - Return {base}. 85 | - Return {null}. 86 | 87 | global_dateTime_validate(args): 88 | 89 | - If the length of {args} is not 1: 90 | - Report an error. 91 | 92 | ### global::defined() 93 | 94 | The defined function checks if the argument is not {null}. 95 | 96 | global_defined(args, scope): 97 | 98 | - Let {baseNode} be the first element of {args}. 99 | - Let {base} be the result of {Evaluate(baseNode, scope)}. 100 | - If {base} is {null}: 101 | - Return {false}. 102 | - Otherwise: 103 | - Return {true}. 104 | 105 | global_defined_validate(args): 106 | 107 | - If the length of {args} is not 1: 108 | - Report an error. 109 | 110 | ### global::length() 111 | 112 | The length function returns the length of a string or an array. 113 | 114 | global_length(args, scope): 115 | 116 | - Let {baseNode} be the first element of {args}. 117 | - Let {base} be the result of {Evaluate(baseNode, scope)}. 118 | - If {base} is a string: 119 | - Return the length of {base}. 120 | - If {base} is an array: 121 | - Return the length of {base}. 122 | - Return {null}. 123 | 124 | global_length_validate(args): 125 | 126 | - If the length of {args} is not 1: 127 | - Report an error. 128 | 129 | ### global::now() 130 | 131 | The now function returns the current point in time as a string. 132 | 133 | Note: This function returns a string due to backwards compatibility. 134 | It's recommended to use `dateTime::now()` instead which returns a proper datetime. 135 | 136 | global_now(args, scope): 137 | 138 | - Let {ts} be a datetime representing the current point in time. 139 | - Let {result} be a [RFC 3339](https://tools.ietf.org/html/rfc3339) string formatting of {ts}. 140 | - Return {result}. 141 | 142 | global_now_validate(args): 143 | 144 | - If the length of {args} is not 0: 145 | - Report an error. 146 | 147 | ### global::references() 148 | 149 | The references function implicitly takes this value of the current scope and recursively checks whether it contains any references to the given document ID. 150 | 151 | global_references(args, scope): 152 | 153 | - Let {pathSet} be an empty array. 154 | - For each {arg} of {args}: 155 | - Let {path} be the result of {Evaluate(arg, scope)}. 156 | - If {path} is a string: 157 | - Append {path} to {pathSet}. 158 | - If {path} is an array: 159 | - Concatenate all strings of {path} to {pathSet}. 160 | - If {pathSet} is empty: 161 | - Return {false}. 162 | - Let {base} be the this value of {scope}. 163 | - Return the result of {HasReferenceTo(base, pathSet)}. 164 | 165 | HasReferenceTo(base, pathSet): 166 | 167 | - If {base} is an array: 168 | - For each {value} in {base}: 169 | - Let {result} be the result of {HasReferenceTo(value, pathSet)}. 170 | - If {result} is {true}: 171 | - Return {true}. 172 | - Return {false}. 173 | - If {base} is an object: 174 | - If {base} has an attribute `_ref`: 175 | - Let {ref} be the value of the attribute `_ref` in {base}. 176 | - If {ref} exists in {pathSet}: 177 | - Return {true}. 178 | - Otherwise: 179 | - Return {false}. 180 | - For each {key} and {value} in {base}: 181 | - Let {result} be the result of {HasReferenceTo(value, pathSet)}. 182 | - If {result} is {true}: 183 | - Return {true}. 184 | - Return {false}. 185 | 186 | global_references_validate(args): 187 | 188 | - If the length of {args} is 0: 189 | - Report an error. 190 | 191 | ### global::round() 192 | 193 | The round function accepts a number and rounds it to a certain precision. 194 | 195 | global_round(args, scope): 196 | 197 | - Let {numNode} be the first element of {args}. 198 | - Let {num} be the result of {Evaluate(numNode, scope)}. 199 | - If {num} is not a number: 200 | - Return {null}. 201 | - If the length of {args} is 2: 202 | - Let {precNode} be the second element of {args}. 203 | - Let {prec} be the result of {Evaluate(precNode, scope)}. 204 | - If {prec} is not a number: 205 | - Return {null}. 206 | - Otherwise: 207 | - Let {prec} be 0. 208 | - Return {num} rounded to {prec} number of digits after the decimal point. 209 | 210 | global_round_validate(args): 211 | 212 | - If the length of {args} is less than 1 or greater than 2: 213 | - Report an error. 214 | 215 | ### global::select() 216 | 217 | The select function chooses takes a variable number of arguments that are either pairs or any other type and iterates over them. When encountering a pair whose left-hand value evaluates to {true}, the right-hand value is returned immediately. When encountering a non-pair argument, that argument is returned immediately. Falls back to returning {null}. 218 | 219 | global_select(args, scope): 220 | 221 | - For each {arg} in {args}: 222 | - If {arg} is a {Pair}: 223 | - Let {condNode} be the first {Expression} of the {Pair}. 224 | - Let {resultNode} be the second {Expression} of the {Pair}. 225 | - Let {cond} be the result of {Evaluate(condNode, scope)}. 226 | - If {cond} is {true}: 227 | - Return the result of {Evaluate(resultNode, scope)}. 228 | - Otherwise: 229 | - Return the result of {Evaluate(arg, scope)}. 230 | 231 | global_select_validate(args): 232 | 233 | - Let {seenDefault} be {false}. 234 | - For each {arg} in {args}: 235 | - If {seenDefault} is {true}: 236 | - Report an error. 237 | - If {arg} is not a {Pair}: 238 | - Set {seenDefault} to {true}. 239 | 240 | ### global::string() 241 | 242 | The string function returns the string representation of scalar values or {null} for any other values. 243 | 244 | global_string(args, scope): 245 | 246 | - Let {node} be the first element of {args}. 247 | - Let {val} be the result of {Evaluate(node, scope)}. 248 | - If {val} is {true}: 249 | - Return the string `"true"`. 250 | - If {val} is {false}: 251 | - Return the string `"false"`. 252 | - If {val} is a string: 253 | - Return {val}. 254 | - If {val} is a number: 255 | - Return a string representation of the number. 256 | - If {val} is a datetime: 257 | - Return the datetime in the [RFC 3339](https://tools.ietf.org/html/rfc3339) timestamp format with a Z suffix. 258 | - Otherwise: 259 | - Return {null}. 260 | 261 | global_string_validate(args): 262 | 263 | - If the length of {args} is not 1: 264 | - Report an error. 265 | 266 | ### global::boost() 267 | 268 | The `boost` function accepts an expression and a boost value, and increases or decreases the score computed by `score()` (see ["Pipe functions"](#sec-Pipe-functions)) accordingly. `boost` can only be used within the argument list to `score()`. 269 | 270 | ```example 271 | * | score(boost(title matches "milk", 5.0), body matches "milk") 272 | ``` 273 | 274 | The expression must be a predicate expressions that evaluates to a single boolean value. Any other result value is ignored. 275 | 276 | The value argument must be a number >= 0. 277 | 278 | The return value is the same as the input predicate. Internally, the scoring execution model uses the provided boost value to increase the computed score if the predicate matches. 279 | 280 | boost(args, scope): 281 | 282 | - Let {predicateNode} be the first element of {args}. 283 | - Let {result} be the result of {Evaluate(predicateNode, scope)}. 284 | - Let {numNode} be the second element of {args}. 285 | - Let {num} be the result of {Evaluate(numNode, scope)}. 286 | - If {num} is not a number: 287 | - Return {null}. 288 | - If {num} is negative: 289 | - Return {null}. 290 | - Return {result}. 291 | 292 | boost_validate(args): 293 | 294 | - If the length of {args} is not 2: 295 | - Report an error. 296 | 297 | ### global::lower() 298 | 299 | The lower function returns lowercased string. 300 | 301 | global_lower(args, scope): 302 | 303 | - Let {value} be the result of {Evaluate(arg, scope)}. 304 | - If {value} is not {null}: 305 | - Return lowercase form of {value}. 306 | - Return {null}. 307 | 308 | global_lower_validate(args): 309 | 310 | - If the length of {args} is not 1: 311 | - Report an error. 312 | 313 | ### global::upper() 314 | 315 | The upper function returns uppercased string. 316 | 317 | global_upper(args, scope): 318 | 319 | - Let {value} be the result of {Evaluate(arg, scope)}. 320 | - If {value} is not {null}: 321 | - Return uppercase form of {value}. 322 | - Return {null}. 323 | 324 | global_upper_validate(args): 325 | 326 | - If the length of {args} is not 1: 327 | - Report an error. 328 | 329 | In addition to the functions mentioned above, constructors for [extensions](#sec-Extensions) are global as well. 330 | 331 | ## Date/time namespace 332 | 333 | The `dateTime` namespace contains functions to work with datetimes. 334 | 335 | ### dateTime::now() 336 | 337 | The now function in the `dateTime` namespace returns the current point in time as a datetime. 338 | 339 | dateTime_now(args, scope): 340 | 341 | - Let {result} be a datetime representing the current point in time. 342 | - Return {result}. 343 | 344 | dateTime_now_validate(args): 345 | 346 | - If the length of {args} is not 0: 347 | - Report an error. 348 | 349 | ## Diff namespace 350 | 351 | The diff namespace contains functionality for comparing objects. 352 | 353 | ### diff::changedAny() 354 | 355 | The `changedAny` function in the `diff` namespace returns a boolean if any of the key paths matched by the selector are changed. 356 | 357 | diff_changedAny(args, scope): 358 | 359 | - Let {lhs} be the first element of {args}. 360 | - Let {rhs} be the second element of {args}. 361 | - Let {selector} be the third element of {args}. 362 | - Let {before} be the result of {Evaluate(lhs, scope)}. 363 | - Let {after} be the result of {Evaluate(rhs, scope)}. 364 | - Let {selectedKeyPaths} be the result of {EvaluateSelector(selector, before, scope)}. 365 | - Let {diffKeyPaths} be the list of key paths that are different in {before} and {after}. 366 | - If {diffKeyPaths} overlaps with {selectedKeyPaths}: 367 | - Return {true}. 368 | - Otherwise: 369 | - Return {false}. 370 | 371 | diff_changedAny_validate(args): 372 | 373 | - If the length of {args} is not 3: 374 | - Report an error. 375 | - If the third element is not a {Selector}: 376 | - Report an error. 377 | 378 | ### diff::changedOnly() 379 | 380 | The `changedOnly` function in the `diff` namespace returns a boolean if given two nodes only the given key paths matched by the selector are changed. 381 | 382 | diff_changedOnly(args, scope): 383 | 384 | - Let {lhs} be the first element of {args}. 385 | - Let {rhs} be the second element of {args}. 386 | - Let {selector} be the third element of {args}. 387 | - Let {before} be the result of {Evaluate(lhs, scope)}. 388 | - Let {after} be the result of {Evaluate(rhs, scope)}. 389 | - Let {selectedKeyPaths} be the result of {EvaluateSelector(selector, before, scope)}. 390 | - Let {diffKeyPaths} be the list of key paths that are different in {before} and {after}. 391 | - If {diffKeyPaths} is a subset of {selectedKeyPaths}: 392 | - Return {true}. 393 | - Otherwise: 394 | - Return {false}. 395 | 396 | diff_changedOnly_validate(args): 397 | 398 | - If the length of {args} is not 3: 399 | - Report an error. 400 | - If the third element is not a {Selector}: 401 | - Report an error. 402 | 403 | ## Delta namespace 404 | 405 | The `delta` namespace contains functions which are valid in delta mode. 406 | 407 | ### delta::changedAny 408 | 409 | `delta::changedAny` is a variant of `diff::changedAny` which works on the before/after objects. 410 | 411 | delta_changedAny(args, scope): 412 | 413 | - Let {before} and {after} be the before/after objects of the query context to {scope}. 414 | - Let {selector} by the first element of {args}. 415 | - Let {result} be the result of {diff_changedAny(before, after, selector)}. 416 | - Return {result}. 417 | 418 | delta_changedAny_validate(args, scope): 419 | 420 | - If the mode of the query context of {scope} is not "delta": 421 | - Report an error. 422 | - If the first element is not a {Selector}: 423 | - Report an error. 424 | 425 | ### delta::changedOnly 426 | 427 | `delta::changedOnly` is a variant of `diff::changedOnly` which works on the before/after objects. 428 | 429 | delta_changedOnly(args, scope): 430 | 431 | - Let {before} and {after} be the before/after objects of the query context to {scope}. 432 | - Let {selector} by the first element of {args}. 433 | - Let {result} be the result of {diff_changedOnly(before, after, selector)}. 434 | - Return {result}. 435 | 436 | delta_changedOnly_validate(args, scope): 437 | 438 | - If the mode of the query context of {scope} is not "delta": 439 | - Report an error. 440 | - If the first element is not a {Selector}: 441 | - Report an error. 442 | 443 | ### delta::operation() 444 | 445 | The operation function returns the current operation ({"create"}, {"update"}, {"delete"}) of a change in delta mode. 446 | 447 | delta_operation(args, scope): 448 | 449 | - Let {before} and {after} be the before/after objects of the query context to {scope}. 450 | - If {before} is {null}: 451 | - Return {"create"}. 452 | - If {after} is {null}: 453 | - Return {"delete"}. 454 | - Return {"update"}. 455 | 456 | delta_operation_validate(args): 457 | 458 | - If the length of {args} is not 0: 459 | - Report an error. 460 | 461 | ## Array namespace 462 | 463 | The `array` namespace contains functions to work with arrays. 464 | 465 | ### array::join() 466 | 467 | The `join` function concatenates together the elements of an array into a single output string. Only primitive values supported by `global::string()` can be collected. Objects, arrays, etc. are considered composite values that cannot be joined. 468 | 469 | array_join(args, scope): 470 | 471 | - Let {arrNode} be the first element of {args}. 472 | - Let {sepNode} be the second element of {args}. 473 | - Let {arr} be the result of {Evaluate(arrNode, scope)}. 474 | - Let {sep} be the result of {Evaluate(sepNode, scope)}. 475 | - If {arr} is not an array: 476 | - Return {null}. 477 | - If {sep} is not a string: 478 | - Return {null}. 479 | - Let {output} be an empty string. 480 | - For each element in {arr}: 481 | - Let {elem} be the element. 482 | - Let {index} be the index of the element. 483 | - If {index} is greater than or equal to 1, append {sep} to {output}. 484 | - Let {str} be the result of evaluating `global::string(elem)`. 485 | - If {str} is {null}: 486 | - Return {null}. 487 | - Otherwise: 488 | - Append {str} to {output}. 489 | - Return {output}. 490 | 491 | array_join_validate(args): 492 | 493 | - If the length of {args} is not 2: 494 | - Report an error. 495 | 496 | ### array::compact() 497 | 498 | The `compact` function filters null values from an array. 499 | 500 | array_compact(args, scope): 501 | 502 | - Let {arrNode} be the first element of {args}. 503 | - Let {arr} be the result of {Evaluate(arrNode, scope)}. 504 | - If {arr} is not an array: 505 | - Return null 506 | - Let {output} be an empty array. 507 | - For each element in {arr}: 508 | - Let {elem} be the element 509 | - If {elem} is not null: 510 | - Append {elem} to {output}. 511 | - Return {output}. 512 | 513 | array_compact_validate(args): 514 | 515 | - If the length of {args} is not 1: 516 | - Report an error. 517 | 518 | ### array::unique() 519 | 520 | The `unique` function filters duplicate values from an array. 521 | 522 | Only values that can be compared for [equality](#sec-Equality) are compared for uniqueness. 523 | All other values are considered individually unique. For example, `array::unique([[1], [1]])` should 524 | return `[[1], [1]]`. 525 | 526 | The algorithm below specifies a linear search, but since an implementation can choose to use 527 | hashing or a similar non-order-preserving data structure for efficiency, the order of the output 528 | cannot be guaranteed to be the same as the input. 529 | 530 | array_unique(args, scope): 531 | 532 | - Let {arrNode} be the first element of {args}. 533 | - Let {arr} be the result of {Evaluate(arrNode, scope)}. 534 | - If {arr} is not an array: 535 | - Return {null}. 536 | - Let {output} be an empty array. 537 | - For each element in {arr}: 538 | - Let {elem} be the element 539 | - Let {found} be false. 540 | - If {elem} is comparable (see above): 541 | - For each element in {arr}: 542 | - Let {b} be the element. 543 | - Set {found} be the result of `Equal(elem, b)` 544 | - If {found} is true: 545 | - Break loop 546 | - If {found} is false: 547 | - Add {elem} to {output} at any position. 548 | - Return {output}. 549 | 550 | array_unique_validate(args): 551 | 552 | - If the length of {args} is not 1: 553 | - Report an error. 554 | 555 | ### array::intersects() 556 | 557 | The `intersects` function compares two arrays, returning `true` if they have any elements in common. 558 | 559 | Only values that can be compared for [equality](#sec-Equality) are considered when determining whether there are common values. 560 | 561 | array_intersects(args, scope): 562 | 563 | - Let {firstNode} be the first element of {args}. 564 | - Let {first} be the result of {Evaluate(firstNode, scope)}. 565 | - If {first} is not an array: 566 | - Return {null}. 567 | - Let {secondNode} be the first element of {args}. 568 | - Let {second} be the result of {Evaluate(secondNode, scope)}. 569 | - If {second} is not an array: 570 | - Return {null}. 571 | - For each element in {first}: 572 | - Let {a} be the element. 573 | - For each element in {second}: 574 | - Let {b} be the element. 575 | - Set {equal} to be the result of `Equal(a, b)`. 576 | - If {equal} is true: 577 | - Return {true}. 578 | - Return {false}. 579 | 580 | array_intersects_validate(args): 581 | 582 | - If the length of {args} is not 2: 583 | - Report an error. 584 | 585 | ## String namespace 586 | 587 | The `string` namespace contains functions to work with strings. 588 | 589 | ### string::split() 590 | 591 | The `split` function splits a string into multiple strings, given a separator string. 592 | 593 | string_split(args, scope): 594 | 595 | - Let {strNode} be the first element of {args}. 596 | - Let {sepNode} be the second element of {args}. 597 | - Let {str} be the result of {Evaluate(strNode, scope)}. 598 | - If {str} is not a string, return {null}. 599 | - Let {sep} be the result of {Evaluate(sepNode, scope)}. 600 | - If {sep} is not a string, return {null}. 601 | - Let {output} be an empty array. 602 | - If {sep} is an empty string: 603 | - Let {output} be each character of {str}, according to Unicode character splitting rules. 604 | - Otherwise: 605 | - Let {output} be each substring of {str} as separated by {sep}. An empty string is considered a substring, and will be included when {sep} is present at the beginning, the end, or consecutively of {str}. For example, the string `,a,b,` when split by `,` will result in four substrings `['', 'a', 'b', '']`. 606 | - Return {output}. 607 | 608 | string_split_validate(args): 609 | 610 | - If the length of {args} is not 2: 611 | - Report an error. 612 | 613 | ### string::startsWith() 614 | 615 | The `startsWith` function evaluates whether a string starts with a given prefix. 616 | 617 | string_startsWith(args, scope): 618 | 619 | - Let {strNode} be the first element of {args}. 620 | - Let {prefixNode} be the second element of {args}. 621 | - Let {str} be the result of {Evaluate(strNode, scope)}. 622 | - If {str} is not a string, return {null}. 623 | - Let {prefix} be the result of {Evaluate(sepNode, scope)}. 624 | - If {prefix} is not a string, return {null}. 625 | - Let {n} be the length of {prefix}. 626 | - If {n} is zero: 627 | - Return true. 628 | - If the first {n} characters of {str} equal {prefix}: 629 | - Return true. 630 | - Otherwise return false. 631 | 632 | string_startsWith_validate(args): 633 | 634 | - If the length of {args} is not 2: 635 | - Report an error. 636 | 637 | ## Math namespace 638 | 639 | The `math` namespace contains functions for performing mathematical operations. 640 | 641 | ### math::sum() 642 | 643 | The `sum` function computes the sum of all numbers in an array. {null} values are 644 | ignored, but non-numbers cause the function to return {null}. If the array does not contain at least 645 | one numeric value, it returns 0. 646 | 647 | math_sum(args, scope): 648 | 649 | - Let {arrNode} be the first element of {args}. 650 | - Let {arr} be the result of {Evaluate(arrNode, scope)}. 651 | - If {arr} is not an array, return {null}. 652 | - Let {n} be zero. 653 | - For each element {elem} in {arr}: 654 | - If {elem} is null: 655 | - Ignore it. 656 | - If {elem} is not a number: 657 | - Return {null}. 658 | - Otherwise: 659 | - Add {elem} to {n}. 660 | - Return {n}. 661 | 662 | math_sum_validate(args): 663 | 664 | - If the length of {args} is not 1: 665 | - Report an error. 666 | 667 | ### math::avg() 668 | 669 | The `avg` function computes the arithmetic mean of all numbers in an array. {null} values are 670 | ignored, but non-numbers cause the function to return {null}. If the array does not contain at least 671 | one numeric value, it returns {null}. 672 | 673 | math_avg(args, scope): 674 | 675 | - Let {arrNode} be the first element of {args}. 676 | - Let {arr} be the result of {Evaluate(arrNode, scope)}. 677 | - If {arr} is not an array, return {null}. 678 | - Let {n} be zero. 679 | - Let {count} be zero. 680 | - For each element {elem} in {arr}: 681 | - If {elem} is null: 682 | - Ignore it. 683 | - If {elem} is not a number: 684 | - Return {null}. 685 | - Otherwise: 686 | - Increment {count}. 687 | - Add {elem} to {n}. 688 | - If {count} is zero: 689 | - Return {null}. 690 | - Return {n} divided by the {count}. 691 | 692 | math_avg_validate(args): 693 | 694 | - If the length of {args} is not 1: 695 | - Report an error. 696 | 697 | ### math::min() 698 | 699 | The `min` function finds the smallest numeric value in an array. {null} values are 700 | ignored, but non-numbers cause the function to return {null}. If the array does not contain at least 701 | one numeric value, it returns {null}. 702 | 703 | math_min(args, scope): 704 | 705 | - Let {arrNode} be the first element of {args}. 706 | - Let {arr} be the result of {Evaluate(arrNode, scope)}. 707 | - If {arr} is not an array, return {null}. 708 | - Let {min} be {null}. 709 | - For each element {elem} in {arr}: 710 | - If {elem} is null: 711 | - Ignore it. 712 | - If {elem} is not a number: 713 | - Return {null}. 714 | - Otherwise: 715 | - If {min} is {null} or {PartialCompare(elem, min)} is {Lower}: 716 | - Set {min} to {elem}. 717 | - Return {min}. 718 | 719 | math_min_validate(args): 720 | 721 | - If the length of {args} is not 1: 722 | - Report an error. 723 | 724 | ### math::max() 725 | 726 | The `max` function finds the largest numeric value in an array. {null} values are 727 | ignored, but non-numbers cause the function to return {null}. If the array does not contain at least 728 | one numeric value, it returns {null}. 729 | 730 | math_max(args, scope): 731 | 732 | - Let {arrNode} be the first element of {args}. 733 | - Let {arr} be the result of {Evaluate(arrNode, scope)}. 734 | - If {arr} is not an array, return {null}. 735 | - Let {max} be {null}. 736 | - For each element {elem} in {arr}: 737 | - If {elem} is null: 738 | - Ignore it. 739 | - If {elem} is not a number: 740 | - Return {null}. 741 | - Otherwise: 742 | - If {max} is {null} or {PartialCompare(elem, max)} is {Greater}: 743 | - Set {max} to {elem}. 744 | - Return {max}. 745 | 746 | math_max_validate(args): 747 | 748 | - If the length of {args} is not 1: 749 | - Report an error. 750 | -------------------------------------------------------------------------------- /spec/12-custom-functions.md: -------------------------------------------------------------------------------- 1 | # Custom Functions 2 | 3 | Custom functions are reusable sets of query substructures that allow for the modular composition of GROQ queries. 4 | 5 | ## Function Definition 6 | 7 | Functions MUST be defined in the beginning of a query using the delimiter `;`. 8 | A function definition MUST start with the keyword `fn`. 9 | All custom defined functions MUST live in a namespace. 10 | 11 | FuncDecl : `fn` FuncNamespace FuncIdentifier FuncParams `=` FuncBody `;` 12 | 13 | ## Function invocation 14 | 15 | FuncParams : `(` Param `)` 16 | -------------------------------------------------------------------------------- /spec/12-pipe-functions.md: -------------------------------------------------------------------------------- 1 | # Pipe functions 2 | 3 | Pipe functions provide additional functionality to GROQ queries. They are invoked through a [Pipe function call expression](#sec-Pipe-function-call-expression). They differ from regular functions in that they always accept an array as input and returns another array (or {null}). As such, the syntax is optimized for chaining (the array it works on comes on the left-hand side instead of being an argument): 4 | 5 | ```example 6 | *[_type == "person"] | order(name) | {age} 7 | ``` 8 | 9 | Note that function arguments are not evaluated eagerly, and it's up to the function to decide which scope the arguments are evaluated in. All definitions below take an array of nodes. 10 | 11 | An implementation may provide additional pipe functions, but should be aware that this can cause problems when interoperating with future versions of GROQ. 12 | 13 | ## global::order() 14 | 15 | The order function sorts an array based on arbitrary expressions. 16 | 17 | order(base, args, scope): 18 | 19 | - Let {cmp} be a function which takes two arguments and returns either {Less}, {Equal} or {Greater}. 20 | - Define {cmp(left, right)} as follows: 21 | - Let {leftScope} be the result of {NewNestedScope(left, scope)}. 22 | - Let {rightScope} be the result of {NewNestedScope(right, scope)}. 23 | - For each {argNode} of {args}: 24 | - Let {direction} be {Normal}. 25 | - Let {valueNode} be {argNode}. 26 | - If {valueNode} is an {Asc} operator: \* Set {valueNode} to be the Expression of the {Asc} operator. 27 | - Else if {valueNode} is a {Desc} operator: \* Set direction to {Reverse}. 28 | - Set {valueNode} to be the Expression of the {Desc} operator. 29 | - Let {leftValue} be the result of {Evaluate(valueNode, leftScope)}. 30 | - Let {rightValue} be the result of {Evaluate(valueNode, rightScope)}. 31 | - Let {order} be the result of {TotalCompare(leftValue, rightValue)}. 32 | - If {direction} is {Reverse} and {order} is {Less}: \* Set {order} to {Greater}. 33 | - Else if {direction} is {Reverse} and {order} is {Greater}: \* Set {order} to {Less}. 34 | - If {order} is not {Equal}: \* Return {order}. 35 | - Return {Equal}. 36 | - Return a sorted array using {cmp} as the comparator function. 37 | 38 | order_validate(args): 39 | 40 | - If the length of {args} is 0: 41 | - Report an error. 42 | 43 | ## global::score() 44 | 45 | The `score` function assigns a score to an array of results, based on one or more scoring expressions. The `score` function may only be used as a pipe function. 46 | 47 | ```example 48 | *[_type == "listing"] | score(body match "jacuzzi") 49 | ``` 50 | 51 | In this query, anything where `body match "jacuzzi"` returns true will be scored higher than other results. Multiple expressions can be used: 52 | 53 | ```example 54 | *[_type == "listing"] | score(body match "jacuzzi", bedrooms > 2, available && !inContract) 55 | ``` 56 | 57 | When multiple expressions are provided, the scores are merged into a single score for each result (see [score evaluation](#sec-Score-evaluation)) 58 | 59 | Only predicate expressions — that is, expressions that evaluate to a single boolean value or to `null` — may be used, including `boost()`. However, an implementation can put further constraints on which expressions are permitted as a score expression for optimization purposes. 60 | 61 | Each score is assigned to the result as the new attribute `_score`, set to a positive number. 62 | 63 | Scoring is additive. That is `* | score(a == 1) | score(b == 2)` is equivalent to `* | score(a == 1, b == 2)`. 64 | 65 | score(base, args, scope): 66 | 67 | - Let {baseNode} be the {Expression}. 68 | - Let {base} be the result of {Evaluate(baseNode, scope)}. 69 | - If {base} is an array: 70 | - Let {result} be an empty {Array}. 71 | - For each {element} of {base}: 72 | - If {element} is an object: 73 | - Let {elementScope} be the result of {NewNestedScope(element, scope)}. 74 | - Let {newElement} be a new empty {Object}. 75 | - Add the attributes from {element} to it. 76 | - If {element} already has a `_score`: 77 | - Let {scoreSum} be the current value of `_score`. 78 | - Otherwise let {scoreSum} be 1.0. 79 | - For each {predicateNode} of {args}: 80 | - Let {scoreValue} be the result of {EvaluateScore(predicateNode, elementScope)}. 81 | - Add {scoreValue} to {scoreSum}. 82 | - Add the attribute `_score` set to {scoreSum}. 83 | - Add {newElement} to {result}. 84 | - Otherwise add {element} to {result}. 85 | - Return {result} sorted by the score, in descending order. 86 | - Return {null}. 87 | 88 | EvaluateScore(expr, scope): 89 | 90 | - Let {evaluator} be the score evaluator of {expr}. 91 | - Return the result of {evaluator(scope)}. 92 | 93 | score_validate(args): 94 | 95 | - If the length of {args} is 0: 96 | - Report an error. 97 | -------------------------------------------------------------------------------- /spec/13-vendor-functions.md: -------------------------------------------------------------------------------- 1 | # Vendor functions 2 | 3 | An implementation is free to introduce additional functions than what is presented in this specification, but this is problematic if a function with the same name is introduced in a future version. The following section defines optional _vendor functions_ which are guaranteed to never be a regular function in a future specification. There's also a short description of each vendor function so different implementations can attempt to be compatible with each other. The description is intentionally brief and it's up to the vendor to define it completely. 4 | 5 | ## global::identity() 6 | 7 | The identity function should accept zero arguments and return a string which represents the identity of the client executing the query. 8 | 9 | ## global::path() 10 | 11 | The path function should accept a single argument and return a path object. 12 | -------------------------------------------------------------------------------- /spec/14-extensions.md: -------------------------------------------------------------------------------- 1 | # Extensions 2 | 3 | Extensions are the capabilities which extend GROQ queries beyond basic Spec. These capabilities can include function namespaces, functions and operators. However, extensions can not introduce a new syntax. 4 | 5 | ## Portable Text Extension 6 | 7 | Functions available in Portable text extension are grouped under `pt` namespace except for the constructor which is global. 8 | 9 | ### pt type 10 | 11 | PT type represents an object following [portable text spec](https://github.com/portabletext/portabletext). 12 | 13 | ### global::pt() 14 | 15 | This function takes in an object or an array of objects, and returns a PT value. 16 | 17 | global_pt(args, scope): 18 | 19 | - Let {baseNode} be the first element of {args}. 20 | - Let {base} be the result of {Evaluate(baseNode, scope)}. 21 | - If {base} is an object: 22 | - Try to parse it as Portable Text Block: 23 | - If {base} is a valid Portable Text Block: 24 | - Return {base}. 25 | - If {base} is an array of objects: 26 | - Try to parse it as an array of Portable Text blocks: 27 | - If all elements in {base} array are valid Portable Text blocks: 28 | - Return {base}. 29 | - Otherwise: 30 | - Return {null}. 31 | 32 | global_pt_validate(args): 33 | 34 | - If the length of {args} is not 1: 35 | - Report an error. 36 | 37 | ### pt::text() 38 | 39 | This function takes in a PT value and returns a string versions of text. PT value which consists of more than one Portable text block has blocks appended with double newline character (`\n\n`) in the string version. 40 | 41 | pt_text(args, scope): 42 | 43 | - Let {baseNode} be the first element of {args}. 44 | - Let {base} be the result of {Evaluate(baseNode, scope)}. 45 | - If {base} is an object: 46 | - Try to parse it as Portable Text Block: 47 | - If {base }is a valid Portable Text Block: 48 | - Return string version of text in {base}. 49 | - If {base} is an array of objects: 50 | - Try to parse it as an array of Portable Text blocks: 51 | - If all elements in {base} array are valid Portable Text blocks: 52 | - Return string version of text in {base}. 53 | - Otherwise: 54 | - Return {null}. 55 | 56 | pt_text_validate(args): 57 | 58 | - If the length of {args} is not 1: 59 | - Report an error. 60 | 61 | ## Geography Extension 62 | 63 | Functions available in Geography extension are grouped under `geo` namespace except for the constructor which is global. 64 | 65 | ### geo type 66 | 67 | The geo type represents a geography and can contain points, lines, and polygons which can be expressed with a single latitude/longitude coordinate, or as a GeoJSON object. Concretely, an object is coerced to the geo type if: 68 | 69 | 1. If the object is coerced to a geographic point, that is it has a key `lat` for latitude and a key `lng` or `lon` for longitude (but not both). 70 | 2. If the object has [GeoJSON](https://tools.ietf.org/html/rfc7946) representation. 71 | 72 | Geo type supports following GeoJSON Geometry Objects: 73 | 74 | 1. `Position` 75 | 2. `Point` 76 | 3. `MultiPoint` 77 | 4. `LineString` 78 | 5. `MultiLineString` 79 | 6. `Polygon` 80 | 7. `MultiPolygon` 81 | 8. `GeometryCollection` 82 | 83 | And, it does not support: 84 | 85 | 1. GeoJSON Object `Feature` and `FeatureCollection`. 86 | 2. Arrays of geographic values. Instead, one of the GeoJSON `Multi` types should be used. 87 | 88 | ### global::geo() 89 | 90 | This function is a constructor for geographic value. It takes an object or another geo value, returning a geo value. 91 | 92 | global_geo(args, scope): 93 | 94 | - Let {baseNode} be the first element of {args}. 95 | - Let {base} be the result of {Evaluate(baseNode, scope)}. 96 | - If {base} is an object: 97 | - Try to parse it as Geo Point and GeoJSON: 98 | - If {base} is a valid geo value: 99 | - Return {base}. 100 | - If {base} is a geo value: 101 | - Return {base}. 102 | - Otherwise: 103 | - Return {null}. 104 | 105 | global_geo_validate(args): 106 | 107 | - If the length of {args} is not 1: 108 | - Report an error. 109 | 110 | ### geo::latLng() 111 | 112 | Takes latitude and longitude as arguments and returns a Geo Point. 113 | 114 | geo_latLng(args, scope): 115 | 116 | - Let {latNode} be the first element of {args}. 117 | - Let {lngNode} be the second element of {args}. 118 | - Let {lat} be the result of {Evaluate(latNode, scope)}. 119 | - Let {lng} be the result of {Evaluate(lngNode, scope)}. 120 | - If {lat} or {lng} is not a number: 121 | - Return {null}. 122 | - If {lat} is not in the range of -90 to 90: 123 | - Return {null}. 124 | - If {lng} is not in the range of -180 to 180: 125 | - Return {null}. 126 | - Otherwise: 127 | - Return a GeoJSON Point with {lat} and {lng} as coordinates, in lng, lat order. 128 | 129 | geo_latLng_validate(args): 130 | 131 | - If the length of {args} is not 2: 132 | - Report an error. 133 | 134 | ### geo::contains() 135 | 136 | Returns true if first geo argument completely contains the second one, using a planar (non-spherical) coordinate system. Both geo argument can be any geo value. A geo value is considered contained if all its points are within the boundaries of the first geo value. For `MultiPolygon`, it's sufficient that only one of the polygons contains the first geo value. 137 | 138 | geo_contains(args, scope): 139 | 140 | - Let {firstNode} be the first element of {args}. 141 | - Let {secondNode} be the second element of {args}. 142 | - Let {first} be the result of {Evaluate(firstNode, scope)}. 143 | - Let {second} be the result of {Evaluate(secondNode, scope)}. 144 | - If {first} or {second} is a not a geo value: 145 | - Return {null}. 146 | - If {first} completely contains {second}: 147 | - Return {true}. 148 | - Otherwise: 149 | - Return {false}. 150 | 151 | geo_contains_validate(args): 152 | 153 | - If the length of {args} is not 2: 154 | - Report an error. 155 | 156 | ### geo::intersects() 157 | 158 | This function takes two geo values, and returns true if they intersect in a planar (non-spherical) coordinate system. The arguments can be any geo values. A geo value intersects with another if it shares any geometric points with the second value; for example, a line crossing a polygon. 159 | 160 | geo_intersects(args, scope): 161 | 162 | - Let {firstNode} be the first element of {args}. 163 | - Let {secondNode} be the second element of {args}. 164 | - Let {first} be the result of {Evaluate(firstNode, scope)}. 165 | - Let {second} be the result of {Evaluate(secondNode, scope)}. 166 | - If {first} or {second} is a not a geo value: 167 | - Return {null}. 168 | - If {first} intersects {second}: 169 | - Return {true}. 170 | - Otherwise: 171 | - Return {false}. 172 | 173 | geo_intersects_validate(args): 174 | 175 | - If the length of {args} is not 2: 176 | - Report an error. 177 | 178 | ### geo::distance() 179 | 180 | This functions accepts two geo values, which must be point values, and returns the distance in meters. While exact algorithm is implementation-defined — for example, it may use the [Haversine formula](https://en.wikipedia.org/wiki/Haversine_formula) — it should use as close an approximation to a real Earth distance as possible. 181 | 182 | geo_distance(args, scope): 183 | 184 | - Let {firstNode} be the first element of {args}. 185 | - Let {secondNode} be the second element of {args}. 186 | - Let {first} be the result of {Evaluate(firstNode, scope)}. 187 | - Let {second} be the result of {Evaluate(secondNode, scope)}. 188 | - If {first} or {second} is a not a geo value: 189 | - Return {null}. 190 | - If {first} or {second} is a not a Geo Point or GeoJSON Point: 191 | - Return {null}. 192 | - Let {distance} be the geographic distance between {first} and {second}: 193 | - Return {distance}. 194 | - Otherwise: 195 | - Return {null}. 196 | 197 | geo_distance_validate(args): 198 | 199 | - If the length of {args} is not 2: 200 | - Report an error. 201 | -------------------------------------------------------------------------------- /spec/GROQ.md: -------------------------------------------------------------------------------- 1 | ## GROQ 2 | 3 | _Current Working Draft_ 4 | 5 | This is the specification for GROQ (**G**raph-**R**elational **O**bject **Q**ueries), a query language and execution engine made at Sanity, Inc, for filtering and projecting JSON documents. The work started in 2015. The development of this open standard started in 2019. 6 | 7 | GROQ is authored by [Alexander Staubo](https://twitter.com/purefiction) and [Simen Svale Skogsrud](https://twitter.com/svale). 8 | Additional follow up work by [Erik Grinaker](https://twitter.com/erikgrinaker), [Magnus Holm](https://twitter.com/judofyr), [Radhe](https://github.com/j33ty), [Israel Roldan](https://github.com/israelroldan), [Sindre Gulseth](https://github.com/sgulseth), [Matt Craig](https://github.com/codebymatt), [Espen Hovlandsdal](https://github.com/rexxars), [Tonina Zhelyazkova](https://github.com/tzhelyazkova). 9 | 10 | This specification should be considered _work in progress_ until the first release. 11 | 12 | **Copyright notice** 13 | 14 | Copyright © 2015–present, Sanity, Inc. 15 | 16 | As of July 9, 2010, the following persons or entities have made this Specification available under the Open Web Foundation Final Specification Agreement (OWFa 1.0), which is available at [openwebfoundation.org](http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0). 17 | 18 | - Sanity, Inc. 19 | 20 | You can review the signed copies of the Open Web Foundation Final Specification Agreement Version 1.0 for this specification at [github.com/sanity-io/GROQ](https://github.com/sanity-io/GROQ), which may also include additional parties to those listed above. 21 | 22 | Your use of this Specification may be subject to other third party rights. THIS SPECIFICATION IS PROVIDED “AS IS.” The contributors expressly disclaim any warranties (express, implied, or otherwise), including implied warranties of merchantability, non‐infringement, fitness for a particular purpose, or title, related to the Specification. The entire risk as to implementing or otherwise using the Specification is assumed by the Specification implementer and user. IN NO EVENT WILL ANY PARTY BE LIABLE TO ANY OTHER PARTY FOR LOST PROFITS OR ANY FORM OF INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER FROM ANY CAUSES OF ACTION OF ANY KIND WITH RESPECT TO THIS SPECIFICATION OR ITS GOVERNING AGREEMENT, WHETHER BASED ON BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), OR OTHERWISE, AND WHETHER OR NOT THE OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | **Conformance** 25 | 26 | A conforming implementation of GROQ must fulfill all normative requirements. Conformance requirements are described in this document via both descriptive assertions and key words with clearly defined meanings. 27 | 28 | The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative portions of this document are to be interpreted as described in [IETF RFC 2119](https://tools.ietf.org/html/rfc2119). These key words may appear in lowercase and still retain their meaning unless explicitly declared as non‐normative. 29 | 30 | A conforming implementation of GROQ may provide additional functionality, but must not where explicitly disallowed or would otherwise result in non‐conformance. 31 | 32 | **Conforming Algorithms** 33 | 34 | Algorithm steps phrased in imperative grammar (e.g. “Return the result”) are to be interpreted with the same level of requirement as the algorithm it is contained within. Any algorithm referenced within an algorithm step (e.g. “Let completedResult be the result of calling CompleteValue()”) is to be interpreted as having at least the same level of requirement as the algorithm containing that step. 35 | 36 | Conformance requirements expressed as algorithms can be fulfilled by an implementation of this specification in any way as long as the perceived result is equivalent. Algorithms described in this document are written to be easy to understand. Implementers are encouraged to include equivalent but optimized implementations. 37 | 38 | # [Overview](01-overview.md) 39 | 40 | # [Syntax](02-syntax.md) 41 | 42 | # [Execution](03-execution.md) 43 | 44 | # [Data types](04-data-types.md) 45 | 46 | # [Equality and comparison](05-equality-comparison.md) 47 | 48 | # [Simple expressions](06-simple-expressions.md) 49 | 50 | # [Compound expressions](07-compound-expressions.md) 51 | 52 | # [Operators](08-traversal-operators.md) 53 | 54 | # [Operators](09-operators.md) 55 | 56 | # [Precedence and associativity](10-precedence-associativity.md) 57 | 58 | # [Functions](11-functions.md) 59 | 60 | # [Custom functions](12-custom-functions.md) 61 | 62 | # [Pipe functions](12-pipe-functions.md) 63 | 64 | # [Vendor functions](13-vendor-functions.md) 65 | 66 | # [Extensions](14-extensions.md) 67 | --------------------------------------------------------------------------------