• Home
Name Date Size #Lines LOC

..--

inc/12-May-2024-1,4421,352

tests/12-May-2024-434356

README.mdD12-May-202421.9 KiB730557

jql.cD12-May-202446.6 KiB1,8751,749

jql.hD12-May-20246.4 KiB17566

jql_internal.hD12-May-20241.3 KiB6748

jqp.cD12-May-2024104.6 KiB3,1703,124

jqp.hD12-May-20247.9 KiB314230

jqp.legD12-May-202410.5 KiB255176

README.md

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 &#124; 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>&#124;</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
445**Query placeholders API is supported in projections.**
446
447Lets add one more document to our collection:
448
449```sh
450$ cat << EOF | curl -d @- -H'X-Access-Token:myaccess01' -X POST http://localhost:9191/family
451{
452"firstName":"Jack",
453"lastName":"Parker",
454"age":35,
455"pets":[{"name":"Sonic", "kind":"mouse", "likes":[]}]
456}
457EOF
458```
459Now query only pet owners firstName and lastName from collection.
460
461```
462> k query family /* | /{firstName,lastName}
463
464< k     3       {"firstName":"Jack","lastName":"Parker"}
465< k     1       {"firstName":"John","lastName":"Doe"}
466< k
467```
468
469Add `pets` array for every document
470```
471> k query family /* | /{firstName,lastName} + /pets
472
473< k     3       {"firstName":"Jack","lastName":"Parker","pets":[...
474< k     1       {"firstName":"John","lastName":"Doe","pets":[...
475```
476
477Exclude only `pets` field from documents
478```
479> k query family /* | all - /pets
480
481< k     3       {"firstName":"Jack","lastName":"Parker","age":35}
482< k     1       {"firstName":"John","lastName":"Doe","age":28,"address":{"city":"New York","street":"Fifth Avenue"}}
483< k
484```
485Here `all` keyword used denoting whole document.
486
487Get `age` and the first pet in `pets` array.
488```
489> k query family /[age > 20] | /age + /pets/0
490
491< k     3       {"age":35,"pets":[{"name":"Sonic","kind":"mouse","likes":[]}]}
492< k     1       {"age":28,"pets":[{"name":"Rexy rex","kind":"dog","likes":["bones","jumping","toys"]}]}
493< k
494```
495
496## JQL collection joins
497
498Join materializes reference to document to a real document objects which will replace reference inplace.
499
500Documents are joined by their primary keys only.
501
502Reference keys should be stored in referrer document as number or string field.
503
504Joins can be specified as part of projection expression
505in the following form:
506
507```
508/.../field<collection
509```
510Where
511
512* `field` &dash; JSON field contains primary key of joined document.
513* `<` &dash; The special mark symbol which instructs EJDB engine to replace `field` key by body of joined document.
514* `collection` &dash; name of DB collection where joined documents located.
515
516A referrer document will be untouched if associated document is not found.
517
518Here is the simple demonstration of collection joins in our interactive websocket shell:
519
520```
521> k add artists {"name":"Leonardo Da Vinci", "years":[1452,1519]}
522< k     1
523> k add paintings {"name":"Mona Lisa", "year":1490, "origin":"Italy", "artist": 1}
524< k     1
525> k add paintings {"name":"Madonna Litta - Madonna And The Child", "year":1490, "origin":"Italy", "artist": 1}
526< k     2
527
528# Lists paintings documents
529
530> k @paintings/*
531< k     2       {"name":"Madonna Litta - Madonna And The Child","year":1490,"origin":"Italy","artist":1}
532< k     1       {"name":"Mona Lisa","year":1490,"origin":"Italy","artist":1}
533< k
534>
535
536# Do simple join with artists collection
537
538> k @paintings/* | /artist<artists
539< k     2       {"name":"Madonna Litta - Madonna And The Child","year":1490,"origin":"Italy",
540                  "artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}}
541
542< k     1       {"name":"Mona Lisa","year":1490,"origin":"Italy",
543                  "artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}}
544< k
545
546
547# Strip all document fields except `name` and `artist` join
548
549> k @paintings/* | /artist<artists + /name + /artist/*
550< k     2       {"name":"Madonna Litta - Madonna And The Child","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}}
551< k     1       {"name":"Mona Lisa","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}}
552< k
553>
554
555# Same results as above:
556
557> k @paintings/* | /{name, artist<artists} + /artist/*
558< k     2       {"name":"Madonna Litta - Madonna And The Child","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}}
559< k     1       {"name":"Mona Lisa","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}}
560< k
561
562```
563
564Invalid references:
565
566```
567>  k add paintings {"name":"Mona Lisa2", "year":1490, "origin":"Italy", "artist": 9999}
568< k     3
569> k @paintings/* |  /artist<artists
570< k     3       {"name":"Mona Lisa2","year":1490,"origin":"Italy","artist":9999}
571< k     2       {"name":"Madonna Litta - Madonna And The Child","year":1490,"origin":"Italy","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}}
572< k     1       {"name":"Mona Lisa","year":1490,"origin":"Italy","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}}
573
574```
575
576## JQL results ordering
577
578```
579  ORDERBY = ({ 'asc' | 'desc' } PLACEHOLDER | json_path)...
580```
581
582Lets add one more document then sort documents in collection according to `firstName` ascending and `age` descending order.
583
584```
585> k add family {"firstName":"John", "lastName":"Ryan", "age":39}
586< k     4
587```
588
589```
590> k query family /* | /{firstName,lastName,age} | asc /firstName desc /age
591< k     3       {"firstName":"Jack","lastName":"Parker","age":35}
592< k     4       {"firstName":"John","lastName":"Ryan","age":39}
593< k     1       {"firstName":"John","lastName":"Doe","age":28}
594< k
595```
596
597`asc, desc` instructions may use indexes defined for collection to avoid a separate documents sorting stage.
598
599## JQL Options
600
601```
602OPTS = { 'skip' n | 'limit' n | 'count' | 'noidx' | 'inverse' | ORDERBY }...
603```
604
605* `skip n` Skip first `n` records before first element in result set
606* `limit n` Set max number of documents in result set
607* `count` Returns only `count` of matched documents
608  ```
609  > k query family /* | count
610  < k     3
611  < k
612  ```
613* `noidx` Do not use any indexes for query execution.
614* `inverse` By default query scans documents from most recently added to older ones.
615   This option inverts scan direction to opposite and activates `noidx` mode.
616   Has no effect if query has `asc/desc` sorting clauses.
617
618## JQL Indexes and performance tips
619
620Database index can be build for any JSON field path containing values of number or string type.
621Index can be an `unique` &dash; not allowing value duplication and `non unique`.
622The following index mode bit mask flags are used (defined in `ejdb2.h`):
623
624Index mode | Description
625--- | ---
626<code>0x01 EJDB_IDX_UNIQUE</code> | Index is unique
627<code>0x04 EJDB_IDX_STR</code> | Index for JSON `string` field value type
628<code>0x08 EJDB_IDX_I64</code> | Index for `8 bytes width` signed integer field values
629<code>0x10 EJDB_IDX_F64</code> | Index for `8 bytes width` signed floating point field values.
630
631For example unique index of string type will be specified by `EJDB_IDX_UNIQUE | EJDB_IDX_STR` = `0x05`.
632Index can be defined for only one value type located under specific path in json document.
633
634Lets define non unique string index for `/lastName` path:
635```
636> k idx family 4 /lastName
637< k
638```
639Index selection for queries based on set of heuristic rules.
640
641You can always check index usage by issuing `explain` command in WS API:
642```
643> k explain family /[lastName=Doe] and /[age!=27]
644< k     explain [INDEX] MATCHED  STR|3 /lastName EXPR1: 'lastName = Doe' INIT: IWKV_CURSOR_EQ
645[INDEX] SELECTED STR|3 /lastName EXPR1: 'lastName = Doe' INIT: IWKV_CURSOR_EQ
646 [COLLECTOR] PLAIN
647```
648
649The following statements are taken into account when using EJDB2 indexes:
650* Only one index can be used for particular query execution
651* If query consist of `or` joined part at top level or contains `negated` expressions at the top level
652  of query expression - indexes will not be in use at all.
653  So no indexes below:
654  ```
655  /[lastName != Andy]
656
657  /[lastName = "John"] or /[lastName = Peter]
658
659  ```
660  But will be used `/lastName` index defined above
661  ```
662  /[lastName = Doe]
663
664  /[lastName = Doe] and /[age = 28]
665
666  /[lastName = Doe] and not /[age = 28]
667
668  /[lastName = Doe] and /[age != 28]
669  ```
670* The following operators are supported by indexes (ejdb 2.0.x):
671  * `eq, =`
672  * `gt, >`
673  * `gte, >=`
674  * `lt, <`
675  * `lte, <=`
676  * `in`
677  * `~` (Prefix matching since ejdb 2.0.53)
678
679* `ORDERBY` clauses may use indexes to avoid result set sorting.
680* Array fields can also be indexed. Let's outline typical use case: indexing of some entity tags:
681  ```
682  > k add books {"name":"Mastering Ultra", "tags":["ultra", "language", "bestseller"]}
683  < k     1
684  > k add books {"name":"Learn something in 24 hours", "tags":["bestseller"]}
685  < k     2
686  > k query books /*
687  < k     2       {"name":"Learn something in 24 hours","tags":["bestseller"]}
688  < k     1       {"name":"Mastering Ultra","tags":["ultra","language","bestseller"]}
689  < k
690  ```
691  Create string index for `/tags`
692  ```
693  > k idx books 4 /tags
694  < k
695  ```
696  Filter books by `bestseller` tag and show index usage in query:
697  ```
698  > k explain books /tags/[** in ["bestseller"]]
699  < k     explain [INDEX] MATCHED  STR|4 /tags EXPR1: '** in ["bestseller"]' INIT: IWKV_CURSOR_EQ
700  [INDEX] SELECTED STR|4 /tags EXPR1: '** in ["bestseller"]' INIT: IWKV_CURSOR_EQ
701  [COLLECTOR] PLAIN
702
703  < k     1       {"name":"Mastering Ultra","tags":["ultra","language","bestseller"]}
704  < k     2       {"name":"Learn something in 24 hours","tags":["bestseller"]}
705  < k
706  ```
707
708### Performance tip: Physical ordering of documents
709
710All documents in collection are sorted by their primary key in `descending` order.
711So if you use auto generated keys (`ejdb_put_new`) you may be sure what documents fetched as result of
712full scan query will be ordered according to the time of insertion in descendant order,
713unless you don't use query sorting, indexes or `inverse` keyword.
714
715### Performance tip: Brute force scan vs indexed access
716
717In many cases, using index may drop down the overall query performance.
718Because index collection contains only document references (`id`) and engine may perform
719an addition document fetching by its primary key to finish query matching.
720So for not so large collections a brute scan may perform better than scan using indexes.
721However, exact matching operations: `eq`, `in` and `sorting` by natural index order
722will benefit from index in most cases.
723
724
725### Performance tip: Get rid of unnecessary document data
726
727If you'd like update some set of documents with `apply` or `del` operations
728but don't want fetching all of them as result of query - just add `count`
729modifier to the query to get rid of unnecessary data transferring and json data conversion.
730