json-fs-query 3.1.4 → 3.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 +257 -15
- package/browser/jsonFsQuery.js +3884 -5057
- package/browser/jsonFsQuery.min.js +1 -1
- package/dist/data/introspect/alter.d.ts +12 -0
- package/dist/data/introspect/alter.js +304 -0
- package/dist/data/introspect/alter.min.js +1 -0
- package/dist/data/introspect/create.d.ts +10 -0
- package/dist/data/introspect/create.js +9 -23
- package/dist/data/introspect/create.min.js +1 -1
- package/dist/data/introspect/drop.js +7 -15
- package/dist/data/introspect/drop.min.js +1 -1
- package/dist/data/introspect/formatDatabase.d.ts +17 -0
- package/dist/data/introspect/formatDatabase.js +56 -0
- package/dist/data/introspect/formatDatabase.min.js +1 -0
- package/dist/data/introspect/index.d.ts +9 -0
- package/dist/data/introspect/index.js +18 -5
- package/dist/data/introspect/index.min.js +1 -1
- package/dist/data/introspect/packDatabase.d.ts +27 -0
- package/dist/data/introspect/packDatabase.js +113 -0
- package/dist/data/introspect/packDatabase.min.js +1 -0
- package/dist/data/introspect/policies/foreignKeyExists.d.ts +2 -0
- package/dist/data/introspect/policies/foreignKeyExists.js +9 -3
- package/dist/data/introspect/policies/foreignKeyExists.min.js +1 -1
- package/dist/data/introspect/policies/hasDependents.d.ts +3 -3
- package/dist/data/introspect/policies/hasDependents.js +11 -3
- package/dist/data/introspect/policies/hasDependents.min.js +1 -1
- package/dist/data/introspect/policies/keyUnique.d.ts +3 -0
- package/dist/data/introspect/policies/keyUnique.js +10 -0
- package/dist/data/introspect/policies/keyUnique.min.js +1 -1
- package/dist/data/queries/databaseStatements.d.ts +31 -0
- package/dist/data/queries/databaseStatements.js +97 -0
- package/dist/data/queries/databaseStatements.min.js +1 -0
- package/dist/data/queries/deleteEntity.js +35 -19
- package/dist/data/queries/deleteEntity.min.js +1 -1
- package/dist/data/queries/insertEntity.d.ts +8 -0
- package/dist/data/queries/insertEntity.js +340 -43
- package/dist/data/queries/insertEntity.min.js +1 -1
- package/dist/data/queries/joinEntity.d.ts +15 -0
- package/dist/data/queries/joinEntity.js +91 -15
- package/dist/data/queries/joinEntity.min.js +1 -1
- package/dist/data/queries/query.d.ts +2 -1
- package/dist/data/queries/query.js +5 -2
- package/dist/data/queries/query.min.js +1 -1
- package/dist/data/queries/selectFields.d.ts +5 -4
- package/dist/data/queries/selectFields.js +25 -11
- package/dist/data/queries/selectFields.min.js +1 -1
- package/dist/data/queries/updateEntity.js +54 -15
- package/dist/data/queries/updateEntity.min.js +1 -1
- package/dist/data/storage/databases.d.ts +32 -0
- package/dist/data/storage/databases.js +93 -0
- package/dist/data/storage/databases.min.js +1 -0
- package/dist/data/storage/fileExists.d.ts +7 -0
- package/dist/data/storage/fileExists.js +25 -0
- package/dist/data/storage/fileExists.min.js +1 -0
- package/dist/data/storage/index.d.ts +19 -0
- package/dist/data/storage/index.js +124 -0
- package/dist/data/storage/index.min.js +1 -0
- package/dist/data/storage/listFiles.d.ts +8 -0
- package/dist/data/storage/listFiles.js +52 -0
- package/dist/data/storage/listFiles.min.js +1 -0
- package/dist/data/storage/makeDirectory.d.ts +7 -0
- package/dist/data/storage/makeDirectory.js +24 -0
- package/dist/data/storage/makeDirectory.min.js +1 -0
- package/dist/data/storage/nodeFiles.d.ts +6 -0
- package/dist/data/storage/nodeFiles.js +12 -0
- package/dist/data/storage/nodeFiles.min.js +1 -0
- package/dist/data/storage/readCache.d.ts +37 -0
- package/dist/data/storage/readCache.js +117 -0
- package/dist/data/storage/readCache.min.js +1 -0
- package/dist/data/storage/readJson.d.ts +16 -0
- package/dist/data/storage/readJson.js +38 -0
- package/dist/data/storage/readJson.min.js +1 -0
- package/dist/data/storage/removeDirectory.d.ts +8 -0
- package/dist/data/storage/removeDirectory.js +30 -0
- package/dist/data/storage/removeDirectory.min.js +1 -0
- package/dist/data/storage/removeFile.d.ts +8 -0
- package/dist/data/storage/removeFile.js +31 -0
- package/dist/data/storage/removeFile.min.js +1 -0
- package/dist/data/storage/storagePath.d.ts +11 -0
- package/dist/data/storage/storagePath.js +25 -0
- package/dist/data/storage/storagePath.min.js +1 -0
- package/dist/data/storage/writeJson.d.ts +17 -0
- package/dist/data/storage/writeJson.js +37 -0
- package/dist/data/storage/writeJson.min.js +1 -0
- package/dist/data/storage/writeSet.d.ts +64 -0
- package/dist/data/storage/writeSet.js +306 -0
- package/dist/data/storage/writeSet.min.js +1 -0
- package/dist/data/utilities/compareAs.d.ts +1 -0
- package/dist/data/utilities/compareAs.js +2 -2
- package/dist/data/utilities/compareAs.min.js +1 -1
- package/dist/data/utilities/config.d.ts +37 -0
- package/dist/data/utilities/config.js +77 -5
- package/dist/data/utilities/config.min.js +1 -1
- package/dist/data/utilities/databaseLock.d.ts +7 -0
- package/dist/data/utilities/databaseLock.js +19 -4
- package/dist/data/utilities/databaseLock.min.js +1 -1
- package/dist/data/utilities/deregisterRecord.js +4 -10
- package/dist/data/utilities/deregisterRecord.min.js +1 -1
- package/dist/data/utilities/getConditionIndexedList.js +3 -4
- package/dist/data/utilities/getConditionIndexedList.min.js +1 -1
- package/dist/data/utilities/indexBuffer.d.ts +35 -0
- package/dist/data/utilities/indexBuffer.js +139 -0
- package/dist/data/utilities/indexBuffer.min.js +1 -0
- package/dist/data/utilities/indexHas.d.ts +13 -0
- package/dist/data/utilities/indexHas.js +48 -0
- package/dist/data/utilities/indexHas.min.js +1 -0
- package/dist/data/utilities/joinBases.d.ts +29 -0
- package/dist/data/utilities/joinBases.js +128 -0
- package/dist/data/utilities/joinBases.min.js +1 -0
- package/dist/data/utilities/joinKey.d.ts +17 -0
- package/dist/data/utilities/joinKey.js +31 -0
- package/dist/data/utilities/joinKey.min.js +1 -0
- package/dist/data/utilities/keyIndexFiles.d.ts +16 -0
- package/dist/data/utilities/keyIndexFiles.js +60 -0
- package/dist/data/utilities/keyIndexFiles.min.js +1 -0
- package/dist/data/utilities/knownFields.d.ts +26 -0
- package/dist/data/utilities/knownFields.js +128 -0
- package/dist/data/utilities/knownFields.min.js +1 -0
- package/dist/data/utilities/packedTable.d.ts +34 -0
- package/dist/data/utilities/packedTable.js +60 -0
- package/dist/data/utilities/packedTable.min.js +1 -0
- package/dist/data/utilities/parsers/findInsertValues.d.ts +3 -3
- package/dist/data/utilities/parsers/findInsertValues.js +28 -12
- package/dist/data/utilities/parsers/findInsertValues.min.js +1 -1
- package/dist/data/utilities/parsers/maskStrings.js +11 -2
- package/dist/data/utilities/parsers/maskStrings.min.js +1 -1
- package/dist/data/utilities/parsers/parseAlter.d.ts +33 -0
- package/dist/data/utilities/parsers/parseAlter.js +84 -0
- package/dist/data/utilities/parsers/parseAlter.min.js +1 -0
- package/dist/data/utilities/parsers/parseCreate.d.ts +14 -0
- package/dist/data/utilities/parsers/parseCreate.js +13 -5
- package/dist/data/utilities/parsers/parseCreate.min.js +1 -1
- package/dist/data/utilities/parsers/parseDatabaseStatement.d.ts +17 -0
- package/dist/data/utilities/parsers/parseDatabaseStatement.js +82 -0
- package/dist/data/utilities/parsers/parseDatabaseStatement.min.js +1 -0
- package/dist/data/utilities/parsers/parseQuery.d.ts +3 -0
- package/dist/data/utilities/parsers/parseQuery.js +24 -0
- package/dist/data/utilities/parsers/parseQuery.min.js +1 -1
- package/dist/data/utilities/parsers/startsQuery.js +1 -1
- package/dist/data/utilities/parsers/startsQuery.min.js +1 -1
- package/dist/data/utilities/recordExists.js +2 -7
- package/dist/data/utilities/recordExists.min.js +1 -1
- package/dist/data/utilities/registerRecord.js +4 -10
- package/dist/data/utilities/registerRecord.min.js +1 -1
- package/dist/data/utilities/removeFromIndex.js +4 -10
- package/dist/data/utilities/removeFromIndex.min.js +1 -1
- package/dist/data/utilities/retrieveFile.d.ts +5 -0
- package/dist/data/utilities/retrieveFile.js +7 -16
- package/dist/data/utilities/retrieveFile.min.js +1 -1
- package/dist/data/utilities/retrieveFiles.d.ts +15 -0
- package/dist/data/utilities/retrieveFiles.js +48 -0
- package/dist/data/utilities/retrieveFiles.min.js +1 -0
- package/dist/data/utilities/retrieveRecord.d.ts +6 -0
- package/dist/data/utilities/retrieveRecord.js +24 -1
- package/dist/data/utilities/retrieveRecord.min.js +1 -1
- package/dist/data/utilities/retrieveRecords.d.ts +22 -0
- package/dist/data/utilities/retrieveRecords.js +60 -10
- package/dist/data/utilities/retrieveRecords.min.js +1 -1
- package/dist/data/utilities/rowLookup.d.ts +38 -0
- package/dist/data/utilities/rowLookup.js +99 -0
- package/dist/data/utilities/rowLookup.min.js +1 -0
- package/dist/data/utilities/runQuery.js +28 -4
- package/dist/data/utilities/runQuery.min.js +1 -1
- package/dist/data/utilities/semiJoin.d.ts +27 -0
- package/dist/data/utilities/semiJoin.js +97 -0
- package/dist/data/utilities/semiJoin.min.js +1 -0
- package/dist/data/utilities/updateIndex.js +5 -14
- package/dist/data/utilities/updateIndex.min.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +24 -0
- package/dist/index.min.js +1 -1
- package/package.json +10 -4
- package/dist/data/utilities/getJoinIndexedList.d.ts +0 -3
- package/dist/data/utilities/getJoinIndexedList.js +0 -76
- package/dist/data/utilities/getJoinIndexedList.min.js +0 -1
- package/dist/data/utilities/getMatchingIndexes.d.ts +0 -2
- package/dist/data/utilities/getMatchingIndexes.js +0 -28
- package/dist/data/utilities/getMatchingIndexes.min.js +0 -1
- package/dist/data/utilities/matchIndexedJoins.d.ts +0 -2
- package/dist/data/utilities/matchIndexedJoins.js +0 -63
- package/dist/data/utilities/matchIndexedJoins.min.js +0 -1
- package/dist/data/utilities/restrictIndexedList.d.ts +0 -2
- package/dist/data/utilities/restrictIndexedList.js +0 -45
- package/dist/data/utilities/restrictIndexedList.min.js +0 -1
- package/dist/data/utilities/useJoinClause.d.ts +0 -6
- package/dist/data/utilities/useJoinClause.js +0 -57
- package/dist/data/utilities/useJoinClause.min.js +0 -1
|
@@ -4,14 +4,44 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.insertEntity = void 0;
|
|
7
|
+
require("core-js/modules/es.array.includes.js");
|
|
7
8
|
require("core-js/modules/es.json.stringify.js");
|
|
8
9
|
require("core-js/modules/esnext.iterator.constructor.js");
|
|
10
|
+
require("core-js/modules/esnext.iterator.every.js");
|
|
9
11
|
require("core-js/modules/esnext.iterator.find.js");
|
|
12
|
+
require("core-js/modules/esnext.iterator.for-each.js");
|
|
10
13
|
require("core-js/modules/esnext.iterator.map.js");
|
|
11
14
|
require("core-js/modules/esnext.iterator.some.js");
|
|
15
|
+
require("core-js/modules/esnext.map.delete-all.js");
|
|
16
|
+
require("core-js/modules/esnext.map.every.js");
|
|
17
|
+
require("core-js/modules/esnext.map.filter.js");
|
|
18
|
+
require("core-js/modules/esnext.map.find.js");
|
|
19
|
+
require("core-js/modules/esnext.map.find-key.js");
|
|
20
|
+
require("core-js/modules/esnext.map.includes.js");
|
|
21
|
+
require("core-js/modules/esnext.map.key-of.js");
|
|
22
|
+
require("core-js/modules/esnext.map.map-keys.js");
|
|
23
|
+
require("core-js/modules/esnext.map.map-values.js");
|
|
24
|
+
require("core-js/modules/esnext.map.merge.js");
|
|
25
|
+
require("core-js/modules/esnext.map.reduce.js");
|
|
26
|
+
require("core-js/modules/esnext.map.some.js");
|
|
27
|
+
require("core-js/modules/esnext.map.update.js");
|
|
28
|
+
require("core-js/modules/esnext.set.add-all.js");
|
|
29
|
+
require("core-js/modules/esnext.set.delete-all.js");
|
|
30
|
+
require("core-js/modules/esnext.set.difference.js");
|
|
31
|
+
require("core-js/modules/esnext.set.every.js");
|
|
32
|
+
require("core-js/modules/esnext.set.filter.js");
|
|
33
|
+
require("core-js/modules/esnext.set.find.js");
|
|
34
|
+
require("core-js/modules/esnext.set.intersection.js");
|
|
35
|
+
require("core-js/modules/esnext.set.is-disjoint-from.js");
|
|
36
|
+
require("core-js/modules/esnext.set.is-subset-of.js");
|
|
37
|
+
require("core-js/modules/esnext.set.is-superset-of.js");
|
|
38
|
+
require("core-js/modules/esnext.set.join.js");
|
|
39
|
+
require("core-js/modules/esnext.set.map.js");
|
|
40
|
+
require("core-js/modules/esnext.set.reduce.js");
|
|
41
|
+
require("core-js/modules/esnext.set.some.js");
|
|
42
|
+
require("core-js/modules/esnext.set.symmetric-difference.js");
|
|
43
|
+
require("core-js/modules/esnext.set.union.js");
|
|
12
44
|
var _retrieveRecord = require("../utilities/retrieveRecord");
|
|
13
|
-
var _updateIndex = require("../utilities/updateIndex");
|
|
14
|
-
var _keyGenerate = require("../introspect/policies/keyGenerate");
|
|
15
45
|
var _keyUnique = require("../introspect/policies/keyUnique");
|
|
16
46
|
var _useDefault = require("../introspect/policies/useDefault");
|
|
17
47
|
var _typeCheck = require("../introspect/policies/typeCheck");
|
|
@@ -19,30 +49,290 @@ var _foreignKeyExists = require("../introspect/policies/foreignKeyExists");
|
|
|
19
49
|
var _splitEntityProperty = require("../utilities/parsers/splitEntityProperty");
|
|
20
50
|
var _computeKeyValue = require("../utilities/computeKeyValue");
|
|
21
51
|
var _fieldType = require("../utilities/fieldType");
|
|
22
|
-
var
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
52
|
+
var _compareAs = require("../utilities/compareAs");
|
|
53
|
+
var _retrieveRecords = require("../utilities/retrieveRecords");
|
|
54
|
+
var _isSingleFieldKey = require("../utilities/isSingleFieldKey");
|
|
55
|
+
var _indexBuffer = require("../utilities/indexBuffer");
|
|
56
|
+
var _storage = require("../storage");
|
|
57
|
+
var _packedTable = require("../utilities/packedTable");
|
|
58
|
+
var _knownFields = require("../utilities/knownFields");
|
|
59
|
+
/**
|
|
60
|
+
* The rows of a record that hold each combination of values for some fields, compared field by field by type, as
|
|
61
|
+
* where's = compares them: built from the rows in storage once per statement, for a key no index can answer (several
|
|
62
|
+
* fields, or no index file), instead of looking at every row in storage once per inserted row.
|
|
63
|
+
*/
|
|
64
|
+
class RowValues {
|
|
65
|
+
constructor(record, fields) {
|
|
66
|
+
this.seen = new Set();
|
|
67
|
+
this.fields = fields;
|
|
68
|
+
this.types = fields.map(field => (0, _fieldType.fieldType)(record, field));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Whether these fields' values can be compared this way: every field of a comparable type (see compareAs).
|
|
72
|
+
* @param record
|
|
73
|
+
* @param fields
|
|
74
|
+
*/
|
|
75
|
+
static usable(record, fields) {
|
|
76
|
+
return fields.every(field => _compareAs.comparableTypes.includes((0, _fieldType.fieldType)(record, field)));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The values' key, or undefined when one is null or missing (where's = never matches those) or compareAs leaves it an
|
|
80
|
+
* object (compared by identity, so not by a key).
|
|
81
|
+
* @param row
|
|
82
|
+
*/
|
|
83
|
+
keyOf(row) {
|
|
84
|
+
const typed = [];
|
|
85
|
+
for (let position = 0; position < this.fields.length; position++) {
|
|
86
|
+
const value = row[this.fields[position]];
|
|
87
|
+
if (value === null || typeof value === 'undefined') {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
const key = (0, _compareAs.compareAs)(this.types[position], value);
|
|
91
|
+
if (typeof key === 'object') {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
typed.push([typeof key, key]);
|
|
95
|
+
}
|
|
96
|
+
return JSON.stringify(typed);
|
|
97
|
+
}
|
|
98
|
+
add(row) {
|
|
99
|
+
const key = this.keyOf(row);
|
|
100
|
+
if (typeof key !== 'undefined') {
|
|
101
|
+
this.seen.add(key);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
has(row) {
|
|
105
|
+
const key = this.keyOf(row);
|
|
106
|
+
return typeof key !== 'undefined' && this.seen.has(key);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* One insert statement's view of the keys it checks, so that each index is read and parsed once for all its rows
|
|
111
|
+
* instead of once per row, and the record file and its indexes are written once at the end instead of once per row.
|
|
112
|
+
*
|
|
113
|
+
* It answers every check exactly as reading the files after each row would (as inserts did one row at a time): the
|
|
114
|
+
* record's own indexes are held in memory (see IndexBuffer) and gain each row as it's added, so a row is checked
|
|
115
|
+
* against the rows before it in the statement too; another record's index is read once, since an insert never changes
|
|
116
|
+
* another record. Where a check can't use an index it looks at every row in storage as before, plus this statement's
|
|
117
|
+
* rows, which aren't in storage until the end.
|
|
118
|
+
*/
|
|
119
|
+
class InsertStatement {
|
|
120
|
+
constructor(entity, record) {
|
|
121
|
+
this.rows = [];
|
|
122
|
+
this.buffers = new Map();
|
|
123
|
+
this.unreadable = new Set();
|
|
124
|
+
this.otherLookups = new Map();
|
|
125
|
+
this.scans = new Map();
|
|
126
|
+
this.entity = entity;
|
|
127
|
+
this.record = record;
|
|
128
|
+
this.hadEntries = (record.entries || []).length > 0;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Read the record's index files, once. An index that can't be read starts empty (as updateIndex starts one) and its
|
|
132
|
+
* key is checked by looking at every row.
|
|
133
|
+
*/
|
|
134
|
+
async load() {
|
|
135
|
+
for (const key of this.record.keys) {
|
|
136
|
+
if (!key.lookup) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
let entries = [];
|
|
140
|
+
try {
|
|
141
|
+
entries = await (0, _storage.readJson)(`__indexes/${this.record.path}/${key.lookup}`);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
this.unreadable.add(key);
|
|
144
|
+
}
|
|
145
|
+
this.buffers.set(key, new _indexBuffer.IndexBuffer(Array.isArray(entries) ? entries : [], (0, _fieldType.keyValueType)(this.record, key)));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* How to look up a non-null value of `property` in the record being inserted into: through its index buffer, which
|
|
150
|
+
* holds the statement's rows so far. indexHas reads a record with no entries as holding nothing, whatever its index
|
|
151
|
+
* file says, so until this statement adds a row to a record that had none, nothing is found.
|
|
152
|
+
* @param property
|
|
153
|
+
*/
|
|
154
|
+
ownLookup(property) {
|
|
155
|
+
const key = this.record.keys.find(key => (0, _isSingleFieldKey.isSingleFieldKey)(key, property) && key.lookup);
|
|
156
|
+
if (!key || this.unreadable.has(key)) {
|
|
157
|
+
return () => undefined;
|
|
158
|
+
}
|
|
159
|
+
const buffer = this.buffers.get(key);
|
|
160
|
+
return value => (this.hadEntries || this.rows.length > 0) && buffer.has(value);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* How to look up a non-null value of `property` in another record, read once (an insert doesn't change it).
|
|
164
|
+
* @param entity
|
|
165
|
+
* @param property
|
|
166
|
+
*/
|
|
167
|
+
otherLookup(entity, property) {
|
|
168
|
+
const name = `${entity}\u0000${property}`;
|
|
169
|
+
if (!this.otherLookups.has(name)) {
|
|
170
|
+
this.otherLookups.set(name, (async () => {
|
|
171
|
+
const record = await (0, _retrieveRecord.retrieveRecord)(entity);
|
|
172
|
+
const key = record.keys.find(key => (0, _isSingleFieldKey.isSingleFieldKey)(key, property) && key.lookup);
|
|
173
|
+
if (!key) {
|
|
174
|
+
return () => undefined;
|
|
175
|
+
}
|
|
176
|
+
if (!(record.entries || []).length) {
|
|
177
|
+
return () => false;
|
|
178
|
+
}
|
|
179
|
+
let entries;
|
|
180
|
+
try {
|
|
181
|
+
entries = await (0, _storage.readJson)(`__indexes/${record.path}/${key.lookup}`);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
return () => undefined;
|
|
184
|
+
}
|
|
185
|
+
const buffer = new _indexBuffer.IndexBuffer(Array.isArray(entries) ? entries : [], (0, _fieldType.fieldType)(record, property));
|
|
186
|
+
return value => buffer.has(value);
|
|
187
|
+
})());
|
|
188
|
+
}
|
|
189
|
+
return this.otherLookups.get(name);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* The values of these fields in every row of `entity` (read once per statement), plus, for the record being inserted
|
|
193
|
+
* into, every row this statement adds; undefined when a field's type can't be compared by key.
|
|
194
|
+
* @param entity
|
|
195
|
+
* @param fields
|
|
196
|
+
*/
|
|
197
|
+
scan(entity, fields) {
|
|
198
|
+
const name = `${entity}\u0000${fields.join('\u0000')}`;
|
|
199
|
+
if (!this.scans.has(name)) {
|
|
200
|
+
this.scans.set(name, (async () => {
|
|
201
|
+
const record = entity === this.entity ? this.record : await (0, _retrieveRecord.retrieveRecord)(entity);
|
|
202
|
+
if (!RowValues.usable(record, fields)) {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
const values = new RowValues(record, fields);
|
|
206
|
+
// The record's rows in storage: this statement's own rows aren't there until the end, and are added as they come
|
|
207
|
+
for (const row of await (0, _retrieveRecords.retrieveRecords)(entity)) {
|
|
208
|
+
values.add(row);
|
|
209
|
+
}
|
|
210
|
+
if (entity === this.entity) {
|
|
211
|
+
this.rows.forEach(row => values.add(row));
|
|
212
|
+
}
|
|
213
|
+
return values;
|
|
214
|
+
})());
|
|
215
|
+
}
|
|
216
|
+
return this.scans.get(name);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Whether one of this statement's rows already holds these values in every one of these fields (none of them null).
|
|
220
|
+
* @param fields
|
|
221
|
+
* @param values
|
|
222
|
+
*/
|
|
223
|
+
claimed(fields, values) {
|
|
224
|
+
return this.rows.some(row => fields.every(field => row[field] !== null && typeof row[field] !== 'undefined' && (0, _compareAs.sameValue)((0, _fieldType.fieldType)(this.record, field), row[field], values[field])));
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Whether no row (in storage or earlier in this statement) holds these values for these fields, as keyUnique answers
|
|
228
|
+
* it after each earlier row is written.
|
|
229
|
+
* @param fields
|
|
230
|
+
* @param values
|
|
231
|
+
*/
|
|
232
|
+
async isUnique(fields, values) {
|
|
233
|
+
if (fields.length === 1) {
|
|
234
|
+
const found = this.ownLookup(fields[0])(values[fields[0]]);
|
|
235
|
+
if (typeof found !== 'undefined') {
|
|
236
|
+
return !found;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const scanned = await this.scan(this.entity, fields);
|
|
240
|
+
if (scanned) {
|
|
241
|
+
return !scanned.has(values);
|
|
242
|
+
}
|
|
243
|
+
return (await (0, _keyUnique.keyUnique)(this.entity, fields, fields.map(field => values[field]))) && !this.claimed(fields, values);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Whether `entity` holds `value` in `property`, as foreignKeyExists answers it after each earlier row is written: a
|
|
247
|
+
* row may reference one added earlier in the same statement when the key points at its own record.
|
|
248
|
+
* @param entity
|
|
249
|
+
* @param property
|
|
250
|
+
* @param value
|
|
251
|
+
*/
|
|
252
|
+
async exists(entity, property, value) {
|
|
253
|
+
const own = entity === this.entity;
|
|
254
|
+
const found = own ? this.ownLookup(property)(value) : (await this.otherLookup(entity, property))(value);
|
|
255
|
+
if (typeof found !== 'undefined') {
|
|
256
|
+
return found;
|
|
257
|
+
}
|
|
258
|
+
const scanned = await this.scan(entity, [property]);
|
|
259
|
+
if (scanned) {
|
|
260
|
+
return scanned.has({
|
|
261
|
+
[property]: value
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return (await (0, _foreignKeyExists.foreignKeyExists)(entity, property, value)) || own && this.claimed([property], {
|
|
265
|
+
[property]: value
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Note a row added to the statement, filed under `fileName`, in the record's entries and every index buffer.
|
|
270
|
+
* @param entityValues
|
|
271
|
+
* @param fileName
|
|
272
|
+
*/
|
|
273
|
+
async add(entityValues, fileName) {
|
|
274
|
+
this.rows.push(entityValues);
|
|
275
|
+
// Scans already built for this record hold the rows in storage; a row added now joins them
|
|
276
|
+
for (const [name, scanned] of this.scans) {
|
|
277
|
+
if (name.startsWith(`${this.entity}\u0000`)) {
|
|
278
|
+
(await scanned)?.add(entityValues);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
this.record.entries.push(fileName);
|
|
282
|
+
for (const [key, buffer] of this.buffers) {
|
|
283
|
+
buffer.add((0, _computeKeyValue.computeKeyValue)(key.fields, entityValues), fileName);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Write the record file and each index once, with every row the statement added.
|
|
288
|
+
*/
|
|
289
|
+
async write() {
|
|
290
|
+
await (0, _storage.writeJson)(`${this.record.path}.json`, this.record);
|
|
291
|
+
if (this.buffers.size) {
|
|
292
|
+
await (0, _storage.makeDirectory)(`__indexes/${this.record.path}`);
|
|
293
|
+
}
|
|
294
|
+
for (const [key, buffer] of this.buffers) {
|
|
295
|
+
await (0, _storage.writeJson)(`__indexes/${this.record.path}/${key.lookup}`, buffer.entries);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* A new id for an autoGenerate field that no row holds yet, in storage or earlier in the statement.
|
|
301
|
+
* @param statement
|
|
302
|
+
* @param field
|
|
303
|
+
*/
|
|
304
|
+
const generateKey = async (statement, field) => {
|
|
305
|
+
let uuid;
|
|
306
|
+
do {
|
|
307
|
+
uuid = crypto.randomUUID();
|
|
308
|
+
} while (!(await statement.isUnique([field], {
|
|
309
|
+
[field]: uuid
|
|
310
|
+
})));
|
|
311
|
+
return uuid;
|
|
312
|
+
};
|
|
27
313
|
/**
|
|
28
314
|
* Build one entity's stored values from the raw input values, generating any autoGenerate
|
|
29
|
-
* fields and applying field defaults, then throw if a required field still has no value.
|
|
30
|
-
*
|
|
31
|
-
*
|
|
315
|
+
* fields and applying field defaults, then throw if a required field still has no value. A null
|
|
316
|
+
* in an optional field that isn't nullable means the same as leaving it out (the field can't
|
|
317
|
+
* hold null, so nothing is lost): it isn't stored, and a default fills it in as for a field left out.
|
|
318
|
+
* @param statement
|
|
32
319
|
* @param rowValues
|
|
33
320
|
*/
|
|
34
|
-
const buildEntityValues = async (
|
|
321
|
+
const buildEntityValues = async (statement, rowValues = {}) => {
|
|
35
322
|
const entityValues = {};
|
|
36
|
-
for (const field of record.definition) {
|
|
323
|
+
for (const field of statement.record.definition) {
|
|
37
324
|
let value = rowValues[field.name];
|
|
325
|
+
if (value === null && field.optional && !field.nullable) {
|
|
326
|
+
value = undefined;
|
|
327
|
+
}
|
|
38
328
|
if (field.autoGenerate) {
|
|
39
|
-
value = await (
|
|
329
|
+
value = await generateKey(statement, field.name);
|
|
40
330
|
} else if (Object.prototype.hasOwnProperty.call(field, 'default')) {
|
|
41
331
|
value = (0, _useDefault.useDefault)(field['default'], value);
|
|
42
332
|
}
|
|
43
333
|
if (typeof value === 'undefined') {
|
|
44
334
|
if (!field.optional) {
|
|
45
|
-
throw new Error(`Missing required field "${field.name}" for ${entity}`);
|
|
335
|
+
throw new Error(`Missing required field "${field.name}" for ${statement.entity}`);
|
|
46
336
|
}
|
|
47
337
|
continue;
|
|
48
338
|
}
|
|
@@ -54,34 +344,30 @@ const buildEntityValues = async (entity, record, rowValues = {}) => {
|
|
|
54
344
|
* Reject the insert if it collides with an existing primary or unique key value - a single field,
|
|
55
345
|
* or a composite of several for a multi-field key. As in SQL, a value with a null in it is never a
|
|
56
346
|
* duplicate, so a nullable unique field can hold null in any number of rows.
|
|
57
|
-
* @param
|
|
58
|
-
* @param record
|
|
347
|
+
* @param statement
|
|
59
348
|
* @param entityValues
|
|
60
349
|
*/
|
|
61
|
-
const assertUnique = async (
|
|
62
|
-
for (const key of record.keys) {
|
|
350
|
+
const assertUnique = async (statement, entityValues) => {
|
|
351
|
+
for (const key of statement.record.keys) {
|
|
63
352
|
if (key.type !== 'primary' && key.type !== 'unique') {
|
|
64
353
|
continue;
|
|
65
354
|
}
|
|
66
|
-
|
|
67
|
-
if (checkKeys.some(value => value === null)) {
|
|
355
|
+
if (key.fields.some(field => entityValues[field] === null)) {
|
|
68
356
|
continue;
|
|
69
357
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
throw new Error(`Duplicate value for ${key.type} key "${key.fields.join(', ')}" on ${entity}`);
|
|
358
|
+
if (!(await statement.isUnique(key.fields, entityValues))) {
|
|
359
|
+
throw new Error(`Duplicate value for ${key.type} key "${key.fields.join(', ')}" on ${statement.entity}`);
|
|
73
360
|
}
|
|
74
361
|
}
|
|
75
362
|
};
|
|
76
363
|
/**
|
|
77
364
|
* Reject the insert if a single-field foreign key's value doesn't exist in the record it
|
|
78
365
|
* references. A null references nothing, so it isn't checked (as in SQL).
|
|
79
|
-
* @param
|
|
80
|
-
* @param record
|
|
366
|
+
* @param statement
|
|
81
367
|
* @param entityValues
|
|
82
368
|
*/
|
|
83
|
-
const assertForeignKeys = async (
|
|
84
|
-
for (const key of record.keys) {
|
|
369
|
+
const assertForeignKeys = async (statement, entityValues) => {
|
|
370
|
+
for (const key of statement.record.keys) {
|
|
85
371
|
if (key.type !== 'foreign' || key.fields.length !== 1 || !key.references || key.references.length !== 1) {
|
|
86
372
|
continue;
|
|
87
373
|
}
|
|
@@ -93,14 +379,20 @@ const assertForeignKeys = async (entity, record, entityValues) => {
|
|
|
93
379
|
entity: refEntity,
|
|
94
380
|
property: refProperty
|
|
95
381
|
} = (0, _splitEntityProperty.splitEntityProperty)(key.references[0].fields[0]);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
throw new Error(`Foreign key "${field}" on ${entity} references a nonexistent ${refEntity}.${refProperty} = ${entityValues[field]}`);
|
|
382
|
+
if (!(await statement.exists(refEntity, refProperty, entityValues[field]))) {
|
|
383
|
+
throw new Error(`Foreign key "${field}" on ${statement.entity} references a nonexistent ${refEntity}.${refProperty} = ${entityValues[field]}`);
|
|
99
384
|
}
|
|
100
385
|
}
|
|
101
386
|
};
|
|
387
|
+
/**
|
|
388
|
+
* Insert rows into a record, all in one statement: every row is checked (against the rows in storage and the rows
|
|
389
|
+
* before it in the statement) and its entry file written, then the record file and each index are written once.
|
|
390
|
+
* Inside a write set (as every query runs, see withWriteSet) a row that fails leaves none of them written.
|
|
391
|
+
* @param entity
|
|
392
|
+
* @param values - The rows, as field -> value.
|
|
393
|
+
* @param dataSet
|
|
394
|
+
*/
|
|
102
395
|
const insertEntity = async (entity = '', values = [], dataSet = {}) => {
|
|
103
|
-
const databasePath = (0, _config.getSetting)('databasePath', 'database/');
|
|
104
396
|
const record = await (0, _retrieveRecord.retrieveRecord)(entity);
|
|
105
397
|
const primaryKey = record.keys.find(key => key.type === 'primary' && key.fields.length === 1);
|
|
106
398
|
if (!Object.prototype.hasOwnProperty.call(dataSet, entity)) {
|
|
@@ -108,22 +400,27 @@ const insertEntity = async (entity = '', values = [], dataSet = {}) => {
|
|
|
108
400
|
}
|
|
109
401
|
// The record's folder can be missing even though the record exists: git doesn't keep empty folders, so a database
|
|
110
402
|
// committed before its first insert comes back from a fresh clone without it
|
|
111
|
-
await
|
|
112
|
-
|
|
113
|
-
|
|
403
|
+
await (0, _storage.makeDirectory)(record.path);
|
|
404
|
+
if (!values.length) {
|
|
405
|
+
return dataSet;
|
|
406
|
+
}
|
|
407
|
+
// A field the record doesn't define would otherwise be dropped without a word
|
|
114
408
|
for (const rowValues of values) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
409
|
+
(0, _knownFields.assertFieldsDefined)(entity, record, Object.keys(rowValues || {}), 'insert');
|
|
410
|
+
}
|
|
411
|
+
await (0, _packedTable.discardPacked)(record.path);
|
|
412
|
+
const statement = new InsertStatement(entity, record);
|
|
413
|
+
await statement.load();
|
|
414
|
+
for (const rowValues of values) {
|
|
415
|
+
const entityValues = await buildEntityValues(statement, rowValues);
|
|
416
|
+
await assertUnique(statement, entityValues);
|
|
417
|
+
await assertForeignKeys(statement, entityValues);
|
|
118
418
|
const fileName = `${primaryKey ? entityValues[primaryKey.fields[0]] : crypto.randomUUID()}.json`;
|
|
119
|
-
await
|
|
120
|
-
|
|
121
|
-
await writeFile(`${databasePath}${record.path}.json`, JSON.stringify(record, null, 2));
|
|
122
|
-
for (const key of record.keys) {
|
|
123
|
-
await (0, _updateIndex.updateIndex)(record.path, key, (0, _computeKeyValue.computeKeyValue)(key.fields, entityValues), fileName, (0, _fieldType.keyValueType)(record, key));
|
|
124
|
-
}
|
|
419
|
+
await (0, _storage.writeJson)(`${record.path}/${fileName}`, entityValues);
|
|
420
|
+
await statement.add(entityValues, fileName);
|
|
125
421
|
dataSet[entity].push(entityValues);
|
|
126
422
|
}
|
|
423
|
+
await statement.write();
|
|
127
424
|
return dataSet;
|
|
128
425
|
};
|
|
129
426
|
exports.insertEntity = insertEntity;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.insertEntity=void 0,require("core-js/modules/es.json.stringify.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.some.js")
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.insertEntity=void 0,require("core-js/modules/es.array.includes.js"),require("core-js/modules/es.json.stringify.js"),require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.every.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.for-each.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.some.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"),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 _retrieveRecord=require("../utilities/retrieveRecord"),_keyUnique=require("../introspect/policies/keyUnique"),_useDefault=require("../introspect/policies/useDefault"),_typeCheck=require("../introspect/policies/typeCheck"),_foreignKeyExists=require("../introspect/policies/foreignKeyExists"),_splitEntityProperty=require("../utilities/parsers/splitEntityProperty"),_computeKeyValue=require("../utilities/computeKeyValue"),_fieldType=require("../utilities/fieldType"),_compareAs=require("../utilities/compareAs"),_retrieveRecords=require("../utilities/retrieveRecords"),_isSingleFieldKey=require("../utilities/isSingleFieldKey"),_indexBuffer=require("../utilities/indexBuffer"),_storage=require("../storage"),_packedTable=require("../utilities/packedTable"),_knownFields=require("../utilities/knownFields");class RowValues{constructor(e,s){this.seen=new Set,this.fields=s,this.types=s.map((s=>(0,_fieldType.fieldType)(e,s)))}static usable(e,s){return s.every((s=>_compareAs.comparableTypes.includes((0,_fieldType.fieldType)(e,s))))}keyOf(e){const s=[];for(let t=0;t<this.fields.length;t++){const r=e[this.fields[t]];if(null==r)return;const i=(0,_compareAs.compareAs)(this.types[t],r);if("object"==typeof i)return;s.push([typeof i,i])}return JSON.stringify(s)}add(e){const s=this.keyOf(e);void 0!==s&&this.seen.add(s)}has(e){const s=this.keyOf(e);return void 0!==s&&this.seen.has(s)}}class InsertStatement{constructor(e,s){this.rows=[],this.buffers=new Map,this.unreadable=new Set,this.otherLookups=new Map,this.scans=new Map,this.entity=e,this.record=s,this.hadEntries=(s.entries||[]).length>0}async load(){for(const e of this.record.keys){if(!e.lookup)continue;let s=[];try{s=await(0,_storage.readJson)(`__indexes/${this.record.path}/${e.lookup}`)}catch(s){this.unreadable.add(e)}this.buffers.set(e,new _indexBuffer.IndexBuffer(Array.isArray(s)?s:[],(0,_fieldType.keyValueType)(this.record,e)))}}ownLookup(e){const s=this.record.keys.find((s=>(0,_isSingleFieldKey.isSingleFieldKey)(s,e)&&s.lookup));if(!s||this.unreadable.has(s))return()=>{};const t=this.buffers.get(s);return e=>(this.hadEntries||this.rows.length>0)&&t.has(e)}otherLookup(e,s){const t=`${e}\0${s}`;return this.otherLookups.has(t)||this.otherLookups.set(t,(async()=>{const t=await(0,_retrieveRecord.retrieveRecord)(e),r=t.keys.find((e=>(0,_isSingleFieldKey.isSingleFieldKey)(e,s)&&e.lookup));if(!r)return()=>{};if(!(t.entries||[]).length)return()=>!1;let i;try{i=await(0,_storage.readJson)(`__indexes/${t.path}/${r.lookup}`)}catch(e){return()=>{}}const o=new _indexBuffer.IndexBuffer(Array.isArray(i)?i:[],(0,_fieldType.fieldType)(t,s));return e=>o.has(e)})()),this.otherLookups.get(t)}scan(e,s){const t=`${e}\0${s.join("\0")}`;return this.scans.has(t)||this.scans.set(t,(async()=>{const t=e===this.entity?this.record:await(0,_retrieveRecord.retrieveRecord)(e);if(!RowValues.usable(t,s))return;const r=new RowValues(t,s);for(const s of await(0,_retrieveRecords.retrieveRecords)(e))r.add(s);return e===this.entity&&this.rows.forEach((e=>r.add(e))),r})()),this.scans.get(t)}claimed(e,s){return this.rows.some((t=>e.every((e=>null!==t[e]&&void 0!==t[e]&&(0,_compareAs.sameValue)((0,_fieldType.fieldType)(this.record,e),t[e],s[e])))))}async isUnique(e,s){if(1===e.length){const t=this.ownLookup(e[0])(s[e[0]]);if(void 0!==t)return!t}const t=await this.scan(this.entity,e);return t?!t.has(s):await(0,_keyUnique.keyUnique)(this.entity,e,e.map((e=>s[e])))&&!this.claimed(e,s)}async exists(e,s,t){const r=e===this.entity,i=r?this.ownLookup(s)(t):(await this.otherLookup(e,s))(t);if(void 0!==i)return i;const o=await this.scan(e,[s]);return o?o.has({[s]:t}):await(0,_foreignKeyExists.foreignKeyExists)(e,s,t)||r&&this.claimed([s],{[s]:t})}async add(e,s){this.rows.push(e);for(const[s,t]of this.scans)s.startsWith(`${this.entity}\0`)&&(await t)?.add(e);this.record.entries.push(s);for(const[t,r]of this.buffers)r.add((0,_computeKeyValue.computeKeyValue)(t.fields,e),s)}async write(){await(0,_storage.writeJson)(`${this.record.path}.json`,this.record),this.buffers.size&&await(0,_storage.makeDirectory)(`__indexes/${this.record.path}`);for(const[e,s]of this.buffers)await(0,_storage.writeJson)(`__indexes/${this.record.path}/${e.lookup}`,s.entries)}}const generateKey=async(e,s)=>{let t;do{t=crypto.randomUUID()}while(!await e.isUnique([s],{[s]:t}));return t},buildEntityValues=async(e,s={})=>{const t={};for(const r of e.record.definition){let i=s[r.name];if(null===i&&r.optional&&!r.nullable&&(i=void 0),r.autoGenerate?i=await generateKey(e,r.name):Object.prototype.hasOwnProperty.call(r,"default")&&(i=(0,_useDefault.useDefault)(r.default,i)),void 0!==i)t[r.name]=(0,_typeCheck.typeCheck)(r.type,i,r.nullable);else if(!r.optional)throw new Error(`Missing required field "${r.name}" for ${e.entity}`)}return t},assertUnique=async(e,s)=>{for(const t of e.record.keys)if(!("primary"!==t.type&&"unique"!==t.type||t.fields.some((e=>null===s[e]))||await e.isUnique(t.fields,s)))throw new Error(`Duplicate value for ${t.type} key "${t.fields.join(", ")}" on ${e.entity}`)},assertForeignKeys=async(e,s)=>{for(const t of e.record.keys){if("foreign"!==t.type||1!==t.fields.length||!t.references||1!==t.references.length)continue;const r=t.fields[0];if(!Object.prototype.hasOwnProperty.call(s,r)||null===s[r])continue;const{entity:i,property:o}=(0,_splitEntityProperty.splitEntityProperty)(t.references[0].fields[0]);if(!await e.exists(i,o,s[r]))throw new Error(`Foreign key "${r}" on ${e.entity} references a nonexistent ${i}.${o} = ${s[r]}`)}},insertEntity=async(e="",s=[],t={})=>{const r=await(0,_retrieveRecord.retrieveRecord)(e),i=r.keys.find((e=>"primary"===e.type&&1===e.fields.length));if(Object.prototype.hasOwnProperty.call(t,e)||(t[e]=[]),await(0,_storage.makeDirectory)(r.path),!s.length)return t;for(const t of s)(0,_knownFields.assertFieldsDefined)(e,r,Object.keys(t||{}),"insert");await(0,_packedTable.discardPacked)(r.path);const o=new InsertStatement(e,r);await o.load();for(const n of s){const s=await buildEntityValues(o,n);await assertUnique(o,s),await assertForeignKeys(o,s);const a=`${i?s[i.fields[0]]:crypto.randomUUID()}.json`;await(0,_storage.writeJson)(`${r.path}/${a}`,s),await o.add(s,a),t[e].push(s)}return await o.write(),t};exports.insertEntity=insertEntity;
|
|
@@ -1,2 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attach to each row of `entity` the rows of `joinEntity` its join reaches, through the `on` clauses in order.
|
|
3
|
+
*
|
|
4
|
+
* Each clause narrows both its records to the rows that match the other side: first the rows of the left one that
|
|
5
|
+
* match a right one, then the right ones that match those (see semiJoin for the order). A row's join starts from the
|
|
6
|
+
* row alone and from the rows read for every other record (see joinBases), so each record is read once for the whole
|
|
7
|
+
* join and each lookup is built once, rather than every loaded row being copied for each row joined.
|
|
8
|
+
* @param entity
|
|
9
|
+
* @param joinEntity
|
|
10
|
+
* @param dataSets
|
|
11
|
+
* @param joinClauses
|
|
12
|
+
* @param entityAlreadyResolved - Whether the where clause already narrowed entity's rows (then they aren't read again:
|
|
13
|
+
* an empty list reads as "not loaded yet", see readEntity).
|
|
14
|
+
* @param joinAlreadyResolved - The same for joinEntity.
|
|
15
|
+
*/
|
|
1
16
|
export declare const joinEntity: (entity: any, joinEntity: any, dataSets?: {}, joinClauses?: any[], entityAlreadyResolved?: boolean, joinAlreadyResolved?: boolean) => Promise<{}>;
|
|
2
17
|
//# sourceMappingURL=joinEntity.d.ts.map
|
|
@@ -4,31 +4,107 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.joinEntity = void 0;
|
|
7
|
-
|
|
7
|
+
require("core-js/modules/esnext.iterator.constructor.js");
|
|
8
|
+
require("core-js/modules/esnext.iterator.map.js");
|
|
9
|
+
require("core-js/modules/esnext.map.delete-all.js");
|
|
10
|
+
require("core-js/modules/esnext.map.every.js");
|
|
11
|
+
require("core-js/modules/esnext.map.filter.js");
|
|
12
|
+
require("core-js/modules/esnext.map.find.js");
|
|
13
|
+
require("core-js/modules/esnext.map.find-key.js");
|
|
14
|
+
require("core-js/modules/esnext.map.includes.js");
|
|
15
|
+
require("core-js/modules/esnext.map.key-of.js");
|
|
16
|
+
require("core-js/modules/esnext.map.map-keys.js");
|
|
17
|
+
require("core-js/modules/esnext.map.map-values.js");
|
|
18
|
+
require("core-js/modules/esnext.map.merge.js");
|
|
19
|
+
require("core-js/modules/esnext.map.reduce.js");
|
|
20
|
+
require("core-js/modules/esnext.map.some.js");
|
|
21
|
+
require("core-js/modules/esnext.map.update.js");
|
|
8
22
|
var _readEntity = require("./readEntity");
|
|
9
|
-
var
|
|
10
|
-
|
|
23
|
+
var _retrieveRecord = require("../utilities/retrieveRecord");
|
|
24
|
+
var _splitEntityProperty = require("../utilities/parsers/splitEntityProperty");
|
|
25
|
+
var _fieldType = require("../utilities/fieldType");
|
|
26
|
+
var _semiJoin = require("../utilities/semiJoin");
|
|
27
|
+
var _joinBases = require("../utilities/joinBases");
|
|
28
|
+
/**
|
|
29
|
+
* Each `on` clause with the types its two sides are compared by. Each record is read here, before its type is looked
|
|
30
|
+
* up, so a record that doesn't exist fails with "Could not read <record>.json", as it always has.
|
|
31
|
+
* @param joinClauses
|
|
32
|
+
*/
|
|
33
|
+
const joinSteps = async joinClauses => {
|
|
34
|
+
const steps = [];
|
|
35
|
+
for (const {
|
|
36
|
+
propertyA,
|
|
37
|
+
comparator,
|
|
38
|
+
propertyB
|
|
39
|
+
} of joinClauses) {
|
|
40
|
+
const {
|
|
41
|
+
entity: entityA,
|
|
42
|
+
property: a
|
|
43
|
+
} = (0, _splitEntityProperty.splitEntityProperty)(propertyA);
|
|
44
|
+
const {
|
|
45
|
+
entity: entityB,
|
|
46
|
+
property: b
|
|
47
|
+
} = (0, _splitEntityProperty.splitEntityProperty)(propertyB);
|
|
48
|
+
steps.push({
|
|
49
|
+
entityA,
|
|
50
|
+
propertyA: a,
|
|
51
|
+
typeA: (0, _fieldType.fieldType)(await (0, _retrieveRecord.retrieveRecord)(entityA), a),
|
|
52
|
+
comparator,
|
|
53
|
+
entityB,
|
|
54
|
+
propertyB: b,
|
|
55
|
+
typeB: (0, _fieldType.fieldType)(await (0, _retrieveRecord.retrieveRecord)(entityB), b)
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return steps;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* What a row gets for its joined rows. Today every row is kept with the list of rows its join found (none is an empty
|
|
62
|
+
* list), and a condition on the joined record's fields then drops rows with none (whereTree): an inner join. This is
|
|
63
|
+
* the one place a join type (left, right, outer) would decide differently.
|
|
64
|
+
* @param matches - The joined rows the row's join found, shared with other rows' joins until copied here.
|
|
65
|
+
*/
|
|
66
|
+
const joinedRows = matches => matches.map(match => structuredClone(match));
|
|
67
|
+
/**
|
|
68
|
+
* Attach to each row of `entity` the rows of `joinEntity` its join reaches, through the `on` clauses in order.
|
|
69
|
+
*
|
|
70
|
+
* Each clause narrows both its records to the rows that match the other side: first the rows of the left one that
|
|
71
|
+
* match a right one, then the right ones that match those (see semiJoin for the order). A row's join starts from the
|
|
72
|
+
* row alone and from the rows read for every other record (see joinBases), so each record is read once for the whole
|
|
73
|
+
* join and each lookup is built once, rather than every loaded row being copied for each row joined.
|
|
74
|
+
* @param entity
|
|
75
|
+
* @param joinEntity
|
|
76
|
+
* @param dataSets
|
|
77
|
+
* @param joinClauses
|
|
78
|
+
* @param entityAlreadyResolved - Whether the where clause already narrowed entity's rows (then they aren't read again:
|
|
79
|
+
* an empty list reads as "not loaded yet", see readEntity).
|
|
80
|
+
* @param joinAlreadyResolved - The same for joinEntity.
|
|
81
|
+
*/
|
|
11
82
|
const joinEntity = async (entity, joinEntity, dataSets = {}, joinClauses = [], entityAlreadyResolved = false, joinAlreadyResolved = false) => {
|
|
12
|
-
// Same ambiguity readEntity() always has (can't tell "never loaded" from "loaded, zero rows
|
|
13
|
-
// matched") - runQuery already resolved entity's rows via an indexed condition in that second
|
|
14
|
-
// case, so entityAlreadyResolved tells this call not to reload and silently discard that result.
|
|
15
83
|
if (!entityAlreadyResolved) {
|
|
16
84
|
dataSets = await (0, _readEntity.readEntity)(entity, dataSets);
|
|
17
85
|
}
|
|
18
86
|
if (!joinClauses.length) {
|
|
19
87
|
return dataSets;
|
|
20
88
|
}
|
|
21
|
-
|
|
22
|
-
if (!
|
|
23
|
-
|
|
89
|
+
const steps = await joinSteps(joinClauses);
|
|
90
|
+
if (!dataSets[entity].length) {
|
|
91
|
+
return dataSets;
|
|
24
92
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
93
|
+
const bases = await (0, _joinBases.joinBases)(entity, joinEntity, dataSets, steps, joinAlreadyResolved);
|
|
94
|
+
for (const row of dataSets[entity]) {
|
|
95
|
+
const sides = new Map(bases);
|
|
96
|
+
sides.set(entity, {
|
|
97
|
+
rows: [row]
|
|
98
|
+
});
|
|
99
|
+
for (const step of steps) {
|
|
100
|
+
sides.set(step.entityA, {
|
|
101
|
+
rows: (0, _semiJoin.semiJoin)(sides.get(step.entityA), sides.get(step.entityB), step.propertyA, step.propertyB, step.comparator, step.typeA)
|
|
102
|
+
});
|
|
103
|
+
sides.set(step.entityB, {
|
|
104
|
+
rows: (0, _semiJoin.semiJoin)(sides.get(step.entityB), sides.get(step.entityA), step.propertyB, step.propertyA, step.comparator, step.typeB)
|
|
105
|
+
});
|
|
30
106
|
}
|
|
31
|
-
|
|
107
|
+
row[joinEntity] = joinedRows(sides.get(joinEntity).rows);
|
|
32
108
|
}
|
|
33
109
|
return dataSets;
|
|
34
110
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.joinEntity=void 0
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.joinEntity=void 0,require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.map.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");var _readEntity=require("./readEntity"),_retrieveRecord=require("../utilities/retrieveRecord"),_splitEntityProperty=require("../utilities/parsers/splitEntityProperty"),_fieldType=require("../utilities/fieldType"),_semiJoin=require("../utilities/semiJoin"),_joinBases=require("../utilities/joinBases");const joinSteps=async e=>{const t=[];for(const{propertyA:r,comparator:s,propertyB:i}of e){const{entity:e,property:o}=(0,_splitEntityProperty.splitEntityProperty)(r),{entity:n,property:p}=(0,_splitEntityProperty.splitEntityProperty)(i);t.push({entityA:e,propertyA:o,typeA:(0,_fieldType.fieldType)(await(0,_retrieveRecord.retrieveRecord)(e),o),comparator:s,entityB:n,propertyB:p,typeB:(0,_fieldType.fieldType)(await(0,_retrieveRecord.retrieveRecord)(n),p)})}return t},joinedRows=e=>e.map((e=>structuredClone(e))),joinEntity=async(e,t,r={},s=[],i=!1,o=!1)=>{if(i||(r=await(0,_readEntity.readEntity)(e,r)),!s.length)return r;const n=await joinSteps(s);if(!r[e].length)return r;const p=await(0,_joinBases.joinBases)(e,t,r,n,o);for(const s of r[e]){const r=new Map(p);r.set(e,{rows:[s]});for(const e of n)r.set(e.entityA,{rows:(0,_semiJoin.semiJoin)(r.get(e.entityA),r.get(e.entityB),e.propertyA,e.propertyB,e.comparator,e.typeA)}),r.set(e.entityB,{rows:(0,_semiJoin.semiJoin)(r.get(e.entityB),r.get(e.entityA),e.propertyB,e.propertyA,e.comparator,e.typeB)});s[t]=joinedRows(r.get(t).rows)}return r};exports.joinEntity=joinEntity;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Parse a query string (one query, or several separated by `;`) and run each query in turn, resolving to one result
|
|
3
3
|
* per query. The queries of one call run together, under the database lock: no other call's queries run between
|
|
4
|
-
* them (see withDatabaseLock).
|
|
4
|
+
* them (see withDatabaseLock). Each query is all or nothing: one that fails changes nothing, and the queries before it
|
|
5
|
+
* keep their changes (see withWriteSet).
|
|
5
6
|
* @param queryString
|
|
6
7
|
*/
|
|
7
8
|
export declare const query: (queryString?: string) => Promise<any[]>;
|