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
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const joinEntity: (entity: any, joinEntity: any, dataSets?: {}, joinClauses?: any[], entityAlreadyResolved?: boolean) => Promise<{}>;
|
|
1
|
+
export declare const joinEntity: (entity: any, joinEntity: any, dataSets?: {}, joinClauses?: any[], entityAlreadyResolved?: boolean, joinAlreadyResolved?: boolean) => Promise<{}>;
|
|
2
2
|
//# sourceMappingURL=joinEntity.d.ts.map
|
|
@@ -8,7 +8,7 @@ var _siFunciona = _interopRequireDefault(require("si-funciona"));
|
|
|
8
8
|
var _readEntity = require("./readEntity");
|
|
9
9
|
var _useJoinClause = require("../utilities/useJoinClause");
|
|
10
10
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
-
const joinEntity = async (entity, joinEntity, dataSets = {}, joinClauses = [], entityAlreadyResolved = false) => {
|
|
11
|
+
const joinEntity = async (entity, joinEntity, dataSets = {}, joinClauses = [], entityAlreadyResolved = false, joinAlreadyResolved = false) => {
|
|
12
12
|
// Same ambiguity readEntity() always has (can't tell "never loaded" from "loaded, zero rows
|
|
13
13
|
// matched") - runQuery already resolved entity's rows via an indexed condition in that second
|
|
14
14
|
// case, so entityAlreadyResolved tells this call not to reload and silently discard that result.
|
|
@@ -18,7 +18,10 @@ const joinEntity = async (entity, joinEntity, dataSets = {}, joinClauses = [], e
|
|
|
18
18
|
if (!joinClauses.length) {
|
|
19
19
|
return dataSets;
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
// The same for joinEntity: an indexed condition on its field may have resolved its rows to none
|
|
22
|
+
if (!joinAlreadyResolved) {
|
|
23
|
+
dataSets = await (0, _readEntity.readEntity)(joinEntity, dataSets);
|
|
24
|
+
}
|
|
22
25
|
for (const anEntity in dataSets[entity]) {
|
|
23
26
|
let filterJoins = _siFunciona.default.cloneObject(dataSets);
|
|
24
27
|
filterJoins[entity] = [dataSets[entity][anEntity]];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.joinEntity=void 0;var _siFunciona=_interopRequireDefault(require("si-funciona")),_readEntity=require("./readEntity"),_useJoinClause=require("../utilities/useJoinClause");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const joinEntity=async(e,t,i={},n=[],r=!1)=>{if(r||(i=await(0,_readEntity.readEntity)(e,i)),!n.length)return i;i=await(0,_readEntity.readEntity)(t,i);for(const r in i[e]){let u=_siFunciona.default.cloneObject(i);u[e]=[i[e][r]];for(const e of n)u=await(0,_useJoinClause.useJoinClause)(e,i,u);i[e][r][t]=u[t]}return i};exports.joinEntity=joinEntity;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.joinEntity=void 0;var _siFunciona=_interopRequireDefault(require("si-funciona")),_readEntity=require("./readEntity"),_useJoinClause=require("../utilities/useJoinClause");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e}}const joinEntity=async(e,t,i={},n=[],r=!1,u=!1)=>{if(r||(i=await(0,_readEntity.readEntity)(e,i)),!n.length)return i;u||(i=await(0,_readEntity.readEntity)(t,i));for(const r in i[e]){let u=_siFunciona.default.cloneObject(i);u[e]=[i[e][r]];for(const e of n)u=await(0,_useJoinClause.useJoinClause)(e,i,u);i[e][r][t]=u[t]}return i};exports.joinEntity=joinEntity;
|
|
@@ -1,8 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { ConditionNode } from '../utilities/conditionTree';
|
|
2
|
+
/**
|
|
3
|
+
* SQL's three-valued logic: a condition is true, false, or unknown (null here), see makeCondition.
|
|
4
|
+
*/
|
|
5
|
+
export type Truth = boolean | null;
|
|
6
|
+
/**
|
|
7
|
+
* The rows for which one condition is true (unknown, from a null or missing field, isn't).
|
|
8
|
+
* @param dataSet
|
|
9
|
+
* @param condition
|
|
10
|
+
*/
|
|
11
|
+
export declare const where: (dataSet?: any[], condition?: {}) => any[];
|
|
12
|
+
/**
|
|
13
|
+
* A where-clause tree on one row, with SQL's three-valued logic: unknown (null) stays unknown through `not`, and
|
|
14
|
+
* `and` / `or` decide on it only when another child settles the result.
|
|
15
|
+
*
|
|
16
|
+
* | | result |
|
|
17
|
+
* |---|---|
|
|
18
|
+
* | `not unknown` | unknown |
|
|
19
|
+
* | `false and unknown` | false |
|
|
20
|
+
* | `true and unknown` | unknown |
|
|
21
|
+
* | `true or unknown` | true |
|
|
22
|
+
* | `false or unknown` | unknown |
|
|
23
|
+
* @param data
|
|
24
|
+
* @param node
|
|
25
|
+
*/
|
|
26
|
+
export declare const treeTruth: (data: any, node: ConditionNode) => Truth;
|
|
27
|
+
/**
|
|
28
|
+
* The rows that match a where-clause tree (see ConditionNode): only those for which the whole tree is true, so
|
|
29
|
+
* `not (n = 5)` returns the same rows as `n != 5` (neither returns a row where n is null or missing). Every row when
|
|
30
|
+
* there is no where clause.
|
|
31
|
+
*
|
|
32
|
+
* With a joinEntity (the tree uses its fields, see conditionEntities), it's SQL's inner join, evaluated per pair: each
|
|
33
|
+
* row keeps the joined rows for which the tree is true with that pair, and a row left with none is dropped.
|
|
34
|
+
* @param dataSet
|
|
35
|
+
* @param node
|
|
36
|
+
* @param joinEntity
|
|
37
|
+
*/
|
|
38
|
+
export declare const whereTree: (dataSet?: any[], node?: ConditionNode | undefined, joinEntity?: string | undefined) => any[];
|
|
8
39
|
//# sourceMappingURL=where.d.ts.map
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.where = void 0;
|
|
6
|
+
exports.whereTree = exports.where = exports.treeTruth = void 0;
|
|
7
7
|
require("core-js/modules/es.array.includes.js");
|
|
8
8
|
require("core-js/modules/esnext.iterator.constructor.js");
|
|
9
9
|
require("core-js/modules/esnext.iterator.filter.js");
|
|
10
|
+
require("core-js/modules/esnext.iterator.flat-map.js");
|
|
10
11
|
require("core-js/modules/esnext.iterator.some.js");
|
|
11
12
|
var _compareAs = require("../utilities/compareAs");
|
|
13
|
+
var _conditionTree = require("../utilities/conditionTree");
|
|
12
14
|
const likeMatch = (dataValue, pattern) => {
|
|
13
15
|
if (typeof dataValue !== 'string' || typeof pattern !== 'string') {
|
|
14
16
|
return false;
|
|
@@ -21,8 +23,9 @@ const likeMatch = (dataValue, pattern) => {
|
|
|
21
23
|
* '3' and 3 match in a string or int field. Ordering (>, >=, <, <=, between) compares by it too (see compareAs): a
|
|
22
24
|
* string field orders as text ('10' < '9'), an int field as numbers. like compares the raw values.
|
|
23
25
|
*
|
|
24
|
-
* Null and missing are their own cases, as in SQL: a field that is null or missing
|
|
25
|
-
*
|
|
26
|
+
* Null and missing are their own cases, as in SQL: a comparison on a field that is null or missing is unknown (null),
|
|
27
|
+
* neither true nor false, so it matches nothing (not even `!=`) and `not` doesn't turn it into a match. A null never
|
|
28
|
+
* equals anything, not even another null. Only these test for them, and they are never unknown:
|
|
26
29
|
* - `is null`: the field is there and holds null
|
|
27
30
|
* - `is not null`: the field is there and holds a value (so a missing field doesn't match either)
|
|
28
31
|
* - `exists` / `does not exist`: the field is there (null counts) / is missing
|
|
@@ -45,7 +48,7 @@ const makeCondition = (data, property, comparator, value, type = undefined) => {
|
|
|
45
48
|
return exists && data[property] !== null;
|
|
46
49
|
}
|
|
47
50
|
if (!exists || data[property] === null || value === null) {
|
|
48
|
-
return
|
|
51
|
+
return null;
|
|
49
52
|
}
|
|
50
53
|
const dataValue = data[property];
|
|
51
54
|
const typedValue = (0, _compareAs.compareAs)(type, dataValue);
|
|
@@ -64,25 +67,128 @@ const makeCondition = (data, property, comparator, value, type = undefined) => {
|
|
|
64
67
|
case '<=':
|
|
65
68
|
return typedValue <= (0, _compareAs.compareAs)(type, value);
|
|
66
69
|
case 'in':
|
|
67
|
-
|
|
70
|
+
{
|
|
71
|
+
if (!Array.isArray(value)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
if (type ? value.some(item => item !== null && (0, _compareAs.sameValue)(type, dataValue, item)) : value.includes(dataValue)) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
// SQL: `x in [1, null]` is x = 1 or x = null, so with no match it's unknown (not false), and not (...) keeps nothing
|
|
78
|
+
return value.includes(null) ? null : false;
|
|
79
|
+
}
|
|
68
80
|
case 'between':
|
|
69
|
-
|
|
70
|
-
|
|
81
|
+
if (!Array.isArray(value) || value.length !== 2) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
// A null bound would act as 0 in >= / <=: it bounds nothing, so the result is unknown (the parser refuses it)
|
|
85
|
+
if (value.includes(null)) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
return typedValue >= (0, _compareAs.compareAs)(type, value[0]) && typedValue <= (0, _compareAs.compareAs)(type, value[1]);
|
|
71
89
|
case 'like':
|
|
72
90
|
return likeMatch(dataValue, value);
|
|
73
91
|
}
|
|
74
92
|
return false;
|
|
75
93
|
};
|
|
76
|
-
|
|
94
|
+
/**
|
|
95
|
+
* One condition on one row: true, false or unknown (shared by where and whereTree).
|
|
96
|
+
* @param data
|
|
97
|
+
* @param condition
|
|
98
|
+
*/
|
|
99
|
+
const conditionTruth = (data, {
|
|
77
100
|
property = '',
|
|
78
101
|
joinEntity = undefined,
|
|
79
102
|
comparator = '=',
|
|
80
103
|
value = null,
|
|
81
104
|
type = undefined
|
|
82
|
-
} = {}) => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
105
|
+
} = {}) => makeCondition(data, joinEntity ? `${joinEntity}.${property}` : property, comparator, value, type);
|
|
106
|
+
/**
|
|
107
|
+
* The rows for which one condition is true (unknown, from a null or missing field, isn't).
|
|
108
|
+
* @param dataSet
|
|
109
|
+
* @param condition
|
|
110
|
+
*/
|
|
111
|
+
const where = (dataSet = [], condition = {}) => dataSet.filter(data => conditionTruth(data, condition) === true);
|
|
112
|
+
/**
|
|
113
|
+
* A where-clause tree on one row, with SQL's three-valued logic: unknown (null) stays unknown through `not`, and
|
|
114
|
+
* `and` / `or` decide on it only when another child settles the result.
|
|
115
|
+
*
|
|
116
|
+
* | | result |
|
|
117
|
+
* |---|---|
|
|
118
|
+
* | `not unknown` | unknown |
|
|
119
|
+
* | `false and unknown` | false |
|
|
120
|
+
* | `true and unknown` | unknown |
|
|
121
|
+
* | `true or unknown` | true |
|
|
122
|
+
* | `false or unknown` | unknown |
|
|
123
|
+
* @param data
|
|
124
|
+
* @param node
|
|
125
|
+
*/
|
|
126
|
+
exports.where = where;
|
|
127
|
+
const treeTruth = (data, node) => {
|
|
128
|
+
if ((0, _conditionTree.isCondition)(node)) {
|
|
129
|
+
return conditionTruth(data, node);
|
|
130
|
+
}
|
|
131
|
+
if ('not' in node) {
|
|
132
|
+
const truth = treeTruth(data, node.not);
|
|
133
|
+
return truth === null ? null : !truth;
|
|
134
|
+
}
|
|
135
|
+
// and: false wins, then unknown; or: true wins, then unknown
|
|
136
|
+
const settles = !('and' in node);
|
|
137
|
+
let result = !settles;
|
|
138
|
+
for (const child of 'and' in node ? node.and : node.or) {
|
|
139
|
+
const truth = treeTruth(data, child);
|
|
140
|
+
if (truth === settles) {
|
|
141
|
+
return settles;
|
|
142
|
+
}
|
|
143
|
+
if (truth === null) {
|
|
144
|
+
result = null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* One row seen together with one of its joined rows, for evaluating a where clause on the pair: the row's own fields,
|
|
151
|
+
* plus the joined row's as `joinEntity.field` (a field already on the row, such as a merged one, is kept).
|
|
152
|
+
* @param row
|
|
153
|
+
* @param joinEntity
|
|
154
|
+
* @param joinedRow
|
|
155
|
+
*/
|
|
156
|
+
exports.treeTruth = treeTruth;
|
|
157
|
+
const pairView = (row, joinEntity, joinedRow) => {
|
|
158
|
+
const view = Object.assign({}, row);
|
|
159
|
+
for (const field of Object.keys(joinedRow)) {
|
|
160
|
+
// A merged field on the joined row is already named `entity.field`
|
|
161
|
+
const name = field.includes('.') ? field : `${joinEntity}.${field}`;
|
|
162
|
+
if (!Object.prototype.hasOwnProperty.call(view, name)) {
|
|
163
|
+
view[name] = joinedRow[field];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return view;
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* The rows that match a where-clause tree (see ConditionNode): only those for which the whole tree is true, so
|
|
170
|
+
* `not (n = 5)` returns the same rows as `n != 5` (neither returns a row where n is null or missing). Every row when
|
|
171
|
+
* there is no where clause.
|
|
172
|
+
*
|
|
173
|
+
* With a joinEntity (the tree uses its fields, see conditionEntities), it's SQL's inner join, evaluated per pair: each
|
|
174
|
+
* row keeps the joined rows for which the tree is true with that pair, and a row left with none is dropped.
|
|
175
|
+
* @param dataSet
|
|
176
|
+
* @param node
|
|
177
|
+
* @param joinEntity
|
|
178
|
+
*/
|
|
179
|
+
const whereTree = (dataSet = [], node = undefined, joinEntity = undefined) => {
|
|
180
|
+
if (typeof node === 'undefined') {
|
|
181
|
+
return dataSet;
|
|
182
|
+
}
|
|
183
|
+
if (typeof joinEntity === 'undefined') {
|
|
184
|
+
return dataSet.filter(data => treeTruth(data, node) === true);
|
|
185
|
+
}
|
|
186
|
+
return dataSet.flatMap(data => {
|
|
187
|
+
const joined = Array.isArray(data[joinEntity]) ? data[joinEntity] : [];
|
|
188
|
+
const kept = joined.filter(joinedRow => treeTruth(pairView(data, joinEntity, joinedRow), node) === true);
|
|
189
|
+
return kept.length ? [Object.assign({}, data, {
|
|
190
|
+
[joinEntity]: kept
|
|
191
|
+
})] : [];
|
|
86
192
|
});
|
|
87
193
|
};
|
|
88
|
-
exports.
|
|
194
|
+
exports.whereTree = whereTree;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.where=void 0,require("core-js/modules/es.array.includes.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.filter.js"),require("core-js/modules/esnext.iterator.some.js");var _compareAs=require("../utilities/compareAs");const likeMatch=(e,r)=>{if("string"!=typeof e||"string"!=typeof r)return!1;const
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.whereTree=exports.where=exports.treeTruth=void 0,require("core-js/modules/es.array.includes.js"),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.iterator.some.js");var _compareAs=require("../utilities/compareAs"),_conditionTree=require("../utilities/conditionTree");const likeMatch=(e,r)=>{if("string"!=typeof e||"string"!=typeof r)return!1;const t=r.replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/%/g,".*").replace(/_/g,".");return new RegExp(`^${t}$`,"i").test(e)},makeCondition=(e,r,t,s,n=void 0)=>{const o=Object.prototype.hasOwnProperty.call(e,r);switch(t){case"exists":return o;case"does not exist":return!o;case"is null":return o&&null===e[r];case"is not null":return o&&null!==e[r]}if(!o||null===e[r]||null===s)return null;const i=e[r],c=(0,_compareAs.compareAs)(n,i);switch(t){case"=":return(0,_compareAs.sameValue)(n,i,s);case"!=":case"<>":return!(0,_compareAs.sameValue)(n,i,s);case">":return c>(0,_compareAs.compareAs)(n,s);case">=":return c>=(0,_compareAs.compareAs)(n,s);case"<":return c<(0,_compareAs.compareAs)(n,s);case"<=":return c<=(0,_compareAs.compareAs)(n,s);case"in":return!!Array.isArray(s)&&(!!(n?s.some((e=>null!==e&&(0,_compareAs.sameValue)(n,i,e))):s.includes(i))||!!s.includes(null)&&null);case"between":return!(!Array.isArray(s)||2!==s.length)&&(s.includes(null)?null:c>=(0,_compareAs.compareAs)(n,s[0])&&c<=(0,_compareAs.compareAs)(n,s[1]));case"like":return likeMatch(i,s)}return!1},conditionTruth=(e,{property:r="",joinEntity:t,comparator:s="=",value:n=null,type:o}={})=>makeCondition(e,t?`${t}.${r}`:r,s,n,o),where=(e=[],r={})=>e.filter((e=>!0===conditionTruth(e,r)));exports.where=where;const treeTruth=(e,r)=>{if((0,_conditionTree.isCondition)(r))return conditionTruth(e,r);if("not"in r){const t=treeTruth(e,r.not);return null===t?null:!t}const t=!("and"in r);let s=!t;for(const n of"and"in r?r.and:r.or){const r=treeTruth(e,n);if(r===t)return t;null===r&&(s=null)}return s};exports.treeTruth=treeTruth;const pairView=(e,r,t)=>{const s=Object.assign({},e);for(const e of Object.keys(t)){const n=e.includes(".")?e:`${r}.${e}`;Object.prototype.hasOwnProperty.call(s,n)||(s[n]=t[e])}return s},whereTree=(e=[],r=void 0,t=void 0)=>void 0===r?e:void 0===t?e.filter((e=>!0===treeTruth(e,r))):e.flatMap((e=>{const s=(Array.isArray(e[t])?e[t]:[]).filter((s=>!0===treeTruth(pairView(e,t,s),r)));return s.length?[Object.assign({},e,{[t]:s})]:[]}));exports.whereTree=whereTree;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ParsedQuery } from './parsers/parseQuery';
|
|
2
|
+
/**
|
|
3
|
+
* Check which entity each condition's field is on, before anything is read, and say whether the where clause has to be
|
|
4
|
+
* evaluated per pair of a row and one of its joined rows:
|
|
5
|
+
* - the queried entity (`workouts.date`): written as the plain field (`date`), which is where the row holds it
|
|
6
|
+
* - the joined entity (`read workouts.exercises on ...`, `exercises.name`): per pair (see whereTree), so returns true
|
|
7
|
+
* - an entity merged into the rows or the joined rows (`merge workouts.exercise_id with exercises._id`): read from the
|
|
8
|
+
* merged fields (`exercises.name` on the row), as before
|
|
9
|
+
* - anything else is an error: an entity that only links the join (`exercise_muscle` in `workouts.muscles`), or one
|
|
10
|
+
* that isn't in the query at all (these used to match nothing, or fail to read the entity)
|
|
11
|
+
*
|
|
12
|
+
* update and delete apply their conditions before any join or merge runs, so they only take the entity's own fields.
|
|
13
|
+
* (An indexed joined-field condition used to be dropped once its index had narrowed the joined rows, so the command hit
|
|
14
|
+
* every row the other conditions allowed: `delete workouts.exercises on ... where exercises._id = 1` deleted every
|
|
15
|
+
* workout. Refused since #79.)
|
|
16
|
+
* @param parsed - its conditions on the queried entity lose their `entity.` prefix
|
|
17
|
+
* @throws {Error} For a condition on a field the query can't check
|
|
18
|
+
*/
|
|
19
|
+
export declare const conditionEntities: (parsed: ParsedQuery) => boolean;
|
|
20
|
+
//# sourceMappingURL=conditionEntities.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.conditionEntities = void 0;
|
|
7
|
+
require("core-js/modules/esnext.iterator.constructor.js");
|
|
8
|
+
require("core-js/modules/esnext.iterator.filter.js");
|
|
9
|
+
require("core-js/modules/esnext.iterator.flat-map.js");
|
|
10
|
+
require("core-js/modules/esnext.iterator.map.js");
|
|
11
|
+
require("core-js/modules/esnext.set.add-all.js");
|
|
12
|
+
require("core-js/modules/esnext.set.delete-all.js");
|
|
13
|
+
require("core-js/modules/esnext.set.difference.js");
|
|
14
|
+
require("core-js/modules/esnext.set.every.js");
|
|
15
|
+
require("core-js/modules/esnext.set.filter.js");
|
|
16
|
+
require("core-js/modules/esnext.set.find.js");
|
|
17
|
+
require("core-js/modules/esnext.set.intersection.js");
|
|
18
|
+
require("core-js/modules/esnext.set.is-disjoint-from.js");
|
|
19
|
+
require("core-js/modules/esnext.set.is-subset-of.js");
|
|
20
|
+
require("core-js/modules/esnext.set.is-superset-of.js");
|
|
21
|
+
require("core-js/modules/esnext.set.join.js");
|
|
22
|
+
require("core-js/modules/esnext.set.map.js");
|
|
23
|
+
require("core-js/modules/esnext.set.reduce.js");
|
|
24
|
+
require("core-js/modules/esnext.set.some.js");
|
|
25
|
+
require("core-js/modules/esnext.set.symmetric-difference.js");
|
|
26
|
+
require("core-js/modules/esnext.set.union.js");
|
|
27
|
+
var _isEntityProperty = require("./parsers/isEntityProperty");
|
|
28
|
+
var _splitEntityProperty = require("./parsers/splitEntityProperty");
|
|
29
|
+
var _conditionTree = require("./conditionTree");
|
|
30
|
+
/**
|
|
31
|
+
* Check which entity each condition's field is on, before anything is read, and say whether the where clause has to be
|
|
32
|
+
* evaluated per pair of a row and one of its joined rows:
|
|
33
|
+
* - the queried entity (`workouts.date`): written as the plain field (`date`), which is where the row holds it
|
|
34
|
+
* - the joined entity (`read workouts.exercises on ...`, `exercises.name`): per pair (see whereTree), so returns true
|
|
35
|
+
* - an entity merged into the rows or the joined rows (`merge workouts.exercise_id with exercises._id`): read from the
|
|
36
|
+
* merged fields (`exercises.name` on the row), as before
|
|
37
|
+
* - anything else is an error: an entity that only links the join (`exercise_muscle` in `workouts.muscles`), or one
|
|
38
|
+
* that isn't in the query at all (these used to match nothing, or fail to read the entity)
|
|
39
|
+
*
|
|
40
|
+
* update and delete apply their conditions before any join or merge runs, so they only take the entity's own fields.
|
|
41
|
+
* (An indexed joined-field condition used to be dropped once its index had narrowed the joined rows, so the command hit
|
|
42
|
+
* every row the other conditions allowed: `delete workouts.exercises on ... where exercises._id = 1` deleted every
|
|
43
|
+
* workout. Refused since #79.)
|
|
44
|
+
* @param parsed - its conditions on the queried entity lose their `entity.` prefix
|
|
45
|
+
* @throws {Error} For a condition on a field the query can't check
|
|
46
|
+
*/
|
|
47
|
+
const conditionEntities = parsed => {
|
|
48
|
+
const entity = parsed.entity;
|
|
49
|
+
const joinEntity = parsed.joinClauses.length ? parsed.joinEntity : undefined;
|
|
50
|
+
const joined = new Set([entity, joinEntity]);
|
|
51
|
+
const merged = new Set(parsed.mergeJoins.filter(merge => joined.has((0, _splitEntityProperty.splitEntityProperty)(merge.propertyA).entity)).map(merge => (0, _splitEntityProperty.splitEntityProperty)(merge.propertyB).entity));
|
|
52
|
+
const linking = new Set(parsed.joinClauses.flatMap(clause => [clause.propertyA, clause.propertyB].map(property => (0, _splitEntityProperty.splitEntityProperty)(property).entity)).filter(name => !joined.has(name) && !merged.has(name)));
|
|
53
|
+
const changes = parsed.command === 'update' || parsed.command === 'delete';
|
|
54
|
+
let perPair = false;
|
|
55
|
+
for (const condition of (0, _conditionTree.conditionsOf)(parsed.where)) {
|
|
56
|
+
if (!(0, _isEntityProperty.isEntityProperty)(condition.property)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const {
|
|
60
|
+
entity: fieldEntity,
|
|
61
|
+
property
|
|
62
|
+
} = (0, _splitEntityProperty.splitEntityProperty)(condition.property);
|
|
63
|
+
if (fieldEntity === entity) {
|
|
64
|
+
condition.property = property;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (changes) {
|
|
68
|
+
throw new Error(`${parsed.command} can only use conditions on ${entity}'s own fields, not ${condition.property}`);
|
|
69
|
+
}
|
|
70
|
+
if (fieldEntity === joinEntity) {
|
|
71
|
+
perPair = true;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (merged.has(fieldEntity)) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (linking.has(fieldEntity)) {
|
|
78
|
+
throw new Error(`${fieldEntity} only links ${entity} to ${joinEntity}: a condition can't use its fields (${condition.property})`);
|
|
79
|
+
}
|
|
80
|
+
const usable = [entity, joinEntity, ...merged].filter(name => typeof name !== 'undefined').join(', ');
|
|
81
|
+
throw new Error(`${fieldEntity} is not part of this query (${condition.property}): conditions can use the fields of ${usable}`);
|
|
82
|
+
}
|
|
83
|
+
return perPair;
|
|
84
|
+
};
|
|
85
|
+
exports.conditionEntities = conditionEntities;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.conditionEntities=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.iterator.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"),_conditionTree=require("./conditionTree");const conditionEntities=e=>{const t=e.entity,r=e.joinClauses.length?e.joinEntity:void 0,s=new Set([t,r]),o=new Set(e.mergeJoins.filter((e=>s.has((0,_splitEntityProperty.splitEntityProperty)(e.propertyA).entity))).map((e=>(0,_splitEntityProperty.splitEntityProperty)(e.propertyB).entity))),i=new Set(e.joinClauses.flatMap((e=>[e.propertyA,e.propertyB].map((e=>(0,_splitEntityProperty.splitEntityProperty)(e).entity)))).filter((e=>!s.has(e)&&!o.has(e)))),n="update"===e.command||"delete"===e.command;let u=!1;for(const s of(0,_conditionTree.conditionsOf)(e.where)){if(!(0,_isEntityProperty.isEntityProperty)(s.property))continue;const{entity:p,property:c}=(0,_splitEntityProperty.splitEntityProperty)(s.property);if(p===t){s.property=c;continue}if(n)throw new Error(`${e.command} can only use conditions on ${t}'s own fields, not ${s.property}`);if(p===r){u=!0;continue}if(o.has(p))continue;if(i.has(p))throw new Error(`${p} only links ${t} to ${r}: a condition can't use its fields (${s.property})`);const l=[t,r,...o].filter((e=>void 0!==e)).join(", ");throw new Error(`${p} is not part of this query (${s.property}): conditions can use the fields of ${l}`)}return u};exports.conditionEntities=conditionEntities;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ParsedCondition } from './parsers/findConditions';
|
|
2
|
+
/**
|
|
3
|
+
* A where clause as a tree: a condition, or `and` / `or` over several nodes, or `not` over one. `a and b and c` is one
|
|
4
|
+
* `and` node with three children, not nested pairs.
|
|
5
|
+
*/
|
|
6
|
+
export type ConditionNode = ParsedCondition | {
|
|
7
|
+
and: ConditionNode[];
|
|
8
|
+
} | {
|
|
9
|
+
or: ConditionNode[];
|
|
10
|
+
} | {
|
|
11
|
+
not: ConditionNode;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Whether a node is a single condition rather than `and` / `or` / `not`.
|
|
15
|
+
* @param node
|
|
16
|
+
*/
|
|
17
|
+
export declare const isCondition: (node: ConditionNode) => node is ParsedCondition;
|
|
18
|
+
/**
|
|
19
|
+
* The tree for conditions that must all match: none gives undefined (no where clause), one gives that condition itself,
|
|
20
|
+
* several give one `and` node.
|
|
21
|
+
* @param conditions
|
|
22
|
+
*/
|
|
23
|
+
export declare const allOf: (conditions: ConditionNode[]) => ConditionNode | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Every condition in the tree, in order, whatever group it is in.
|
|
26
|
+
* @param node
|
|
27
|
+
*/
|
|
28
|
+
export declare const conditionsOf: (node: ConditionNode | undefined) => ParsedCondition[];
|
|
29
|
+
/**
|
|
30
|
+
* The conditions every matching row must meet on their own: the tree itself when it is one condition, or the
|
|
31
|
+
* conditions directly under its top-level `and`. Only these may narrow the rows read through an index (an `or` or a
|
|
32
|
+
* `not` doesn't require any one of its conditions).
|
|
33
|
+
* @param node
|
|
34
|
+
*/
|
|
35
|
+
export declare const requiredConditions: (node: ConditionNode | undefined) => ParsedCondition[];
|
|
36
|
+
//# sourceMappingURL=conditionTree.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.requiredConditions = exports.isCondition = exports.conditionsOf = exports.allOf = void 0;
|
|
7
|
+
require("core-js/modules/esnext.iterator.constructor.js");
|
|
8
|
+
require("core-js/modules/esnext.iterator.filter.js");
|
|
9
|
+
require("core-js/modules/esnext.iterator.flat-map.js");
|
|
10
|
+
/**
|
|
11
|
+
* Whether a node is a single condition rather than `and` / `or` / `not`.
|
|
12
|
+
* @param node
|
|
13
|
+
*/
|
|
14
|
+
const isCondition = node => !('and' in node) && !('or' in node) && !('not' in node);
|
|
15
|
+
/**
|
|
16
|
+
* The tree for conditions that must all match: none gives undefined (no where clause), one gives that condition itself,
|
|
17
|
+
* several give one `and` node.
|
|
18
|
+
* @param conditions
|
|
19
|
+
*/
|
|
20
|
+
exports.isCondition = isCondition;
|
|
21
|
+
const allOf = conditions => {
|
|
22
|
+
if (!conditions.length) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
return conditions.length === 1 ? conditions[0] : {
|
|
26
|
+
and: conditions
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Every condition in the tree, in order, whatever group it is in.
|
|
31
|
+
* @param node
|
|
32
|
+
*/
|
|
33
|
+
exports.allOf = allOf;
|
|
34
|
+
const conditionsOf = node => {
|
|
35
|
+
if (typeof node === 'undefined') {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
if (isCondition(node)) {
|
|
39
|
+
return [node];
|
|
40
|
+
}
|
|
41
|
+
if ('not' in node) {
|
|
42
|
+
return conditionsOf(node.not);
|
|
43
|
+
}
|
|
44
|
+
return ('and' in node ? node.and : node.or).flatMap(conditionsOf);
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* The conditions every matching row must meet on their own: the tree itself when it is one condition, or the
|
|
48
|
+
* conditions directly under its top-level `and`. Only these may narrow the rows read through an index (an `or` or a
|
|
49
|
+
* `not` doesn't require any one of its conditions).
|
|
50
|
+
* @param node
|
|
51
|
+
*/
|
|
52
|
+
exports.conditionsOf = conditionsOf;
|
|
53
|
+
const requiredConditions = node => {
|
|
54
|
+
if (typeof node === 'undefined') {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
if (isCondition(node)) {
|
|
58
|
+
return [node];
|
|
59
|
+
}
|
|
60
|
+
return 'and' in node ? node.and.filter(isCondition) : [];
|
|
61
|
+
};
|
|
62
|
+
exports.requiredConditions = requiredConditions;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.requiredConditions=exports.isCondition=exports.conditionsOf=exports.allOf=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");const isCondition=o=>!("and"in o)&&!("or"in o)&&!("not"in o);exports.isCondition=isCondition;const allOf=o=>{if(o.length)return 1===o.length?o[0]:{and:o}};exports.allOf=allOf;const conditionsOf=o=>void 0===o?[]:isCondition(o)?[o]:"not"in o?conditionsOf(o.not):("and"in o?o.and:o.or).flatMap(conditionsOf);exports.conditionsOf=conditionsOf;const requiredConditions=o=>void 0===o?[]:isCondition(o)?[o]:"and"in o?o.and.filter(isCondition):[];exports.requiredConditions=requiredConditions;
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { ParsedQuery } from './parsers/parseQuery';
|
|
2
2
|
/**
|
|
3
|
-
* Load only the rows the
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* Load only the candidate rows the indexes allow for the where clause (see candidatesOf): an index only narrows which
|
|
4
|
+
* rows are read. Every condition, indexed or not, stays in parsed.where and is still applied to the candidates
|
|
5
|
+
* afterwards (runQuery), so an index can make a query faster but never changes its result (where-clauses decision 3;
|
|
6
|
+
* it used to remove the conditions it answered, and the 3 bugs fixed in #69 came from that split).
|
|
7
|
+
*
|
|
8
|
+
* An indexed condition on a joined entity's field (`exercises._id = 1`) narrows that entity's rows only when every
|
|
9
|
+
* row must meet it (directly under the top-level `and`, see requiredConditions). It stays in the tree too: the where
|
|
10
|
+
* clause is then evaluated per pair of a row and a joined row (see whereTree).
|
|
7
11
|
* @param parsed
|
|
8
12
|
* @param recordData
|
|
9
13
|
* @param dataSet
|
|
10
|
-
* @param resolved - Filled with each entity whose rows were loaded here:
|
|
11
|
-
*
|
|
12
|
-
* loaded yet", see readEntity).
|
|
14
|
+
* @param resolved - Filled with each entity whose candidate rows were loaded here: they must not be reloaded (an
|
|
15
|
+
* empty list otherwise reads as "not loaded yet", see readEntity).
|
|
13
16
|
*/
|
|
14
17
|
export declare const getConditionIndexedList: (parsed: ParsedQuery, recordData: any, dataSet: any, resolved?: Set<string>) => Promise<ParsedQuery>;
|
|
15
18
|
//# sourceMappingURL=getConditionIndexedList.d.ts.map
|