json-fs-query 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -64,6 +64,10 @@ const [workouts, exercises] = await query("read workouts where date = '2024-06-0
64
64
 
65
65
  The `;` after the last (or only) query is optional. A `;` inside a string, a comment or brackets doesn't end a query.
66
66
 
67
+ Write the commands `read`, `insert`, `update` and `delete`, and the sort directions `asc` and `desc`, in lowercase:
68
+ `READ t` is an error, and `sort a ASC` sorts descending. Other keywords (`where`, `and`, `sort`, `limit`, `create`,
69
+ `show records`...) and comparators can be written in any case.
70
+
67
71
  ### Strings
68
72
 
69
73
  Strings use single or double quotes. A string closes only with the quote it opened with, so the other quote is just
@@ -137,16 +141,39 @@ string are just text.
137
141
  read workouts where date = '2024-06-02' /* the Sunday */;
138
142
  ```
139
143
 
140
- ### Several conditions
144
+ ### Where clauses: `and`, `or`, `not` and brackets
141
145
 
142
- Each condition starts with `where`; join them with `and where`:
146
+ A `where` clause is one condition, or several joined with `and` / `or`, each of which can be negated with `not`.
147
+ Brackets group them, nested to any depth:
143
148
 
144
149
  ```sql
145
- read workouts where reps > 8 and where date = '2024-06-02'
150
+ read workouts where date = '2024-06-02' and (exercise_id = 1 or exercise_id = 3)
151
+ read exercises where not (name like 'bench%' or name like 'squat%')
146
152
  ```
147
153
 
148
- `or where` is not supported yet, and is an error (in 1.x it was silently read as `and where`). And/or with grouping
149
- and precedence is planned.
154
+ `not` binds tightest, then `and`, then `or`, as in SQL. So `a or b and c` means `a or (b and c)`. Use brackets
155
+ for anything else:
156
+
157
+ | Where clause | Means |
158
+ |---|---|
159
+ | `where a = 1 or b = 2 and c = 3` | `a = 1 or (b = 2 and c = 3)` |
160
+ | `where (a = 1 or b = 2) and c = 3` | as written |
161
+ | `where not a = 1 and b = 2` | `(not a = 1) and b = 2` |
162
+ | `where not (a = 1 or b = 2)` | neither |
163
+
164
+ The comparators are `=`, `!=` (or `<>`), `>`, `>=`, `<`, `<=`, `in [...]`, `between [low, high]` and `like` (`%`
165
+ matches any text, `_` one character, ignoring case). `is null`, `is not null`, `exists` and `does not exist` test
166
+ for null and missing fields (see below). In a where clause, the keywords (`where`, `and`, `or`, `not`, `is null`...) and comparators (`IN`, `LIKE`) can be
167
+ written in any case (see the casing note under Writing queries).
168
+
169
+ The older form, a `where` per condition (`where a = 1 and where b = 2`, `or where`), still works and means the same.
170
+
171
+ Mistakes are errors that point at the place: an unclosed or unexpected bracket, an operator with nothing after it
172
+ (`where a = 1 and`), empty brackets, or two conditions with nothing between them (`where a = 1 b = 2`).
173
+
174
+ Indexes make a where clause faster but never change its result. An indexed condition only narrows which rows are
175
+ read, and the whole clause is then checked on each of them. That works through `and` and through an `or` whose
176
+ every branch is indexed. A `not` reads every row.
150
177
 
151
178
  ### Comparing by the field's type
152
179
 
@@ -174,6 +201,75 @@ then `null`, then the values (`asc`; `desc` is the reverse).
174
201
 
175
202
  In 2.0 and earlier, a `null` acted as `0` in comparisons and sorting (`where n < 5` matched it), and `= null` matched it.
176
203
 
204
+ Inside `and` / `or` / `not`, a comparison on a null or missing field is **unknown**: neither true nor false, as in
205
+ SQL. A row is returned only when the whole clause is true:
206
+
207
+ | | result |
208
+ |---|---|
209
+ | `not unknown` | unknown |
210
+ | `false and unknown` | false |
211
+ | `true and unknown` | unknown |
212
+ | `true or unknown` | true |
213
+ | `false or unknown` | unknown |
214
+
215
+ So `where not (n = 5)` returns the same rows as `where n != 5`: neither returns a row where `n` is null or missing.
216
+ To include them, ask for them: `where n != 5 or n is null`. The same goes for `in` with a `null` in the list:
217
+ `n in [5, null]` is `n = 5 or n = null`, so a row it doesn't match is unknown and `not (n in [5, null])` returns
218
+ nothing.
219
+
220
+ ### Joins: `on` and `merge`
221
+
222
+ `read workouts.exercises` reads workouts and attaches the matching exercises to each one as a list, using `on` (add
223
+ more with `and on` to go through linking records):
224
+
225
+ ```sql
226
+ read workouts.exercises on workouts.exercise_id = exercises._id
227
+ read workouts.muscles on workouts.exercise_id = exercises._id
228
+ and on exercises._id = exercise_muscle.exercise_id
229
+ and on exercise_muscle.muscle_id = muscles._id
230
+ ```
231
+
232
+ `merge` copies one matching record's fields onto each row instead, named `entity.field`:
233
+
234
+ ```sql
235
+ read workouts merge workouts.exercise_id with exercises._id
236
+ ```
237
+
238
+ A condition can use the fields of the record being read, the joined record and merged records, written
239
+ `entity.field`:
240
+
241
+ ```sql
242
+ read workouts.exercises on workouts.exercise_id = exercises._id
243
+ where date = '2024-06-05' and exercises.name like 'b%'
244
+ read workouts merge workouts.exercise_id with exercises._id where exercises.name = 'Squat'
245
+ ```
246
+
247
+ A condition on the joined record's fields works like SQL's inner join. The clause is checked for each pair of a row
248
+ and one of its joined rows. Each row keeps only the joined rows the clause is true for, and a row left with none
249
+ isn't returned. That includes a row with no joined rows at all, even when an `or` matches its own fields.
250
+ `read exercises.workouts on exercises._id = workouts.exercise_id where workouts.date = '2024-06-02'` returns each
251
+ exercise with only that day's workouts.
252
+
253
+ These are errors:
254
+ - a condition on a record that only links the join (`exercise_muscle` above)
255
+ - a condition on a record that isn't in the query
256
+ - in `update` and `delete`, any condition on another record's fields. They only change the record they name, and
257
+ check its own fields before any join runs.
258
+
259
+ ### Clause order
260
+
261
+ Clauses can come in any order after the record name, but the SQL order reads best and is recommended (a fixed order
262
+ may become required in a later version):
263
+
264
+ ```sql
265
+ read workouts.exercises select date, reps
266
+ on workouts.exercise_id = exercises._id
267
+ where reps > 8 and (exercises.name = 'Squat' or exercises.name = 'Deadlift')
268
+ group by date
269
+ sort date desc
270
+ limit 10 offset 0
271
+ ```
272
+
177
273
  ### Creating a record: `create`
178
274
 
179
275
  The query form of `create()`. Inside the brackets: the fields, then the keys, separated by commas. Everything is
@@ -256,12 +352,24 @@ queryStatus('read notes;') // { complete: true, waitingFor: ''
256
352
  - `null` is only accepted by fields with `nullable: true`, and is stored as a real `null`. Before, a `string` field
257
353
  stored the text `"null"` and an `int` field stored `0`.
258
354
  - `create()` throws for a default that doesn't fit its field's type, and for a nullable primary key field.
259
- - `or where` is an error until or is supported. Before, it was read as `and where`, returning the wrong rows.
355
+ - `or where` means `or` (2.3). In 1.x it was read as `and where`, returning the wrong rows, and in 2.0 to 2.2 it was an
356
+ error.
260
357
  - A select alias needs `as`: `select name as n`. Before, the first field took the next word as its alias, so
261
358
  `select name where a = 1` failed and `select name limit 1` lost its limit; `select name n` is now an error.
262
359
  - Sort fields need a comma between them: `sort a, b desc`. Before, the list ran on into the next clause, so
263
360
  `sort a limit 1` sorted by `a`, `limit` and `1` and lost the limit; `sort a b` is now an error.
264
361
 
362
+ ### Upgrading to 2.3
363
+
364
+ - `and`, `or`, `not` and brackets in a where clause (above). `or where` works again, now as `or`.
365
+ - A condition on a joined record's field works, as an inner join. Before, an unindexed one matched nothing, and an
366
+ indexed one (`exercises._id = 1`) returned every row, most of them with an empty list of joined rows. Only rows
367
+ with a matching joined row are returned now.
368
+ - A condition on the read record's own field written with its name (`workouts.date`) works. It matched nothing before.
369
+ - A condition on a record that isn't in the query, or only links a join, is an error. Before, it matched nothing or
370
+ failed to read the record.
371
+ - Comparators are case-insensitive (`IN`, `LIKE`). Before, only lowercase ones matched anything.
372
+
265
373
  ## Configuration
266
374
 
267
375
  `configure()` sets where this package's data lives and how strictly it enforces foreign keys - call it once,