json-fs-query 2.2.1 → 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.
@@ -5,8 +5,9 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.getConditionIndexedList = void 0;
7
7
  require("core-js/modules/esnext.iterator.constructor.js");
8
- require("core-js/modules/esnext.iterator.filter.js");
9
8
  require("core-js/modules/esnext.iterator.flat-map.js");
9
+ require("core-js/modules/esnext.iterator.for-each.js");
10
+ require("core-js/modules/esnext.iterator.some.js");
10
11
  require("core-js/modules/esnext.set.add-all.js");
11
12
  require("core-js/modules/esnext.set.delete-all.js");
12
13
  require("core-js/modules/esnext.set.difference.js");
@@ -28,47 +29,123 @@ var _splitEntityProperty = require("./parsers/splitEntityProperty");
28
29
  var _retrieveRecord = require("./retrieveRecord");
29
30
  var _where = require("../queries/where");
30
31
  var _retrieveFile = require("./retrieveFile");
31
- var _getMatchingIndexes = require("./getMatchingIndexes");
32
+ var _retrieveIndexes = require("./retrieveIndexes");
33
+ var _isSingleFieldKey = require("./isSingleFieldKey");
34
+ var _conditionTree = require("./conditionTree");
32
35
  /**
33
- * Load only the rows the indexed conditions match, and leave the other conditions for where() to apply later.
34
- * Conditions are joined by `and`, so a row is loaded only when every indexed condition on its entity matched it: each
35
- * condition narrows the files the ones before it matched. (Each indexed condition used to replace the rows of the one
36
- * before, so `where date = 'x' and where exercise_id = 1` returned every row with exercise_id 1, whatever its date.)
36
+ * The entity and field a condition is on (`exercises.name` is on exercises, `name` on the queried entity).
37
+ * @param condition
38
+ * @param mainEntity
39
+ */
40
+ const conditionField = (condition, mainEntity) => (0, _isEntityProperty.isEntityProperty)(condition.property) ? (0, _splitEntityProperty.splitEntityProperty)(condition.property) : {
41
+ entity: mainEntity,
42
+ property: condition.property
43
+ };
44
+ /**
45
+ * The files an index says can match one condition, or undefined when the field has no single-field index.
46
+ * @param entity
47
+ * @param property
48
+ * @param condition
49
+ * @param recordData
50
+ * @param indexed - index files already read, by entity and field (each is read once per query)
51
+ */
52
+ const indexedFiles = async (entity, property, condition, recordData, indexed) => {
53
+ if (!Object.prototype.hasOwnProperty.call(recordData, entity)) {
54
+ recordData[entity] = await (0, _retrieveRecord.retrieveRecord)(entity);
55
+ }
56
+ if (!recordData[entity].keys.some(key => (0, _isSingleFieldKey.isSingleFieldKey)(key, property))) {
57
+ return undefined;
58
+ }
59
+ indexed[entity] = indexed[entity] || {};
60
+ if (!Object.prototype.hasOwnProperty.call(indexed[entity], property)) {
61
+ indexed[entity][property] = (await (0, _retrieveIndexes.retrieveIndexes)(entity, [property], recordData))[property];
62
+ }
63
+ return new Set((0, _where.where)(indexed[entity][property], {
64
+ property: 'value',
65
+ comparator: condition.comparator,
66
+ value: condition.value,
67
+ type: condition.type
68
+ }).flatMap(matchedRecord => matchedRecord.record));
69
+ };
70
+ /**
71
+ * The candidate files for a node, on the queried entity:
72
+ * - a condition: the files its index matches; unindexed (or on another entity): every row
73
+ * - `and`: the intersection of the children that narrow anything
74
+ * - `or`: the union, only when every branch narrows; otherwise every row
75
+ * - `not`: every row (its complement would have to leave out the unknown rows)
76
+ * @param node
77
+ * @param mainEntity
78
+ * @param recordData
79
+ * @param indexed
80
+ */
81
+ const candidatesOf = async (node, mainEntity, recordData, indexed) => {
82
+ if ((0, _conditionTree.isCondition)(node)) {
83
+ const {
84
+ entity,
85
+ property
86
+ } = conditionField(node, mainEntity);
87
+ return entity === mainEntity ? await indexedFiles(entity, property, node, recordData, indexed) : undefined;
88
+ }
89
+ if ('not' in node) {
90
+ return undefined;
91
+ }
92
+ if ('or' in node) {
93
+ const union = new Set();
94
+ for (const child of node.or) {
95
+ const files = await candidatesOf(child, mainEntity, recordData, indexed);
96
+ if (typeof files === 'undefined') {
97
+ return undefined;
98
+ }
99
+ files.forEach(file => union.add(file));
100
+ }
101
+ return union;
102
+ }
103
+ let intersection;
104
+ for (const child of node.and) {
105
+ const files = await candidatesOf(child, mainEntity, recordData, indexed);
106
+ if (typeof files !== 'undefined') {
107
+ intersection = typeof intersection === 'undefined' ? files : new Set([...intersection].filter(file => files.has(file)));
108
+ }
109
+ }
110
+ return intersection;
111
+ };
112
+ /**
113
+ * Load only the candidate rows the indexes allow for the where clause (see candidatesOf): an index only narrows which
114
+ * rows are read. Every condition, indexed or not, stays in parsed.where and is still applied to the candidates
115
+ * afterwards (runQuery), so an index can make a query faster but never changes its result (where-clauses decision 3;
116
+ * it used to remove the conditions it answered, and the 3 bugs fixed in #69 came from that split).
117
+ *
118
+ * An indexed condition on a joined entity's field (`exercises._id = 1`) narrows that entity's rows only when every
119
+ * row must meet it (directly under the top-level `and`, see requiredConditions). It stays in the tree too: the where
120
+ * clause is then evaluated per pair of a row and a joined row (see whereTree).
37
121
  * @param parsed
38
122
  * @param recordData
39
123
  * @param dataSet
40
- * @param resolved - Filled with each entity whose rows were loaded here: its rows in dataSet are final (possibly none)
41
- * and must not be reloaded, even when other conditions are left for where() (an empty list otherwise reads as "not
42
- * loaded yet", see readEntity).
124
+ * @param resolved - Filled with each entity whose candidate rows were loaded here: they must not be reloaded (an
125
+ * empty list otherwise reads as "not loaded yet", see readEntity).
43
126
  */
44
127
  const getConditionIndexedList = async (parsed, recordData, dataSet, resolved = new Set()) => {
45
- const reducedConditions = [];
46
128
  const matchedFiles = {};
47
129
  const indexed = {};
48
- for (const condition of parsed.conditions) {
49
- let entity = parsed.entity;
50
- let property = condition.property;
51
- if ((0, _isEntityProperty.isEntityProperty)(property)) {
52
- const entityProperty = (0, _splitEntityProperty.splitEntityProperty)(property);
53
- entity = entityProperty.entity;
54
- property = entityProperty.property;
130
+ if (typeof parsed.where !== 'undefined') {
131
+ const candidates = await candidatesOf(parsed.where, parsed.entity, recordData, indexed);
132
+ if (typeof candidates !== 'undefined') {
133
+ matchedFiles[parsed.entity] = candidates;
55
134
  }
56
- if (!Object.prototype.hasOwnProperty.call(recordData, entity)) {
57
- recordData[entity] = await (0, _retrieveRecord.retrieveRecord)(entity);
135
+ }
136
+ for (const condition of (0, _conditionTree.requiredConditions)(parsed.where)) {
137
+ const {
138
+ entity,
139
+ property
140
+ } = conditionField(condition, parsed.entity);
141
+ if (entity === parsed.entity) {
142
+ continue;
58
143
  }
59
- const narrowMatchedFiles = async (entity, property, recordData, condition, dataSet, indexed) => {
60
- const files = new Set((0, _where.where)(indexed[entity][property], {
61
- property: 'value',
62
- comparator: condition.comparator,
63
- value: condition.value,
64
- type: condition.type
65
- }).flatMap(matchedRecord => matchedRecord.record));
66
- matchedFiles[entity] = Object.prototype.hasOwnProperty.call(matchedFiles, entity) ? matchedFiles[entity].filter(file => files.has(file)) : [...files];
67
- };
68
- const isIndexed = await (0, _getMatchingIndexes.getMatchingIndexes)(entity, property, recordData, condition, dataSet, indexed, narrowMatchedFiles);
69
- if (!isIndexed) {
70
- reducedConditions.push(condition);
144
+ const files = await indexedFiles(entity, property, condition, recordData, indexed);
145
+ if (typeof files === 'undefined') {
146
+ continue;
71
147
  }
148
+ matchedFiles[entity] = Object.prototype.hasOwnProperty.call(matchedFiles, entity) ? new Set([...matchedFiles[entity]].filter(file => files.has(file))) : files;
72
149
  }
73
150
  for (const entity of Object.keys(matchedFiles)) {
74
151
  resolved.add(entity);
@@ -77,7 +154,6 @@ const getConditionIndexedList = async (parsed, recordData, dataSet, resolved = n
77
154
  dataSet[entity].push(await (0, _retrieveFile.retrieveFile)(`${entity}/${file}`));
78
155
  }
79
156
  }
80
- parsed.conditions = reducedConditions;
81
157
  return parsed;
82
158
  };
83
159
  exports.getConditionIndexedList = getConditionIndexedList;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getConditionIndexedList=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.filter.js"),require("core-js/modules/esnext.iterator.flat-map.js"),require("core-js/modules/esnext.set.add-all.js"),require("core-js/modules/esnext.set.delete-all.js"),require("core-js/modules/esnext.set.difference.js"),require("core-js/modules/esnext.set.every.js"),require("core-js/modules/esnext.set.filter.js"),require("core-js/modules/esnext.set.find.js"),require("core-js/modules/esnext.set.intersection.js"),require("core-js/modules/esnext.set.is-disjoint-from.js"),require("core-js/modules/esnext.set.is-subset-of.js"),require("core-js/modules/esnext.set.is-superset-of.js"),require("core-js/modules/esnext.set.join.js"),require("core-js/modules/esnext.set.map.js"),require("core-js/modules/esnext.set.reduce.js"),require("core-js/modules/esnext.set.some.js"),require("core-js/modules/esnext.set.symmetric-difference.js"),require("core-js/modules/esnext.set.union.js");var _isEntityProperty=require("./parsers/isEntityProperty"),_splitEntityProperty=require("./parsers/splitEntityProperty"),_retrieveRecord=require("./retrieveRecord"),_where=require("../queries/where"),_retrieveFile=require("./retrieveFile"),_getMatchingIndexes=require("./getMatchingIndexes");const getConditionIndexedList=async(e,r,t,s=new Set)=>{const o=[],i={},n={};for(const s of e.conditions){let u=e.entity,c=s.property;if((0,_isEntityProperty.isEntityProperty)(c)){const e=(0,_splitEntityProperty.splitEntityProperty)(c);u=e.entity,c=e.property}Object.prototype.hasOwnProperty.call(r,u)||(r[u]=await(0,_retrieveRecord.retrieveRecord)(u));const d=async(e,r,t,s,o,n)=>{const u=new Set((0,_where.where)(n[e][r],{property:"value",comparator:s.comparator,value:s.value,type:s.type}).flatMap((e=>e.record)));i[e]=Object.prototype.hasOwnProperty.call(i,e)?i[e].filter((e=>u.has(e))):[...u]};await(0,_getMatchingIndexes.getMatchingIndexes)(u,c,r,s,t,n,d)||o.push(s)}for(const e of Object.keys(i)){s.add(e),t[e]=[];for(const r of i[e])t[e].push(await(0,_retrieveFile.retrieveFile)(`${e}/${r}`))}return e.conditions=o,e};exports.getConditionIndexedList=getConditionIndexedList;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.getConditionIndexedList=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.flat-map.js"),require("core-js/modules/esnext.iterator.for-each.js"),require("core-js/modules/esnext.iterator.some.js"),require("core-js/modules/esnext.set.add-all.js"),require("core-js/modules/esnext.set.delete-all.js"),require("core-js/modules/esnext.set.difference.js"),require("core-js/modules/esnext.set.every.js"),require("core-js/modules/esnext.set.filter.js"),require("core-js/modules/esnext.set.find.js"),require("core-js/modules/esnext.set.intersection.js"),require("core-js/modules/esnext.set.is-disjoint-from.js"),require("core-js/modules/esnext.set.is-subset-of.js"),require("core-js/modules/esnext.set.is-superset-of.js"),require("core-js/modules/esnext.set.join.js"),require("core-js/modules/esnext.set.map.js"),require("core-js/modules/esnext.set.reduce.js"),require("core-js/modules/esnext.set.some.js"),require("core-js/modules/esnext.set.symmetric-difference.js"),require("core-js/modules/esnext.set.union.js");var _isEntityProperty=require("./parsers/isEntityProperty"),_splitEntityProperty=require("./parsers/splitEntityProperty"),_retrieveRecord=require("./retrieveRecord"),_where=require("../queries/where"),_retrieveFile=require("./retrieveFile"),_retrieveIndexes=require("./retrieveIndexes"),_isSingleFieldKey=require("./isSingleFieldKey"),_conditionTree=require("./conditionTree");const conditionField=(e,r)=>(0,_isEntityProperty.isEntityProperty)(e.property)?(0,_splitEntityProperty.splitEntityProperty)(e.property):{entity:r,property:e.property},indexedFiles=async(e,r,t,s,i)=>{if(Object.prototype.hasOwnProperty.call(s,e)||(s[e]=await(0,_retrieveRecord.retrieveRecord)(e)),s[e].keys.some((e=>(0,_isSingleFieldKey.isSingleFieldKey)(e,r))))return i[e]=i[e]||{},Object.prototype.hasOwnProperty.call(i[e],r)||(i[e][r]=(await(0,_retrieveIndexes.retrieveIndexes)(e,[r],s))[r]),new Set((0,_where.where)(i[e][r],{property:"value",comparator:t.comparator,value:t.value,type:t.type}).flatMap((e=>e.record)))},candidatesOf=async(e,r,t,s)=>{if((0,_conditionTree.isCondition)(e)){const{entity:i,property:o}=conditionField(e,r);return i===r?await indexedFiles(i,o,e,t,s):void 0}if("not"in e)return;if("or"in e){const i=new Set;for(const o of e.or){const e=await candidatesOf(o,r,t,s);if(void 0===e)return;e.forEach((e=>i.add(e)))}return i}let i;for(const o of e.and){const e=await candidatesOf(o,r,t,s);void 0!==e&&(i=void 0===i?e:new Set([...i].filter((r=>e.has(r)))))}return i},getConditionIndexedList=async(e,r,t,s=new Set)=>{const i={},o={};if(void 0!==e.where){const t=await candidatesOf(e.where,e.entity,r,o);void 0!==t&&(i[e.entity]=t)}for(const t of(0,_conditionTree.requiredConditions)(e.where)){const{entity:s,property:n}=conditionField(t,e.entity);if(s===e.entity)continue;const d=await indexedFiles(s,n,t,r,o);void 0!==d&&(i[s]=Object.prototype.hasOwnProperty.call(i,s)?new Set([...i[s]].filter((e=>d.has(e)))):d)}for(const e of Object.keys(i)){s.add(e),t[e]=[];for(const r of i[e])t[e].push(await(0,_retrieveFile.retrieveFile)(`${e}/${r}`))}return e};exports.getConditionIndexedList=getConditionIndexedList;
@@ -8,13 +8,15 @@ export type ParsedCondition = {
8
8
  type?: string;
9
9
  };
10
10
  /**
11
- * Extract condition clauses from the query: `where <field> <comparator> <value>`, or `where <field> is null`,
12
- * `is not null`, `exists` or `does not exist` (no value), each further one joined with `and where`. A comparison
13
- * with the literal null (`= null`) is refused: it would never match, as null equals nothing (see where). `or where` is refused until or is built (TASKS: the where-clauses epic): it used to be read the same as
14
- * `and where`, which silently returned the wrong rows.
11
+ * Extract the where clause from the query into a tree (see ConditionNode): `where` followed by conditions joined with
12
+ * `and` / `or`, `not`, and brackets, nested to any depth. Each condition is `<field> <comparator> <value>`, or
13
+ * `<field> is null`, `is not null`, `exists` or `does not exist`. A comparison with the literal null (`= null`) is
14
+ * refused: it would never match, as null equals nothing (see where). The expression ends at the next clause keyword
15
+ * outside brackets, so clauses may come in any order; several separate `where`s (or `and where` after another clause)
16
+ * are joined with `and`, as before.
15
17
  * @param parsed
16
18
  * @param query
17
- * @throws {Error} For `or where`, and for a comparison with null.
19
+ * @throws {Error} For the errors readExpression and readCondition describe, and `or where` after another clause.
18
20
  */
19
21
  export declare const findConditions: (parsed: ParsedQuery, query: string) => string;
20
22
  //# sourceMappingURL=findConditions.d.ts.map
@@ -5,50 +5,178 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.findConditions = void 0;
7
7
  require("core-js/modules/es.array.includes.js");
8
- var _removeFromQuery = require("./removeFromQuery");
8
+ require("core-js/modules/esnext.iterator.constructor.js");
9
+ require("core-js/modules/esnext.iterator.flat-map.js");
9
10
  var _parseLiteralValue = require("./parseLiteralValue");
11
+ var _conditionTree = require("../conditionTree");
10
12
  var _maskStrings = require("./maskStrings");
11
- // `and`/`or`/`where` count only as whole words: in `on t.x = u.color where ...` the `or` is part of the name `color`
12
- const conditionMatch = new RegExp(`(^|[^a-z0-9._-])(?:(and|or)\\s+)?(where)\\s+([a-z0-9_-]+\\.?[a-z0-9_-]*)\\s+(?:(is\\s+not\\s+null|is\\s+null|exists|does\\s+not\\s+exist)(?![a-z0-9_.-])|(=|!=|>|<|>=|<=|in|between|like)\\s+(${_maskStrings.placeholderSource}|(${_parseLiteralValue.numberSource})|${_maskStrings.arraySource}|(null)|(true)|(false)))`, 'ig');
13
+ // Keywords count only as whole words: in `on t.x = u.color where ...` the `or` is part of the name `color`
14
+ const wordEnd = '(?![a-z0-9_.-])';
15
+ const keyword = words => new RegExp(`^(?:${words})${wordEnd}`, 'i');
16
+ // One condition, at the start of the text: a field, then `is [not] null` / `exists` / `does not exist`, or a comparator
17
+ // and a value (strings and arrays are placeholders by then, see maskStrings)
18
+ 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');
19
+ // The clause keywords that end a where expression (outside brackets), and `and on` (a join clause's own `and`)
20
+ const expressionEnd = keyword('sort|group|limit|offset|select|merge|on|where|and\\s+on');
21
+ const whereMatch = new RegExp(`(^|[^a-z0-9._-])((?:(and|or)\\s+)?where)${wordEnd}`, 'i');
13
22
  /**
14
- * Extract condition clauses from the query: `where <field> <comparator> <value>`, or `where <field> is null`,
15
- * `is not null`, `exists` or `does not exist` (no value), each further one joined with `and where`. A comparison
16
- * with the literal null (`= null`) is refused: it would never match, as null equals nothing (see where). `or where` is refused until or is built (TASKS: the where-clauses epic): it used to be read the same as
17
- * `and where`, which silently returned the wrong rows.
18
- * @param parsed
19
- * @param query
20
- * @throws {Error} For `or where`, and for a comparison with null.
23
+ * Read one condition from a match of conditionMatch.
24
+ * @param match
25
+ * @throws {Error} For a comparison with null, and a null between bound: they could never match (null equals nothing).
21
26
  */
22
- const findConditions = (parsed, query) => {
23
- const conditionsFound = query.matchAll(conditionMatch);
24
- // @ts-ignore
25
- for (const condition of conditionsFound) {
26
- if (typeof condition[2] !== 'undefined' && condition[2].toLowerCase() === 'or') {
27
- throw new Error('`or where` is not supported yet: use `and where`, or run one query per alternative');
27
+ const readCondition = match => {
28
+ const property = match[1];
29
+ if (typeof match[2] !== 'undefined') {
30
+ return {
31
+ property,
32
+ comparator: match[2].toLowerCase().replace(/\s+/g, ' '),
33
+ value: null
34
+ };
35
+ }
36
+ const comparator = match[3].toLowerCase();
37
+ const value = (0, _parseLiteralValue.parseLiteralValue)(match, 5);
38
+ if (value === null) {
39
+ throw new Error(`\`${property} ${comparator} null\` never matches (null equals nothing, as in SQL): use \`${property} is null\` or \`${property} is not null\``);
40
+ }
41
+ if (comparator === 'between' && Array.isArray(value) && value.includes(null)) {
42
+ throw new Error(`\`${property} between\` can't have a null bound: it would never match`);
43
+ }
44
+ return {
45
+ property,
46
+ comparator,
47
+ value
48
+ };
49
+ };
50
+ /**
51
+ * Join nodes with `and` or `or`, flattening a child of the same kind into its parent (`a and (b and c)` is one `and` of
52
+ * three), so the conditions every row must meet are all directly under the top-level `and` (see requiredConditions).
53
+ * @param kind
54
+ * @param nodes
55
+ */
56
+ const join = (kind, nodes) => {
57
+ const children = nodes.flatMap(node => !(0, _conditionTree.isCondition)(node) && kind in node ? node[kind] : [node]);
58
+ return children.length === 1 ? children[0] : kind === 'and' ? {
59
+ and: children
60
+ } : {
61
+ or: children
62
+ };
63
+ };
64
+ /**
65
+ * Read one where expression from the masked query, starting at `start` (just after its `where`). Precedence, loosest
66
+ * first: `or`, `and`, `not`; brackets group. `and where` / `or where` are the older spellings of `and` / `or`.
67
+ * @param text
68
+ * @param start
69
+ * @returns The expression's tree and where it ends (the next clause keyword outside brackets, or the end).
70
+ * @throws {Error} For an unbalanced bracket, empty brackets, or an operator with no condition after it.
71
+ */
72
+ const readExpression = (text, start) => {
73
+ let at = start;
74
+ const rest = () => text.slice(at);
75
+ const skipSpace = () => {
76
+ at += rest().length - rest().trimStart().length;
77
+ };
78
+ const take = pattern => {
79
+ skipSpace();
80
+ const found = rest().match(pattern);
81
+ if (!found) {
82
+ return undefined;
83
+ }
84
+ at += found[0].length;
85
+ return found[0];
86
+ };
87
+ const shown = () => (0, _maskStrings.unmaskStrings)(rest().trim(), '\'');
88
+ const factor = after => {
89
+ skipSpace();
90
+ // A condition first, so a field named like a keyword (`not = 1`, `exists exists`) still reads as a field
91
+ const condition = rest().match(conditionMatch);
92
+ if (condition) {
93
+ at += condition[0].length;
94
+ return readCondition(condition);
95
+ }
96
+ if (take(keyword('not'))) {
97
+ return {
98
+ not: factor('not')
99
+ };
28
100
  }
29
- if (typeof condition[5] !== 'undefined') {
30
- parsed['conditions'].push({
31
- property: condition[4],
32
- comparator: condition[5].toLowerCase().replace(/\s+/g, ' '),
33
- value: null
34
- });
35
- } else {
36
- const value = (0, _parseLiteralValue.parseLiteralValue)(condition, 8);
37
- if (value === null) {
38
- throw new Error(`\`${condition[4]} ${condition[6]} null\` never matches (null equals nothing, as in SQL): use \`${condition[4]} is null\` or \`${condition[4]} is not null\``);
101
+ if (take(/^\(/)) {
102
+ skipSpace();
103
+ if (rest().startsWith(')')) {
104
+ throw new Error('Empty brackets in a where clause');
39
105
  }
40
- if (condition[6].toLowerCase() === 'between' && Array.isArray(value) && value.includes(null)) {
41
- throw new Error(`\`${condition[4]} between\` can't have a null bound: it would never match`);
106
+ const node = expression('(');
107
+ // The query scanner already refuses unbalanced brackets (scanQuery); this guards a direct call
108
+ if (!take(/^\)/)) {
109
+ throw new Error(`Missing ) ${rest().trim() ? `before: ${shown()}` : 'at the end of the where clause'}`);
42
110
  }
43
- parsed['conditions'].push({
44
- property: condition[4],
45
- comparator: condition[6],
46
- value
47
- });
111
+ return node;
112
+ }
113
+ throw new Error(`Missing a condition after ${after}${rest().trim() ? `: ${shown()}` : ''}`);
114
+ };
115
+ const term = after => {
116
+ const nodes = [factor(after)];
117
+ for (;;) {
118
+ skipSpace();
119
+ if (keyword('and\\s+on').test(rest())) {
120
+ break;
121
+ }
122
+ const connector = take(keyword('and(?:\\s+where)?'));
123
+ if (!connector) {
124
+ break;
125
+ }
126
+ nodes.push(factor(connector.toLowerCase().replace(/\s+/g, ' ')));
127
+ }
128
+ return join('and', nodes);
129
+ };
130
+ const expression = after => {
131
+ const nodes = [term(after)];
132
+ for (;;) {
133
+ const connector = take(keyword('or(?:\\s+where)?'));
134
+ if (!connector) {
135
+ break;
136
+ }
137
+ nodes.push(term(connector.toLowerCase().replace(/\s+/g, ' ')));
138
+ }
139
+ return join('or', nodes);
140
+ };
141
+ const node = expression('where');
142
+ skipSpace();
143
+ if (rest().startsWith(')')) {
144
+ throw new Error(`Unexpected ) in a where clause: ${shown()}`);
145
+ }
146
+ if (rest() && !expressionEnd.test(rest()) && conditionMatch.test(rest())) {
147
+ throw new Error(`Missing and/or before: ${shown()}`);
148
+ }
149
+ return {
150
+ node,
151
+ end: at
152
+ };
153
+ };
154
+ /**
155
+ * Extract the where clause from the query into a tree (see ConditionNode): `where` followed by conditions joined with
156
+ * `and` / `or`, `not`, and brackets, nested to any depth. Each condition is `<field> <comparator> <value>`, or
157
+ * `<field> is null`, `is not null`, `exists` or `does not exist`. A comparison with the literal null (`= null`) is
158
+ * refused: it would never match, as null equals nothing (see where). The expression ends at the next clause keyword
159
+ * outside brackets, so clauses may come in any order; several separate `where`s (or `and where` after another clause)
160
+ * are joined with `and`, as before.
161
+ * @param parsed
162
+ * @param query
163
+ * @throws {Error} For the errors readExpression and readCondition describe, and `or where` after another clause.
164
+ */
165
+ const findConditions = (parsed, query) => {
166
+ const expressions = [];
167
+ for (let found = query.match(whereMatch); found; found = query.match(whereMatch)) {
168
+ if ((found[3] || '').toLowerCase() === 'or') {
169
+ throw new Error('`or where` must follow the conditions it joins: put them in one where clause');
48
170
  }
49
- // Leave out the character before the condition (group 1): it isn't part of it, and an earlier removal may have trimmed it
50
- query = (0, _removeFromQuery.removeFromQuery)(condition[0].slice(condition[1].length), query);
171
+ const whereStart = found.index + found[1].length;
172
+ const {
173
+ node,
174
+ end
175
+ } = readExpression(query, whereStart + found[2].length);
176
+ expressions.push(node);
177
+ query = `${query.slice(0, whereStart)} ${query.slice(end)}`.replace(/\s+/g, ' ').trim();
51
178
  }
179
+ parsed['where'] = expressions.length ? join('and', expressions) : (0, _conditionTree.allOf)([]);
52
180
  return query;
53
181
  };
54
182
  exports.findConditions = findConditions;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.findConditions=void 0,require("core-js/modules/es.array.includes.js");var _removeFromQuery=require("./removeFromQuery"),_parseLiteralValue=require("./parseLiteralValue"),_maskStrings=require("./maskStrings");const conditionMatch=new RegExp(`(^|[^a-z0-9._-])(?:(and|or)\\s+)?(where)\\s+([a-z0-9_-]+\\.?[a-z0-9_-]*)\\s+(?:(is\\s+not\\s+null|is\\s+null|exists|does\\s+not\\s+exist)(?![a-z0-9_.-])|(=|!=|>|<|>=|<=|in|between|like)\\s+(${_maskStrings.placeholderSource}|(${_parseLiteralValue.numberSource})|${_maskStrings.arraySource}|(null)|(true)|(false)))`,"ig"),findConditions=(e,r)=>{const o=r.matchAll(conditionMatch);for(const n of o){if(void 0!==n[2]&&"or"===n[2].toLowerCase())throw new Error("`or where` is not supported yet: use `and where`, or run one query per alternative");if(void 0!==n[5])e.conditions.push({property:n[4],comparator:n[5].toLowerCase().replace(/\s+/g," "),value:null});else{const r=(0,_parseLiteralValue.parseLiteralValue)(n,8);if(null===r)throw new Error(`\`${n[4]} ${n[6]} null\` never matches (null equals nothing, as in SQL): use \`${n[4]} is null\` or \`${n[4]} is not null\``);if("between"===n[6].toLowerCase()&&Array.isArray(r)&&r.includes(null))throw new Error(`\`${n[4]} between\` can't have a null bound: it would never match`);e.conditions.push({property:n[4],comparator:n[6],value:r})}r=(0,_removeFromQuery.removeFromQuery)(n[0].slice(n[1].length),r)}return r};exports.findConditions=findConditions;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.findConditions=void 0,require("core-js/modules/es.array.includes.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.flat-map.js");var _parseLiteralValue=require("./parseLiteralValue"),_conditionTree=require("../conditionTree"),_maskStrings=require("./maskStrings");const wordEnd="(?![a-z0-9_.-])",keyword=e=>new RegExp(`^(?:${e})${wordEnd}`,"i"),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"),expressionEnd=keyword("sort|group|limit|offset|select|merge|on|where|and\\s+on"),whereMatch=new RegExp(`(^|[^a-z0-9._-])((?:(and|or)\\s+)?where)${wordEnd}`,"i"),readCondition=e=>{const r=e[1];if(void 0!==e[2])return{property:r,comparator:e[2].toLowerCase().replace(/\s+/g," "),value:null};const n=e[3].toLowerCase(),t=(0,_parseLiteralValue.parseLiteralValue)(e,5);if(null===t)throw new Error(`\`${r} ${n} null\` never matches (null equals nothing, as in SQL): use \`${r} is null\` or \`${r} is not null\``);if("between"===n&&Array.isArray(t)&&t.includes(null))throw new Error(`\`${r} between\` can't have a null bound: it would never match`);return{property:r,comparator:n,value:t}},join=(e,r)=>{const n=r.flatMap((r=>!(0,_conditionTree.isCondition)(r)&&e in r?r[e]:[r]));return 1===n.length?n[0]:"and"===e?{and:n}:{or:n}},readExpression=(e,r)=>{let n=r;const t=()=>e.slice(n),o=()=>{n+=t().length-t().trimStart().length},s=e=>{o();const r=t().match(e);if(r)return n+=r[0].length,r[0]},i=()=>(0,_maskStrings.unmaskStrings)(t().trim(),"'"),a=e=>{o();const r=t().match(conditionMatch);if(r)return n+=r[0].length,readCondition(r);if(s(keyword("not")))return{not:a("not")};if(s(/^\(/)){if(o(),t().startsWith(")"))throw new Error("Empty brackets in a where clause");const e=d("(");if(!s(/^\)/))throw new Error("Missing ) "+(t().trim()?`before: ${i()}`:"at the end of the where clause"));return e}throw new Error(`Missing a condition after ${e}${t().trim()?`: ${i()}`:""}`)},l=e=>{const r=[a(e)];for(;o(),!keyword("and\\s+on").test(t());){const e=s(keyword("and(?:\\s+where)?"));if(!e)break;r.push(a(e.toLowerCase().replace(/\s+/g," ")))}return join("and",r)},d=e=>{const r=[l(e)];for(;;){const e=s(keyword("or(?:\\s+where)?"));if(!e)break;r.push(l(e.toLowerCase().replace(/\s+/g," ")))}return join("or",r)},c=d("where");if(o(),t().startsWith(")"))throw new Error(`Unexpected ) in a where clause: ${i()}`);if(t()&&!expressionEnd.test(t())&&conditionMatch.test(t()))throw new Error(`Missing and/or before: ${i()}`);return{node:c,end:n}},findConditions=(e,r)=>{const n=[];for(let e=r.match(whereMatch);e;e=r.match(whereMatch)){if("or"===(e[3]||"").toLowerCase())throw new Error("`or where` must follow the conditions it joins: put them in one where clause");const t=e.index+e[1].length,{node:o,end:s}=readExpression(r,t+e[2].length);n.push(o),r=`${r.slice(0,t)} ${r.slice(s)}`.replace(/\s+/g," ").trim()}return e.where=n.length?join("and",n):(0,_conditionTree.allOf)([]),r};exports.findConditions=findConditions;
@@ -3,7 +3,7 @@ import { ParsedInsertRow } from './findInsertValues';
3
3
  import { ParsedUpdateValues } from './findUpdateValues';
4
4
  import { ParsedJoinEntity } from './findJoinEntity';
5
5
  import { ParsedSelect } from './findSelects';
6
- import { ParsedCondition } from './findConditions';
6
+ import { ConditionNode } from '../conditionTree';
7
7
  import { ParsedJoinClause } from './findJoinClauses';
8
8
  import { ParsedMergeJoin } from './findMergeJoins';
9
9
  import { ParsedSortClause } from './findSortClauses';
@@ -18,7 +18,7 @@ export type ParsedQuery = {
18
18
  selectClauses: ParsedSelect[];
19
19
  insertValues: ParsedInsertRow[];
20
20
  updateValues: ParsedUpdateValues;
21
- conditions: ParsedCondition[];
21
+ where: ConditionNode | undefined;
22
22
  joinClauses: ParsedJoinClause[];
23
23
  mergeJoins: ParsedMergeJoin[];
24
24
  sortClauses: ParsedSortClause[];
@@ -34,7 +34,7 @@ const parseQuery = query => {
34
34
  selectClauses: [],
35
35
  insertValues: [],
36
36
  updateValues: {},
37
- conditions: [],
37
+ where: undefined,
38
38
  joinClauses: [],
39
39
  mergeJoins: [],
40
40
  sortClauses: [],
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.parseQuery=void 0;var _siFunciona=_interopRequireDefault(require("si-funciona")),_findCommand=require("./findCommand"),_findInsertValues=require("./findInsertValues"),_findUpdateValues=require("./findUpdateValues"),_findJoinEntity=require("./findJoinEntity"),_findSelects=require("./findSelects"),_findConditions=require("./findConditions"),_findJoinClauses=require("./findJoinClauses"),_findMergeJoins=require("./findMergeJoins"),_findSortClauses=require("./findSortClauses"),_findGroupBy=require("./findGroupBy"),_findLimit=require("./findLimit"),_findOffset=require("./findOffset"),_maskStrings=require("./maskStrings"),_parseCreate=require("./parseCreate"),_parseSchemaStatement=require("./parseSchemaStatement"),_startsQuery=require("./startsQuery");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const parseQuery=e=>{const i={command:void 0,entity:void 0,joinEntity:void 0,selectClauses:[],insertValues:[],updateValues:{},conditions:[],joinClauses:[],mergeJoins:[],sortClauses:[],groupBy:void 0,limit:void 0,offset:void 0,create:void 0,ifExists:void 0},{curry:n,pipe:r}=_siFunciona.default;if(e=(0,_maskStrings.maskArrays)((0,_maskStrings.maskStrings)(e)),_parseCreate.createMatch.test(e)){const{entity:n,create:r}=(0,_parseCreate.parseCreate)(e);return Object.assign(i,{command:"create",entity:n,create:r})}if(_parseSchemaStatement.schemaStatementMatch.test(e))return Object.assign(i,(0,_parseSchemaStatement.parseSchemaStatement)(e));if(e=r(n(_findCommand.findCommand)(i),n(_findInsertValues.findInsertValues)(i),n(_findUpdateValues.findUpdateValues)(i),n(_findJoinEntity.findJoinEntity)(i),n(_findSelects.findSelects)(i),n(_findConditions.findConditions)(i),n(_findJoinClauses.findJoinClauses)(i),n(_findMergeJoins.findMergeJoins)(i),n(_findSortClauses.findSortClauses)(i),n(_findGroupBy.findGroupBy)(i),n(_findLimit.findLimit)(i),n(_findOffset.findOffset)(i))(e)){if(_startsQuery.startsQueryMatch.test(e))throw new Error(`Missing ; before: ${(0,_maskStrings.unmaskStrings)(e.trim(),"'")}`);throw new Error(`Unrecognized query value: ${(0,_maskStrings.unmaskStrings)(e,"'")}`)}return i};exports.parseQuery=parseQuery;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.parseQuery=void 0;var _siFunciona=_interopRequireDefault(require("si-funciona")),_findCommand=require("./findCommand"),_findInsertValues=require("./findInsertValues"),_findUpdateValues=require("./findUpdateValues"),_findJoinEntity=require("./findJoinEntity"),_findSelects=require("./findSelects"),_findConditions=require("./findConditions"),_findJoinClauses=require("./findJoinClauses"),_findMergeJoins=require("./findMergeJoins"),_findSortClauses=require("./findSortClauses"),_findGroupBy=require("./findGroupBy"),_findLimit=require("./findLimit"),_findOffset=require("./findOffset"),_maskStrings=require("./maskStrings"),_parseCreate=require("./parseCreate"),_parseSchemaStatement=require("./parseSchemaStatement"),_startsQuery=require("./startsQuery");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const parseQuery=e=>{const i={command:void 0,entity:void 0,joinEntity:void 0,selectClauses:[],insertValues:[],updateValues:{},where:void 0,joinClauses:[],mergeJoins:[],sortClauses:[],groupBy:void 0,limit:void 0,offset:void 0,create:void 0,ifExists:void 0},{curry:r,pipe:n}=_siFunciona.default;if(e=(0,_maskStrings.maskArrays)((0,_maskStrings.maskStrings)(e)),_parseCreate.createMatch.test(e)){const{entity:r,create:n}=(0,_parseCreate.parseCreate)(e);return Object.assign(i,{command:"create",entity:r,create:n})}if(_parseSchemaStatement.schemaStatementMatch.test(e))return Object.assign(i,(0,_parseSchemaStatement.parseSchemaStatement)(e));if(e=n(r(_findCommand.findCommand)(i),r(_findInsertValues.findInsertValues)(i),r(_findUpdateValues.findUpdateValues)(i),r(_findJoinEntity.findJoinEntity)(i),r(_findSelects.findSelects)(i),r(_findConditions.findConditions)(i),r(_findJoinClauses.findJoinClauses)(i),r(_findMergeJoins.findMergeJoins)(i),r(_findSortClauses.findSortClauses)(i),r(_findGroupBy.findGroupBy)(i),r(_findLimit.findLimit)(i),r(_findOffset.findOffset)(i))(e)){if(_startsQuery.startsQueryMatch.test(e))throw new Error(`Missing ; before: ${(0,_maskStrings.unmaskStrings)(e.trim(),"'")}`);throw new Error(`Unrecognized query value: ${(0,_maskStrings.unmaskStrings)(e,"'")}`)}return i};exports.parseQuery=parseQuery;
@@ -4,9 +4,6 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.runQuery = void 0;
7
- require("core-js/modules/esnext.iterator.constructor.js");
8
- require("core-js/modules/esnext.iterator.find.js");
9
- require("core-js/modules/esnext.iterator.reduce.js");
10
7
  require("core-js/modules/esnext.set.add-all.js");
11
8
  require("core-js/modules/esnext.set.delete-all.js");
12
9
  require("core-js/modules/esnext.set.difference.js");
@@ -44,6 +41,8 @@ var _getJoinIndexedList = require("./getJoinIndexedList");
44
41
  var _isEntityProperty = require("./parsers/isEntityProperty");
45
42
  var _splitEntityProperty = require("./parsers/splitEntityProperty");
46
43
  var _fieldType = require("./fieldType");
44
+ var _conditionTree = require("./conditionTree");
45
+ var _conditionEntities = require("./conditionEntities");
47
46
  /**
48
47
  * Give each condition and sort clause its field's declared type, so where and sortBy compare by it
49
48
  * (a joined entity's field is looked up on that entity's record).
@@ -51,7 +50,7 @@ var _fieldType = require("./fieldType");
51
50
  * @param recordData
52
51
  */
53
52
  const typeClauses = async (parsed, recordData) => {
54
- for (const clause of [...parsed.conditions, ...parsed.sortClauses]) {
53
+ for (const clause of [...(0, _conditionTree.conditionsOf)(parsed.where), ...parsed.sortClauses]) {
55
54
  let entity = parsed.entity;
56
55
  let property = clause.property;
57
56
  if ((0, _isEntityProperty.isEntityProperty)(property)) {
@@ -67,6 +66,8 @@ const typeClauses = async (parsed, recordData) => {
67
66
  }
68
67
  };
69
68
  const runQuery = async parsed => {
69
+ // Checked before anything is read: which entity each condition is on, and whether it needs per-pair evaluation
70
+ const perPair = typeof parsed.where !== 'undefined' && (0, _conditionEntities.conditionEntities)(parsed);
70
71
  // Schema statements act on the database, not on entries (for create there is no record to read yet)
71
72
  switch (parsed.command) {
72
73
  case 'create':
@@ -86,16 +87,6 @@ const runQuery = async parsed => {
86
87
  [parsed.entity]: await (0, _showRecords.showRecords)()
87
88
  };
88
89
  }
89
- // update/delete apply their conditions before any join or merge runs, so a condition on another entity's field
90
- // can't be checked there. An indexed one used to be dropped once its index had narrowed the joined rows, and the
91
- // command then hit every row the other conditions allowed (`delete workouts.exercises on ... where
92
- // exercises._id = 1` deleted every workout)
93
- if (parsed.command === 'update' || parsed.command === 'delete') {
94
- const other = parsed.conditions.find(condition => (0, _isEntityProperty.isEntityProperty)(condition.property) && (0, _splitEntityProperty.splitEntityProperty)(condition.property).entity !== parsed.entity);
95
- if (other) {
96
- throw new Error(`${parsed.command} can only use conditions on ${parsed.entity}'s own fields, not ${other.property}`);
97
- }
98
- }
99
90
  const recordData = {
100
91
  [parsed.entity]: await (0, _retrieveRecord.retrieveRecord)(parsed.entity)
101
92
  };
@@ -109,7 +100,7 @@ const runQuery = async parsed => {
109
100
  // An indexed condition that matches nothing leaves dataSet[entity] at [] - the same state it starts in before
110
101
  // anything has looked at it at all. readEntity() (below) can't tell those two states apart on its own (it reloads
111
102
  // whenever the array is empty), so getConditionIndexedList reports which entities it resolved: once any indexed
112
- // condition has narrowed entity's rows, they are final (the conditions left over are applied to them below) and
103
+ // condition has narrowed entity's rows, they are the candidates (every condition is applied to them below) and
113
104
  // must not be reloaded. This used to ask whether EVERY condition was indexed, so `where date = 'none' and where
114
105
  // reps = 10` reloaded every row and returned, updated or deleted all rows with reps 10.
115
106
  const resolved = new Set();
@@ -118,13 +109,13 @@ const runQuery = async parsed => {
118
109
  parsed = await (0, _getJoinIndexedList.getJoinIndexedList)(parsed, recordData, dataSet);
119
110
  // delete/update act on whatever is already in dataSet[entity] - unlike read, which loads
120
111
  // everything and lets the where-reduce below narrow it down afterward, they never get a second
121
- // pass. So the matching rows have to be fully resolved (indexed conditions already are, via
122
- // getConditionIndexedList above; anything left in parsed.conditions is not) before either runs.
112
+ // pass. So every condition is applied to the candidates (all rows, or the ones the indexes picked)
113
+ // before either runs.
123
114
  if (parsed.command === 'delete' || parsed.command === 'update') {
124
115
  if (!entityResolved) {
125
116
  dataSet = await (0, _readEntity.readEntity)(parsed.entity, dataSet);
126
117
  }
127
- dataSet[parsed.entity] = parsed.conditions.reduce((remainingData, condition) => (0, _where.where)(remainingData, condition), dataSet[parsed.entity]);
118
+ dataSet[parsed.entity] = (0, _where.whereTree)(dataSet[parsed.entity], parsed.where);
128
119
  }
129
120
  switch (parsed.command) {
130
121
  case 'delete':
@@ -144,16 +135,14 @@ const runQuery = async parsed => {
144
135
  default:
145
136
  throw new Error(`Unknown command: ${parsed.command}`);
146
137
  }
147
- dataSet = await (0, _joinEntity.joinEntity)(parsed.entity, parsed.joinEntity, dataSet, parsed.joinClauses, entityResolved);
138
+ dataSet = await (0, _joinEntity.joinEntity)(parsed.entity, parsed.joinEntity, dataSet, parsed.joinClauses, entityResolved, resolved.has(parsed.joinEntity));
148
139
  dataSet = await (0, _mergeJoins.mergeJoins)(dataSet, parsed.mergeJoins);
149
- // delete/update already had parsed.conditions applied above, before they ran. Re-applying the
140
+ // delete/update already had parsed.where applied above, before they ran. Re-applying the
150
141
  // same conditions here would be wrong, not just redundant, for update specifically: if the
151
142
  // update just changed the very field a condition filters on, re-checking that condition against
152
143
  // the now-changed data would filter the just-updated rows back out.
153
144
  if (parsed.command !== 'delete' && parsed.command !== 'update') {
154
- dataSet[parsed.entity] = parsed.conditions.reduce((remainingData, condition) => {
155
- return (0, _where.where)(remainingData, condition);
156
- }, dataSet[parsed.entity]);
145
+ dataSet[parsed.entity] = (0, _where.whereTree)(dataSet[parsed.entity], parsed.where, perPair ? parsed.joinEntity : undefined);
157
146
  }
158
147
  dataSet[parsed.entity] = (0, _sortBy.sortBy)(dataSet[parsed.entity], parsed.sortClauses);
159
148
  dataSet[parsed.entity] = (0, _limitOffset.limitOffset)(dataSet[parsed.entity], parsed.offset, parsed.limit);
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.runQuery=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.reduce.js"),require("core-js/modules/esnext.set.add-all.js"),require("core-js/modules/esnext.set.delete-all.js"),require("core-js/modules/esnext.set.difference.js"),require("core-js/modules/esnext.set.every.js"),require("core-js/modules/esnext.set.filter.js"),require("core-js/modules/esnext.set.find.js"),require("core-js/modules/esnext.set.intersection.js"),require("core-js/modules/esnext.set.is-disjoint-from.js"),require("core-js/modules/esnext.set.is-subset-of.js"),require("core-js/modules/esnext.set.is-superset-of.js"),require("core-js/modules/esnext.set.join.js"),require("core-js/modules/esnext.set.map.js"),require("core-js/modules/esnext.set.reduce.js"),require("core-js/modules/esnext.set.some.js"),require("core-js/modules/esnext.set.symmetric-difference.js"),require("core-js/modules/esnext.set.union.js");var _deleteEntity=require("../queries/deleteEntity"),_insertEntity=require("../queries/insertEntity"),_readEntity=require("../queries/readEntity"),_updateEntity=require("../queries/updateEntity"),_createRecord=require("../queries/createRecord"),_dropRecord=require("../queries/dropRecord"),_describeRecord=require("../queries/describeRecord"),_showRecords=require("../queries/showRecords"),_joinEntity=require("../queries/joinEntity"),_mergeJoins=require("../queries/mergeJoins"),_where=require("../queries/where"),_sortBy=require("../queries/sortBy"),_groupBy=require("../queries/groupBy"),_limitOffset=require("../queries/limitOffset"),_selectFields=require("../queries/selectFields"),_retrieveRecord=require("./retrieveRecord"),_getConditionIndexedList=require("./getConditionIndexedList"),_getJoinIndexedList=require("./getJoinIndexedList"),_isEntityProperty=require("./parsers/isEntityProperty"),_splitEntityProperty=require("./parsers/splitEntityProperty"),_fieldType=require("./fieldType");const typeClauses=async(e,t)=>{for(const r of[...e.conditions,...e.sortClauses]){let i=e.entity,s=r.property;(0,_isEntityProperty.isEntityProperty)(s)&&({entity:i,property:s}=(0,_splitEntityProperty.splitEntityProperty)(s)),Object.prototype.hasOwnProperty.call(t,i)||(t[i]=await(0,_retrieveRecord.retrieveRecord)(i)),r.type=(0,_fieldType.fieldType)(t[i],s)}},runQuery=async e=>{switch(e.command){case"create":return{[e.entity]:await(0,_createRecord.createRecord)(e.entity,e.create)};case"drop":return{[e.entity]:await(0,_dropRecord.dropRecord)(e.entity,e.ifExists)};case"describe":return{[e.entity]:await(0,_describeRecord.describeRecord)(e.entity)};case"show":return{[e.entity]:await(0,_showRecords.showRecords)()}}if("update"===e.command||"delete"===e.command){const t=e.conditions.find((t=>(0,_isEntityProperty.isEntityProperty)(t.property)&&(0,_splitEntityProperty.splitEntityProperty)(t.property).entity!==e.entity));if(t)throw new Error(`${e.command} can only use conditions on ${e.entity}'s own fields, not ${t.property}`)}const t={[e.entity]:await(0,_retrieveRecord.retrieveRecord)(e.entity)};void 0!==e.joinEntity&&(t[e.joinEntity]=await(0,_retrieveRecord.retrieveRecord)(e.joinEntity)),await typeClauses(e,t);let r={[e.entity]:[]};const i=new Set;e=await(0,_getConditionIndexedList.getConditionIndexedList)(e,t,r,i);const s=i.has(e.entity);switch("delete"!==(e=await(0,_getJoinIndexedList.getJoinIndexedList)(e,t,r)).command&&"update"!==e.command||(s||(r=await(0,_readEntity.readEntity)(e.entity,r)),r[e.entity]=e.conditions.reduce(((e,t)=>(0,_where.where)(e,t)),r[e.entity])),e.command){case"delete":r=await(0,_deleteEntity.deleteEntity)(e.entity,r);break;case"insert":r=await(0,_insertEntity.insertEntity)(e.entity,e.insertValues,r);break;case"read":s||(r=await(0,_readEntity.readEntity)(e.entity,r));break;case"update":r=await(0,_updateEntity.updateEntity)(e.entity,e.updateValues,r);break;default:throw new Error(`Unknown command: ${e.command}`)}return r=await(0,_joinEntity.joinEntity)(e.entity,e.joinEntity,r,e.joinClauses,s),r=await(0,_mergeJoins.mergeJoins)(r,e.mergeJoins),"delete"!==e.command&&"update"!==e.command&&(r[e.entity]=e.conditions.reduce(((e,t)=>(0,_where.where)(e,t)),r[e.entity])),r[e.entity]=(0,_sortBy.sortBy)(r[e.entity],e.sortClauses),r[e.entity]=(0,_limitOffset.limitOffset)(r[e.entity],e.offset,e.limit),r[e.entity]=(0,_groupBy.groupBy)(r[e.entity],e.groupBy),r[e.entity]=(0,_selectFields.selectFields)(r[e.entity],e.selectClauses),r};exports.runQuery=runQuery;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.runQuery=void 0,require("core-js/modules/esnext.set.add-all.js"),require("core-js/modules/esnext.set.delete-all.js"),require("core-js/modules/esnext.set.difference.js"),require("core-js/modules/esnext.set.every.js"),require("core-js/modules/esnext.set.filter.js"),require("core-js/modules/esnext.set.find.js"),require("core-js/modules/esnext.set.intersection.js"),require("core-js/modules/esnext.set.is-disjoint-from.js"),require("core-js/modules/esnext.set.is-subset-of.js"),require("core-js/modules/esnext.set.is-superset-of.js"),require("core-js/modules/esnext.set.join.js"),require("core-js/modules/esnext.set.map.js"),require("core-js/modules/esnext.set.reduce.js"),require("core-js/modules/esnext.set.some.js"),require("core-js/modules/esnext.set.symmetric-difference.js"),require("core-js/modules/esnext.set.union.js");var _deleteEntity=require("../queries/deleteEntity"),_insertEntity=require("../queries/insertEntity"),_readEntity=require("../queries/readEntity"),_updateEntity=require("../queries/updateEntity"),_createRecord=require("../queries/createRecord"),_dropRecord=require("../queries/dropRecord"),_describeRecord=require("../queries/describeRecord"),_showRecords=require("../queries/showRecords"),_joinEntity=require("../queries/joinEntity"),_mergeJoins=require("../queries/mergeJoins"),_where=require("../queries/where"),_sortBy=require("../queries/sortBy"),_groupBy=require("../queries/groupBy"),_limitOffset=require("../queries/limitOffset"),_selectFields=require("../queries/selectFields"),_retrieveRecord=require("./retrieveRecord"),_getConditionIndexedList=require("./getConditionIndexedList"),_getJoinIndexedList=require("./getJoinIndexedList"),_isEntityProperty=require("./parsers/isEntityProperty"),_splitEntityProperty=require("./parsers/splitEntityProperty"),_fieldType=require("./fieldType"),_conditionTree=require("./conditionTree"),_conditionEntities=require("./conditionEntities");const typeClauses=async(e,t)=>{for(const r of[...(0,_conditionTree.conditionsOf)(e.where),...e.sortClauses]){let i=e.entity,s=r.property;(0,_isEntityProperty.isEntityProperty)(s)&&({entity:i,property:s}=(0,_splitEntityProperty.splitEntityProperty)(s)),Object.prototype.hasOwnProperty.call(t,i)||(t[i]=await(0,_retrieveRecord.retrieveRecord)(i)),r.type=(0,_fieldType.fieldType)(t[i],s)}},runQuery=async e=>{const t=void 0!==e.where&&(0,_conditionEntities.conditionEntities)(e);switch(e.command){case"create":return{[e.entity]:await(0,_createRecord.createRecord)(e.entity,e.create)};case"drop":return{[e.entity]:await(0,_dropRecord.dropRecord)(e.entity,e.ifExists)};case"describe":return{[e.entity]:await(0,_describeRecord.describeRecord)(e.entity)};case"show":return{[e.entity]:await(0,_showRecords.showRecords)()}}const r={[e.entity]:await(0,_retrieveRecord.retrieveRecord)(e.entity)};void 0!==e.joinEntity&&(r[e.joinEntity]=await(0,_retrieveRecord.retrieveRecord)(e.joinEntity)),await typeClauses(e,r);let i={[e.entity]:[]};const s=new Set;e=await(0,_getConditionIndexedList.getConditionIndexedList)(e,r,i,s);const n=s.has(e.entity);switch("delete"!==(e=await(0,_getJoinIndexedList.getJoinIndexedList)(e,r,i)).command&&"update"!==e.command||(n||(i=await(0,_readEntity.readEntity)(e.entity,i)),i[e.entity]=(0,_where.whereTree)(i[e.entity],e.where)),e.command){case"delete":i=await(0,_deleteEntity.deleteEntity)(e.entity,i);break;case"insert":i=await(0,_insertEntity.insertEntity)(e.entity,e.insertValues,i);break;case"read":n||(i=await(0,_readEntity.readEntity)(e.entity,i));break;case"update":i=await(0,_updateEntity.updateEntity)(e.entity,e.updateValues,i);break;default:throw new Error(`Unknown command: ${e.command}`)}return i=await(0,_joinEntity.joinEntity)(e.entity,e.joinEntity,i,e.joinClauses,n,s.has(e.joinEntity)),i=await(0,_mergeJoins.mergeJoins)(i,e.mergeJoins),"delete"!==e.command&&"update"!==e.command&&(i[e.entity]=(0,_where.whereTree)(i[e.entity],e.where,t?e.joinEntity:void 0)),i[e.entity]=(0,_sortBy.sortBy)(i[e.entity],e.sortClauses),i[e.entity]=(0,_limitOffset.limitOffset)(i[e.entity],e.offset,e.limit),i[e.entity]=(0,_groupBy.groupBy)(i[e.entity],e.groupBy),i[e.entity]=(0,_selectFields.selectFields)(i[e.entity],e.selectClauses),i};exports.runQuery=runQuery;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-fs-query",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "Use json files to manage and read data.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",