json-fs-query 2.3.0 → 3.0.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.
Files changed (58) hide show
  1. package/README.md +82 -9
  2. package/browser/jsonFsQuery.js +486 -209
  3. package/browser/jsonFsQuery.min.js +1 -1
  4. package/dist/data/queries/groupBy.d.ts +20 -1
  5. package/dist/data/queries/groupBy.js +71 -9
  6. package/dist/data/queries/groupBy.min.js +1 -1
  7. package/dist/data/queries/limitOffset.d.ts +1 -1
  8. package/dist/data/queries/limitOffset.js +1 -1
  9. package/dist/data/queries/selectFields.d.ts +6 -4
  10. package/dist/data/queries/selectFields.js +15 -10
  11. package/dist/data/queries/selectFields.min.js +1 -1
  12. package/dist/data/utilities/parsers/findCommand.d.ts +2 -1
  13. package/dist/data/utilities/parsers/findCommand.js +5 -5
  14. package/dist/data/utilities/parsers/findCommand.min.js +1 -1
  15. package/dist/data/utilities/parsers/findConditions.d.ts +8 -9
  16. package/dist/data/utilities/parsers/findConditions.js +29 -41
  17. package/dist/data/utilities/parsers/findConditions.min.js +1 -1
  18. package/dist/data/utilities/parsers/findGroupBy.d.ts +6 -4
  19. package/dist/data/utilities/parsers/findGroupBy.js +17 -9
  20. package/dist/data/utilities/parsers/findGroupBy.min.js +1 -1
  21. package/dist/data/utilities/parsers/findInsertValues.d.ts +4 -3
  22. package/dist/data/utilities/parsers/findInsertValues.js +9 -9
  23. package/dist/data/utilities/parsers/findInsertValues.min.js +1 -1
  24. package/dist/data/utilities/parsers/findJoinClauses.d.ts +4 -3
  25. package/dist/data/utilities/parsers/findJoinClauses.js +20 -13
  26. package/dist/data/utilities/parsers/findJoinClauses.min.js +1 -1
  27. package/dist/data/utilities/parsers/findJoinEntity.d.ts +1 -1
  28. package/dist/data/utilities/parsers/findJoinEntity.js +3 -5
  29. package/dist/data/utilities/parsers/findJoinEntity.min.js +1 -1
  30. package/dist/data/utilities/parsers/findLimit.d.ts +4 -3
  31. package/dist/data/utilities/parsers/findLimit.js +8 -8
  32. package/dist/data/utilities/parsers/findLimit.min.js +1 -1
  33. package/dist/data/utilities/parsers/findMergeJoins.d.ts +4 -3
  34. package/dist/data/utilities/parsers/findMergeJoins.js +19 -12
  35. package/dist/data/utilities/parsers/findMergeJoins.min.js +1 -1
  36. package/dist/data/utilities/parsers/findOffset.d.ts +4 -3
  37. package/dist/data/utilities/parsers/findOffset.js +8 -8
  38. package/dist/data/utilities/parsers/findOffset.min.js +1 -1
  39. package/dist/data/utilities/parsers/findSelects.d.ts +4 -3
  40. package/dist/data/utilities/parsers/findSelects.js +13 -14
  41. package/dist/data/utilities/parsers/findSelects.min.js +1 -1
  42. package/dist/data/utilities/parsers/findSortClauses.d.ts +4 -3
  43. package/dist/data/utilities/parsers/findSortClauses.js +12 -12
  44. package/dist/data/utilities/parsers/findSortClauses.min.js +1 -1
  45. package/dist/data/utilities/parsers/findUpdateValues.d.ts +5 -7
  46. package/dist/data/utilities/parsers/findUpdateValues.js +10 -13
  47. package/dist/data/utilities/parsers/findUpdateValues.min.js +1 -1
  48. package/dist/data/utilities/parsers/parseQuery.js +83 -9
  49. package/dist/data/utilities/parsers/parseQuery.min.js +1 -1
  50. package/dist/data/utilities/parsers/splitClauses.d.ts +29 -0
  51. package/dist/data/utilities/parsers/splitClauses.js +139 -0
  52. package/dist/data/utilities/parsers/splitClauses.min.js +1 -0
  53. package/dist/data/utilities/runQuery.js +5 -4
  54. package/dist/data/utilities/runQuery.min.js +1 -1
  55. package/package.json +1 -1
  56. package/dist/data/utilities/parsers/removeFromQuery.d.ts +0 -2
  57. package/dist/data/utilities/parsers/removeFromQuery.js +0 -8
  58. package/dist/data/utilities/parsers/removeFromQuery.min.js +0 -1
@@ -793,23 +793,85 @@ Object.defineProperty(exports, "__esModule", {
793
793
  value: true
794
794
  });
795
795
  exports.groupBy = void 0;
796
+ require("core-js/modules/es.json.stringify.js");
796
797
  require("core-js/modules/esnext.iterator.constructor.js");
798
+ require("core-js/modules/esnext.iterator.map.js");
797
799
  require("core-js/modules/esnext.iterator.reduce.js");
798
- const groupBy = (dataSet = [], groupProperty = '') => {
799
- if (!groupProperty) {
800
+ require("core-js/modules/esnext.map.delete-all.js");
801
+ require("core-js/modules/esnext.map.every.js");
802
+ require("core-js/modules/esnext.map.filter.js");
803
+ require("core-js/modules/esnext.map.find.js");
804
+ require("core-js/modules/esnext.map.find-key.js");
805
+ require("core-js/modules/esnext.map.includes.js");
806
+ require("core-js/modules/esnext.map.key-of.js");
807
+ require("core-js/modules/esnext.map.map-keys.js");
808
+ require("core-js/modules/esnext.map.map-values.js");
809
+ require("core-js/modules/esnext.map.merge.js");
810
+ require("core-js/modules/esnext.map.reduce.js");
811
+ require("core-js/modules/esnext.map.some.js");
812
+ require("core-js/modules/esnext.map.update.js");
813
+ // An object's keys in sorted order, at any depth, so { a, b } and { b, a } are the same value; arrays keep their order
814
+ const sortedKeys = value => {
815
+ if (Array.isArray(value)) {
816
+ return value.map(sortedKeys);
817
+ }
818
+ if (value !== null && typeof value === 'object') {
819
+ return Object.keys(value).sort().reduce((sorted, key) => {
820
+ sorted[key] = sortedKeys(value[key]);
821
+ return sorted;
822
+ }, {});
823
+ }
824
+ return value;
825
+ };
826
+ // Rows share a group when their values are the same, type included: 1 and '1' are two groups, as are null and missing
827
+ const groupKey = (row, field) => {
828
+ if (!Object.prototype.hasOwnProperty.call(row, field)) {
829
+ return 'missing';
830
+ }
831
+ const value = row[field];
832
+ return value !== null && typeof value === 'object' ? `json:${JSON.stringify(sortedKeys(value))}` : `${typeof value}:${String(value)}`;
833
+ };
834
+ /**
835
+ * Group rows by each field in turn. The groups are a list in the order their first row came, so a sort before
836
+ * grouping orders the groups too (and the rows within each one).
837
+ * @param dataSet
838
+ * @param fields
839
+ * @returns The rows as they were when there's no field, otherwise the groups.
840
+ */
841
+ const groupBy = (dataSet = [], fields = []) => {
842
+ if (!fields.length) {
800
843
  return dataSet;
801
844
  }
802
- return dataSet.reduce((groupedData, data) => {
803
- const property = data[groupProperty];
804
- if (!Object.prototype.hasOwnProperty.call(groupedData, property)) {
805
- groupedData[property] = [];
845
+ const [field, ...rest] = fields;
846
+ const groups = new Map();
847
+ for (const row of dataSet) {
848
+ const key = groupKey(row, field);
849
+ if (!groups.has(key)) {
850
+ groups.set(key, key === 'missing' ? {
851
+ field,
852
+ missing: true,
853
+ rows: []
854
+ } : {
855
+ field,
856
+ value: row[field],
857
+ rows: []
858
+ });
806
859
  }
807
- groupedData[property].push(data);
808
- return groupedData;
809
- }, {});
860
+ groups.get(key).rows.push(row);
861
+ }
862
+ return [...groups.values()].map(({
863
+ rows,
864
+ ...group
865
+ }) => rest.length ? {
866
+ ...group,
867
+ groups: groupBy(rows, rest)
868
+ } : {
869
+ ...group,
870
+ rows
871
+ });
810
872
  };
811
873
  exports.groupBy = groupBy;
812
- },{"core-js/modules/esnext.iterator.constructor.js":237,"core-js/modules/esnext.iterator.reduce.js":245}],20:[function(require,module,exports){
874
+ },{"core-js/modules/es.json.stringify.js":233,"core-js/modules/esnext.iterator.constructor.js":237,"core-js/modules/esnext.iterator.map.js":244,"core-js/modules/esnext.iterator.reduce.js":245,"core-js/modules/esnext.map.delete-all.js":247,"core-js/modules/esnext.map.every.js":248,"core-js/modules/esnext.map.filter.js":249,"core-js/modules/esnext.map.find-key.js":250,"core-js/modules/esnext.map.find.js":251,"core-js/modules/esnext.map.includes.js":252,"core-js/modules/esnext.map.key-of.js":253,"core-js/modules/esnext.map.map-keys.js":254,"core-js/modules/esnext.map.map-values.js":255,"core-js/modules/esnext.map.merge.js":256,"core-js/modules/esnext.map.reduce.js":257,"core-js/modules/esnext.map.some.js":258,"core-js/modules/esnext.map.update.js":259}],20:[function(require,module,exports){
813
875
  "use strict";
814
876
 
815
877
  Object.defineProperty(exports, "__esModule", {
@@ -977,7 +1039,7 @@ Object.defineProperty(exports, "__esModule", {
977
1039
  });
978
1040
  exports.limitOffset = void 0;
979
1041
  /**
980
- * Slice a dataSet down to at most `limit` rows, starting after the first `offset` of them.
1042
+ * Slice a dataSet down to at most `limit` rows (or groups, after group by), starting after the first `offset` of them.
981
1043
  * @param dataSet
982
1044
  * @param offset
983
1045
  * @param limit
@@ -1115,7 +1177,7 @@ const queryStatus = (text = '') => {
1115
1177
  };
1116
1178
  };
1117
1179
  exports.queryStatus = queryStatus;
1118
- },{"../utilities/parsers/scanQuery":67}],27:[function(require,module,exports){
1180
+ },{"../utilities/parsers/scanQuery":66}],27:[function(require,module,exports){
1119
1181
  "use strict";
1120
1182
 
1121
1183
  Object.defineProperty(exports, "__esModule", {
@@ -1165,24 +1227,29 @@ const shapeRow = (row, selectClauses) => selectClauses.reduce((shaped, {
1165
1227
  shaped[alias || property] = row[property];
1166
1228
  return shaped;
1167
1229
  }, {});
1230
+ const shapeGroup = (group, selectClauses) => group.groups ? {
1231
+ ...group,
1232
+ groups: group.groups.map(inner => shapeGroup(inner, selectClauses))
1233
+ } : {
1234
+ ...group,
1235
+ rows: group.rows.map(row => shapeRow(row, selectClauses))
1236
+ };
1168
1237
  /**
1169
- * Shape each row down to only the selected properties (aliased, when given). dataSet is either a plain array of
1170
- * rows, or (once groupBy has run) a plain object keyed by group value, each value an array of rows - both are
1171
- * shaped the same way, row by row.
1238
+ * Shape each row down to only the selected properties (aliased, when given). dataSet is either a list of rows, or
1239
+ * (once groupBy has run) a list of groups, whose rows are shaped the same way at any depth; the groups keep their
1240
+ * field and value, selected or not.
1172
1241
  * @param dataSet
1173
1242
  * @param selectClauses
1243
+ * @param grouped Whether dataSet is a list of groups (see groupBy)
1174
1244
  */
1175
- const selectFields = (dataSet = [], selectClauses = []) => {
1245
+ const selectFields = (dataSet = [], selectClauses = [], grouped = false) => {
1176
1246
  if (!selectClauses.length) {
1177
1247
  return dataSet;
1178
1248
  }
1179
- if (Array.isArray(dataSet)) {
1180
- return dataSet.map(row => shapeRow(row, selectClauses));
1249
+ if (grouped) {
1250
+ return dataSet.map(group => shapeGroup(group, selectClauses));
1181
1251
  }
1182
- return Object.keys(dataSet).reduce((shaped, key) => {
1183
- shaped[key] = dataSet[key].map(row => shapeRow(row, selectClauses));
1184
- return shaped;
1185
- }, {});
1252
+ return dataSet.map(row => shapeRow(row, selectClauses));
1186
1253
  };
1187
1254
  exports.selectFields = selectFields;
1188
1255
  },{"core-js/modules/esnext.iterator.constructor.js":237,"core-js/modules/esnext.iterator.map.js":244,"core-js/modules/esnext.iterator.reduce.js":245}],30:[function(require,module,exports){
@@ -2536,26 +2603,26 @@ Object.defineProperty(exports, "__esModule", {
2536
2603
  value: true
2537
2604
  });
2538
2605
  exports.findCommand = void 0;
2539
- var _removeFromQuery = require("./removeFromQuery");
2540
- const commandMatch = /^(delete|insert|read|update)\s+([a-z0-9_-]+)[\w\n]*/i;
2606
+ const commandMatch = /^(delete|insert|read|update)\s+([a-z0-9_-]+)/i;
2541
2607
  /**
2542
- * Extract command clauses from the query.
2608
+ * Read the command and record name from the start of a query's head (see splitClauses).
2543
2609
  * @param parsed
2544
2610
  * @param query
2611
+ * @returns The rest of the head (a joined record, `.joined`, for findJoinEntity).
2545
2612
  */
2546
2613
  const findCommand = (parsed, query) => {
2547
2614
  const commandFound = query.match(commandMatch);
2548
2615
  parsed['command'] = undefined;
2549
2616
  parsed['entity'] = undefined;
2550
2617
  if (commandFound) {
2551
- parsed['command'] = commandFound[1];
2618
+ parsed['command'] = commandFound[1].toLowerCase();
2552
2619
  parsed['entity'] = commandFound[2];
2553
- return (0, _removeFromQuery.removeFromQuery)(commandFound[0], query);
2620
+ return query.slice(commandFound[0].length).trim();
2554
2621
  }
2555
2622
  return query;
2556
2623
  };
2557
2624
  exports.findCommand = findCommand;
2558
- },{"./removeFromQuery":66}],49:[function(require,module,exports){
2625
+ },{}],49:[function(require,module,exports){
2559
2626
  "use strict";
2560
2627
 
2561
2628
  Object.defineProperty(exports, "__esModule", {
@@ -2568,15 +2635,13 @@ require("core-js/modules/esnext.iterator.flat-map.js");
2568
2635
  var _parseLiteralValue = require("./parseLiteralValue");
2569
2636
  var _conditionTree = require("../conditionTree");
2570
2637
  var _maskStrings = require("./maskStrings");
2638
+ var _splitClauses = require("./splitClauses");
2571
2639
  // Keywords count only as whole words: in `on t.x = u.color where ...` the `or` is part of the name `color`
2572
2640
  const wordEnd = '(?![a-z0-9_.-])';
2573
2641
  const keyword = words => new RegExp(`^(?:${words})${wordEnd}`, 'i');
2574
2642
  // One condition, at the start of the text: a field, then `is [not] null` / `exists` / `does not exist`, or a comparator
2575
2643
  // and a value (strings and arrays are placeholders by then, see maskStrings)
2576
2644
  const conditionMatch = new RegExp(`^([a-z0-9_-]+\\.?[a-z0-9_-]*)\\s+(?:(is\\s+not\\s+null|is\\s+null|exists|does\\s+not\\s+exist)${wordEnd}|(<>|!=|>=|<=|=|>|<|in|between|like)\\s+(${_maskStrings.placeholderSource}|(${_parseLiteralValue.numberSource})|${_maskStrings.arraySource}|(null)|(true)|(false)))`, 'i');
2577
- // The clause keywords that end a where expression (outside brackets), and `and on` (a join clause's own `and`)
2578
- const expressionEnd = keyword('sort|group|limit|offset|select|merge|on|where|and\\s+on');
2579
- const whereMatch = new RegExp(`(^|[^a-z0-9._-])((?:(and|or)\\s+)?where)${wordEnd}`, 'i');
2580
2645
  /**
2581
2646
  * Read one condition from a match of conditionMatch.
2582
2647
  * @param match
@@ -2620,15 +2685,15 @@ const join = (kind, nodes) => {
2620
2685
  };
2621
2686
  };
2622
2687
  /**
2623
- * Read one where expression from the masked query, starting at `start` (just after its `where`). Precedence, loosest
2624
- * first: `or`, `and`, `not`; brackets group. `and where` / `or where` are the older spellings of `and` / `or`.
2688
+ * Read one where expression from the start of a where clause body (masked, see maskStrings). Precedence, loosest
2689
+ * first: `or`, `and`, `not`; brackets group.
2625
2690
  * @param text
2626
- * @param start
2627
- * @returns The expression's tree and where it ends (the next clause keyword outside brackets, or the end).
2628
- * @throws {Error} For an unbalanced bracket, empty brackets, or an operator with no condition after it.
2691
+ * @returns The expression's tree and where it ends (at the first text that can't carry it on, or the end).
2692
+ * @throws {Error} For an unbalanced bracket, empty brackets, an operator with no condition after it, or two conditions
2693
+ * with no and/or between them.
2629
2694
  */
2630
- const readExpression = (text, start) => {
2631
- let at = start;
2695
+ const readExpression = text => {
2696
+ let at = 0;
2632
2697
  const rest = () => text.slice(at);
2633
2698
  const skipSpace = () => {
2634
2699
  at += rest().length - rest().trimStart().length;
@@ -2651,6 +2716,10 @@ const readExpression = (text, start) => {
2651
2716
  at += condition[0].length;
2652
2717
  return readCondition(condition);
2653
2718
  }
2719
+ // The 2.x `and where` / `or where` inside brackets, where splitClauses doesn't look
2720
+ if ((after === 'and' || after === 'or') && keyword('where').test(rest())) {
2721
+ throw new Error(`"${after} where" is no longer supported: ${_splitClauses.whereHint}`);
2722
+ }
2654
2723
  if (take(keyword('not'))) {
2655
2724
  return {
2656
2725
  not: factor('not')
@@ -2673,11 +2742,7 @@ const readExpression = (text, start) => {
2673
2742
  const term = after => {
2674
2743
  const nodes = [factor(after)];
2675
2744
  for (;;) {
2676
- skipSpace();
2677
- if (keyword('and\\s+on').test(rest())) {
2678
- break;
2679
- }
2680
- const connector = take(keyword('and(?:\\s+where)?'));
2745
+ const connector = take(keyword('and'));
2681
2746
  if (!connector) {
2682
2747
  break;
2683
2748
  }
@@ -2688,7 +2753,7 @@ const readExpression = (text, start) => {
2688
2753
  const expression = after => {
2689
2754
  const nodes = [term(after)];
2690
2755
  for (;;) {
2691
- const connector = take(keyword('or(?:\\s+where)?'));
2756
+ const connector = take(keyword('or'));
2692
2757
  if (!connector) {
2693
2758
  break;
2694
2759
  }
@@ -2701,7 +2766,7 @@ const readExpression = (text, start) => {
2701
2766
  if (rest().startsWith(')')) {
2702
2767
  throw new Error(`Unexpected ) in a where clause: ${shown()}`);
2703
2768
  }
2704
- if (rest() && !expressionEnd.test(rest()) && conditionMatch.test(rest())) {
2769
+ if (conditionMatch.test(rest())) {
2705
2770
  throw new Error(`Missing and/or before: ${shown()}`);
2706
2771
  }
2707
2772
  return {
@@ -2710,134 +2775,137 @@ const readExpression = (text, start) => {
2710
2775
  };
2711
2776
  };
2712
2777
  /**
2713
- * Extract the where clause from the query into a tree (see ConditionNode): `where` followed by conditions joined with
2714
- * `and` / `or`, `not`, and brackets, nested to any depth. Each condition is `<field> <comparator> <value>`, or
2715
- * `<field> is null`, `is not null`, `exists` or `does not exist`. A comparison with the literal null (`= null`) is
2716
- * refused: it would never match, as null equals nothing (see where). The expression ends at the next clause keyword
2717
- * outside brackets, so clauses may come in any order; several separate `where`s (or `and where` after another clause)
2718
- * are joined with `and`, as before.
2778
+ * Read a where clause body (see splitClauses) into a tree (see ConditionNode): conditions joined with `and` / `or`,
2779
+ * `not`, and brackets, nested to any depth. Each condition is `<field> <comparator> <value>`, or `<field> is null`,
2780
+ * `is not null`, `exists` or `does not exist`. A comparison with the literal null (`= null`) is refused: it would never
2781
+ * match, as null equals nothing (see where).
2719
2782
  * @param parsed
2720
- * @param query
2721
- * @throws {Error} For the errors readExpression and readCondition describe, and `or where` after another clause.
2783
+ * @param body
2784
+ * @returns The rest of the body, after the expression.
2785
+ * @throws {Error} For the errors readExpression and readCondition describe.
2722
2786
  */
2723
- const findConditions = (parsed, query) => {
2724
- const expressions = [];
2725
- for (let found = query.match(whereMatch); found; found = query.match(whereMatch)) {
2726
- if ((found[3] || '').toLowerCase() === 'or') {
2727
- throw new Error('`or where` must follow the conditions it joins: put them in one where clause');
2728
- }
2729
- const whereStart = found.index + found[1].length;
2730
- const {
2731
- node,
2732
- end
2733
- } = readExpression(query, whereStart + found[2].length);
2734
- expressions.push(node);
2735
- query = `${query.slice(0, whereStart)} ${query.slice(end)}`.replace(/\s+/g, ' ').trim();
2736
- }
2737
- parsed['where'] = expressions.length ? join('and', expressions) : (0, _conditionTree.allOf)([]);
2738
- return query;
2787
+ const findConditions = (parsed, body) => {
2788
+ const {
2789
+ node,
2790
+ end
2791
+ } = readExpression(body);
2792
+ parsed['where'] = node;
2793
+ return body.slice(end).trim();
2739
2794
  };
2740
2795
  exports.findConditions = findConditions;
2741
- },{"../conditionTree":37,"./maskStrings":61,"./parseLiteralValue":63,"core-js/modules/es.array.includes.js":222,"core-js/modules/esnext.iterator.constructor.js":237,"core-js/modules/esnext.iterator.flat-map.js":242}],50:[function(require,module,exports){
2796
+ },{"../conditionTree":37,"./maskStrings":61,"./parseLiteralValue":63,"./splitClauses":67,"core-js/modules/es.array.includes.js":222,"core-js/modules/esnext.iterator.constructor.js":237,"core-js/modules/esnext.iterator.flat-map.js":242}],50:[function(require,module,exports){
2742
2797
  "use strict";
2743
2798
 
2744
2799
  Object.defineProperty(exports, "__esModule", {
2745
2800
  value: true
2746
2801
  });
2747
2802
  exports.findGroupBy = void 0;
2748
- var _removeFromQuery = require("./removeFromQuery");
2749
- const groupMatch = /group\s+by\s+(,?[a-z0-9._-]+)+/i;
2803
+ require("core-js/modules/esnext.iterator.constructor.js");
2804
+ require("core-js/modules/esnext.iterator.find.js");
2805
+ const groupMatch = /^[a-z0-9._-]+(?:\s*,\s*[a-z0-9._-]+)*/i;
2750
2806
  /**
2751
- * Extract the group by clauses from the query.
2807
+ * Read a group by clause body (`a, b`, see splitClauses).
2752
2808
  * @param parsed
2753
- * @param query
2809
+ * @param body
2810
+ * @returns The rest of the body, after the last field.
2811
+ * @throws {Error} For a field named twice.
2754
2812
  */
2755
- const findGroupBy = (parsed, query) => {
2756
- const groupFound = query.match(groupMatch);
2813
+ const findGroupBy = (parsed, body) => {
2814
+ const groupFound = body.match(groupMatch);
2757
2815
  if (groupFound) {
2758
- parsed['groupBy'] = groupFound ? groupFound[1] : undefined;
2759
- return (0, _removeFromQuery.removeFromQuery)(groupFound[0], query);
2816
+ const fields = groupFound[0].split(/\s*,\s*/);
2817
+ const repeated = fields.find((field, index) => fields.indexOf(field) !== index);
2818
+ if (repeated) {
2819
+ throw new Error(`"${repeated}" is in group by twice`);
2820
+ }
2821
+ parsed['groupBy'] = fields;
2822
+ return body.slice(groupFound[0].length).trim();
2760
2823
  }
2761
- return query;
2824
+ return body;
2762
2825
  };
2763
2826
  exports.findGroupBy = findGroupBy;
2764
- },{"./removeFromQuery":66}],51:[function(require,module,exports){
2827
+ },{"core-js/modules/esnext.iterator.constructor.js":237,"core-js/modules/esnext.iterator.find.js":241}],51:[function(require,module,exports){
2765
2828
  "use strict";
2766
2829
 
2767
2830
  Object.defineProperty(exports, "__esModule", {
2768
2831
  value: true
2769
2832
  });
2770
2833
  exports.findInsertValues = void 0;
2771
- var _removeFromQuery = require("./removeFromQuery");
2772
2834
  var _parseLiteralValue = require("./parseLiteralValue");
2773
2835
  var _maskStrings = require("./maskStrings");
2774
2836
  const literal = `(?:${_maskStrings.placeholderSource.replace('(', '(?:')}|${_parseLiteralValue.numberSource}|${_maskStrings.arraySource.replace('(', '(?:')}|null|true|false)`;
2775
- const valuesClauseMatch = new RegExp(`(values)\\s+([a-z0-9_-]+\\s*=\\s*${literal}(?:\\s*,\\s*[a-z0-9_-]+\\s*=\\s*${literal})*)`, 'i');
2837
+ const valuesMatch = new RegExp(`^([a-z0-9_-]+\\s*=\\s*${literal}(?:\\s*,\\s*[a-z0-9_-]+\\s*=\\s*${literal})*)`, 'i');
2776
2838
  const assignmentMatch = new RegExp(`([a-z0-9_-]+)\\s*=\\s*(${_maskStrings.placeholderSource}|(${_parseLiteralValue.numberSource})|${_maskStrings.arraySource}|(null)|(true)|(false))`, 'ig');
2777
2839
  /**
2778
- * Extract an insert's "values field = value, field2 = value2" clause from the query, the same way
2840
+ * Read an insert's values clause body (`field = value, field2 = value2`, see splitClauses), the same way
2779
2841
  * findUpdateValues() does for "set". insertEntity() accepts an array of rows for bulk inserts, but
2780
2842
  * a query string only describes a single row for now - wrapped in a one-element array to match.
2781
2843
  * @param parsed
2782
- * @param query
2844
+ * @param body
2845
+ * @returns The rest of the body, after the last assignment.
2783
2846
  */
2784
- const findInsertValues = (parsed, query) => {
2785
- const clauseFound = query.match(valuesClauseMatch);
2847
+ const findInsertValues = (parsed, body) => {
2848
+ const clauseFound = body.match(valuesMatch);
2786
2849
  parsed['insertValues'] = [];
2787
2850
  if (!clauseFound) {
2788
- return query;
2851
+ return body;
2789
2852
  }
2790
2853
  const row = {};
2791
- const assignmentsFound = clauseFound[2].matchAll(assignmentMatch);
2854
+ const assignmentsFound = clauseFound[1].matchAll(assignmentMatch);
2792
2855
  // @ts-ignore
2793
2856
  for (const assignment of assignmentsFound) {
2794
2857
  row[assignment[1]] = (0, _parseLiteralValue.parseLiteralValue)(assignment, 3);
2795
2858
  }
2796
2859
  parsed['insertValues'] = [row];
2797
- return (0, _removeFromQuery.removeFromQuery)(clauseFound[0], query);
2860
+ return body.slice(clauseFound[0].length).trim();
2798
2861
  };
2799
2862
  exports.findInsertValues = findInsertValues;
2800
- },{"./maskStrings":61,"./parseLiteralValue":63,"./removeFromQuery":66}],52:[function(require,module,exports){
2863
+ },{"./maskStrings":61,"./parseLiteralValue":63}],52:[function(require,module,exports){
2801
2864
  "use strict";
2802
2865
 
2803
2866
  Object.defineProperty(exports, "__esModule", {
2804
2867
  value: true
2805
2868
  });
2806
2869
  exports.findJoinClauses = void 0;
2807
- var _removeFromQuery = require("./removeFromQuery");
2808
- const joinClauseMatch = /(and)?\s*on\s+([a-z0-9_-]+\.[a-z0-9_-]+)\s+(=|!=|>|<|>=|<=|in|between|like)\s+([a-z0-9_-]+\.[a-z0-9_-]+)/ig;
2870
+ // One join condition, at the start of the text; every one after the first starts with `and on` (see splitClauses)
2871
+ const joinClauseMatch = /^([a-z0-9_-]+\.[a-z0-9_-]+)\s+(=|!=|>|<|>=|<=|in|between|like)\s+([a-z0-9_-]+\.[a-z0-9_-]+)(?![a-z0-9._-])/i;
2872
+ const nextMatch = /^and\s+on\s+/i;
2809
2873
  /**
2810
- * Extract condition clauses from the query.
2874
+ * Read an on clause body (`a.x = b.y and on b.z = c.w`, see splitClauses).
2811
2875
  * @param parsed
2812
- * @param query
2876
+ * @param body
2877
+ * @returns The rest of the body, after the last join condition.
2813
2878
  */
2814
- const findJoinClauses = (parsed, query) => {
2815
- const joinClausesFound = query.matchAll(joinClauseMatch);
2816
- // @ts-ignore
2817
- for (const joinClause of joinClausesFound) {
2879
+ const findJoinClauses = (parsed, body) => {
2880
+ let rest = body;
2881
+ for (let joinClause = rest.match(joinClauseMatch); joinClause; joinClause = rest.match(joinClauseMatch)) {
2818
2882
  parsed['joinClauses'].push({
2819
- propertyA: joinClause[2],
2820
- comparator: joinClause[3],
2821
- propertyB: joinClause[4]
2883
+ propertyA: joinClause[1],
2884
+ comparator: joinClause[2],
2885
+ propertyB: joinClause[3]
2822
2886
  });
2823
- query = (0, _removeFromQuery.removeFromQuery)(joinClause[0], query);
2887
+ rest = rest.slice(joinClause[0].length).trim();
2888
+ // The connector only with another one after it, so a bad one is shown with its connector
2889
+ const next = rest.match(nextMatch);
2890
+ if (!next || !joinClauseMatch.test(rest.slice(next[0].length))) {
2891
+ break;
2892
+ }
2893
+ rest = rest.slice(next[0].length);
2824
2894
  }
2825
- return query;
2895
+ return rest;
2826
2896
  };
2827
2897
  exports.findJoinClauses = findJoinClauses;
2828
- },{"./removeFromQuery":66}],53:[function(require,module,exports){
2898
+ },{}],53:[function(require,module,exports){
2829
2899
  "use strict";
2830
2900
 
2831
2901
  Object.defineProperty(exports, "__esModule", {
2832
2902
  value: true
2833
2903
  });
2834
2904
  exports.findJoinEntity = void 0;
2835
- var _removeFromQuery = require("./removeFromQuery");
2836
- // Only straight after the entity (findCommand leaves `.joined ...`): a `.` later on is a number's (`1.5`) or a
2837
- // condition's `entity.field`, not the join
2905
+ // What findCommand leaves of the head: `.joined`
2838
2906
  const joinEntityMatch = /^\.([a-z0-9_-]+)/i;
2839
2907
  /**
2840
- * Extract the main join entity from the query.
2908
+ * Read the joined record from what findCommand left of the head.
2841
2909
  * @param parsed
2842
2910
  * @param query
2843
2911
  */
@@ -2845,189 +2913,192 @@ const findJoinEntity = (parsed, query) => {
2845
2913
  const joinEntityFound = query.match(joinEntityMatch);
2846
2914
  if (joinEntityFound) {
2847
2915
  parsed['joinEntity'] = joinEntityFound ? joinEntityFound[1] : undefined;
2848
- return (0, _removeFromQuery.removeFromQuery)(joinEntityFound[0], query);
2916
+ return query.slice(joinEntityFound[0].length).trim();
2849
2917
  }
2850
2918
  return query;
2851
2919
  };
2852
2920
  exports.findJoinEntity = findJoinEntity;
2853
- },{"./removeFromQuery":66}],54:[function(require,module,exports){
2921
+ },{}],54:[function(require,module,exports){
2854
2922
  "use strict";
2855
2923
 
2856
2924
  Object.defineProperty(exports, "__esModule", {
2857
2925
  value: true
2858
2926
  });
2859
2927
  exports.findLimit = void 0;
2860
- var _removeFromQuery = require("./removeFromQuery");
2861
- const limitMatch = /limit\s*(\d+)/i;
2928
+ const limitMatch = /^(\d+)/;
2862
2929
  /**
2863
- * Extract the limit clauses from the query.
2930
+ * Read a limit clause body (a whole number, see splitClauses).
2864
2931
  * @param parsed
2865
- * @param query
2932
+ * @param body
2933
+ * @returns The rest of the body, after the number.
2866
2934
  */
2867
- const findLimit = (parsed, query) => {
2868
- const limitFound = query.match(limitMatch);
2935
+ const findLimit = (parsed, body) => {
2936
+ const limitFound = body.match(limitMatch);
2869
2937
  if (limitFound) {
2870
2938
  parsed['limit'] = limitFound ? parseInt(limitFound[1]) : undefined;
2871
- return (0, _removeFromQuery.removeFromQuery)(limitFound[0], query);
2939
+ return body.slice(limitFound[0].length).trim();
2872
2940
  }
2873
- return query;
2941
+ return body;
2874
2942
  };
2875
2943
  exports.findLimit = findLimit;
2876
- },{"./removeFromQuery":66}],55:[function(require,module,exports){
2944
+ },{}],55:[function(require,module,exports){
2877
2945
  "use strict";
2878
2946
 
2879
2947
  Object.defineProperty(exports, "__esModule", {
2880
2948
  value: true
2881
2949
  });
2882
2950
  exports.findMergeJoins = void 0;
2883
- var _removeFromQuery = require("./removeFromQuery");
2884
- const mergeJoinMatch = /(and)?\s*merge\s+([a-z0-9_-]+\.[a-z0-9_-]+)\s+with\s+([a-z0-9_-]+\.[a-z0-9_-]+)/ig;
2951
+ // One merge, at the start of the text; every one after the first starts with `merge` or `and merge` (see splitClauses)
2952
+ const mergeJoinMatch = /^([a-z0-9_-]+\.[a-z0-9_-]+)\s+with\s+([a-z0-9_-]+\.[a-z0-9_-]+)(?![a-z0-9._-])/i;
2953
+ const nextMatch = /^(?:and\s+)?merge\s+/i;
2885
2954
  /**
2886
- * Extract merge-join clauses from the query.
2955
+ * Read a merge clause body (`a.x with b.y merge c.z with d.w`, see splitClauses).
2887
2956
  * @param parsed
2888
- * @param query
2957
+ * @param body
2958
+ * @returns The rest of the body, after the last merge.
2889
2959
  */
2890
- const findMergeJoins = (parsed, query) => {
2891
- const mergeJoinsFound = query.matchAll(mergeJoinMatch);
2892
- // @ts-ignore
2893
- for (const mergeJoin of mergeJoinsFound) {
2960
+ const findMergeJoins = (parsed, body) => {
2961
+ let rest = body;
2962
+ for (let mergeJoin = rest.match(mergeJoinMatch); mergeJoin; mergeJoin = rest.match(mergeJoinMatch)) {
2894
2963
  parsed['mergeJoins'].push({
2895
- propertyA: mergeJoin[2],
2896
- propertyB: mergeJoin[3]
2964
+ propertyA: mergeJoin[1],
2965
+ propertyB: mergeJoin[2]
2897
2966
  });
2898
- query = (0, _removeFromQuery.removeFromQuery)(mergeJoin[0].trim(), query);
2967
+ rest = rest.slice(mergeJoin[0].length).trim();
2968
+ // The connector only with another one after it, so a bad one is shown with its connector
2969
+ const next = rest.match(nextMatch);
2970
+ if (!next || !mergeJoinMatch.test(rest.slice(next[0].length))) {
2971
+ break;
2972
+ }
2973
+ rest = rest.slice(next[0].length);
2899
2974
  }
2900
- return query;
2975
+ return rest;
2901
2976
  };
2902
2977
  exports.findMergeJoins = findMergeJoins;
2903
- },{"./removeFromQuery":66}],56:[function(require,module,exports){
2978
+ },{}],56:[function(require,module,exports){
2904
2979
  "use strict";
2905
2980
 
2906
2981
  Object.defineProperty(exports, "__esModule", {
2907
2982
  value: true
2908
2983
  });
2909
2984
  exports.findOffset = void 0;
2910
- var _removeFromQuery = require("./removeFromQuery");
2911
- const offsetMatch = /offset\s*(\d+)/i;
2985
+ const offsetMatch = /^(\d+)/;
2912
2986
  /**
2913
- * Extract the offset clauses from the query.
2987
+ * Read a offset clause body (a whole number, see splitClauses).
2914
2988
  * @param parsed
2915
- * @param query
2989
+ * @param body
2990
+ * @returns The rest of the body, after the number.
2916
2991
  */
2917
- const findOffset = (parsed, query) => {
2918
- const offsetFound = query.match(offsetMatch);
2992
+ const findOffset = (parsed, body) => {
2993
+ const offsetFound = body.match(offsetMatch);
2919
2994
  if (offsetFound) {
2920
2995
  parsed['offset'] = offsetFound ? parseInt(offsetFound[1]) : undefined;
2921
- return (0, _removeFromQuery.removeFromQuery)(offsetFound[0], query);
2996
+ return body.slice(offsetFound[0].length).trim();
2922
2997
  }
2923
- return query;
2998
+ return body;
2924
2999
  };
2925
3000
  exports.findOffset = findOffset;
2926
- },{"./removeFromQuery":66}],57:[function(require,module,exports){
3001
+ },{}],57:[function(require,module,exports){
2927
3002
  "use strict";
2928
3003
 
2929
3004
  Object.defineProperty(exports, "__esModule", {
2930
3005
  value: true
2931
3006
  });
2932
3007
  exports.findSelects = void 0;
2933
- var _removeFromQuery = require("./removeFromQuery");
2934
- // One field is a name, then optionally `as <alias>` (`as` as a whole word); fields are separated by commas. The first
2935
- // field used to accept an alias without `as`, so in `select name where a = 1` the word `where` became the alias.
3008
+ // One field is a name, then optionally `as <alias>` (`as` as a whole word); fields are separated by commas
2936
3009
  const selectField = '[a-z0-9=._-]+(?:\\s+as\\s+[a-z0-9_-]+)?';
2937
- const selectClausesMatch = new RegExp(`select\\s+${selectField}(?:\\s*,\\s*${selectField})*`, 'i');
2938
- const selectClauseMatch = /(select)?\s*,?\s*([a-z0-9=._-]+)(?:\s+as\s+([a-z0-9_-]+))?/ig;
3010
+ const selectsMatch = new RegExp(`^${selectField}(?:\\s*,\\s*${selectField})*`, 'i');
3011
+ const selectFieldMatch = /,?\s*([a-z0-9=._-]+)(?:\s+as\s+([a-z0-9_-]+))?/ig;
2939
3012
  /**
2940
- * Extract select clauses from the query.
3013
+ * Read a select clause body (`a, b as c`, see splitClauses).
2941
3014
  * @param parsed
2942
- * @param query
3015
+ * @param body
3016
+ * @returns The rest of the body, after the last field.
2943
3017
  */
2944
- const findSelects = (parsed, query) => {
2945
- const selectsFound = query.match(selectClausesMatch);
3018
+ const findSelects = (parsed, body) => {
3019
+ const selectsFound = body.match(selectsMatch);
2946
3020
  if (selectsFound) {
2947
- const selectClausesFound = selectsFound[0].matchAll(selectClauseMatch);
3021
+ const selectClausesFound = selectsFound[0].matchAll(selectFieldMatch);
2948
3022
  // @ts-ignore
2949
3023
  for (const selectClause of selectClausesFound) {
2950
3024
  parsed['selectClauses'].push({
2951
- property: selectClause[2],
2952
- alias: selectClause[3]
3025
+ property: selectClause[1],
3026
+ alias: selectClause[2]
2953
3027
  });
2954
3028
  }
2955
- return (0, _removeFromQuery.removeFromQuery)(selectsFound[0], query);
3029
+ return body.slice(selectsFound[0].length).trim();
2956
3030
  }
2957
- return query;
3031
+ return body;
2958
3032
  };
2959
3033
  exports.findSelects = findSelects;
2960
- },{"./removeFromQuery":66}],58:[function(require,module,exports){
3034
+ },{}],58:[function(require,module,exports){
2961
3035
  "use strict";
2962
3036
 
2963
3037
  Object.defineProperty(exports, "__esModule", {
2964
3038
  value: true
2965
3039
  });
2966
3040
  exports.findSortClauses = void 0;
2967
- var _removeFromQuery = require("./removeFromQuery");
2968
3041
  // One rule is a field, then optionally asc or desc as a whole word. Rules are separated by commas, so the list ends
2969
- // at the first word that doesn't follow a comma: in `sort a limit 1`, `limit` starts the next clause.
3042
+ // at the first word that doesn't follow a comma: in `sort a b`, `b` is left over.
2970
3043
  const sortRule = '[a-z0-9._-]+(?:\\s+(?:asc|desc)(?![a-z0-9._-]))?';
2971
- const sortMatch = new RegExp(`sort\\s+${sortRule}(?:\\s*,\\s*${sortRule})*`, 'i');
2972
- const sortRuleMatch = /(sort)?\s*,?\s*([a-z0-9._-]+)(?:\s+(asc|desc)(?![a-z0-9._-]))?/ig;
3044
+ const sortMatch = new RegExp(`^${sortRule}(?:\\s*,\\s*${sortRule})*`, 'i');
3045
+ const sortRuleMatch = /,?\s*([a-z0-9._-]+)(?:\s+(asc|desc)(?![a-z0-9._-]))?/ig;
2973
3046
  /**
2974
- * Extract sort clauses from the query.
3047
+ * Read a sort clause body (`a, b desc`, see splitClauses).
2975
3048
  * @param parsed
2976
- * @param query
3049
+ * @param body
3050
+ * @returns The rest of the body, after the last rule.
2977
3051
  */
2978
- const findSortClauses = (parsed, query) => {
2979
- const sortFound = query.match(sortMatch);
3052
+ const findSortClauses = (parsed, body) => {
3053
+ const sortFound = body.match(sortMatch);
2980
3054
  if (sortFound) {
2981
3055
  const sortRulesFound = sortFound[0].matchAll(sortRuleMatch);
2982
3056
  // @ts-ignore
2983
3057
  for (const sortClause of sortRulesFound) {
2984
3058
  parsed['sortClauses'].push({
2985
- property: sortClause[2],
2986
- direction: sortClause[3] || 'asc'
3059
+ property: sortClause[1],
3060
+ direction: (sortClause[2] || 'asc').toLowerCase()
2987
3061
  });
2988
3062
  }
2989
- return (0, _removeFromQuery.removeFromQuery)(sortFound[0], query);
3063
+ return body.slice(sortFound[0].length).trim();
2990
3064
  }
2991
- return query;
3065
+ return body;
2992
3066
  };
2993
3067
  exports.findSortClauses = findSortClauses;
2994
- },{"./removeFromQuery":66}],59:[function(require,module,exports){
3068
+ },{}],59:[function(require,module,exports){
2995
3069
  "use strict";
2996
3070
 
2997
3071
  Object.defineProperty(exports, "__esModule", {
2998
3072
  value: true
2999
3073
  });
3000
3074
  exports.findUpdateValues = void 0;
3001
- var _removeFromQuery = require("./removeFromQuery");
3002
3075
  var _parseLiteralValue = require("./parseLiteralValue");
3003
3076
  var _maskStrings = require("./maskStrings");
3004
3077
  const literal = `(?:${_maskStrings.placeholderSource.replace('(', '(?:')}|${_parseLiteralValue.numberSource}|${_maskStrings.arraySource.replace('(', '(?:')}|null|true|false)`;
3005
- const setClauseMatch = new RegExp(`(set)\\s+([a-z0-9_-]+\\s*=\\s*${literal}(?:\\s*,\\s*[a-z0-9_-]+\\s*=\\s*${literal})*)`, 'i');
3078
+ const setMatch = new RegExp(`^([a-z0-9_-]+\\s*=\\s*${literal}(?:\\s*,\\s*[a-z0-9_-]+\\s*=\\s*${literal})*)`, 'i');
3006
3079
  const assignmentMatch = new RegExp(`([a-z0-9_-]+)\\s*=\\s*(${_maskStrings.placeholderSource}|(${_parseLiteralValue.numberSource})|${_maskStrings.arraySource}|(null)|(true)|(false))`, 'ig');
3007
3080
  /**
3008
- * Extract an update's "set field = value, field2 = value2" clause from the query. Uses a two-pass
3009
- * match, unlike the other parsers here: the set clause's own boundary is found first (so it can
3010
- * be removed from the query as a whole, and so the comma-separated list doesn't get confused with
3011
- * anything that follows, like a where clause), then each individual assignment is parsed out of
3012
- * that captured substring.
3081
+ * Read an update's set clause body (`field = value, field2 = value2`, see splitClauses). Uses a two-pass match: the
3082
+ * whole comma-separated list first, so anything after it is left over, then each assignment out of that list.
3013
3083
  * @param parsed
3014
- * @param query
3084
+ * @param body
3085
+ * @returns The rest of the body, after the last assignment.
3015
3086
  */
3016
- const findUpdateValues = (parsed, query) => {
3017
- const clauseFound = query.match(setClauseMatch);
3087
+ const findUpdateValues = (parsed, body) => {
3088
+ const clauseFound = body.match(setMatch);
3018
3089
  parsed['updateValues'] = {};
3019
3090
  if (!clauseFound) {
3020
- return query;
3091
+ return body;
3021
3092
  }
3022
- const assignmentsFound = clauseFound[2].matchAll(assignmentMatch);
3093
+ const assignmentsFound = clauseFound[1].matchAll(assignmentMatch);
3023
3094
  // @ts-ignore
3024
3095
  for (const assignment of assignmentsFound) {
3025
3096
  parsed['updateValues'][assignment[1]] = (0, _parseLiteralValue.parseLiteralValue)(assignment, 3);
3026
3097
  }
3027
- return (0, _removeFromQuery.removeFromQuery)(clauseFound[0], query);
3098
+ return body.slice(clauseFound[0].length).trim();
3028
3099
  };
3029
3100
  exports.findUpdateValues = findUpdateValues;
3030
- },{"./maskStrings":61,"./parseLiteralValue":63,"./removeFromQuery":66}],60:[function(require,module,exports){
3101
+ },{"./maskStrings":61,"./parseLiteralValue":63}],60:[function(require,module,exports){
3031
3102
  "use strict";
3032
3103
 
3033
3104
  Object.defineProperty(exports, "__esModule", {
@@ -3164,7 +3235,7 @@ const maskArrays = masked => {
3164
3235
  return depth > 0 ? result + masked.slice(start) : result;
3165
3236
  };
3166
3237
  exports.maskArrays = maskArrays;
3167
- },{"./scanQuery":67,"core-js/modules/es.json.stringify.js":233,"core-js/modules/esnext.iterator.constructor.js":237,"core-js/modules/esnext.iterator.map.js":244,"core-js/modules/esnext.iterator.some.js":246}],62:[function(require,module,exports){
3238
+ },{"./scanQuery":66,"core-js/modules/es.json.stringify.js":233,"core-js/modules/esnext.iterator.constructor.js":237,"core-js/modules/esnext.iterator.map.js":244,"core-js/modules/esnext.iterator.some.js":246}],62:[function(require,module,exports){
3168
3239
  "use strict";
3169
3240
 
3170
3241
  Object.defineProperty(exports, "__esModule", {
@@ -3568,13 +3639,13 @@ Object.defineProperty(exports, "__esModule", {
3568
3639
  value: true
3569
3640
  });
3570
3641
  exports.parseQuery = void 0;
3571
- var _siFunciona = _interopRequireDefault(require("si-funciona"));
3572
3642
  var _findCommand = require("./findCommand");
3573
3643
  var _findInsertValues = require("./findInsertValues");
3574
3644
  var _findUpdateValues = require("./findUpdateValues");
3575
3645
  var _findJoinEntity = require("./findJoinEntity");
3576
3646
  var _findSelects = require("./findSelects");
3577
3647
  var _findConditions = require("./findConditions");
3648
+ var _conditionTree = require("../conditionTree");
3578
3649
  var _findJoinClauses = require("./findJoinClauses");
3579
3650
  var _findMergeJoins = require("./findMergeJoins");
3580
3651
  var _findSortClauses = require("./findSortClauses");
@@ -3584,8 +3655,63 @@ var _findOffset = require("./findOffset");
3584
3655
  var _maskStrings = require("./maskStrings");
3585
3656
  var _parseCreate = require("./parseCreate");
3586
3657
  var _parseSchemaStatement = require("./parseSchemaStatement");
3658
+ var _splitClauses = require("./splitClauses");
3587
3659
  var _startsQuery = require("./startsQuery");
3588
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
3660
+ // Each clause's own step, and what its body must start with (for the error when it doesn't)
3661
+ const clauseSteps = {
3662
+ values: {
3663
+ step: _findInsertValues.findInsertValues,
3664
+ expected: 'an assignment (field = value)'
3665
+ },
3666
+ set: {
3667
+ step: _findUpdateValues.findUpdateValues,
3668
+ expected: 'an assignment (field = value)'
3669
+ },
3670
+ select: {
3671
+ step: _findSelects.findSelects,
3672
+ expected: 'a field'
3673
+ },
3674
+ on: {
3675
+ step: _findJoinClauses.findJoinClauses,
3676
+ expected: 'a join condition (a.x = b.y)'
3677
+ },
3678
+ merge: {
3679
+ step: _findMergeJoins.findMergeJoins,
3680
+ expected: 'a merge (a.x with b.y)'
3681
+ },
3682
+ where: {
3683
+ step: _findConditions.findConditions,
3684
+ expected: 'a condition'
3685
+ },
3686
+ 'group by': {
3687
+ step: _findGroupBy.findGroupBy,
3688
+ expected: 'a field'
3689
+ },
3690
+ sort: {
3691
+ step: _findSortClauses.findSortClauses,
3692
+ expected: 'a field'
3693
+ },
3694
+ limit: {
3695
+ step: _findLimit.findLimit,
3696
+ expected: 'a whole number'
3697
+ },
3698
+ offset: {
3699
+ step: _findOffset.findOffset,
3700
+ expected: 'a whole number'
3701
+ }
3702
+ };
3703
+ /**
3704
+ * The error for text a step couldn't read: another query run on without a `;`, or text that doesn't belong.
3705
+ * @param rest The text left over, masked
3706
+ * @param after What was read before it, masked
3707
+ */
3708
+ const leftoverError = (rest, after) => {
3709
+ const shown = text => (0, _maskStrings.unmaskStrings)(text, '\'');
3710
+ if (_startsQuery.startsQueryMatch.test(rest)) {
3711
+ return new Error(`Missing ; before: ${shown(rest)}`);
3712
+ }
3713
+ return new Error(`Unexpected "${shown(rest)}" after "${shown(after)}"`);
3714
+ };
3589
3715
  /**
3590
3716
  * Break a query down into its components.
3591
3717
  * @param query
@@ -3608,10 +3734,6 @@ const parseQuery = query => {
3608
3734
  create: undefined,
3609
3735
  ifExists: undefined
3610
3736
  };
3611
- const {
3612
- curry,
3613
- pipe
3614
- } = _siFunciona.default;
3615
3737
  // No find* step may see inside a string or an array: each one becomes a placeholder holding its value
3616
3738
  query = (0, _maskStrings.maskArrays)((0, _maskStrings.maskStrings)(query));
3617
3739
  // A create statement has its own shape (a bracketed list of fields and keys), so it gets its own parser
@@ -3629,17 +3751,40 @@ const parseQuery = query => {
3629
3751
  if (_parseSchemaStatement.schemaStatementMatch.test(query)) {
3630
3752
  return Object.assign(parsedQuery, (0, _parseSchemaStatement.parseSchemaStatement)(query));
3631
3753
  }
3632
- query = pipe(curry(_findCommand.findCommand)(parsedQuery), curry(_findInsertValues.findInsertValues)(parsedQuery), curry(_findUpdateValues.findUpdateValues)(parsedQuery), curry(_findJoinEntity.findJoinEntity)(parsedQuery), curry(_findSelects.findSelects)(parsedQuery), curry(_findConditions.findConditions)(parsedQuery), curry(_findJoinClauses.findJoinClauses)(parsedQuery), curry(_findMergeJoins.findMergeJoins)(parsedQuery), curry(_findSortClauses.findSortClauses)(parsedQuery), curry(_findGroupBy.findGroupBy)(parsedQuery), curry(_findLimit.findLimit)(parsedQuery), curry(_findOffset.findOffset)(parsedQuery))(query);
3633
- if (query) {
3754
+ // Clauses come at most once each, in a fixed order (see clauseOrder); each step reads only its own clause's body
3755
+ const split = (0, _splitClauses.splitClauses)(query);
3756
+ if (!split) {
3757
+ query = query.trim();
3634
3758
  if (_startsQuery.startsQueryMatch.test(query)) {
3635
- throw new Error(`Missing ; before: ${(0, _maskStrings.unmaskStrings)(query.trim(), '\'')}`);
3759
+ throw new Error(`Missing ; before: ${(0, _maskStrings.unmaskStrings)(query, '\'')}`);
3636
3760
  }
3637
3761
  throw new Error(`Unrecognized query value: ${(0, _maskStrings.unmaskStrings)(query, '\'')}`);
3638
3762
  }
3763
+ (0, _findJoinEntity.findJoinEntity)(parsedQuery, (0, _findCommand.findCommand)(parsedQuery, split.head));
3764
+ parsedQuery.where = (0, _conditionTree.allOf)([]);
3765
+ if (split.leading) {
3766
+ throw leftoverError(split.leading, split.head);
3767
+ }
3768
+ for (const {
3769
+ keyword,
3770
+ body
3771
+ } of split.clauses) {
3772
+ const {
3773
+ step,
3774
+ expected
3775
+ } = clauseSteps[keyword];
3776
+ const rest = step(parsedQuery, body);
3777
+ if (rest === body) {
3778
+ throw new Error(`Missing ${expected} after ${keyword}${body ? `: ${(0, _maskStrings.unmaskStrings)(body, '\'')}` : ''}`);
3779
+ }
3780
+ if (rest) {
3781
+ throw leftoverError(rest, `${keyword} ${body.slice(0, body.length - rest.length).trim()}`);
3782
+ }
3783
+ }
3639
3784
  return parsedQuery;
3640
3785
  };
3641
3786
  exports.parseQuery = parseQuery;
3642
- },{"./findCommand":48,"./findConditions":49,"./findGroupBy":50,"./findInsertValues":51,"./findJoinClauses":52,"./findJoinEntity":53,"./findLimit":54,"./findMergeJoins":55,"./findOffset":56,"./findSelects":57,"./findSortClauses":58,"./findUpdateValues":59,"./maskStrings":61,"./parseCreate":62,"./parseSchemaStatement":65,"./startsQuery":70,"si-funciona":360}],65:[function(require,module,exports){
3787
+ },{"../conditionTree":37,"./findCommand":48,"./findConditions":49,"./findGroupBy":50,"./findInsertValues":51,"./findJoinClauses":52,"./findJoinEntity":53,"./findLimit":54,"./findMergeJoins":55,"./findOffset":56,"./findSelects":57,"./findSortClauses":58,"./findUpdateValues":59,"./maskStrings":61,"./parseCreate":62,"./parseSchemaStatement":65,"./splitClauses":67,"./startsQuery":70}],65:[function(require,module,exports){
3643
3788
  "use strict";
3644
3789
 
3645
3790
  Object.defineProperty(exports, "__esModule", {
@@ -3725,15 +3870,6 @@ exports.parseSchemaStatement = parseSchemaStatement;
3725
3870
  },{"./maskStrings":61,"./startsQuery":70}],66:[function(require,module,exports){
3726
3871
  "use strict";
3727
3872
 
3728
- Object.defineProperty(exports, "__esModule", {
3729
- value: true
3730
- });
3731
- exports.removeFromQuery = void 0;
3732
- const removeFromQuery = (substring, query) => query.replace(substring, '').trim();
3733
- exports.removeFromQuery = removeFromQuery;
3734
- },{}],67:[function(require,module,exports){
3735
- "use strict";
3736
-
3737
3873
  Object.defineProperty(exports, "__esModule", {
3738
3874
  value: true
3739
3875
  });
@@ -3890,7 +4026,147 @@ const assertClosed = scanned => {
3890
4026
  return scanned;
3891
4027
  };
3892
4028
  exports.assertClosed = assertClosed;
3893
- },{}],68:[function(require,module,exports){
4029
+ },{}],67:[function(require,module,exports){
4030
+ "use strict";
4031
+
4032
+ Object.defineProperty(exports, "__esModule", {
4033
+ value: true
4034
+ });
4035
+ exports.whereHint = exports.splitClauses = exports.clauseOrder = void 0;
4036
+ require("core-js/modules/es.array.includes.js");
4037
+ require("core-js/modules/esnext.set.add-all.js");
4038
+ require("core-js/modules/esnext.set.delete-all.js");
4039
+ require("core-js/modules/esnext.set.difference.js");
4040
+ require("core-js/modules/esnext.set.every.js");
4041
+ require("core-js/modules/esnext.set.filter.js");
4042
+ require("core-js/modules/esnext.set.find.js");
4043
+ require("core-js/modules/esnext.set.intersection.js");
4044
+ require("core-js/modules/esnext.set.is-disjoint-from.js");
4045
+ require("core-js/modules/esnext.set.is-subset-of.js");
4046
+ require("core-js/modules/esnext.set.is-superset-of.js");
4047
+ require("core-js/modules/esnext.set.join.js");
4048
+ require("core-js/modules/esnext.set.map.js");
4049
+ require("core-js/modules/esnext.set.reduce.js");
4050
+ require("core-js/modules/esnext.set.some.js");
4051
+ require("core-js/modules/esnext.set.symmetric-difference.js");
4052
+ require("core-js/modules/esnext.set.union.js");
4053
+ var _maskStrings = require("./maskStrings");
4054
+ /**
4055
+ * The clauses each command allows, in the order they must come. create, drop, describe and show have their own
4056
+ * parsers and never get here.
4057
+ */
4058
+ const clauseOrder = exports.clauseOrder = {
4059
+ read: ['select', 'on', 'merge', 'where', 'group by', 'sort', 'limit', 'offset'],
4060
+ insert: ['values'],
4061
+ update: ['set', 'where'],
4062
+ delete: ['where']
4063
+ };
4064
+ const clauseWords = [...new Set(Object.values(clauseOrder).flat())];
4065
+ // The command, the record name and an optional joined record (`read workouts.exercises`, or `read workouts .exercises`)
4066
+ const headMatch = /^\s*(delete|insert|read|update)\s+([a-z0-9_-]+(\s*\.[a-z0-9_-]+)?)/i;
4067
+ // Placeholders (see maskStrings) as one token each, so their hex can't read as a word; words; then the two-character
4068
+ // comparators before any other single character
4069
+ const tokenMatch = new RegExp(`${_maskStrings.placeholderSource}|${_maskStrings.arraySource}|[a-z0-9_.-]+|<>|!=|>=|<=|\\S`, 'gi');
4070
+ // What can follow a field in a condition: a clause word followed by one of these is a field (`where limit = 1`)
4071
+ const conditionWords = ['=', '!=', '<>', '>', '<', '>=', '<=', 'in', 'between', 'like', 'is', 'exists', 'does'];
4072
+ // `and on` carries on the on clause instead of starting another; so does a merge straight after another
4073
+ // (`merge a.x with b.y merge c.z with d.w`, with or without `and`)
4074
+ const continues = {
4075
+ on: previous => previous === 'and',
4076
+ merge: () => true
4077
+ };
4078
+ // What a second where clause should have been: its conditions joined with and / or inside the first
4079
+ const whereHint = exports.whereHint = 'use and / or inside one where clause (where a = 1 and b = 2)';
4080
+ /**
4081
+ * Split a masked query (see maskStrings) into its clauses, reading its top-level words once, left to right. A clause
4082
+ * word counts only as a whole word outside brackets, and not where it must be a name: as a clause's first word
4083
+ * (`sort limit`), after a comma or `as`, or before a comparator (`where limit = 1`).
4084
+ * @param query
4085
+ * @returns undefined when the query doesn't start with read, insert, update or delete (left to the find* steps).
4086
+ * @throws {Error} For a joined record outside read, a clause the command doesn't allow, a repeated clause, a clause out
4087
+ * of order, or the 2.x forms of a second where (`and where`, `or where`, or a plain repeat).
4088
+ */
4089
+ const splitClauses = query => {
4090
+ const head = query.match(headMatch);
4091
+ if (!head) {
4092
+ return undefined;
4093
+ }
4094
+ const command = head[1].toLowerCase();
4095
+ const allowed = clauseOrder[command];
4096
+ const article = /^[aeiou]/.test(command) ? 'an' : 'a';
4097
+ if (head[3] && command !== 'read') {
4098
+ throw new Error(`A joined record (${head[2]}) isn't allowed in ${article} ${command} query`);
4099
+ }
4100
+ const tokens = [...query.slice(head[0].length).matchAll(tokenMatch)].map(({
4101
+ 0: text,
4102
+ index
4103
+ }) => ({
4104
+ text,
4105
+ lower: text.toLowerCase(),
4106
+ start: head[0].length + index
4107
+ }));
4108
+ const found = [];
4109
+ let depth = 0;
4110
+ let bodyStarts = false;
4111
+ for (let index = 0; index < tokens.length; index++) {
4112
+ const {
4113
+ lower,
4114
+ start
4115
+ } = tokens[index];
4116
+ const previous = tokens[index - 1]?.lower;
4117
+ const next = tokens[index + 1]?.lower;
4118
+ if (lower === '(' || lower === ')') {
4119
+ depth += lower === '(' ? 1 : -1;
4120
+ }
4121
+ const keyword = lower === 'group' && next === 'by' ? 'group by' : lower;
4122
+ const isName = bodyStarts || previous === ',' || previous === 'as' || conditionWords.includes(next);
4123
+ bodyStarts = false;
4124
+ if (depth > 0 || keyword === 'by' || !clauseWords.includes(keyword) || isName) {
4125
+ continue;
4126
+ }
4127
+ const current = found[found.length - 1]?.keyword;
4128
+ if (current === keyword && continues[keyword]?.(previous)) {
4129
+ continue;
4130
+ }
4131
+ // 2.x read `and where` / `or where` as `and` / `or`, and a second where as `and`
4132
+ const hasWhere = found.some(clause => clause.keyword === 'where');
4133
+ if (keyword === 'where' && hasWhere && (previous === 'and' || previous === 'or')) {
4134
+ throw new Error(`"${previous} where" is no longer supported: ${whereHint}`);
4135
+ }
4136
+ if (!allowed.includes(keyword)) {
4137
+ throw new Error(`"${keyword}" isn't allowed in ${article} ${command} query (allowed: ${allowed.join(', ')})`);
4138
+ }
4139
+ if (current && allowed.indexOf(keyword) < allowed.indexOf(current)) {
4140
+ throw new Error(`"${keyword}" can't come after "${current}" (order: ${allowed.join(', ')})`);
4141
+ }
4142
+ if (found.some(clause => clause.keyword === keyword)) {
4143
+ throw new Error(`Only one "${keyword}" clause is allowed${keyword === 'where' ? `: ${whereHint}` : ''}`);
4144
+ }
4145
+ if (keyword === 'group by') {
4146
+ index++;
4147
+ }
4148
+ found.push({
4149
+ keyword,
4150
+ start,
4151
+ end: tokens[index].start + tokens[index].text.length
4152
+ });
4153
+ bodyStarts = true;
4154
+ }
4155
+ return {
4156
+ command,
4157
+ head: head[0].trim(),
4158
+ leading: query.slice(head[0].length, found[0]?.start ?? query.length).trim(),
4159
+ clauses: found.map(({
4160
+ keyword,
4161
+ end
4162
+ }, index) => ({
4163
+ keyword,
4164
+ body: query.slice(end, found[index + 1]?.start ?? query.length).trim()
4165
+ }))
4166
+ };
4167
+ };
4168
+ exports.splitClauses = splitClauses;
4169
+ },{"./maskStrings":61,"core-js/modules/es.array.includes.js":222,"core-js/modules/esnext.set.add-all.js":260,"core-js/modules/esnext.set.delete-all.js":261,"core-js/modules/esnext.set.difference.js":262,"core-js/modules/esnext.set.every.js":263,"core-js/modules/esnext.set.filter.js":264,"core-js/modules/esnext.set.find.js":265,"core-js/modules/esnext.set.intersection.js":266,"core-js/modules/esnext.set.is-disjoint-from.js":267,"core-js/modules/esnext.set.is-subset-of.js":268,"core-js/modules/esnext.set.is-superset-of.js":269,"core-js/modules/esnext.set.join.js":270,"core-js/modules/esnext.set.map.js":271,"core-js/modules/esnext.set.reduce.js":272,"core-js/modules/esnext.set.some.js":273,"core-js/modules/esnext.set.symmetric-difference.js":274,"core-js/modules/esnext.set.union.js":275}],68:[function(require,module,exports){
3894
4170
  "use strict";
3895
4171
 
3896
4172
  Object.defineProperty(exports, "__esModule", {
@@ -3944,7 +4220,7 @@ const splitQueries = (queries = '') => {
3944
4220
  return [...semicolons, uncommented.length].map((end, index) => uncommented.slice(index === 0 ? 0 : semicolons[index - 1] + 1, end).trim()).filter(query => query.length > 0);
3945
4221
  };
3946
4222
  exports.splitQueries = splitQueries;
3947
- },{"./scanQuery":67,"core-js/modules/esnext.iterator.constructor.js":237,"core-js/modules/esnext.iterator.filter.js":240,"core-js/modules/esnext.iterator.reduce.js":245}],70:[function(require,module,exports){
4223
+ },{"./scanQuery":66,"core-js/modules/esnext.iterator.constructor.js":237,"core-js/modules/esnext.iterator.filter.js":240,"core-js/modules/esnext.iterator.reduce.js":245}],70:[function(require,module,exports){
3948
4224
  "use strict";
3949
4225
 
3950
4226
  Object.defineProperty(exports, "__esModule", {
@@ -4379,8 +4655,8 @@ const runQuery = async parsed => {
4379
4655
  // anything has looked at it at all. readEntity() (below) can't tell those two states apart on its own (it reloads
4380
4656
  // whenever the array is empty), so getConditionIndexedList reports which entities it resolved: once any indexed
4381
4657
  // condition has narrowed entity's rows, they are the candidates (every condition is applied to them below) and
4382
- // must not be reloaded. This used to ask whether EVERY condition was indexed, so `where date = 'none' and where
4383
- // reps = 10` reloaded every row and returned, updated or deleted all rows with reps 10.
4658
+ // must not be reloaded. This used to ask whether EVERY condition was indexed, so `where date = 'none' and reps =
4659
+ // 10` reloaded every row and returned, updated or deleted all rows with reps 10.
4384
4660
  const resolved = new Set();
4385
4661
  parsed = await (0, _getConditionIndexedList.getConditionIndexedList)(parsed, recordData, dataSet, resolved);
4386
4662
  const entityResolved = resolved.has(parsed.entity);
@@ -4422,10 +4698,11 @@ const runQuery = async parsed => {
4422
4698
  if (parsed.command !== 'delete' && parsed.command !== 'update') {
4423
4699
  dataSet[parsed.entity] = (0, _where.whereTree)(dataSet[parsed.entity], parsed.where, perPair ? parsed.joinEntity : undefined);
4424
4700
  }
4701
+ // Sorted before grouping, so the groups come in sorted order; limit and offset count groups once there are any
4425
4702
  dataSet[parsed.entity] = (0, _sortBy.sortBy)(dataSet[parsed.entity], parsed.sortClauses);
4426
- dataSet[parsed.entity] = (0, _limitOffset.limitOffset)(dataSet[parsed.entity], parsed.offset, parsed.limit);
4427
4703
  dataSet[parsed.entity] = (0, _groupBy.groupBy)(dataSet[parsed.entity], parsed.groupBy);
4428
- dataSet[parsed.entity] = (0, _selectFields.selectFields)(dataSet[parsed.entity], parsed.selectClauses);
4704
+ dataSet[parsed.entity] = (0, _limitOffset.limitOffset)(dataSet[parsed.entity], parsed.offset, parsed.limit);
4705
+ dataSet[parsed.entity] = (0, _selectFields.selectFields)(dataSet[parsed.entity], parsed.selectClauses, Boolean(parsed.groupBy?.length));
4429
4706
  return dataSet;
4430
4707
  };
4431
4708
  exports.runQuery = runQuery;