json-fs-query 2.3.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -6
- package/browser/jsonFsQuery.js +485 -208
- package/browser/jsonFsQuery.min.js +1 -1
- package/dist/data/queries/groupBy.d.ts +20 -1
- package/dist/data/queries/groupBy.js +71 -9
- package/dist/data/queries/groupBy.min.js +1 -1
- package/dist/data/queries/limitOffset.d.ts +1 -1
- package/dist/data/queries/limitOffset.js +1 -1
- package/dist/data/queries/selectFields.d.ts +6 -4
- package/dist/data/queries/selectFields.js +15 -10
- package/dist/data/queries/selectFields.min.js +1 -1
- package/dist/data/utilities/parsers/findCommand.d.ts +2 -1
- package/dist/data/utilities/parsers/findCommand.js +4 -4
- package/dist/data/utilities/parsers/findCommand.min.js +1 -1
- package/dist/data/utilities/parsers/findConditions.d.ts +8 -9
- package/dist/data/utilities/parsers/findConditions.js +29 -41
- package/dist/data/utilities/parsers/findConditions.min.js +1 -1
- package/dist/data/utilities/parsers/findGroupBy.d.ts +6 -4
- package/dist/data/utilities/parsers/findGroupBy.js +17 -9
- package/dist/data/utilities/parsers/findGroupBy.min.js +1 -1
- package/dist/data/utilities/parsers/findInsertValues.d.ts +4 -3
- package/dist/data/utilities/parsers/findInsertValues.js +9 -9
- package/dist/data/utilities/parsers/findInsertValues.min.js +1 -1
- package/dist/data/utilities/parsers/findJoinClauses.d.ts +4 -3
- package/dist/data/utilities/parsers/findJoinClauses.js +20 -13
- package/dist/data/utilities/parsers/findJoinClauses.min.js +1 -1
- package/dist/data/utilities/parsers/findJoinEntity.d.ts +1 -1
- package/dist/data/utilities/parsers/findJoinEntity.js +3 -5
- package/dist/data/utilities/parsers/findJoinEntity.min.js +1 -1
- package/dist/data/utilities/parsers/findLimit.d.ts +4 -3
- package/dist/data/utilities/parsers/findLimit.js +8 -8
- package/dist/data/utilities/parsers/findLimit.min.js +1 -1
- package/dist/data/utilities/parsers/findMergeJoins.d.ts +4 -3
- package/dist/data/utilities/parsers/findMergeJoins.js +19 -12
- package/dist/data/utilities/parsers/findMergeJoins.min.js +1 -1
- package/dist/data/utilities/parsers/findOffset.d.ts +4 -3
- package/dist/data/utilities/parsers/findOffset.js +8 -8
- package/dist/data/utilities/parsers/findOffset.min.js +1 -1
- package/dist/data/utilities/parsers/findSelects.d.ts +4 -3
- package/dist/data/utilities/parsers/findSelects.js +13 -14
- package/dist/data/utilities/parsers/findSelects.min.js +1 -1
- package/dist/data/utilities/parsers/findSortClauses.d.ts +4 -3
- package/dist/data/utilities/parsers/findSortClauses.js +12 -12
- package/dist/data/utilities/parsers/findSortClauses.min.js +1 -1
- package/dist/data/utilities/parsers/findUpdateValues.d.ts +5 -7
- package/dist/data/utilities/parsers/findUpdateValues.js +10 -13
- package/dist/data/utilities/parsers/findUpdateValues.min.js +1 -1
- package/dist/data/utilities/parsers/parseQuery.js +83 -9
- package/dist/data/utilities/parsers/parseQuery.min.js +1 -1
- package/dist/data/utilities/parsers/splitClauses.d.ts +29 -0
- package/dist/data/utilities/parsers/splitClauses.js +139 -0
- package/dist/data/utilities/parsers/splitClauses.min.js +1 -0
- package/dist/data/utilities/runQuery.js +5 -4
- package/dist/data/utilities/runQuery.min.js +1 -1
- package/package.json +1 -1
- package/dist/data/utilities/parsers/removeFromQuery.d.ts +0 -2
- package/dist/data/utilities/parsers/removeFromQuery.js +0 -8
- package/dist/data/utilities/parsers/removeFromQuery.min.js +0 -1
|
@@ -1,2 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* One group of a grouped read: the field and the value its rows share, then either the rows themselves or, for
|
|
3
|
+
* `group by a, b`, the groups within it by the next field. A group of rows missing the field has `missing: true`
|
|
4
|
+
* and no value; `null` is a value of its own (see Null and missing fields in the README).
|
|
5
|
+
*/
|
|
6
|
+
export type Group = {
|
|
7
|
+
field: string;
|
|
8
|
+
value?: any;
|
|
9
|
+
missing?: true;
|
|
10
|
+
rows?: Object[];
|
|
11
|
+
groups?: Group[];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Group rows by each field in turn. The groups are a list in the order their first row came, so a sort before
|
|
15
|
+
* grouping orders the groups too (and the rows within each one).
|
|
16
|
+
* @param dataSet
|
|
17
|
+
* @param fields
|
|
18
|
+
* @returns The rows as they were when there's no field, otherwise the groups.
|
|
19
|
+
*/
|
|
20
|
+
export declare const groupBy: (dataSet?: Object[], fields?: string[]) => Object[] | Group[];
|
|
2
21
|
//# sourceMappingURL=groupBy.d.ts.map
|
|
@@ -4,19 +4,81 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.groupBy = void 0;
|
|
7
|
+
require("core-js/modules/es.json.stringify.js");
|
|
7
8
|
require("core-js/modules/esnext.iterator.constructor.js");
|
|
9
|
+
require("core-js/modules/esnext.iterator.map.js");
|
|
8
10
|
require("core-js/modules/esnext.iterator.reduce.js");
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
require("core-js/modules/esnext.map.delete-all.js");
|
|
12
|
+
require("core-js/modules/esnext.map.every.js");
|
|
13
|
+
require("core-js/modules/esnext.map.filter.js");
|
|
14
|
+
require("core-js/modules/esnext.map.find.js");
|
|
15
|
+
require("core-js/modules/esnext.map.find-key.js");
|
|
16
|
+
require("core-js/modules/esnext.map.includes.js");
|
|
17
|
+
require("core-js/modules/esnext.map.key-of.js");
|
|
18
|
+
require("core-js/modules/esnext.map.map-keys.js");
|
|
19
|
+
require("core-js/modules/esnext.map.map-values.js");
|
|
20
|
+
require("core-js/modules/esnext.map.merge.js");
|
|
21
|
+
require("core-js/modules/esnext.map.reduce.js");
|
|
22
|
+
require("core-js/modules/esnext.map.some.js");
|
|
23
|
+
require("core-js/modules/esnext.map.update.js");
|
|
24
|
+
// An object's keys in sorted order, at any depth, so { a, b } and { b, a } are the same value; arrays keep their order
|
|
25
|
+
const sortedKeys = value => {
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
return value.map(sortedKeys);
|
|
28
|
+
}
|
|
29
|
+
if (value !== null && typeof value === 'object') {
|
|
30
|
+
return Object.keys(value).sort().reduce((sorted, key) => {
|
|
31
|
+
sorted[key] = sortedKeys(value[key]);
|
|
32
|
+
return sorted;
|
|
33
|
+
}, {});
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
};
|
|
37
|
+
// Rows share a group when their values are the same, type included: 1 and '1' are two groups, as are null and missing
|
|
38
|
+
const groupKey = (row, field) => {
|
|
39
|
+
if (!Object.prototype.hasOwnProperty.call(row, field)) {
|
|
40
|
+
return 'missing';
|
|
41
|
+
}
|
|
42
|
+
const value = row[field];
|
|
43
|
+
return value !== null && typeof value === 'object' ? `json:${JSON.stringify(sortedKeys(value))}` : `${typeof value}:${String(value)}`;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Group rows by each field in turn. The groups are a list in the order their first row came, so a sort before
|
|
47
|
+
* grouping orders the groups too (and the rows within each one).
|
|
48
|
+
* @param dataSet
|
|
49
|
+
* @param fields
|
|
50
|
+
* @returns The rows as they were when there's no field, otherwise the groups.
|
|
51
|
+
*/
|
|
52
|
+
const groupBy = (dataSet = [], fields = []) => {
|
|
53
|
+
if (!fields.length) {
|
|
11
54
|
return dataSet;
|
|
12
55
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
56
|
+
const [field, ...rest] = fields;
|
|
57
|
+
const groups = new Map();
|
|
58
|
+
for (const row of dataSet) {
|
|
59
|
+
const key = groupKey(row, field);
|
|
60
|
+
if (!groups.has(key)) {
|
|
61
|
+
groups.set(key, key === 'missing' ? {
|
|
62
|
+
field,
|
|
63
|
+
missing: true,
|
|
64
|
+
rows: []
|
|
65
|
+
} : {
|
|
66
|
+
field,
|
|
67
|
+
value: row[field],
|
|
68
|
+
rows: []
|
|
69
|
+
});
|
|
17
70
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
71
|
+
groups.get(key).rows.push(row);
|
|
72
|
+
}
|
|
73
|
+
return [...groups.values()].map(({
|
|
74
|
+
rows,
|
|
75
|
+
...group
|
|
76
|
+
}) => rest.length ? {
|
|
77
|
+
...group,
|
|
78
|
+
groups: groupBy(rows, rest)
|
|
79
|
+
} : {
|
|
80
|
+
...group,
|
|
81
|
+
rows
|
|
82
|
+
});
|
|
21
83
|
};
|
|
22
84
|
exports.groupBy = groupBy;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.groupBy=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.reduce.js");const
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.groupBy=void 0,require("core-js/modules/es.json.stringify.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.reduce.js"),require("core-js/modules/esnext.map.delete-all.js"),require("core-js/modules/esnext.map.every.js"),require("core-js/modules/esnext.map.filter.js"),require("core-js/modules/esnext.map.find.js"),require("core-js/modules/esnext.map.find-key.js"),require("core-js/modules/esnext.map.includes.js"),require("core-js/modules/esnext.map.key-of.js"),require("core-js/modules/esnext.map.map-keys.js"),require("core-js/modules/esnext.map.map-values.js"),require("core-js/modules/esnext.map.merge.js"),require("core-js/modules/esnext.map.reduce.js"),require("core-js/modules/esnext.map.some.js"),require("core-js/modules/esnext.map.update.js");const sortedKeys=e=>Array.isArray(e)?e.map(sortedKeys):null!==e&&"object"==typeof e?Object.keys(e).sort().reduce(((s,r)=>(s[r]=sortedKeys(e[r]),s)),{}):e,groupKey=(e,s)=>{if(!Object.prototype.hasOwnProperty.call(e,s))return"missing";const r=e[s];return null!==r&&"object"==typeof r?`json:${JSON.stringify(sortedKeys(r))}`:`${typeof r}:${String(r)}`},groupBy=(e=[],s=[])=>{if(!s.length)return e;const[r,...o]=s,t=new Map;for(const s of e){const e=groupKey(s,r);t.has(e)||t.set(e,"missing"===e?{field:r,missing:!0,rows:[]}:{field:r,value:s[r],rows:[]}),t.get(e).rows.push(s)}return[...t.values()].map((({rows:e,...s})=>o.length?{...s,groups:groupBy(e,o)}:{...s,rows:e}))};exports.groupBy=groupBy;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Slice a dataSet down to at most `limit` rows, starting after the first `offset` of them.
|
|
2
|
+
* Slice a dataSet down to at most `limit` rows (or groups, after group by), starting after the first `offset` of them.
|
|
3
3
|
* @param dataSet
|
|
4
4
|
* @param offset
|
|
5
5
|
* @param limit
|
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.limitOffset = void 0;
|
|
7
7
|
/**
|
|
8
|
-
* Slice a dataSet down to at most `limit` rows, starting after the first `offset` of them.
|
|
8
|
+
* Slice a dataSet down to at most `limit` rows (or groups, after group by), starting after the first `offset` of them.
|
|
9
9
|
* @param dataSet
|
|
10
10
|
* @param offset
|
|
11
11
|
* @param limit
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ParsedSelect } from '../utilities/parsers/findSelects';
|
|
2
|
+
import { Group } from './groupBy';
|
|
2
3
|
/**
|
|
3
|
-
* Shape each row down to only the selected properties (aliased, when given). dataSet is either a
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Shape each row down to only the selected properties (aliased, when given). dataSet is either a list of rows, or
|
|
5
|
+
* (once groupBy has run) a list of groups, whose rows are shaped the same way at any depth; the groups keep their
|
|
6
|
+
* field and value, selected or not.
|
|
6
7
|
* @param dataSet
|
|
7
8
|
* @param selectClauses
|
|
9
|
+
* @param grouped Whether dataSet is a list of groups (see groupBy)
|
|
8
10
|
*/
|
|
9
|
-
export declare const selectFields: (dataSet?: Object[] |
|
|
11
|
+
export declare const selectFields: (dataSet?: Object[] | Group[], selectClauses?: ParsedSelect[], grouped?: boolean) => Object[] | Group[];
|
|
10
12
|
//# sourceMappingURL=selectFields.d.ts.map
|
|
@@ -14,23 +14,28 @@ const shapeRow = (row, selectClauses) => selectClauses.reduce((shaped, {
|
|
|
14
14
|
shaped[alias || property] = row[property];
|
|
15
15
|
return shaped;
|
|
16
16
|
}, {});
|
|
17
|
+
const shapeGroup = (group, selectClauses) => group.groups ? {
|
|
18
|
+
...group,
|
|
19
|
+
groups: group.groups.map(inner => shapeGroup(inner, selectClauses))
|
|
20
|
+
} : {
|
|
21
|
+
...group,
|
|
22
|
+
rows: group.rows.map(row => shapeRow(row, selectClauses))
|
|
23
|
+
};
|
|
17
24
|
/**
|
|
18
|
-
* Shape each row down to only the selected properties (aliased, when given). dataSet is either a
|
|
19
|
-
*
|
|
20
|
-
*
|
|
25
|
+
* Shape each row down to only the selected properties (aliased, when given). dataSet is either a list of rows, or
|
|
26
|
+
* (once groupBy has run) a list of groups, whose rows are shaped the same way at any depth; the groups keep their
|
|
27
|
+
* field and value, selected or not.
|
|
21
28
|
* @param dataSet
|
|
22
29
|
* @param selectClauses
|
|
30
|
+
* @param grouped Whether dataSet is a list of groups (see groupBy)
|
|
23
31
|
*/
|
|
24
|
-
const selectFields = (dataSet = [], selectClauses = []) => {
|
|
32
|
+
const selectFields = (dataSet = [], selectClauses = [], grouped = false) => {
|
|
25
33
|
if (!selectClauses.length) {
|
|
26
34
|
return dataSet;
|
|
27
35
|
}
|
|
28
|
-
if (
|
|
29
|
-
return dataSet.map(
|
|
36
|
+
if (grouped) {
|
|
37
|
+
return dataSet.map(group => shapeGroup(group, selectClauses));
|
|
30
38
|
}
|
|
31
|
-
return
|
|
32
|
-
shaped[key] = dataSet[key].map(row => shapeRow(row, selectClauses));
|
|
33
|
-
return shaped;
|
|
34
|
-
}, {});
|
|
39
|
+
return dataSet.map(row => shapeRow(row, selectClauses));
|
|
35
40
|
};
|
|
36
41
|
exports.selectFields = selectFields;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.selectFields=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.reduce.js");const shapeRow=(e,s)=>s.reduce(((s,{property:r,alias:
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.selectFields=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.reduce.js");const shapeRow=(e,s)=>s.reduce(((s,{property:r,alias:o})=>(s[o||r]=e[r],s)),{}),shapeGroup=(e,s)=>e.groups?{...e,groups:e.groups.map((e=>shapeGroup(e,s)))}:{...e,rows:e.rows.map((e=>shapeRow(e,s)))},selectFields=(e=[],s=[],r=!1)=>s.length?r?e.map((e=>shapeGroup(e,s))):e.map((e=>shapeRow(e,s))):e;exports.selectFields=selectFields;
|
|
@@ -2,9 +2,10 @@ import { ParsedQuery } from './parseQuery';
|
|
|
2
2
|
export type ParsedCommand = string | 'delete' | 'insert' | 'read' | 'update';
|
|
3
3
|
export type ParsedEntity = string;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Read the command and record name from the start of a query's head (see splitClauses).
|
|
6
6
|
* @param parsed
|
|
7
7
|
* @param query
|
|
8
|
+
* @returns The rest of the head (a joined record, `.joined`, for findJoinEntity).
|
|
8
9
|
*/
|
|
9
10
|
export declare const findCommand: (parsed: ParsedQuery, query: string) => string;
|
|
10
11
|
//# sourceMappingURL=findCommand.d.ts.map
|
|
@@ -4,12 +4,12 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.findCommand = void 0;
|
|
7
|
-
|
|
8
|
-
const commandMatch = /^(delete|insert|read|update)\s+([a-z0-9_-]+)[\w\n]*/i;
|
|
7
|
+
const commandMatch = /^(delete|insert|read|update)\s+([a-z0-9_-]+)/i;
|
|
9
8
|
/**
|
|
10
|
-
*
|
|
9
|
+
* Read the command and record name from the start of a query's head (see splitClauses).
|
|
11
10
|
* @param parsed
|
|
12
11
|
* @param query
|
|
12
|
+
* @returns The rest of the head (a joined record, `.joined`, for findJoinEntity).
|
|
13
13
|
*/
|
|
14
14
|
const findCommand = (parsed, query) => {
|
|
15
15
|
const commandFound = query.match(commandMatch);
|
|
@@ -18,7 +18,7 @@ const findCommand = (parsed, query) => {
|
|
|
18
18
|
if (commandFound) {
|
|
19
19
|
parsed['command'] = commandFound[1].toLowerCase();
|
|
20
20
|
parsed['entity'] = commandFound[2];
|
|
21
|
-
return
|
|
21
|
+
return query.slice(commandFound[0].length).trim();
|
|
22
22
|
}
|
|
23
23
|
return query;
|
|
24
24
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.findCommand=void 0;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.findCommand=void 0;const commandMatch=/^(delete|insert|read|update)\s+([a-z0-9_-]+)/i,findCommand=(e,t)=>{const d=t.match(commandMatch);return e.command=void 0,e.entity=void 0,d?(e.command=d[1].toLowerCase(),e.entity=d[2],t.slice(d[0].length).trim()):t};exports.findCommand=findCommand;
|
|
@@ -8,15 +8,14 @@ export type ParsedCondition = {
|
|
|
8
8
|
type?: string;
|
|
9
9
|
};
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
* `
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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.
|
|
11
|
+
* Read a where clause body (see splitClauses) into a tree (see ConditionNode): conditions joined with `and` / `or`,
|
|
12
|
+
* `not`, and brackets, nested to any depth. Each condition is `<field> <comparator> <value>`, or `<field> is null`,
|
|
13
|
+
* `is not null`, `exists` or `does not exist`. A comparison with the literal null (`= null`) is refused: it would never
|
|
14
|
+
* match, as null equals nothing (see where).
|
|
17
15
|
* @param parsed
|
|
18
|
-
* @param
|
|
19
|
-
* @
|
|
16
|
+
* @param body
|
|
17
|
+
* @returns The rest of the body, after the expression.
|
|
18
|
+
* @throws {Error} For the errors readExpression and readCondition describe.
|
|
20
19
|
*/
|
|
21
|
-
export declare const findConditions: (parsed: ParsedQuery,
|
|
20
|
+
export declare const findConditions: (parsed: ParsedQuery, body: string) => string;
|
|
22
21
|
//# sourceMappingURL=findConditions.d.ts.map
|
|
@@ -10,15 +10,13 @@ require("core-js/modules/esnext.iterator.flat-map.js");
|
|
|
10
10
|
var _parseLiteralValue = require("./parseLiteralValue");
|
|
11
11
|
var _conditionTree = require("../conditionTree");
|
|
12
12
|
var _maskStrings = require("./maskStrings");
|
|
13
|
+
var _splitClauses = require("./splitClauses");
|
|
13
14
|
// Keywords count only as whole words: in `on t.x = u.color where ...` the `or` is part of the name `color`
|
|
14
15
|
const wordEnd = '(?![a-z0-9_.-])';
|
|
15
16
|
const keyword = words => new RegExp(`^(?:${words})${wordEnd}`, 'i');
|
|
16
17
|
// One condition, at the start of the text: a field, then `is [not] null` / `exists` / `does not exist`, or a comparator
|
|
17
18
|
// and a value (strings and arrays are placeholders by then, see maskStrings)
|
|
18
19
|
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');
|
|
22
20
|
/**
|
|
23
21
|
* Read one condition from a match of conditionMatch.
|
|
24
22
|
* @param match
|
|
@@ -62,15 +60,15 @@ const join = (kind, nodes) => {
|
|
|
62
60
|
};
|
|
63
61
|
};
|
|
64
62
|
/**
|
|
65
|
-
* Read one where expression from the
|
|
66
|
-
* first: `or`, `and`, `not`; brackets group.
|
|
63
|
+
* Read one where expression from the start of a where clause body (masked, see maskStrings). Precedence, loosest
|
|
64
|
+
* first: `or`, `and`, `not`; brackets group.
|
|
67
65
|
* @param text
|
|
68
|
-
* @
|
|
69
|
-
* @
|
|
70
|
-
*
|
|
66
|
+
* @returns The expression's tree and where it ends (at the first text that can't carry it on, or the end).
|
|
67
|
+
* @throws {Error} For an unbalanced bracket, empty brackets, an operator with no condition after it, or two conditions
|
|
68
|
+
* with no and/or between them.
|
|
71
69
|
*/
|
|
72
|
-
const readExpression =
|
|
73
|
-
let at =
|
|
70
|
+
const readExpression = text => {
|
|
71
|
+
let at = 0;
|
|
74
72
|
const rest = () => text.slice(at);
|
|
75
73
|
const skipSpace = () => {
|
|
76
74
|
at += rest().length - rest().trimStart().length;
|
|
@@ -93,6 +91,10 @@ const readExpression = (text, start) => {
|
|
|
93
91
|
at += condition[0].length;
|
|
94
92
|
return readCondition(condition);
|
|
95
93
|
}
|
|
94
|
+
// The 2.x `and where` / `or where` inside brackets, where splitClauses doesn't look
|
|
95
|
+
if ((after === 'and' || after === 'or') && keyword('where').test(rest())) {
|
|
96
|
+
throw new Error(`"${after} where" is no longer supported: ${_splitClauses.whereHint}`);
|
|
97
|
+
}
|
|
96
98
|
if (take(keyword('not'))) {
|
|
97
99
|
return {
|
|
98
100
|
not: factor('not')
|
|
@@ -115,11 +117,7 @@ const readExpression = (text, start) => {
|
|
|
115
117
|
const term = after => {
|
|
116
118
|
const nodes = [factor(after)];
|
|
117
119
|
for (;;) {
|
|
118
|
-
|
|
119
|
-
if (keyword('and\\s+on').test(rest())) {
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
const connector = take(keyword('and(?:\\s+where)?'));
|
|
120
|
+
const connector = take(keyword('and'));
|
|
123
121
|
if (!connector) {
|
|
124
122
|
break;
|
|
125
123
|
}
|
|
@@ -130,7 +128,7 @@ const readExpression = (text, start) => {
|
|
|
130
128
|
const expression = after => {
|
|
131
129
|
const nodes = [term(after)];
|
|
132
130
|
for (;;) {
|
|
133
|
-
const connector = take(keyword('or
|
|
131
|
+
const connector = take(keyword('or'));
|
|
134
132
|
if (!connector) {
|
|
135
133
|
break;
|
|
136
134
|
}
|
|
@@ -143,7 +141,7 @@ const readExpression = (text, start) => {
|
|
|
143
141
|
if (rest().startsWith(')')) {
|
|
144
142
|
throw new Error(`Unexpected ) in a where clause: ${shown()}`);
|
|
145
143
|
}
|
|
146
|
-
if (
|
|
144
|
+
if (conditionMatch.test(rest())) {
|
|
147
145
|
throw new Error(`Missing and/or before: ${shown()}`);
|
|
148
146
|
}
|
|
149
147
|
return {
|
|
@@ -152,31 +150,21 @@ const readExpression = (text, start) => {
|
|
|
152
150
|
};
|
|
153
151
|
};
|
|
154
152
|
/**
|
|
155
|
-
*
|
|
156
|
-
* `
|
|
157
|
-
*
|
|
158
|
-
*
|
|
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.
|
|
153
|
+
* Read a where clause body (see splitClauses) into a tree (see ConditionNode): conditions joined with `and` / `or`,
|
|
154
|
+
* `not`, and brackets, nested to any depth. Each condition is `<field> <comparator> <value>`, or `<field> is null`,
|
|
155
|
+
* `is not null`, `exists` or `does not exist`. A comparison with the literal null (`= null`) is refused: it would never
|
|
156
|
+
* match, as null equals nothing (see where).
|
|
161
157
|
* @param parsed
|
|
162
|
-
* @param
|
|
163
|
-
* @
|
|
158
|
+
* @param body
|
|
159
|
+
* @returns The rest of the body, after the expression.
|
|
160
|
+
* @throws {Error} For the errors readExpression and readCondition describe.
|
|
164
161
|
*/
|
|
165
|
-
const findConditions = (parsed,
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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();
|
|
178
|
-
}
|
|
179
|
-
parsed['where'] = expressions.length ? join('and', expressions) : (0, _conditionTree.allOf)([]);
|
|
180
|
-
return query;
|
|
162
|
+
const findConditions = (parsed, body) => {
|
|
163
|
+
const {
|
|
164
|
+
node,
|
|
165
|
+
end
|
|
166
|
+
} = readExpression(body);
|
|
167
|
+
parsed['where'] = node;
|
|
168
|
+
return body.slice(end).trim();
|
|
181
169
|
};
|
|
182
170
|
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"),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"),
|
|
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"),_splitClauses=require("./splitClauses");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"),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=>{let r=0;const n=()=>e.slice(r),t=()=>{r+=n().length-n().trimStart().length},o=e=>{t();const o=n().match(e);if(o)return r+=o[0].length,o[0]},s=()=>(0,_maskStrings.unmaskStrings)(n().trim(),"'"),i=e=>{t();const a=n().match(conditionMatch);if(a)return r+=a[0].length,readCondition(a);if(("and"===e||"or"===e)&&keyword("where").test(n()))throw new Error(`"${e} where" is no longer supported: ${_splitClauses.whereHint}`);if(o(keyword("not")))return{not:i("not")};if(o(/^\(/)){if(t(),n().startsWith(")"))throw new Error("Empty brackets in a where clause");const e=l("(");if(!o(/^\)/))throw new Error("Missing ) "+(n().trim()?`before: ${s()}`:"at the end of the where clause"));return e}throw new Error(`Missing a condition after ${e}${n().trim()?`: ${s()}`:""}`)},a=e=>{const r=[i(e)];for(;;){const e=o(keyword("and"));if(!e)break;r.push(i(e.toLowerCase().replace(/\s+/g," ")))}return join("and",r)},l=e=>{const r=[a(e)];for(;;){const e=o(keyword("or"));if(!e)break;r.push(a(e.toLowerCase().replace(/\s+/g," ")))}return join("or",r)},u=l("where");if(t(),n().startsWith(")"))throw new Error(`Unexpected ) in a where clause: ${s()}`);if(conditionMatch.test(n()))throw new Error(`Missing and/or before: ${s()}`);return{node:u,end:r}},findConditions=(e,r)=>{const{node:n,end:t}=readExpression(r);return e.where=n,r.slice(t).trim()};exports.findConditions=findConditions;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { ParsedQuery } from './parseQuery';
|
|
2
|
-
export type ParsedGroupBy = string;
|
|
2
|
+
export type ParsedGroupBy = string[];
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Read a group by clause body (`a, b`, see splitClauses).
|
|
5
5
|
* @param parsed
|
|
6
|
-
* @param
|
|
6
|
+
* @param body
|
|
7
|
+
* @returns The rest of the body, after the last field.
|
|
8
|
+
* @throws {Error} For a field named twice.
|
|
7
9
|
*/
|
|
8
|
-
export declare const findGroupBy: (parsed: ParsedQuery,
|
|
10
|
+
export declare const findGroupBy: (parsed: ParsedQuery, body: string) => string;
|
|
9
11
|
//# sourceMappingURL=findGroupBy.d.ts.map
|
|
@@ -4,19 +4,27 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.findGroupBy = void 0;
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
require("core-js/modules/esnext.iterator.constructor.js");
|
|
8
|
+
require("core-js/modules/esnext.iterator.find.js");
|
|
9
|
+
const groupMatch = /^[a-z0-9._-]+(?:\s*,\s*[a-z0-9._-]+)*/i;
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
+
* Read a group by clause body (`a, b`, see splitClauses).
|
|
11
12
|
* @param parsed
|
|
12
|
-
* @param
|
|
13
|
+
* @param body
|
|
14
|
+
* @returns The rest of the body, after the last field.
|
|
15
|
+
* @throws {Error} For a field named twice.
|
|
13
16
|
*/
|
|
14
|
-
const findGroupBy = (parsed,
|
|
15
|
-
const groupFound =
|
|
17
|
+
const findGroupBy = (parsed, body) => {
|
|
18
|
+
const groupFound = body.match(groupMatch);
|
|
16
19
|
if (groupFound) {
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
const fields = groupFound[0].split(/\s*,\s*/);
|
|
21
|
+
const repeated = fields.find((field, index) => fields.indexOf(field) !== index);
|
|
22
|
+
if (repeated) {
|
|
23
|
+
throw new Error(`"${repeated}" is in group by twice`);
|
|
24
|
+
}
|
|
25
|
+
parsed['groupBy'] = fields;
|
|
26
|
+
return body.slice(groupFound[0].length).trim();
|
|
19
27
|
}
|
|
20
|
-
return
|
|
28
|
+
return body;
|
|
21
29
|
};
|
|
22
30
|
exports.findGroupBy = findGroupBy;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.findGroupBy=void 0
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.findGroupBy=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.find.js");const groupMatch=/^[a-z0-9._-]+(?:\s*,\s*[a-z0-9._-]+)*/i,findGroupBy=(r,e)=>{const t=e.match(groupMatch);if(t){const o=t[0].split(/\s*,\s*/),s=o.find(((r,e)=>o.indexOf(r)!==e));if(s)throw new Error(`"${s}" is in group by twice`);return r.groupBy=o,e.slice(t[0].length).trim()}return e};exports.findGroupBy=findGroupBy;
|
|
@@ -3,11 +3,12 @@ export type ParsedInsertRow = {
|
|
|
3
3
|
[field: string]: any;
|
|
4
4
|
};
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Read an insert's values clause body (`field = value, field2 = value2`, see splitClauses), the same way
|
|
7
7
|
* findUpdateValues() does for "set". insertEntity() accepts an array of rows for bulk inserts, but
|
|
8
8
|
* a query string only describes a single row for now - wrapped in a one-element array to match.
|
|
9
9
|
* @param parsed
|
|
10
|
-
* @param
|
|
10
|
+
* @param body
|
|
11
|
+
* @returns The rest of the body, after the last assignment.
|
|
11
12
|
*/
|
|
12
|
-
export declare const findInsertValues: (parsed: ParsedQuery,
|
|
13
|
+
export declare const findInsertValues: (parsed: ParsedQuery, body: string) => string;
|
|
13
14
|
//# sourceMappingURL=findInsertValues.d.ts.map
|
|
@@ -4,32 +4,32 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.findInsertValues = void 0;
|
|
7
|
-
var _removeFromQuery = require("./removeFromQuery");
|
|
8
7
|
var _parseLiteralValue = require("./parseLiteralValue");
|
|
9
8
|
var _maskStrings = require("./maskStrings");
|
|
10
9
|
const literal = `(?:${_maskStrings.placeholderSource.replace('(', '(?:')}|${_parseLiteralValue.numberSource}|${_maskStrings.arraySource.replace('(', '(?:')}|null|true|false)`;
|
|
11
|
-
const
|
|
10
|
+
const valuesMatch = new RegExp(`^([a-z0-9_-]+\\s*=\\s*${literal}(?:\\s*,\\s*[a-z0-9_-]+\\s*=\\s*${literal})*)`, 'i');
|
|
12
11
|
const assignmentMatch = new RegExp(`([a-z0-9_-]+)\\s*=\\s*(${_maskStrings.placeholderSource}|(${_parseLiteralValue.numberSource})|${_maskStrings.arraySource}|(null)|(true)|(false))`, 'ig');
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
13
|
+
* Read an insert's values clause body (`field = value, field2 = value2`, see splitClauses), the same way
|
|
15
14
|
* findUpdateValues() does for "set". insertEntity() accepts an array of rows for bulk inserts, but
|
|
16
15
|
* a query string only describes a single row for now - wrapped in a one-element array to match.
|
|
17
16
|
* @param parsed
|
|
18
|
-
* @param
|
|
17
|
+
* @param body
|
|
18
|
+
* @returns The rest of the body, after the last assignment.
|
|
19
19
|
*/
|
|
20
|
-
const findInsertValues = (parsed,
|
|
21
|
-
const clauseFound =
|
|
20
|
+
const findInsertValues = (parsed, body) => {
|
|
21
|
+
const clauseFound = body.match(valuesMatch);
|
|
22
22
|
parsed['insertValues'] = [];
|
|
23
23
|
if (!clauseFound) {
|
|
24
|
-
return
|
|
24
|
+
return body;
|
|
25
25
|
}
|
|
26
26
|
const row = {};
|
|
27
|
-
const assignmentsFound = clauseFound[
|
|
27
|
+
const assignmentsFound = clauseFound[1].matchAll(assignmentMatch);
|
|
28
28
|
// @ts-ignore
|
|
29
29
|
for (const assignment of assignmentsFound) {
|
|
30
30
|
row[assignment[1]] = (0, _parseLiteralValue.parseLiteralValue)(assignment, 3);
|
|
31
31
|
}
|
|
32
32
|
parsed['insertValues'] = [row];
|
|
33
|
-
return
|
|
33
|
+
return body.slice(clauseFound[0].length).trim();
|
|
34
34
|
};
|
|
35
35
|
exports.findInsertValues = findInsertValues;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.findInsertValues=void 0;var
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.findInsertValues=void 0;var _parseLiteralValue=require("./parseLiteralValue"),_maskStrings=require("./maskStrings");const literal=`(?:${_maskStrings.placeholderSource.replace("(","(?:")}|${_parseLiteralValue.numberSource}|${_maskStrings.arraySource.replace("(","(?:")}|null|true|false)`,valuesMatch=new RegExp(`^([a-z0-9_-]+\\s*=\\s*${literal}(?:\\s*,\\s*[a-z0-9_-]+\\s*=\\s*${literal})*)`,"i"),assignmentMatch=new RegExp(`([a-z0-9_-]+)\\s*=\\s*(${_maskStrings.placeholderSource}|(${_parseLiteralValue.numberSource})|${_maskStrings.arraySource}|(null)|(true)|(false))`,"ig"),findInsertValues=(e,r)=>{const a=r.match(valuesMatch);if(e.insertValues=[],!a)return r;const s={},t=a[1].matchAll(assignmentMatch);for(const e of t)s[e[1]]=(0,_parseLiteralValue.parseLiteralValue)(e,3);return e.insertValues=[s],r.slice(a[0].length).trim()};exports.findInsertValues=findInsertValues;
|
|
@@ -5,9 +5,10 @@ export type ParsedJoinClause = {
|
|
|
5
5
|
propertyB: string;
|
|
6
6
|
};
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Read an on clause body (`a.x = b.y and on b.z = c.w`, see splitClauses).
|
|
9
9
|
* @param parsed
|
|
10
|
-
* @param
|
|
10
|
+
* @param body
|
|
11
|
+
* @returns The rest of the body, after the last join condition.
|
|
11
12
|
*/
|
|
12
|
-
export declare const findJoinClauses: (parsed: ParsedQuery,
|
|
13
|
+
export declare const findJoinClauses: (parsed: ParsedQuery, body: string) => string;
|
|
13
14
|
//# sourceMappingURL=findJoinClauses.d.ts.map
|
|
@@ -4,24 +4,31 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.findJoinClauses = void 0;
|
|
7
|
-
|
|
8
|
-
const joinClauseMatch =
|
|
7
|
+
// One join condition, at the start of the text; every one after the first starts with `and on` (see splitClauses)
|
|
8
|
+
const joinClauseMatch = /^([a-z0-9_-]+\.[a-z0-9_-]+)\s+(=|!=|>|<|>=|<=|in|between|like)\s+([a-z0-9_-]+\.[a-z0-9_-]+)(?![a-z0-9._-])/i;
|
|
9
|
+
const nextMatch = /^and\s+on\s+/i;
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
+
* Read an on clause body (`a.x = b.y and on b.z = c.w`, see splitClauses).
|
|
11
12
|
* @param parsed
|
|
12
|
-
* @param
|
|
13
|
+
* @param body
|
|
14
|
+
* @returns The rest of the body, after the last join condition.
|
|
13
15
|
*/
|
|
14
|
-
const findJoinClauses = (parsed,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
for (const joinClause of joinClausesFound) {
|
|
16
|
+
const findJoinClauses = (parsed, body) => {
|
|
17
|
+
let rest = body;
|
|
18
|
+
for (let joinClause = rest.match(joinClauseMatch); joinClause; joinClause = rest.match(joinClauseMatch)) {
|
|
18
19
|
parsed['joinClauses'].push({
|
|
19
|
-
propertyA: joinClause[
|
|
20
|
-
comparator: joinClause[
|
|
21
|
-
propertyB: joinClause[
|
|
20
|
+
propertyA: joinClause[1],
|
|
21
|
+
comparator: joinClause[2],
|
|
22
|
+
propertyB: joinClause[3]
|
|
22
23
|
});
|
|
23
|
-
|
|
24
|
+
rest = rest.slice(joinClause[0].length).trim();
|
|
25
|
+
// The connector only with another one after it, so a bad one is shown with its connector
|
|
26
|
+
const next = rest.match(nextMatch);
|
|
27
|
+
if (!next || !joinClauseMatch.test(rest.slice(next[0].length))) {
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
rest = rest.slice(next[0].length);
|
|
24
31
|
}
|
|
25
|
-
return
|
|
32
|
+
return rest;
|
|
26
33
|
};
|
|
27
34
|
exports.findJoinClauses = findJoinClauses;
|