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.
- package/README.md +114 -6
- package/browser/jsonFsQuery.js +907 -456
- package/browser/jsonFsQuery.min.js +1 -1
- package/dist/data/queries/joinEntity.d.ts +1 -1
- package/dist/data/queries/joinEntity.js +5 -2
- package/dist/data/queries/joinEntity.min.js +1 -1
- package/dist/data/queries/where.d.ts +38 -7
- package/dist/data/queries/where.js +119 -13
- package/dist/data/queries/where.min.js +1 -1
- package/dist/data/utilities/conditionEntities.d.ts +20 -0
- package/dist/data/utilities/conditionEntities.js +85 -0
- package/dist/data/utilities/conditionEntities.min.js +1 -0
- package/dist/data/utilities/conditionTree.d.ts +36 -0
- package/dist/data/utilities/conditionTree.js +62 -0
- package/dist/data/utilities/conditionTree.min.js +1 -0
- package/dist/data/utilities/getConditionIndexedList.d.ts +10 -7
- package/dist/data/utilities/getConditionIndexedList.js +108 -32
- package/dist/data/utilities/getConditionIndexedList.min.js +1 -1
- package/dist/data/utilities/parsers/findConditions.d.ts +7 -5
- package/dist/data/utilities/parsers/findConditions.js +163 -35
- package/dist/data/utilities/parsers/findConditions.min.js +1 -1
- package/dist/data/utilities/parsers/parseQuery.d.ts +2 -2
- package/dist/data/utilities/parsers/parseQuery.js +1 -1
- package/dist/data/utilities/parsers/parseQuery.min.js +1 -1
- package/dist/data/utilities/runQuery.js +12 -23
- package/dist/data/utilities/runQuery.min.js +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
32
|
+
var _retrieveIndexes = require("./retrieveIndexes");
|
|
33
|
+
var _isSingleFieldKey = require("./isSingleFieldKey");
|
|
34
|
+
var _conditionTree = require("./conditionTree");
|
|
32
35
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
|
|
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:
|
|
41
|
-
*
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
|
60
|
-
|
|
61
|
-
|
|
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.
|
|
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
|
|
12
|
-
* `
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
12
|
-
const
|
|
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
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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 (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
21
|
+
where: ConditionNode | undefined;
|
|
22
22
|
joinClauses: ParsedJoinClause[];
|
|
23
23
|
mergeJoins: ParsedMergeJoin[];
|
|
24
24
|
sortClauses: ParsedSortClause[];
|
|
@@ -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:{},
|
|
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.
|
|
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
|
|
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
|
|
122
|
-
//
|
|
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] =
|
|
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.
|
|
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.
|
|
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.
|
|
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;
|