1# JQL 2 3EJDB query language (JQL) syntax inspired by ideas behind XPath and Unix shell pipes. 4It designed for easy querying and updating sets of JSON documents. 5 6## JQL grammar 7 8JQL parser created created by 9[peg/leg — recursive-descent parser generators for C](http://piumarta.com/software/peg/) Here is the formal parser grammar: https://github.com/Softmotions/ejdb/blob/master/src/jql/jqp.leg 10 11## Non formal JQL grammar adapted for brief overview 12 13Notation used below is based on SQL syntax description: 14 15Rule | Description 16--- | --- 17`' '` | String in single quotes denotes unquoted string literal as part of query. 18<code>{ a | b }</code> | Curly brackets enclose two or more required alternative choices, separated by vertical bars. 19<code>[ ]</code> | Square brackets indicate an optional element or clause. Multiple elements or clauses are separated by vertical bars. 20<code>|</code> | Vertical bars separate two or more alternative syntax elements. 21<code>...</code> | Ellipses indicate that the preceding element can be repeated. The repetition is unlimited unless otherwise indicated. 22<code>( )</code> | Parentheses are grouping symbols. 23Unquoted word in lower case| Denotes semantic of some query part. For example: `placeholder_name` - name of any placeholder. 24``` 25QUERY = FILTERS [ '|' APPLY ] [ '|' PROJECTIONS ] [ '|' OPTS ]; 26 27STR = { quoted_string | unquoted_string }; 28 29JSONVAL = json_value; 30 31PLACEHOLDER = { ':'placeholder_name | '?' } 32 33FILTERS = FILTER [{ and | or } [ not ] FILTER]; 34 35 FILTER = [@collection_name]/NODE[/NODE]...; 36 37 NODE = { '*' | '**' | NODE_EXPRESSION | STR }; 38 39 NODE_EXPRESSION = '[' NODE_EXPR_LEFT OP NODE_EXPR_RIGHT ']' 40 [{ and | or } [ not ] NODE_EXPRESSION]...; 41 42 OP = [ '!' ] { '=' | '>=' | '<=' | '>' | '<' | ~ } 43 | [ '!' ] { 'eq' | 'gte' | 'lte' | 'gt' | 'lt' } 44 | [ not ] { 'in' | 'ni' | 're' }; 45 46 NODE_EXPR_LEFT = { '*' | '**' | STR | NODE_KEY_EXPR }; 47 48 NODE_KEY_EXPR = '[' '*' OP NODE_EXPR_RIGHT ']' 49 50 NODE_EXPR_RIGHT = JSONVAL | STR | PLACEHOLDER 51 52APPLY = { 'apply' | 'upsert' } { PLACEHOLDER | json_object | json_array } | 'del' 53 54OPTS = { 'skip' n | 'limit' n | 'count' | 'noidx' | 'inverse' | ORDERBY }... 55 56 ORDERBY = { 'asc' | 'desc' } PLACEHOLDER | json_path 57 58PROJECTIONS = PROJECTION [ {'+' | '-'} PROJECTION ] 59 60 PROJECTION = 'all' | json_path 61 62``` 63 64* `json_value`: Any valid JSON value: object, array, string, bool, number. 65* `json_path`: Simplified JSON pointer. Eg.: `/foo/bar` or `/foo/"bar with spaces"/` 66* `*` in context of `NODE`: Any JSON object key name at particular nesting level. 67* `**` in context of `NODE`: Any JSON object key name at arbitrary nesting level. 68* `*` in context of `NODE_EXPR_LEFT`: Key name at specific level. 69* `**` in context of `NODE_EXPR_LEFT`: Nested array value of array element under specific key. 70 71## JQL quick introduction 72 73Lets play with some very basic data and queries. 74For simplicity we will use ejdb websocket network API which provides us a kind of interactive CLI. The same job can be done using pure `C` API too (`ejdb2.h jql.h`). 75 76NOTE: Take a look into [JQL test cases](https://github.com/Softmotions/ejdb/blob/master/src/jql/tests/jql_test1.c) for more examples. 77 78```json 79{ 80 "firstName": "John", 81 "lastName": "Doe", 82 "age": 28, 83 "pets": [ 84 {"name": "Rexy rex", "kind": "dog", "likes": ["bones", "jumping", "toys"]}, 85 {"name": "Grenny", "kind": "parrot", "likes": ["green color", "night", "toys"]} 86 ] 87} 88``` 89Save json as `sample.json` then upload it the `family` collection: 90 91```sh 92# Start HTTP/WS server protected by some access token 93./jbs -a 'myaccess01' 948 Mar 16:15:58.601 INFO: HTTP/WS endpoint at localhost:9191 95``` 96 97Server can be accessed using HTTP or Websocket endpoint. [More info](https://github.com/Softmotions/ejdb/blob/master/src/jbr/README.md) 98 99```sh 100curl -d '@sample.json' -H'X-Access-Token:myaccess01' -X POST http://localhost:9191/family 101``` 102 103We can play around using interactive [wscat](https://www.npmjs.com/package/@softmotions/wscat) websocket client. 104 105```sh 106wscat -H 'X-Access-Token:myaccess01' -c http://localhost:9191 107connected (press CTRL+C to quit) 108> k info 109< k { 110 "version": "2.0.0", 111 "file": "db.jb", 112 "size": 8192, 113 "collections": [ 114 { 115 "name": "family", 116 "dbid": 3, 117 "rnum": 1, 118 "indexes": [] 119 } 120 ] 121} 122 123> k get family 1 124< k 1 { 125 "firstName": "John", 126 "lastName": "Doe", 127 "age": 28, 128 "pets": [ 129 { 130 "name": "Rexy rex", 131 "kind": "dog", 132 "likes": [ 133 "bones", 134 "jumping", 135 "toys" 136 ] 137 }, 138 { 139 "name": "Grenny", 140 "kind": "parrot", 141 "likes": [ 142 "green color", 143 "night", 144 "toys" 145 ] 146 } 147 ] 148} 149``` 150 151Note about the `k` prefix before every command; It is an arbitrary key chosen by client and designated to identify particular 152websocket request, this key will be returned with response to request and allows client to 153identify that response for his particular request. [More info](https://github.com/Softmotions/ejdb/blob/master/src/jbr/README.md) 154 155Query command over websocket has the following format: 156 157``` 158<key> query <collection> <query> 159``` 160 161So we will consider only `<query>` part in this document. 162 163### Get all elements in collection 164``` 165k query family /* 166``` 167or 168``` 169k query family /** 170``` 171or specify collection name in query explicitly 172``` 173k @family/* 174``` 175 176We can execute query by HTTP `POST` request 177``` 178curl --data-raw '@family/[firstName = John]' -H'X-Access-Token:myaccess01' -X POST http://localhost:9191 179 1801 {"firstName":"John","lastName":"Doe","age":28,"pets":[{"name":"Rexy rex","kind":"dog","likes":["bones","jumping","toys"]},{"name":"Grenny","kind":"parrot","likes":["green color","night","toys"]}]} 181``` 182 183### Set the maximum number of elements in result set 184 185``` 186k @family/* | limit 10 187``` 188 189### Get documents where specified json path exists 190 191Element at index `1` exists in `likes` array within a `pets` sub-object 192``` 193> k query family /pets/*/likes/1 194< k 1 {"firstName":"John"... 195``` 196 197Element at index `1` exists in `likes` array at any `likes` nesting level 198``` 199> k query family /**/likes/1 200< k 1 {"firstName":"John"... 201``` 202 203**From this point and below I will omit websocket specific prefix `k query family` and 204consider only JQL queries.** 205 206 207### Get documents by primary key 208 209In order to get documents by primary key the following options are available: 210 2111. Use API call `ejdb_get()` 212 ```ts 213 const doc = await db.get('users', 112); 214 ``` 215 2161. Use the special query construction: `/=:?` or `@collection/=:?` 217 218Get document from `users` collection with primary key `112` 219``` 220> k @users/=112 221``` 222 223Update tags array for document in `jobs` collection (TypeScript): 224```ts 225 await db.createQuery('@jobs/ = :? | apply :? | count') 226 .setNumber(0, id) 227 .setJSON(1, { tags }) 228 .completionPromise(); 229``` 230 231Array of primary keys can also be used for matching: 232 233```ts 234 await db.createQuery('@jobs/ = :?| apply :? | count') 235 .setJSON(0, [23, 1, 2]) 236 .setJSON(1, { tags }) 237 .completionPromise(); 238``` 239 240### Matching JSON entry values 241 242Below is a set of self explaining queries: 243 244``` 245/pets/*/[name = "Rexy rex"] 246 247/pets/*/[name eq "Rexy rex"] 248 249/pets/*/[name = "Rexy rex" or name = Grenny] 250``` 251Note about quotes around words with spaces. 252 253Get all documents where owner `age` greater than `20` and have some pet who like `bones` or `toys` 254``` 255/[age > 20] and /pets/*/likes/[** in ["bones", "toys"]] 256``` 257Here `**` denotes some element in `likes` array. 258 259`ni` is the inverse operator to `in`. 260Get documents where `bones` somewhere in `likes` array. 261``` 262/pets/*/[likes ni "bones"] 263``` 264 265We can create more complicated filters 266``` 267( /[age <= 20] or /[lastName re "Do.*"] ) 268 and /pets/*/likes/[** in ["bones", "toys"]] 269``` 270Note about grouping parentheses and regular expression matching using `re` operator. 271 272`~` is a prefix matching operator (Since ejdb `v2.0.53`). 273Prefix matching can benefit from using indexes. 274 275Get documents where `/lastName` starts with `"Do"`. 276``` 277/[lastName ~ Do] 278``` 279 280### Arrays and maps can be matched as is 281 282Filter documents with `likes` array exactly matched to `["bones","jumping","toys"]` 283``` 284/**/[likes = ["bones","jumping","toys"]] 285``` 286Matching algorithms for arrays and maps are different: 287 288* Array elements are matched from start to end. In equal arrays 289 all values at the same index should be equal. 290* Object maps matching consists of the following steps: 291 * Lexicographically sort object keys in both maps. 292 * Do matching keys and its values starting from the lowest key. 293 * If all corresponding keys and values in one map are fully matched to ones in other 294 and vice versa, maps considered to be equal. 295 For example: `{"f":"d","e":"j"}` and `{"e":"j","f":"d"}` are equal maps. 296 297### Conditions on key names 298 299Find JSON document having `firstName` key at root level. 300``` 301/[* = "firstName"] 302``` 303I this context `*` denotes a key name. 304 305You can use conditions on key name and key value at the same time: 306``` 307/[[* = "firstName"] = John] 308``` 309 310Key name can be either `firstName` or `lastName` but should have `John` value in any case. 311``` 312/[[* in ["firstName", "lastName"]] = John] 313``` 314 315It may be useful in queries with dynamic placeholders (C API): 316``` 317/[[* = :keyName] = :keyValue] 318``` 319 320## JQL data modification 321 322`APPLY` section responsible for modification of documents content. 323 324``` 325APPLY = ({'apply' | `upsert`} { PLACEHOLDER | json_object | json_array }) | 'del' 326``` 327 328JSON patch specs conformed to `rfc7386` or `rfc6902` specifications followed after `apply` keyword. 329 330Let's add `address` object to all matched document 331``` 332/[firstName = John] | apply {"address":{"city":"New York", "street":""}} 333``` 334 335If JSON object is an argument of `apply` section it will be treated as merge match (`rfc7386`) otherwise 336it should be array which denotes `rfc6902` JSON patch. Placeholders also supported by `apply` section. 337``` 338/* | apply :? 339``` 340 341Set the street name in `address` 342``` 343/[firstName = John] | apply [{"op":"replace", "path":"/address/street", "value":"Fifth Avenue"}] 344``` 345 346Add `Neo` fish to the set of John's `pets` 347``` 348/[firstName = John] 349| apply [{"op":"add", "path":"/pets/-", "value": {"name":"Neo", "kind":"fish"}}] 350``` 351 352`upsert` updates existing document by given json argument used as merge patch 353 or inserts provided json argument as new document instance. 354 355``` 356/[firstName = John] | upsert {"firstName": "John", "address":{"city":"New York"}} 357``` 358 359### Non standard JSON patch extensions 360 361#### increment 362 363Increments numeric value identified by JSON path by specified value. 364 365Example: 366``` 367 Document: {"foo": 1} 368 Patch: [{"op": "increment", "path": "/foo", "value": 2}] 369 Result: {"foo": 3} 370``` 371#### add_create 372 373Same as JSON patch `add` but creates intermediate object nodes for missing JSON path segments. 374 375Example: 376``` 377Document: {"foo": {"bar": 1}} 378Patch: [{"op": "add_create", "path": "/foo/zaz/gaz", "value": 22}] 379Result: {"foo":{"bar":1,"zaz":{"gaz":22}}} 380``` 381 382Example: 383``` 384Document: {"foo": {"bar": 1}} 385Patch: [{"op": "add_create", "path": "/foo/bar/gaz", "value": 22}] 386Result: Error since element pointed by /foo/bar is not an object 387``` 388 389#### swap 390 391Swaps two values of JSON document starting from `from` path. 392 393Swapping rules 394 3951. If value pointed by `from` not exists error will be raised. 3961. If value pointed by `path` not exists it will be set by value from `from` path, 397 then object pointed by `from` path will be removed. 3981. If both values pointed by `from` and `path` are presented they will be swapped. 399 400Example: 401 402``` 403Document: {"foo": ["bar"], "baz": {"gaz": 11}} 404Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/gaz"}] 405Result: {"foo": [11], "baz": {"gaz": "bar"}} 406``` 407 408Example (Demo of rule 2): 409 410``` 411Document: {"foo": ["bar"], "baz": {"gaz": 11}} 412Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/zaz"}] 413Result: {"foo":[],"baz":{"gaz":11,"zaz":"bar"}} 414``` 415 416### Removing documents 417 418Use `del` keyword to remove matched elements from collection: 419``` 420/FILTERS | del 421``` 422 423Example: 424``` 425> k add family {"firstName":"Jack"} 426< k 2 427> k query family /[firstName re "Ja.*"] 428< k 2 {"firstName":"Jack"} 429 430# Remove selected elements from collection 431> k query family /[firstName=Jack] | del 432< k 2 {"firstName":"Jack"} 433``` 434 435## JQL projections 436 437``` 438PROJECTIONS = PROJECTION [ {'+' | '-'} PROJECTION ] 439 440 PROJECTION = 'all' | json_path | join_clause 441``` 442 443Projection allows to get only subset of JSON document excluding not needed data. 444 445Lets add one more document to our collection: 446 447```sh 448$ cat << EOF | curl -d @- -H'X-Access-Token:myaccess01' -X POST http://localhost:9191/family 449{ 450"firstName":"Jack", 451"lastName":"Parker", 452"age":35, 453"pets":[{"name":"Sonic", "kind":"mouse", "likes":[]}] 454} 455EOF 456``` 457Now query only pet owners firstName and lastName from collection. 458 459``` 460> k query family /* | /{firstName,lastName} 461 462< k 3 {"firstName":"Jack","lastName":"Parker"} 463< k 1 {"firstName":"John","lastName":"Doe"} 464< k 465``` 466 467Add `pets` array for every document 468``` 469> k query family /* | /{firstName,lastName} + /pets 470 471< k 3 {"firstName":"Jack","lastName":"Parker","pets":[... 472< k 1 {"firstName":"John","lastName":"Doe","pets":[... 473``` 474 475Exclude only `pets` field from documents 476``` 477> k query family /* | all - /pets 478 479< k 3 {"firstName":"Jack","lastName":"Parker","age":35} 480< k 1 {"firstName":"John","lastName":"Doe","age":28,"address":{"city":"New York","street":"Fifth Avenue"}} 481< k 482``` 483Here `all` keyword used denoting whole document. 484 485Get `age` and the first pet in `pets` array. 486``` 487> k query family /[age > 20] | /age + /pets/0 488 489< k 3 {"age":35,"pets":[{"name":"Sonic","kind":"mouse","likes":[]}]} 490< k 1 {"age":28,"pets":[{"name":"Rexy rex","kind":"dog","likes":["bones","jumping","toys"]}]} 491< k 492``` 493 494## JQL collection joins 495 496Join materializes reference to document to a real document objects which will replace reference inplace. 497 498Documents are joined by their primary keys only. 499 500Reference keys should be stored in referrer document as number or string field. 501 502Joins can be specified as part of projection expression 503in the following form: 504 505``` 506/.../field<collection 507``` 508Where 509 510* `field` ‐ JSON field contains primary key of joined document. 511* `<` ‐ The special mark symbol which instructs EJDB engine to replace `field` key by body of joined document. 512* `collection` ‐ name of DB collection where joined documents located. 513 514A referrer document will be untouched if associated document is not found. 515 516Here is the simple demonstration of collection joins in our interactive websocket shell: 517 518``` 519> k add artists {"name":"Leonardo Da Vinci", "years":[1452,1519]} 520< k 1 521> k add paintings {"name":"Mona Lisa", "year":1490, "origin":"Italy", "artist": 1} 522< k 1 523> k add paintings {"name":"Madonna Litta - Madonna And The Child", "year":1490, "origin":"Italy", "artist": 1} 524< k 2 525 526# Lists paintings documents 527 528> k @paintings/* 529< k 2 {"name":"Madonna Litta - Madonna And The Child","year":1490,"origin":"Italy","artist":1} 530< k 1 {"name":"Mona Lisa","year":1490,"origin":"Italy","artist":1} 531< k 532> 533 534# Do simple join with artists collection 535 536> k @paintings/* | /artist<artists 537< k 2 {"name":"Madonna Litta - Madonna And The Child","year":1490,"origin":"Italy", 538 "artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}} 539 540< k 1 {"name":"Mona Lisa","year":1490,"origin":"Italy", 541 "artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}} 542< k 543 544 545# Strip all document fields except `name` and `artist` join 546 547> k @paintings/* | /artist<artists + /name + /artist/* 548< k 2 {"name":"Madonna Litta - Madonna And The Child","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}} 549< k 1 {"name":"Mona Lisa","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}} 550< k 551> 552 553# Same results as above: 554 555> k @paintings/* | /{name, artist<artists} + /artist/* 556< k 2 {"name":"Madonna Litta - Madonna And The Child","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}} 557< k 1 {"name":"Mona Lisa","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}} 558< k 559 560``` 561 562Invalid references: 563 564``` 565> k add paintings {"name":"Mona Lisa2", "year":1490, "origin":"Italy", "artist": 9999} 566< k 3 567> k @paintings/* | /artist<artists 568< k 3 {"name":"Mona Lisa2","year":1490,"origin":"Italy","artist":9999} 569< k 2 {"name":"Madonna Litta - Madonna And The Child","year":1490,"origin":"Italy","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}} 570< k 1 {"name":"Mona Lisa","year":1490,"origin":"Italy","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}} 571 572``` 573 574## JQL results ordering 575 576``` 577 ORDERBY = ({ 'asc' | 'desc' } PLACEHOLDER | json_path)... 578``` 579 580Lets add one more document then sort documents in collection according to `firstName` ascending and `age` descending order. 581 582``` 583> k add family {"firstName":"John", "lastName":"Ryan", "age":39} 584< k 4 585``` 586 587``` 588> k query family /* | /{firstName,lastName,age} | asc /firstName desc /age 589< k 3 {"firstName":"Jack","lastName":"Parker","age":35} 590< k 4 {"firstName":"John","lastName":"Ryan","age":39} 591< k 1 {"firstName":"John","lastName":"Doe","age":28} 592< k 593``` 594 595`asc, desc` instructions may use indexes defined for collection to avoid a separate documents sorting stage. 596 597## JQL Options 598 599``` 600OPTS = { 'skip' n | 'limit' n | 'count' | 'noidx' | 'inverse' | ORDERBY }... 601``` 602 603* `skip n` Skip first `n` records before first element in result set 604* `limit n` Set max number of documents in result set 605* `count` Returns only `count` of matched documents 606 ``` 607 > k query family /* | count 608 < k 3 609 < k 610 ``` 611* `noidx` Do not use any indexes for query execution. 612* `inverse` By default query scans documents from most recently added to older ones. 613 This option inverts scan direction to opposite and activates `noidx` mode. 614 Has no effect if query has `asc/desc` sorting clauses. 615 616## JQL Indexes and performance tips 617 618Database index can be build for any JSON field path containing values of number or string type. 619Index can be an `unique` ‐ not allowing value duplication and `non unique`. 620The following index mode bit mask flags are used (defined in `ejdb2.h`): 621 622Index mode | Description 623--- | --- 624<code>0x01 EJDB_IDX_UNIQUE</code> | Index is unique 625<code>0x04 EJDB_IDX_STR</code> | Index for JSON `string` field value type 626<code>0x08 EJDB_IDX_I64</code> | Index for `8 bytes width` signed integer field values 627<code>0x10 EJDB_IDX_F64</code> | Index for `8 bytes width` signed floating point field values. 628 629For example unique index of string type will be specified by `EJDB_IDX_UNIQUE | EJDB_IDX_STR` = `0x05`. 630Index can be defined for only one value type located under specific path in json document. 631 632Lets define non unique string index for `/lastName` path: 633``` 634> k idx family 4 /lastName 635< k 636``` 637Index selection for queries based on set of heuristic rules. 638 639You can always check index usage by issuing `explain` command in WS API: 640``` 641> k explain family /[lastName=Doe] and /[age!=27] 642< k explain [INDEX] MATCHED STR|3 /lastName EXPR1: 'lastName = Doe' INIT: IWKV_CURSOR_EQ 643[INDEX] SELECTED STR|3 /lastName EXPR1: 'lastName = Doe' INIT: IWKV_CURSOR_EQ 644 [COLLECTOR] PLAIN 645``` 646 647The following statements are taken into account when using EJDB2 indexes: 648* Only one index can be used for particular query execution 649* If query consist of `or` joined part at top level or contains `negated` expressions at the top level 650 of query expression - indexes will not be in use at all. 651 So no indexes below: 652 ``` 653 /[lastName != Andy] 654 655 /[lastName = "John"] or /[lastName = Peter] 656 657 ``` 658 But will be used `/lastName` index defined above 659 ``` 660 /[lastName = Doe] 661 662 /[lastName = Doe] and /[age = 28] 663 664 /[lastName = Doe] and not /[age = 28] 665 666 /[lastName = Doe] and /[age != 28] 667 ``` 668* The following operators are supported by indexes (ejdb 2.0.x): 669 * `eq, =` 670 * `gt, >` 671 * `gte, >=` 672 * `lt, <` 673 * `lte, <=` 674 * `in` 675 * `~` (Prefix matching since ejdb 2.0.53) 676 677* `ORDERBY` clauses may use indexes to avoid result set sorting. 678* Array fields can also be indexed. Let's outline typical use case: indexing of some entity tags: 679 ``` 680 > k add books {"name":"Mastering Ultra", "tags":["ultra", "language", "bestseller"]} 681 < k 1 682 > k add books {"name":"Learn something in 24 hours", "tags":["bestseller"]} 683 < k 2 684 > k query books /* 685 < k 2 {"name":"Learn something in 24 hours","tags":["bestseller"]} 686 < k 1 {"name":"Mastering Ultra","tags":["ultra","language","bestseller"]} 687 < k 688 ``` 689 Create string index for `/tags` 690 ``` 691 > k idx books 4 /tags 692 < k 693 ``` 694 Filter books by `bestseller` tag and show index usage in query: 695 ``` 696 > k explain books /tags/[** in ["bestseller"]] 697 < k explain [INDEX] MATCHED STR|4 /tags EXPR1: '** in ["bestseller"]' INIT: IWKV_CURSOR_EQ 698 [INDEX] SELECTED STR|4 /tags EXPR1: '** in ["bestseller"]' INIT: IWKV_CURSOR_EQ 699 [COLLECTOR] PLAIN 700 701 < k 1 {"name":"Mastering Ultra","tags":["ultra","language","bestseller"]} 702 < k 2 {"name":"Learn something in 24 hours","tags":["bestseller"]} 703 < k 704 ``` 705 706### Performance tip: Physical ordering of documents 707 708All documents in collection are sorted by their primary key in `descending` order. 709So if you use auto generated keys (`ejdb_put_new`) you may be sure what documents fetched as result of 710full scan query will be ordered according to the time of insertion in descendant order, 711unless you don't use query sorting, indexes or `inverse` keyword. 712 713### Performance tip: Brute force scan vs indexed access 714 715In many cases, using index may drop down the overall query performance. 716Because index collection contains only document references (`id`) and engine may perform 717an addition document fetching by its primary key to finish query matching. 718So for not so large collections a brute scan may perform better than scan using indexes. 719However, exact matching operations: `eq`, `in` and `sorting` by natural index order 720will benefit from index in most cases. 721 722 723### Performance tip: Get rid of unnecessary document data 724 725If you'd like update some set of documents with `apply` or `del` operations 726but don't want fetching all of them as result of query - just add `count` 727modifier to the query to get rid of unnecessary data transferring and json data conversion. 728