json-fs-query 2.1.1 → 2.2.1

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
@@ -148,6 +148,14 @@ read workouts where reps > 8 and where date = '2024-06-02'
148
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
149
  and precedence is planned.
150
150
 
151
+ ### Comparing by the field's type
152
+
153
+ Conditions compare by the field's declared type, whatever type the stored value or the literal has: in a `string`
154
+ or `int` field, `'3'` and `3` are equal. Ordering (`>`, `>=`, `<`, `<=`, `between`) and `sort` follow it too: a
155
+ `string` field orders as text, so `'10'` comes before `'9'` (even ids stored as numbers), and an `int` field as
156
+ numbers. A field with no declared type, or an `object` or `array` one, compares the stored values as they are.
157
+ `like` only matches values stored as text.
158
+
151
159
  ### Null and missing fields
152
160
 
153
161
  A field can be missing (left out) or hold `null`, and both are their own cases, as in SQL: they match no
@@ -1211,6 +1211,7 @@ Object.defineProperty(exports, "__esModule", {
1211
1211
  value: true
1212
1212
  });
1213
1213
  exports.sortBy = void 0;
1214
+ var _compareAs = require("../utilities/compareAs");
1214
1215
  // Where a value sorts before any other, ascending: missing first, then null, then the values (MySQL's null placement,
1215
1216
  // with missing below null). Descending reverses it.
1216
1217
  const presence = (data, property) => {
@@ -1219,6 +1220,12 @@ const presence = (data, property) => {
1219
1220
  }
1220
1221
  return data[property] === null ? 1 : 2;
1221
1222
  };
1223
+ /**
1224
+ * Sort by each clause in turn: missing, then null, then the values, compared by the field's declared type when the
1225
+ * clause has one (see compareAs), so a string field sorts as text ('10' before '9') and an int field as numbers.
1226
+ * @param dataSet
1227
+ * @param sortClauses
1228
+ */
1222
1229
  const sortBy = (dataSet = [], sortClauses = []) => {
1223
1230
  if (!sortClauses.length) {
1224
1231
  return dataSet;
@@ -1232,13 +1239,15 @@ const sortBy = (dataSet = [], sortClauses = []) => {
1232
1239
  if (rankA !== rankB) {
1233
1240
  return direction === 'asc' ? rankA - rankB : rankB - rankA;
1234
1241
  }
1235
- // Ordering compares the raw stored values, not by declared type (that's only for equality, see where), so
1236
- // numeric ids in a string field still sort numerically. Loose so '3' and 3 still tie, as do two nulls or two missing.
1242
+ // Values compare by the declared type, so numeric ids in a string field sort as text ('10' before '9'). Loose so
1243
+ // '3' and 3 still tie in an untyped field, as do two nulls or two missing.
1244
+ const valueA = (0, _compareAs.compareAs)(sortClause.type, a[property]);
1245
+ const valueB = (0, _compareAs.compareAs)(sortClause.type, b[property]);
1237
1246
  // eslint-disable-next-line eqeqeq
1238
- if (a[property] == b[property]) {
1247
+ if (valueA == valueB) {
1239
1248
  continue;
1240
1249
  }
1241
- const result = a[property] < b[property] ? -1 : 1;
1250
+ const result = valueA < valueB ? -1 : 1;
1242
1251
  return direction === 'asc' ? result : result * -1;
1243
1252
  }
1244
1253
  return 0;
@@ -1246,7 +1255,7 @@ const sortBy = (dataSet = [], sortClauses = []) => {
1246
1255
  return dataSet;
1247
1256
  };
1248
1257
  exports.sortBy = sortBy;
1249
- },{}],32:[function(require,module,exports){
1258
+ },{"../utilities/compareAs":34}],32:[function(require,module,exports){
1250
1259
  "use strict";
1251
1260
 
1252
1261
  Object.defineProperty(exports, "__esModule", {
@@ -1514,7 +1523,8 @@ const likeMatch = (dataValue, pattern) => {
1514
1523
  };
1515
1524
  /**
1516
1525
  * Equality (=, !=, <>, in) compares by the field's declared type when given one (see sameValue), so
1517
- * '3' and 3 match in a string or int field. Ordering (>, <, between, like) compares the raw values.
1526
+ * '3' and 3 match in a string or int field. Ordering (>, >=, <, <=, between) compares by it too (see compareAs): a
1527
+ * string field orders as text ('10' < '9'), an int field as numbers. like compares the raw values.
1518
1528
  *
1519
1529
  * Null and missing are their own cases, as in SQL: a field that is null or missing matches no comparison (not even
1520
1530
  * `!=`), and a null never equals anything, not even another null. Only these test for them:
@@ -1543,6 +1553,7 @@ const makeCondition = (data, property, comparator, value, type = undefined) => {
1543
1553
  return false;
1544
1554
  }
1545
1555
  const dataValue = data[property];
1556
+ const typedValue = (0, _compareAs.compareAs)(type, dataValue);
1546
1557
  switch (comparator) {
1547
1558
  case '=':
1548
1559
  return (0, _compareAs.sameValue)(type, dataValue, value);
@@ -1550,18 +1561,18 @@ const makeCondition = (data, property, comparator, value, type = undefined) => {
1550
1561
  case '<>':
1551
1562
  return !(0, _compareAs.sameValue)(type, dataValue, value);
1552
1563
  case '>':
1553
- return dataValue > value;
1564
+ return typedValue > (0, _compareAs.compareAs)(type, value);
1554
1565
  case '>=':
1555
- return dataValue >= value;
1566
+ return typedValue >= (0, _compareAs.compareAs)(type, value);
1556
1567
  case '<':
1557
- return dataValue < value;
1568
+ return typedValue < (0, _compareAs.compareAs)(type, value);
1558
1569
  case '<=':
1559
- return dataValue <= value;
1570
+ return typedValue <= (0, _compareAs.compareAs)(type, value);
1560
1571
  case 'in':
1561
1572
  return Array.isArray(value) && (type ? value.some(item => (0, _compareAs.sameValue)(type, dataValue, item)) : value.includes(dataValue));
1562
1573
  case 'between':
1563
1574
  // A null bound would act as 0 in >= / <=: it bounds nothing, so nothing matches (the parser refuses it)
1564
- return Array.isArray(value) && value.length === 2 && !value.includes(null) && dataValue >= value[0] && dataValue <= value[1];
1575
+ return Array.isArray(value) && value.length === 2 && !value.includes(null) && typedValue >= (0, _compareAs.compareAs)(type, value[0]) && typedValue <= (0, _compareAs.compareAs)(type, value[1]);
1565
1576
  case 'like':
1566
1577
  return likeMatch(dataValue, value);
1567
1578
  }
@@ -3810,6 +3821,7 @@ Object.defineProperty(exports, "__esModule", {
3810
3821
  });
3811
3822
  exports.runQuery = void 0;
3812
3823
  require("core-js/modules/esnext.iterator.constructor.js");
3824
+ require("core-js/modules/esnext.iterator.find.js");
3813
3825
  require("core-js/modules/esnext.iterator.reduce.js");
3814
3826
  require("core-js/modules/esnext.set.add-all.js");
3815
3827
  require("core-js/modules/esnext.set.delete-all.js");
@@ -3849,15 +3861,15 @@ var _isEntityProperty = require("./parsers/isEntityProperty");
3849
3861
  var _splitEntityProperty = require("./parsers/splitEntityProperty");
3850
3862
  var _fieldType = require("./fieldType");
3851
3863
  /**
3852
- * Give each condition its field's declared type, so where compares by it (a joined entity's field
3853
- * is looked up on that entity's record).
3864
+ * Give each condition and sort clause its field's declared type, so where and sortBy compare by it
3865
+ * (a joined entity's field is looked up on that entity's record).
3854
3866
  * @param parsed
3855
3867
  * @param recordData
3856
3868
  */
3857
- const typeConditions = async (parsed, recordData) => {
3858
- for (const condition of parsed.conditions) {
3869
+ const typeClauses = async (parsed, recordData) => {
3870
+ for (const clause of [...parsed.conditions, ...parsed.sortClauses]) {
3859
3871
  let entity = parsed.entity;
3860
- let property = condition.property;
3872
+ let property = clause.property;
3861
3873
  if ((0, _isEntityProperty.isEntityProperty)(property)) {
3862
3874
  ({
3863
3875
  entity,
@@ -3867,7 +3879,7 @@ const typeConditions = async (parsed, recordData) => {
3867
3879
  if (!Object.prototype.hasOwnProperty.call(recordData, entity)) {
3868
3880
  recordData[entity] = await (0, _retrieveRecord.retrieveRecord)(entity);
3869
3881
  }
3870
- condition.type = (0, _fieldType.fieldType)(recordData[entity], property);
3882
+ clause.type = (0, _fieldType.fieldType)(recordData[entity], property);
3871
3883
  }
3872
3884
  };
3873
3885
  const runQuery = async parsed => {
@@ -3890,13 +3902,23 @@ const runQuery = async parsed => {
3890
3902
  [parsed.entity]: await (0, _showRecords.showRecords)()
3891
3903
  };
3892
3904
  }
3905
+ // update/delete apply their conditions before any join or merge runs, so a condition on another entity's field
3906
+ // can't be checked there. An indexed one used to be dropped once its index had narrowed the joined rows, and the
3907
+ // command then hit every row the other conditions allowed (`delete workouts.exercises on ... where
3908
+ // exercises._id = 1` deleted every workout)
3909
+ if (parsed.command === 'update' || parsed.command === 'delete') {
3910
+ const other = parsed.conditions.find(condition => (0, _isEntityProperty.isEntityProperty)(condition.property) && (0, _splitEntityProperty.splitEntityProperty)(condition.property).entity !== parsed.entity);
3911
+ if (other) {
3912
+ throw new Error(`${parsed.command} can only use conditions on ${parsed.entity}'s own fields, not ${other.property}`);
3913
+ }
3914
+ }
3893
3915
  const recordData = {
3894
3916
  [parsed.entity]: await (0, _retrieveRecord.retrieveRecord)(parsed.entity)
3895
3917
  };
3896
3918
  if (typeof parsed.joinEntity !== 'undefined') {
3897
3919
  recordData[parsed.joinEntity] = await (0, _retrieveRecord.retrieveRecord)(parsed.joinEntity);
3898
3920
  }
3899
- await typeConditions(parsed, recordData);
3921
+ await typeClauses(parsed, recordData);
3900
3922
  let dataSet = {
3901
3923
  [parsed.entity]: []
3902
3924
  };
@@ -3956,7 +3978,7 @@ const runQuery = async parsed => {
3956
3978
  return dataSet;
3957
3979
  };
3958
3980
  exports.runQuery = runQuery;
3959
- },{"../queries/createRecord":15,"../queries/deleteEntity":16,"../queries/describeRecord":17,"../queries/dropRecord":18,"../queries/groupBy":19,"../queries/insertEntity":20,"../queries/joinEntity":21,"../queries/limitOffset":22,"../queries/mergeJoins":23,"../queries/readEntity":28,"../queries/selectFields":29,"../queries/showRecords":30,"../queries/sortBy":31,"../queries/updateEntity":32,"../queries/where":33,"./fieldType":39,"./getConditionIndexedList":40,"./getJoinIndexedList":41,"./parsers/isEntityProperty":58,"./parsers/splitEntityProperty":66,"./retrieveRecord":77,"core-js/modules/esnext.iterator.constructor.js":235,"core-js/modules/esnext.iterator.reduce.js":243,"core-js/modules/esnext.set.add-all.js":258,"core-js/modules/esnext.set.delete-all.js":259,"core-js/modules/esnext.set.difference.js":260,"core-js/modules/esnext.set.every.js":261,"core-js/modules/esnext.set.filter.js":262,"core-js/modules/esnext.set.find.js":263,"core-js/modules/esnext.set.intersection.js":264,"core-js/modules/esnext.set.is-disjoint-from.js":265,"core-js/modules/esnext.set.is-subset-of.js":266,"core-js/modules/esnext.set.is-superset-of.js":267,"core-js/modules/esnext.set.join.js":268,"core-js/modules/esnext.set.map.js":269,"core-js/modules/esnext.set.reduce.js":270,"core-js/modules/esnext.set.some.js":271,"core-js/modules/esnext.set.symmetric-difference.js":272,"core-js/modules/esnext.set.union.js":273}],80:[function(require,module,exports){
3981
+ },{"../queries/createRecord":15,"../queries/deleteEntity":16,"../queries/describeRecord":17,"../queries/dropRecord":18,"../queries/groupBy":19,"../queries/insertEntity":20,"../queries/joinEntity":21,"../queries/limitOffset":22,"../queries/mergeJoins":23,"../queries/readEntity":28,"../queries/selectFields":29,"../queries/showRecords":30,"../queries/sortBy":31,"../queries/updateEntity":32,"../queries/where":33,"./fieldType":39,"./getConditionIndexedList":40,"./getJoinIndexedList":41,"./parsers/isEntityProperty":58,"./parsers/splitEntityProperty":66,"./retrieveRecord":77,"core-js/modules/esnext.iterator.constructor.js":235,"core-js/modules/esnext.iterator.find.js":239,"core-js/modules/esnext.iterator.reduce.js":243,"core-js/modules/esnext.set.add-all.js":258,"core-js/modules/esnext.set.delete-all.js":259,"core-js/modules/esnext.set.difference.js":260,"core-js/modules/esnext.set.every.js":261,"core-js/modules/esnext.set.filter.js":262,"core-js/modules/esnext.set.find.js":263,"core-js/modules/esnext.set.intersection.js":264,"core-js/modules/esnext.set.is-disjoint-from.js":265,"core-js/modules/esnext.set.is-subset-of.js":266,"core-js/modules/esnext.set.is-superset-of.js":267,"core-js/modules/esnext.set.join.js":268,"core-js/modules/esnext.set.map.js":269,"core-js/modules/esnext.set.reduce.js":270,"core-js/modules/esnext.set.some.js":271,"core-js/modules/esnext.set.symmetric-difference.js":272,"core-js/modules/esnext.set.union.js":273}],80:[function(require,module,exports){
3960
3982
  "use strict";
3961
3983
 
3962
3984
  Object.defineProperty(exports, "__esModule", {