@push.rocks/smartmongo 2.2.0 → 4.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.d.ts +1 -1
- package/dist_ts/index.js +3 -3
- package/dist_ts/tsmdb/engine/AggregationEngine.js +189 -0
- package/dist_ts/{congodb → tsmdb}/engine/IndexEngine.d.ts +23 -3
- package/dist_ts/tsmdb/engine/IndexEngine.js +678 -0
- package/dist_ts/tsmdb/engine/QueryEngine.js +271 -0
- package/dist_ts/tsmdb/engine/QueryPlanner.d.ts +64 -0
- package/dist_ts/tsmdb/engine/QueryPlanner.js +308 -0
- package/dist_ts/tsmdb/engine/SessionEngine.d.ts +117 -0
- package/dist_ts/tsmdb/engine/SessionEngine.js +232 -0
- package/dist_ts/{congodb → tsmdb}/engine/TransactionEngine.d.ts +1 -1
- package/dist_ts/tsmdb/engine/TransactionEngine.js +287 -0
- package/dist_ts/tsmdb/engine/UpdateEngine.js +461 -0
- package/dist_ts/{congodb/errors/CongoErrors.d.ts → tsmdb/errors/TsmdbErrors.d.ts} +16 -16
- package/dist_ts/tsmdb/errors/TsmdbErrors.js +155 -0
- package/dist_ts/{congodb → tsmdb}/index.d.ts +11 -4
- package/dist_ts/tsmdb/index.js +31 -0
- package/dist_ts/tsmdb/server/CommandRouter.d.ts +87 -0
- package/dist_ts/tsmdb/server/CommandRouter.js +222 -0
- package/dist_ts/{congodb/server/CongoServer.d.ts → tsmdb/server/TsmdbServer.d.ts} +6 -6
- package/dist_ts/tsmdb/server/TsmdbServer.js +229 -0
- package/dist_ts/{congodb → tsmdb}/server/WireProtocol.d.ts +1 -1
- package/dist_ts/tsmdb/server/WireProtocol.js +298 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/AdminHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/AdminHandler.js +668 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/AggregateHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/AggregateHandler.js +277 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/DeleteHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/DeleteHandler.js +95 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/FindHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/FindHandler.js +291 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/HelloHandler.d.ts +1 -1
- package/dist_ts/{congodb → tsmdb}/server/handlers/HelloHandler.js +2 -2
- package/dist_ts/{congodb → tsmdb}/server/handlers/IndexHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/IndexHandler.js +183 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/InsertHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/InsertHandler.js +79 -0
- package/dist_ts/{congodb → tsmdb}/server/handlers/UpdateHandler.d.ts +1 -1
- package/dist_ts/tsmdb/server/handlers/UpdateHandler.js +296 -0
- package/dist_ts/tsmdb/server/handlers/index.js +10 -0
- package/dist_ts/{congodb → tsmdb}/server/index.d.ts +2 -2
- package/dist_ts/tsmdb/server/index.js +7 -0
- package/dist_ts/{congodb → tsmdb}/storage/FileStorageAdapter.d.ts +27 -3
- package/dist_ts/tsmdb/storage/FileStorageAdapter.js +465 -0
- package/dist_ts/{congodb → tsmdb}/storage/IStorageAdapter.d.ts +7 -2
- package/dist_ts/{congodb → tsmdb}/storage/IStorageAdapter.js +1 -1
- package/dist_ts/{congodb → tsmdb}/storage/MemoryStorageAdapter.d.ts +3 -2
- package/dist_ts/tsmdb/storage/MemoryStorageAdapter.js +378 -0
- package/dist_ts/{congodb → tsmdb}/storage/OpLog.d.ts +1 -1
- package/dist_ts/tsmdb/storage/OpLog.js +221 -0
- package/dist_ts/tsmdb/storage/WAL.d.ts +117 -0
- package/dist_ts/tsmdb/storage/WAL.js +286 -0
- package/dist_ts/tsmdb/tsmdb.plugins.js +14 -0
- package/dist_ts/{congodb → tsmdb}/types/interfaces.d.ts +3 -3
- package/dist_ts/{congodb → tsmdb}/types/interfaces.js +1 -1
- package/dist_ts/tsmdb/utils/checksum.d.ts +30 -0
- package/dist_ts/tsmdb/utils/checksum.js +77 -0
- package/dist_ts/tsmdb/utils/index.d.ts +1 -0
- package/dist_ts/tsmdb/utils/index.js +2 -0
- package/package.json +1 -1
- package/readme.hints.md +7 -12
- package/readme.md +25 -25
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +2 -2
- package/ts/{congodb → tsmdb}/engine/AggregationEngine.ts +1 -1
- package/ts/tsmdb/engine/IndexEngine.ts +798 -0
- package/ts/{congodb → tsmdb}/engine/QueryEngine.ts +1 -1
- package/ts/tsmdb/engine/QueryPlanner.ts +393 -0
- package/ts/tsmdb/engine/SessionEngine.ts +292 -0
- package/ts/{congodb → tsmdb}/engine/TransactionEngine.ts +12 -12
- package/ts/{congodb → tsmdb}/engine/UpdateEngine.ts +1 -1
- package/ts/{congodb/errors/CongoErrors.ts → tsmdb/errors/TsmdbErrors.ts} +34 -34
- package/ts/{congodb → tsmdb}/index.ts +16 -7
- package/ts/{congodb → tsmdb}/server/CommandRouter.ts +114 -5
- package/ts/{congodb/server/CongoServer.ts → tsmdb/server/TsmdbServer.ts} +11 -8
- package/ts/{congodb → tsmdb}/server/WireProtocol.ts +1 -1
- package/ts/{congodb → tsmdb}/server/handlers/AdminHandler.ts +116 -11
- package/ts/{congodb → tsmdb}/server/handlers/AggregateHandler.ts +1 -1
- package/ts/{congodb → tsmdb}/server/handlers/DeleteHandler.ts +18 -3
- package/ts/{congodb → tsmdb}/server/handlers/FindHandler.ts +43 -14
- package/ts/{congodb → tsmdb}/server/handlers/HelloHandler.ts +1 -1
- package/ts/{congodb → tsmdb}/server/handlers/IndexHandler.ts +1 -1
- package/ts/{congodb → tsmdb}/server/handlers/InsertHandler.ts +7 -1
- package/ts/{congodb → tsmdb}/server/handlers/UpdateHandler.ts +34 -5
- package/ts/{congodb → tsmdb}/server/index.ts +2 -2
- package/ts/{congodb → tsmdb}/storage/FileStorageAdapter.ts +90 -7
- package/ts/{congodb → tsmdb}/storage/IStorageAdapter.ts +8 -2
- package/ts/{congodb → tsmdb}/storage/MemoryStorageAdapter.ts +14 -2
- package/ts/{congodb → tsmdb}/storage/OpLog.ts +1 -1
- package/ts/tsmdb/storage/WAL.ts +375 -0
- package/ts/{congodb → tsmdb}/types/interfaces.ts +3 -3
- package/ts/tsmdb/utils/checksum.ts +88 -0
- package/ts/tsmdb/utils/index.ts +1 -0
- package/dist_ts/congodb/congodb.plugins.js +0 -14
- package/dist_ts/congodb/engine/AggregationEngine.js +0 -189
- package/dist_ts/congodb/engine/IndexEngine.js +0 -376
- package/dist_ts/congodb/engine/QueryEngine.js +0 -271
- package/dist_ts/congodb/engine/TransactionEngine.js +0 -287
- package/dist_ts/congodb/engine/UpdateEngine.js +0 -461
- package/dist_ts/congodb/errors/CongoErrors.js +0 -155
- package/dist_ts/congodb/index.js +0 -26
- package/dist_ts/congodb/server/CommandRouter.d.ts +0 -51
- package/dist_ts/congodb/server/CommandRouter.js +0 -132
- package/dist_ts/congodb/server/CongoServer.js +0 -227
- package/dist_ts/congodb/server/WireProtocol.js +0 -298
- package/dist_ts/congodb/server/handlers/AdminHandler.js +0 -568
- package/dist_ts/congodb/server/handlers/AggregateHandler.js +0 -277
- package/dist_ts/congodb/server/handlers/DeleteHandler.js +0 -83
- package/dist_ts/congodb/server/handlers/FindHandler.js +0 -261
- package/dist_ts/congodb/server/handlers/IndexHandler.js +0 -183
- package/dist_ts/congodb/server/handlers/InsertHandler.js +0 -76
- package/dist_ts/congodb/server/handlers/UpdateHandler.js +0 -270
- package/dist_ts/congodb/server/handlers/index.js +0 -10
- package/dist_ts/congodb/server/index.js +0 -7
- package/dist_ts/congodb/storage/FileStorageAdapter.js +0 -396
- package/dist_ts/congodb/storage/MemoryStorageAdapter.js +0 -367
- package/dist_ts/congodb/storage/OpLog.js +0 -221
- package/ts/congodb/engine/IndexEngine.ts +0 -479
- /package/dist_ts/{congodb → tsmdb}/engine/AggregationEngine.d.ts +0 -0
- /package/dist_ts/{congodb → tsmdb}/engine/QueryEngine.d.ts +0 -0
- /package/dist_ts/{congodb → tsmdb}/engine/UpdateEngine.d.ts +0 -0
- /package/dist_ts/{congodb → tsmdb}/server/handlers/index.d.ts +0 -0
- /package/dist_ts/{congodb/congodb.plugins.d.ts → tsmdb/tsmdb.plugins.d.ts} +0 -0
- /package/ts/{congodb → tsmdb}/server/handlers/index.ts +0 -0
- /package/ts/{congodb/congodb.plugins.ts → tsmdb/tsmdb.plugins.ts} +0 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import * as plugins from '../tsmdb.plugins.js';
|
|
2
|
+
// Import mingo Query class
|
|
3
|
+
import { Query } from 'mingo';
|
|
4
|
+
/**
|
|
5
|
+
* Query engine using mingo for MongoDB-compatible query matching
|
|
6
|
+
*/
|
|
7
|
+
export class QueryEngine {
|
|
8
|
+
/**
|
|
9
|
+
* Filter documents by a MongoDB query filter
|
|
10
|
+
*/
|
|
11
|
+
static filter(documents, filter) {
|
|
12
|
+
if (!filter || Object.keys(filter).length === 0) {
|
|
13
|
+
return documents;
|
|
14
|
+
}
|
|
15
|
+
const query = new Query(filter);
|
|
16
|
+
return documents.filter(doc => query.test(doc));
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Test if a single document matches a filter
|
|
20
|
+
*/
|
|
21
|
+
static matches(document, filter) {
|
|
22
|
+
if (!filter || Object.keys(filter).length === 0) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
const query = new Query(filter);
|
|
26
|
+
return query.test(document);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Find a single document matching the filter
|
|
30
|
+
*/
|
|
31
|
+
static findOne(documents, filter) {
|
|
32
|
+
if (!filter || Object.keys(filter).length === 0) {
|
|
33
|
+
return documents[0] || null;
|
|
34
|
+
}
|
|
35
|
+
const query = new Query(filter);
|
|
36
|
+
for (const doc of documents) {
|
|
37
|
+
if (query.test(doc)) {
|
|
38
|
+
return doc;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Sort documents by a sort specification
|
|
45
|
+
*/
|
|
46
|
+
static sort(documents, sort) {
|
|
47
|
+
if (!sort) {
|
|
48
|
+
return documents;
|
|
49
|
+
}
|
|
50
|
+
// Normalize sort specification to array of [field, direction] pairs
|
|
51
|
+
const sortFields = [];
|
|
52
|
+
if (Array.isArray(sort)) {
|
|
53
|
+
for (const [field, direction] of sort) {
|
|
54
|
+
sortFields.push([field, this.normalizeDirection(direction)]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
for (const [field, direction] of Object.entries(sort)) {
|
|
59
|
+
sortFields.push([field, this.normalizeDirection(direction)]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return [...documents].sort((a, b) => {
|
|
63
|
+
for (const [field, direction] of sortFields) {
|
|
64
|
+
const aVal = this.getNestedValue(a, field);
|
|
65
|
+
const bVal = this.getNestedValue(b, field);
|
|
66
|
+
const comparison = this.compareValues(aVal, bVal);
|
|
67
|
+
if (comparison !== 0) {
|
|
68
|
+
return comparison * direction;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return 0;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Apply projection to documents
|
|
76
|
+
*/
|
|
77
|
+
static project(documents, projection) {
|
|
78
|
+
if (!projection || Object.keys(projection).length === 0) {
|
|
79
|
+
return documents;
|
|
80
|
+
}
|
|
81
|
+
// Determine if this is inclusion or exclusion projection
|
|
82
|
+
const keys = Object.keys(projection);
|
|
83
|
+
const hasInclusion = keys.some(k => k !== '_id' && projection[k] === 1);
|
|
84
|
+
const hasExclusion = keys.some(k => k !== '_id' && projection[k] === 0);
|
|
85
|
+
// Can't mix inclusion and exclusion (except for _id)
|
|
86
|
+
if (hasInclusion && hasExclusion) {
|
|
87
|
+
throw new Error('Cannot mix inclusion and exclusion in projection');
|
|
88
|
+
}
|
|
89
|
+
return documents.map(doc => {
|
|
90
|
+
if (hasInclusion) {
|
|
91
|
+
// Inclusion projection
|
|
92
|
+
const result = {};
|
|
93
|
+
// Handle _id
|
|
94
|
+
if (projection._id !== 0 && projection._id !== false) {
|
|
95
|
+
result._id = doc._id;
|
|
96
|
+
}
|
|
97
|
+
for (const key of keys) {
|
|
98
|
+
if (key === '_id')
|
|
99
|
+
continue;
|
|
100
|
+
if (projection[key] === 1 || projection[key] === true) {
|
|
101
|
+
const value = this.getNestedValue(doc, key);
|
|
102
|
+
if (value !== undefined) {
|
|
103
|
+
this.setNestedValue(result, key, value);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// Exclusion projection - start with copy and remove fields
|
|
111
|
+
const result = { ...doc };
|
|
112
|
+
for (const key of keys) {
|
|
113
|
+
if (projection[key] === 0 || projection[key] === false) {
|
|
114
|
+
this.deleteNestedValue(result, key);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get distinct values for a field
|
|
123
|
+
*/
|
|
124
|
+
static distinct(documents, field, filter) {
|
|
125
|
+
let docs = documents;
|
|
126
|
+
if (filter && Object.keys(filter).length > 0) {
|
|
127
|
+
docs = this.filter(documents, filter);
|
|
128
|
+
}
|
|
129
|
+
const values = new Set();
|
|
130
|
+
for (const doc of docs) {
|
|
131
|
+
const value = this.getNestedValue(doc, field);
|
|
132
|
+
if (value !== undefined) {
|
|
133
|
+
if (Array.isArray(value)) {
|
|
134
|
+
// For arrays, add each element
|
|
135
|
+
for (const v of value) {
|
|
136
|
+
values.add(this.toComparable(v));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
values.add(this.toComparable(value));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return Array.from(values);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Normalize sort direction to 1 or -1
|
|
148
|
+
*/
|
|
149
|
+
static normalizeDirection(direction) {
|
|
150
|
+
if (typeof direction === 'number') {
|
|
151
|
+
return direction > 0 ? 1 : -1;
|
|
152
|
+
}
|
|
153
|
+
if (direction === 'asc' || direction === 'ascending') {
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
156
|
+
return -1;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get a nested value from an object using dot notation
|
|
160
|
+
*/
|
|
161
|
+
static getNestedValue(obj, path) {
|
|
162
|
+
const parts = path.split('.');
|
|
163
|
+
let current = obj;
|
|
164
|
+
for (const part of parts) {
|
|
165
|
+
if (current === null || current === undefined) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
if (Array.isArray(current)) {
|
|
169
|
+
// Handle array access
|
|
170
|
+
const index = parseInt(part, 10);
|
|
171
|
+
if (!isNaN(index)) {
|
|
172
|
+
current = current[index];
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
// Get the field from all array elements
|
|
176
|
+
return current.map(item => this.getNestedValue(item, part)).flat();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
current = current[part];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return current;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Set a nested value in an object using dot notation
|
|
187
|
+
*/
|
|
188
|
+
static setNestedValue(obj, path, value) {
|
|
189
|
+
const parts = path.split('.');
|
|
190
|
+
let current = obj;
|
|
191
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
192
|
+
const part = parts[i];
|
|
193
|
+
if (!(part in current)) {
|
|
194
|
+
current[part] = {};
|
|
195
|
+
}
|
|
196
|
+
current = current[part];
|
|
197
|
+
}
|
|
198
|
+
current[parts[parts.length - 1]] = value;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Delete a nested value from an object using dot notation
|
|
202
|
+
*/
|
|
203
|
+
static deleteNestedValue(obj, path) {
|
|
204
|
+
const parts = path.split('.');
|
|
205
|
+
let current = obj;
|
|
206
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
207
|
+
const part = parts[i];
|
|
208
|
+
if (!(part in current)) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
current = current[part];
|
|
212
|
+
}
|
|
213
|
+
delete current[parts[parts.length - 1]];
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Compare two values for sorting
|
|
217
|
+
*/
|
|
218
|
+
static compareValues(a, b) {
|
|
219
|
+
// Handle undefined/null
|
|
220
|
+
if (a === undefined && b === undefined)
|
|
221
|
+
return 0;
|
|
222
|
+
if (a === undefined)
|
|
223
|
+
return -1;
|
|
224
|
+
if (b === undefined)
|
|
225
|
+
return 1;
|
|
226
|
+
if (a === null && b === null)
|
|
227
|
+
return 0;
|
|
228
|
+
if (a === null)
|
|
229
|
+
return -1;
|
|
230
|
+
if (b === null)
|
|
231
|
+
return 1;
|
|
232
|
+
// Handle ObjectId
|
|
233
|
+
if (a instanceof plugins.bson.ObjectId && b instanceof plugins.bson.ObjectId) {
|
|
234
|
+
return a.toHexString().localeCompare(b.toHexString());
|
|
235
|
+
}
|
|
236
|
+
// Handle dates
|
|
237
|
+
if (a instanceof Date && b instanceof Date) {
|
|
238
|
+
return a.getTime() - b.getTime();
|
|
239
|
+
}
|
|
240
|
+
// Handle numbers
|
|
241
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
242
|
+
return a - b;
|
|
243
|
+
}
|
|
244
|
+
// Handle strings
|
|
245
|
+
if (typeof a === 'string' && typeof b === 'string') {
|
|
246
|
+
return a.localeCompare(b);
|
|
247
|
+
}
|
|
248
|
+
// Handle booleans
|
|
249
|
+
if (typeof a === 'boolean' && typeof b === 'boolean') {
|
|
250
|
+
return (a ? 1 : 0) - (b ? 1 : 0);
|
|
251
|
+
}
|
|
252
|
+
// Fall back to string comparison
|
|
253
|
+
return String(a).localeCompare(String(b));
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Convert a value to a comparable form (for distinct)
|
|
257
|
+
*/
|
|
258
|
+
static toComparable(value) {
|
|
259
|
+
if (value instanceof plugins.bson.ObjectId) {
|
|
260
|
+
return value.toHexString();
|
|
261
|
+
}
|
|
262
|
+
if (value instanceof Date) {
|
|
263
|
+
return value.toISOString();
|
|
264
|
+
}
|
|
265
|
+
if (typeof value === 'object' && value !== null) {
|
|
266
|
+
return JSON.stringify(value);
|
|
267
|
+
}
|
|
268
|
+
return value;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUXVlcnlFbmdpbmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy90c21kYi9lbmdpbmUvUXVlcnlFbmdpbmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxxQkFBcUIsQ0FBQztBQUcvQywyQkFBMkI7QUFDM0IsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLE9BQU8sQ0FBQztBQUU5Qjs7R0FFRztBQUNILE1BQU0sT0FBTyxXQUFXO0lBQ3RCOztPQUVHO0lBQ0gsTUFBTSxDQUFDLE1BQU0sQ0FBQyxTQUE0QixFQUFFLE1BQWdCO1FBQzFELElBQUksQ0FBQyxNQUFNLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDaEQsT0FBTyxTQUFTLENBQUM7UUFDbkIsQ0FBQztRQUVELE1BQU0sS0FBSyxHQUFHLElBQUksS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ2hDLE9BQU8sU0FBUyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUNsRCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxNQUFNLENBQUMsT0FBTyxDQUFDLFFBQWtCLEVBQUUsTUFBZ0I7UUFDakQsSUFBSSxDQUFDLE1BQU0sSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUNoRCxPQUFPLElBQUksQ0FBQztRQUNkLENBQUM7UUFFRCxNQUFNLEtBQUssR0FBRyxJQUFJLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNoQyxPQUFPLEtBQUssQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDOUIsQ0FBQztJQUVEOztPQUVHO0lBQ0gsTUFBTSxDQUFDLE9BQU8sQ0FBQyxTQUE0QixFQUFFLE1BQWdCO1FBQzNELElBQUksQ0FBQyxNQUFNLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDaEQsT0FBTyxTQUFTLENBQUMsQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDO1FBQzlCLENBQUM7UUFFRCxNQUFNLEtBQUssR0FBRyxJQUFJLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNoQyxLQUFLLE1BQU0sR0FBRyxJQUFJLFNBQVMsRUFBRSxDQUFDO1lBQzVCLElBQUksS0FBSyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO2dCQUNwQixPQUFPLEdBQUcsQ0FBQztZQUNiLENBQUM7UUFDSCxDQUFDO1FBQ0QsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxNQUFNLENBQUMsSUFBSSxDQUFDLFNBQTRCLEVBQUUsSUFBd0I7UUFDaEUsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ1YsT0FBTyxTQUFTLENBQUM7UUFDbkIsQ0FBQztRQUVELG9FQUFvRTtRQUNwRSxNQUFNLFVBQVUsR0FBNEIsRUFBRSxDQUFDO1FBRS9DLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1lBQ3hCLEtBQUssTUFBTSxDQUFDLEtBQUssRUFBRSxTQUFTLENBQUMsSUFBSSxJQUFJLEVBQUUsQ0FBQztnQkFDdEMsVUFBVSxDQUFDLElBQUksQ0FBQyxDQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsa0JBQWtCLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQy9ELENBQUM7UUFDSCxDQUFDO2FBQU0sQ0FBQztZQUNOLEtBQUssTUFBTSxDQUFDLEtBQUssRUFBRSxTQUFTLENBQUMsSUFBSSxNQUFNLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7Z0JBQ3RELFVBQVUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxLQUFLLEVBQUUsSUFBSSxDQUFDLGtCQUFrQixDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUMvRCxDQUFDO1FBQ0gsQ0FBQztRQUVELE9BQU8sQ0FBQyxHQUFHLFNBQVMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRTtZQUNsQyxLQUFLLE1BQU0sQ0FBQyxLQUFLLEVBQUUsU0FBUyxDQUFDLElBQUksVUFBVSxFQUFFLENBQUM7Z0JBQzVDLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQyxFQUFFLEtBQUssQ0FBQyxDQUFDO2dCQUMzQyxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsY0FBYyxDQUFDLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQztnQkFFM0MsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUM7Z0JBQ2xELElBQUksVUFBVSxLQUFLLENBQUMsRUFBRSxDQUFDO29CQUNyQixPQUFPLFVBQVUsR0FBRyxTQUFTLENBQUM7Z0JBQ2hDLENBQUM7WUFDSCxDQUFDO1lBQ0QsT0FBTyxDQUFDLENBQUM7UUFDWCxDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRDs7T0FFRztJQUNILE1BQU0sQ0FBQyxPQUFPLENBQUMsU0FBNEIsRUFBRSxVQUFvQjtRQUMvRCxJQUFJLENBQUMsVUFBVSxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQ3hELE9BQU8sU0FBUyxDQUFDO1FBQ25CLENBQUM7UUFFRCx5REFBeUQ7UUFDekQsTUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUNyQyxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxLQUFLLEtBQUssSUFBSSxVQUFVLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7UUFDeEUsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsS0FBSyxLQUFLLElBQUksVUFBVSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO1FBRXhFLHFEQUFxRDtRQUNyRCxJQUFJLFlBQVksSUFBSSxZQUFZLEVBQUUsQ0FBQztZQUNqQyxNQUFNLElBQUksS0FBSyxDQUFDLGtEQUFrRCxDQUFDLENBQUM7UUFDdEUsQ0FBQztRQUVELE9BQU8sU0FBUyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsRUFBRTtZQUN6QixJQUFJLFlBQVksRUFBRSxDQUFDO2dCQUNqQix1QkFBdUI7Z0JBQ3ZCLE1BQU0sTUFBTSxHQUFhLEVBQUUsQ0FBQztnQkFFNUIsYUFBYTtnQkFDYixJQUFJLFVBQVUsQ0FBQyxHQUFHLEtBQUssQ0FBQyxJQUFJLFVBQVUsQ0FBQyxHQUFHLEtBQUssS0FBSyxFQUFFLENBQUM7b0JBQ3JELE1BQU0sQ0FBQyxHQUFHLEdBQUcsR0FBRyxDQUFDLEdBQUcsQ0FBQztnQkFDdkIsQ0FBQztnQkFFRCxLQUFLLE1BQU0sR0FBRyxJQUFJLElBQUksRUFBRSxDQUFDO29CQUN2QixJQUFJLEdBQUcsS0FBSyxLQUFLO3dCQUFFLFNBQVM7b0JBQzVCLElBQUksVUFBVSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsSUFBSSxVQUFVLENBQUMsR0FBRyxDQUFDLEtBQUssSUFBSSxFQUFFLENBQUM7d0JBQ3RELE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxjQUFjLENBQUMsR0FBRyxFQUFFLEdBQUcsQ0FBQyxDQUFDO3dCQUM1QyxJQUFJLEtBQUssS0FBSyxTQUFTLEVBQUUsQ0FBQzs0QkFDeEIsSUFBSSxDQUFDLGNBQWMsQ0FBQyxNQUFNLEVBQUUsR0FBRyxFQUFFLEtBQUssQ0FBQyxDQUFDO3dCQUMxQyxDQUFDO29CQUNILENBQUM7Z0JBQ0gsQ0FBQztnQkFFRCxPQUFPLE1BQU0sQ0FBQztZQUNoQixDQUFDO2lCQUFNLENBQUM7Z0JBQ04sMkRBQTJEO2dCQUMzRCxNQUFNLE1BQU0sR0FBRyxFQUFFLEdBQUcsR0FBRyxFQUFFLENBQUM7Z0JBRTFCLEtBQUssTUFBTSxHQUFHLElBQUksSUFBSSxFQUFFLENBQUM7b0JBQ3ZCLElBQUksVUFBVSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsSUFBSSxVQUFVLENBQUMsR0FBRyxDQUFDLEtBQUssS0FBSyxFQUFFLENBQUM7d0JBQ3ZELElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxNQUFNLEVBQUUsR0FBRyxDQUFDLENBQUM7b0JBQ3RDLENBQUM7Z0JBQ0gsQ0FBQztnQkFFRCxPQUFPLE1BQU0sQ0FBQztZQUNoQixDQUFDO1FBQ0gsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxNQUFNLENBQUMsUUFBUSxDQUFDLFNBQTRCLEVBQUUsS0FBYSxFQUFFLE1BQWlCO1FBQzVFLElBQUksSUFBSSxHQUFHLFNBQVMsQ0FBQztRQUNyQixJQUFJLE1BQU0sSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUM3QyxJQUFJLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFDeEMsQ0FBQztRQUVELE1BQU0sTUFBTSxHQUFHLElBQUksR0FBRyxFQUFPLENBQUM7UUFDOUIsS0FBSyxNQUFNLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQztZQUN2QixNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsY0FBYyxDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsQ0FBQztZQUM5QyxJQUFJLEtBQUssS0FBSyxTQUFTLEVBQUUsQ0FBQztnQkFDeEIsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7b0JBQ3pCLCtCQUErQjtvQkFDL0IsS0FBSyxNQUFNLENBQUMsSUFBSSxLQUFLLEVBQUUsQ0FBQzt3QkFDdEIsTUFBTSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7b0JBQ25DLENBQUM7Z0JBQ0gsQ0FBQztxQkFBTSxDQUFDO29CQUNOLE1BQU0sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO2dCQUN2QyxDQUFDO1lBQ0gsQ0FBQztRQUNILENBQUM7UUFFRCxPQUFPLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDNUIsQ0FBQztJQUVEOztPQUVHO0lBQ0ssTUFBTSxDQUFDLGtCQUFrQixDQUFDLFNBQXlCO1FBQ3pELElBQUksT0FBTyxTQUFTLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDbEMsT0FBTyxTQUFTLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ2hDLENBQUM7UUFDRCxJQUFJLFNBQVMsS0FBSyxLQUFLLElBQUksU0FBUyxLQUFLLFdBQVcsRUFBRSxDQUFDO1lBQ3JELE9BQU8sQ0FBQyxDQUFDO1FBQ1gsQ0FBQztRQUNELE9BQU8sQ0FBQyxDQUFDLENBQUM7SUFDWixDQUFDO0lBRUQ7O09BRUc7SUFDSCxNQUFNLENBQUMsY0FBYyxDQUFDLEdBQVEsRUFBRSxJQUFZO1FBQzFDLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDOUIsSUFBSSxPQUFPLEdBQUcsR0FBRyxDQUFDO1FBRWxCLEtBQUssTUFBTSxJQUFJLElBQUksS0FBSyxFQUFFLENBQUM7WUFDekIsSUFBSSxPQUFPLEtBQUssSUFBSSxJQUFJLE9BQU8sS0FBSyxTQUFTLEVBQUUsQ0FBQztnQkFDOUMsT0FBTyxTQUFTLENBQUM7WUFDbkIsQ0FBQztZQUNELElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO2dCQUMzQixzQkFBc0I7Z0JBQ3RCLE1BQU0sS0FBSyxHQUFHLFFBQVEsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUM7Z0JBQ2pDLElBQUksQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztvQkFDbEIsT0FBTyxHQUFHLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQztnQkFDM0IsQ0FBQztxQkFBTSxDQUFDO29CQUNOLHdDQUF3QztvQkFDeEMsT0FBTyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztnQkFDckUsQ0FBQztZQUNILENBQUM7aUJBQU0sQ0FBQztnQkFDTixPQUFPLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQzFCLENBQUM7UUFDSCxDQUFDO1FBRUQsT0FBTyxPQUFPLENBQUM7SUFDakIsQ0FBQztJQUVEOztPQUVHO0lBQ0ssTUFBTSxDQUFDLGNBQWMsQ0FBQyxHQUFRLEVBQUUsSUFBWSxFQUFFLEtBQVU7UUFDOUQsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUM5QixJQUFJLE9BQU8sR0FBRyxHQUFHLENBQUM7UUFFbEIsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLEtBQUssQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUM7WUFDMUMsTUFBTSxJQUFJLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ3RCLElBQUksQ0FBQyxDQUFDLElBQUksSUFBSSxPQUFPLENBQUMsRUFBRSxDQUFDO2dCQUN2QixPQUFPLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1lBQ3JCLENBQUM7WUFDRCxPQUFPLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQzFCLENBQUM7UUFFRCxPQUFPLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRyxLQUFLLENBQUM7SUFDM0MsQ0FBQztJQUVEOztPQUVHO0lBQ0ssTUFBTSxDQUFDLGlCQUFpQixDQUFDLEdBQVEsRUFBRSxJQUFZO1FBQ3JELE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDOUIsSUFBSSxPQUFPLEdBQUcsR0FBRyxDQUFDO1FBRWxCLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxLQUFLLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO1lBQzFDLE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUN0QixJQUFJLENBQUMsQ0FBQyxJQUFJLElBQUksT0FBTyxDQUFDLEVBQUUsQ0FBQztnQkFDdkIsT0FBTztZQUNULENBQUM7WUFDRCxPQUFPLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQzFCLENBQUM7UUFFRCxPQUFPLE9BQU8sQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQzFDLENBQUM7SUFFRDs7T0FFRztJQUNLLE1BQU0sQ0FBQyxhQUFhLENBQUMsQ0FBTSxFQUFFLENBQU07UUFDekMsd0JBQXdCO1FBQ3hCLElBQUksQ0FBQyxLQUFLLFNBQVMsSUFBSSxDQUFDLEtBQUssU0FBUztZQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQ2pELElBQUksQ0FBQyxLQUFLLFNBQVM7WUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDO1FBQy9CLElBQUksQ0FBQyxLQUFLLFNBQVM7WUFBRSxPQUFPLENBQUMsQ0FBQztRQUM5QixJQUFJLENBQUMsS0FBSyxJQUFJLElBQUksQ0FBQyxLQUFLLElBQUk7WUFBRSxPQUFPLENBQUMsQ0FBQztRQUN2QyxJQUFJLENBQUMsS0FBSyxJQUFJO1lBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQztRQUMxQixJQUFJLENBQUMsS0FBSyxJQUFJO1lBQUUsT0FBTyxDQUFDLENBQUM7UUFFekIsa0JBQWtCO1FBQ2xCLElBQUksQ0FBQyxZQUFZLE9BQU8sQ0FBQyxJQUFJLENBQUMsUUFBUSxJQUFJLENBQUMsWUFBWSxPQUFPLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQzdFLE9BQU8sQ0FBQyxDQUFDLFdBQVcsRUFBRSxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsV0FBVyxFQUFFLENBQUMsQ0FBQztRQUN4RCxDQUFDO1FBRUQsZUFBZTtRQUNmLElBQUksQ0FBQyxZQUFZLElBQUksSUFBSSxDQUFDLFlBQVksSUFBSSxFQUFFLENBQUM7WUFDM0MsT0FBTyxDQUFDLENBQUMsT0FBTyxFQUFFLEdBQUcsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ25DLENBQUM7UUFFRCxpQkFBaUI7UUFDakIsSUFBSSxPQUFPLENBQUMsS0FBSyxRQUFRLElBQUksT0FBTyxDQUFDLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDbkQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ2YsQ0FBQztRQUVELGlCQUFpQjtRQUNqQixJQUFJLE9BQU8sQ0FBQyxLQUFLLFFBQVEsSUFBSSxPQUFPLENBQUMsS0FBSyxRQUFRLEVBQUUsQ0FBQztZQUNuRCxPQUFPLENBQUMsQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDNUIsQ0FBQztRQUVELGtCQUFrQjtRQUNsQixJQUFJLE9BQU8sQ0FBQyxLQUFLLFNBQVMsSUFBSSxPQUFPLENBQUMsS0FBSyxTQUFTLEVBQUUsQ0FBQztZQUNyRCxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ25DLENBQUM7UUFFRCxpQ0FBaUM7UUFDakMsT0FBTyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsYUFBYSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQzVDLENBQUM7SUFFRDs7T0FFRztJQUNLLE1BQU0sQ0FBQyxZQUFZLENBQUMsS0FBVTtRQUNwQyxJQUFJLEtBQUssWUFBWSxPQUFPLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQzNDLE9BQU8sS0FBSyxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQzdCLENBQUM7UUFDRCxJQUFJLEtBQUssWUFBWSxJQUFJLEVBQUUsQ0FBQztZQUMxQixPQUFPLEtBQUssQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUM3QixDQUFDO1FBQ0QsSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksS0FBSyxLQUFLLElBQUksRUFBRSxDQUFDO1lBQ2hELE9BQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUMvQixDQUFDO1FBQ0QsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0NBQ0YifQ==
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Document } from '../types/interfaces.js';
|
|
2
|
+
import { IndexEngine } from './IndexEngine.js';
|
|
3
|
+
/**
|
|
4
|
+
* Query execution plan types
|
|
5
|
+
*/
|
|
6
|
+
export type TQueryPlanType = 'IXSCAN' | 'COLLSCAN' | 'FETCH' | 'IXSCAN_RANGE';
|
|
7
|
+
/**
|
|
8
|
+
* Represents a query execution plan
|
|
9
|
+
*/
|
|
10
|
+
export interface IQueryPlan {
|
|
11
|
+
/** The type of scan used */
|
|
12
|
+
type: TQueryPlanType;
|
|
13
|
+
/** Index name if using an index */
|
|
14
|
+
indexName?: string;
|
|
15
|
+
/** Index key specification */
|
|
16
|
+
indexKey?: Record<string, 1 | -1 | string>;
|
|
17
|
+
/** Whether the query can be fully satisfied by the index */
|
|
18
|
+
indexCovering: boolean;
|
|
19
|
+
/** Estimated selectivity (0-1, lower is more selective) */
|
|
20
|
+
selectivity: number;
|
|
21
|
+
/** Whether range operators are used */
|
|
22
|
+
usesRange: boolean;
|
|
23
|
+
/** Fields used from the index */
|
|
24
|
+
indexFieldsUsed: string[];
|
|
25
|
+
/** Filter conditions that must be applied post-index lookup */
|
|
26
|
+
residualFilter?: Document;
|
|
27
|
+
/** Explanation for debugging */
|
|
28
|
+
explanation: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* QueryPlanner - Analyzes queries and selects optimal execution plans
|
|
32
|
+
*/
|
|
33
|
+
export declare class QueryPlanner {
|
|
34
|
+
private indexEngine;
|
|
35
|
+
constructor(indexEngine: IndexEngine);
|
|
36
|
+
/**
|
|
37
|
+
* Generate an execution plan for a query filter
|
|
38
|
+
*/
|
|
39
|
+
plan(filter: Document): Promise<IQueryPlan>;
|
|
40
|
+
/**
|
|
41
|
+
* Analyze filter to extract operator information per field
|
|
42
|
+
*/
|
|
43
|
+
private analyzeFilter;
|
|
44
|
+
/**
|
|
45
|
+
* Score an index for the given filter
|
|
46
|
+
*/
|
|
47
|
+
private scoreIndex;
|
|
48
|
+
/**
|
|
49
|
+
* Calculate overall score for a plan (higher is better)
|
|
50
|
+
*/
|
|
51
|
+
private calculateScore;
|
|
52
|
+
/**
|
|
53
|
+
* Explain a query - returns detailed plan information
|
|
54
|
+
*/
|
|
55
|
+
explain(filter: Document): Promise<{
|
|
56
|
+
queryPlanner: {
|
|
57
|
+
plannerVersion: number;
|
|
58
|
+
namespace: string;
|
|
59
|
+
indexFilterSet: boolean;
|
|
60
|
+
winningPlan: IQueryPlan;
|
|
61
|
+
rejectedPlans: IQueryPlan[];
|
|
62
|
+
};
|
|
63
|
+
}>;
|
|
64
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import * as plugins from '../tsmdb.plugins.js';
|
|
2
|
+
import { IndexEngine } from './IndexEngine.js';
|
|
3
|
+
/**
|
|
4
|
+
* QueryPlanner - Analyzes queries and selects optimal execution plans
|
|
5
|
+
*/
|
|
6
|
+
export class QueryPlanner {
|
|
7
|
+
indexEngine;
|
|
8
|
+
constructor(indexEngine) {
|
|
9
|
+
this.indexEngine = indexEngine;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Generate an execution plan for a query filter
|
|
13
|
+
*/
|
|
14
|
+
async plan(filter) {
|
|
15
|
+
await this.indexEngine['initialize']();
|
|
16
|
+
// Empty filter = full collection scan
|
|
17
|
+
if (!filter || Object.keys(filter).length === 0) {
|
|
18
|
+
return {
|
|
19
|
+
type: 'COLLSCAN',
|
|
20
|
+
indexCovering: false,
|
|
21
|
+
selectivity: 1.0,
|
|
22
|
+
usesRange: false,
|
|
23
|
+
indexFieldsUsed: [],
|
|
24
|
+
explanation: 'No filter specified, full collection scan required',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
// Analyze the filter
|
|
28
|
+
const operatorInfo = this.analyzeFilter(filter);
|
|
29
|
+
// Get available indexes
|
|
30
|
+
const indexes = await this.indexEngine.listIndexes();
|
|
31
|
+
// Score each index
|
|
32
|
+
let bestPlan = null;
|
|
33
|
+
let bestScore = -1;
|
|
34
|
+
for (const index of indexes) {
|
|
35
|
+
const plan = this.scoreIndex(index, operatorInfo, filter);
|
|
36
|
+
if (plan.selectivity < 1.0) {
|
|
37
|
+
const score = this.calculateScore(plan);
|
|
38
|
+
if (score > bestScore) {
|
|
39
|
+
bestScore = score;
|
|
40
|
+
bestPlan = plan;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// If no suitable index found, fall back to collection scan
|
|
45
|
+
if (!bestPlan || bestScore <= 0) {
|
|
46
|
+
return {
|
|
47
|
+
type: 'COLLSCAN',
|
|
48
|
+
indexCovering: false,
|
|
49
|
+
selectivity: 1.0,
|
|
50
|
+
usesRange: false,
|
|
51
|
+
indexFieldsUsed: [],
|
|
52
|
+
explanation: 'No suitable index found for this query',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return bestPlan;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Analyze filter to extract operator information per field
|
|
59
|
+
*/
|
|
60
|
+
analyzeFilter(filter, prefix = '') {
|
|
61
|
+
const result = new Map();
|
|
62
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
63
|
+
// Skip logical operators at the top level
|
|
64
|
+
if (key.startsWith('$')) {
|
|
65
|
+
if (key === '$and' && Array.isArray(value)) {
|
|
66
|
+
// Merge $and conditions
|
|
67
|
+
for (const subFilter of value) {
|
|
68
|
+
const subInfo = this.analyzeFilter(subFilter, prefix);
|
|
69
|
+
for (const [field, info] of subInfo) {
|
|
70
|
+
if (result.has(field)) {
|
|
71
|
+
// Merge operators
|
|
72
|
+
const existing = result.get(field);
|
|
73
|
+
existing.operators.push(...info.operators);
|
|
74
|
+
existing.equality = existing.equality || info.equality;
|
|
75
|
+
existing.range = existing.range || info.range;
|
|
76
|
+
existing.in = existing.in || info.in;
|
|
77
|
+
Object.assign(existing.values, info.values);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
result.set(field, info);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
88
|
+
const info = {
|
|
89
|
+
field: fullKey,
|
|
90
|
+
operators: [],
|
|
91
|
+
equality: false,
|
|
92
|
+
range: false,
|
|
93
|
+
in: false,
|
|
94
|
+
exists: false,
|
|
95
|
+
regex: false,
|
|
96
|
+
values: {},
|
|
97
|
+
};
|
|
98
|
+
if (typeof value !== 'object' || value === null || value instanceof plugins.bson.ObjectId || value instanceof Date) {
|
|
99
|
+
// Direct equality
|
|
100
|
+
info.equality = true;
|
|
101
|
+
info.operators.push('$eq');
|
|
102
|
+
info.values['$eq'] = value;
|
|
103
|
+
}
|
|
104
|
+
else if (Array.isArray(value)) {
|
|
105
|
+
// Array equality (rare, but possible)
|
|
106
|
+
info.equality = true;
|
|
107
|
+
info.operators.push('$eq');
|
|
108
|
+
info.values['$eq'] = value;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Operator object
|
|
112
|
+
for (const [op, opValue] of Object.entries(value)) {
|
|
113
|
+
if (op.startsWith('$')) {
|
|
114
|
+
info.operators.push(op);
|
|
115
|
+
info.values[op] = opValue;
|
|
116
|
+
switch (op) {
|
|
117
|
+
case '$eq':
|
|
118
|
+
info.equality = true;
|
|
119
|
+
break;
|
|
120
|
+
case '$ne':
|
|
121
|
+
case '$not':
|
|
122
|
+
// These can use indexes but with low selectivity
|
|
123
|
+
break;
|
|
124
|
+
case '$in':
|
|
125
|
+
info.in = true;
|
|
126
|
+
break;
|
|
127
|
+
case '$nin':
|
|
128
|
+
// Can't efficiently use indexes
|
|
129
|
+
break;
|
|
130
|
+
case '$gt':
|
|
131
|
+
case '$gte':
|
|
132
|
+
case '$lt':
|
|
133
|
+
case '$lte':
|
|
134
|
+
info.range = true;
|
|
135
|
+
break;
|
|
136
|
+
case '$exists':
|
|
137
|
+
info.exists = true;
|
|
138
|
+
break;
|
|
139
|
+
case '$regex':
|
|
140
|
+
info.regex = true;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// Nested object - recurse
|
|
146
|
+
const nestedInfo = this.analyzeFilter({ [op]: opValue }, fullKey);
|
|
147
|
+
for (const [nestedField, nestedFieldInfo] of nestedInfo) {
|
|
148
|
+
result.set(nestedField, nestedFieldInfo);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (info.operators.length > 0) {
|
|
154
|
+
result.set(fullKey, info);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Score an index for the given filter
|
|
161
|
+
*/
|
|
162
|
+
scoreIndex(index, operatorInfo, filter) {
|
|
163
|
+
const indexFields = Object.keys(index.key);
|
|
164
|
+
const usedFields = [];
|
|
165
|
+
let usesRange = false;
|
|
166
|
+
let canUseIndex = true;
|
|
167
|
+
let selectivity = 1.0;
|
|
168
|
+
let residualFilter;
|
|
169
|
+
// Check each index field in order
|
|
170
|
+
for (const field of indexFields) {
|
|
171
|
+
const info = operatorInfo.get(field);
|
|
172
|
+
if (!info) {
|
|
173
|
+
// Index field not in filter - stop here
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
usedFields.push(field);
|
|
177
|
+
// Calculate selectivity based on operator
|
|
178
|
+
if (info.equality) {
|
|
179
|
+
// Equality has high selectivity
|
|
180
|
+
selectivity *= 0.01; // Assume 1% match
|
|
181
|
+
}
|
|
182
|
+
else if (info.in) {
|
|
183
|
+
// $in selectivity depends on array size
|
|
184
|
+
const inValues = info.values['$in'];
|
|
185
|
+
if (Array.isArray(inValues)) {
|
|
186
|
+
selectivity *= Math.min(0.5, inValues.length * 0.01);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
selectivity *= 0.1;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (info.range) {
|
|
193
|
+
// Range queries have moderate selectivity
|
|
194
|
+
selectivity *= 0.25;
|
|
195
|
+
usesRange = true;
|
|
196
|
+
// After range, can't use more index fields efficiently
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
else if (info.exists) {
|
|
200
|
+
// $exists can use sparse indexes
|
|
201
|
+
selectivity *= 0.5;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
// Other operators may not be indexable
|
|
205
|
+
canUseIndex = false;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (!canUseIndex || usedFields.length === 0) {
|
|
210
|
+
return {
|
|
211
|
+
type: 'COLLSCAN',
|
|
212
|
+
indexCovering: false,
|
|
213
|
+
selectivity: 1.0,
|
|
214
|
+
usesRange: false,
|
|
215
|
+
indexFieldsUsed: [],
|
|
216
|
+
explanation: `Index ${index.name} cannot be used for this query`,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
// Build residual filter for conditions not covered by index
|
|
220
|
+
const coveredFields = new Set(usedFields);
|
|
221
|
+
const residualConditions = {};
|
|
222
|
+
for (const [field, info] of operatorInfo) {
|
|
223
|
+
if (!coveredFields.has(field)) {
|
|
224
|
+
// This field isn't covered by the index
|
|
225
|
+
if (info.equality) {
|
|
226
|
+
residualConditions[field] = info.values['$eq'];
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
residualConditions[field] = info.values;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (Object.keys(residualConditions).length > 0) {
|
|
234
|
+
residualFilter = residualConditions;
|
|
235
|
+
}
|
|
236
|
+
// Unique indexes have better selectivity for equality
|
|
237
|
+
if (index.unique && usedFields.length === indexFields.length) {
|
|
238
|
+
selectivity = Math.min(selectivity, 0.001); // At most 1 document
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
type: usesRange ? 'IXSCAN_RANGE' : 'IXSCAN',
|
|
242
|
+
indexName: index.name,
|
|
243
|
+
indexKey: index.key,
|
|
244
|
+
indexCovering: Object.keys(residualConditions).length === 0,
|
|
245
|
+
selectivity,
|
|
246
|
+
usesRange,
|
|
247
|
+
indexFieldsUsed: usedFields,
|
|
248
|
+
residualFilter,
|
|
249
|
+
explanation: `Using index ${index.name} on fields [${usedFields.join(', ')}]`,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Calculate overall score for a plan (higher is better)
|
|
254
|
+
*/
|
|
255
|
+
calculateScore(plan) {
|
|
256
|
+
let score = 0;
|
|
257
|
+
// Lower selectivity is better (fewer documents to fetch)
|
|
258
|
+
score += (1 - plan.selectivity) * 100;
|
|
259
|
+
// Index covering queries are best
|
|
260
|
+
if (plan.indexCovering) {
|
|
261
|
+
score += 50;
|
|
262
|
+
}
|
|
263
|
+
// More index fields used is better
|
|
264
|
+
score += plan.indexFieldsUsed.length * 10;
|
|
265
|
+
// Equality scans are better than range scans
|
|
266
|
+
if (!plan.usesRange) {
|
|
267
|
+
score += 20;
|
|
268
|
+
}
|
|
269
|
+
return score;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Explain a query - returns detailed plan information
|
|
273
|
+
*/
|
|
274
|
+
async explain(filter) {
|
|
275
|
+
await this.indexEngine['initialize']();
|
|
276
|
+
// Analyze the filter
|
|
277
|
+
const operatorInfo = this.analyzeFilter(filter);
|
|
278
|
+
// Get available indexes
|
|
279
|
+
const indexes = await this.indexEngine.listIndexes();
|
|
280
|
+
// Score all indexes
|
|
281
|
+
const plans = [];
|
|
282
|
+
for (const index of indexes) {
|
|
283
|
+
const plan = this.scoreIndex(index, operatorInfo, filter);
|
|
284
|
+
plans.push(plan);
|
|
285
|
+
}
|
|
286
|
+
// Add collection scan as fallback
|
|
287
|
+
plans.push({
|
|
288
|
+
type: 'COLLSCAN',
|
|
289
|
+
indexCovering: false,
|
|
290
|
+
selectivity: 1.0,
|
|
291
|
+
usesRange: false,
|
|
292
|
+
indexFieldsUsed: [],
|
|
293
|
+
explanation: 'Full collection scan',
|
|
294
|
+
});
|
|
295
|
+
// Sort by score (best first)
|
|
296
|
+
plans.sort((a, b) => this.calculateScore(b) - this.calculateScore(a));
|
|
297
|
+
return {
|
|
298
|
+
queryPlanner: {
|
|
299
|
+
plannerVersion: 1,
|
|
300
|
+
namespace: `${this.indexEngine['dbName']}.${this.indexEngine['collName']}`,
|
|
301
|
+
indexFilterSet: false,
|
|
302
|
+
winningPlan: plans[0],
|
|
303
|
+
rejectedPlans: plans.slice(1),
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUXVlcnlQbGFubmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvdHNtZGIvZW5naW5lL1F1ZXJ5UGxhbm5lci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHFCQUFxQixDQUFDO0FBRS9DLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQTZDL0M7O0dBRUc7QUFDSCxNQUFNLE9BQU8sWUFBWTtJQUNmLFdBQVcsQ0FBYztJQUVqQyxZQUFZLFdBQXdCO1FBQ2xDLElBQUksQ0FBQyxXQUFXLEdBQUcsV0FBVyxDQUFDO0lBQ2pDLENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBZ0I7UUFDekIsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFDLFlBQVksQ0FBQyxFQUFFLENBQUM7UUFFdkMsc0NBQXNDO1FBQ3RDLElBQUksQ0FBQyxNQUFNLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDaEQsT0FBTztnQkFDTCxJQUFJLEVBQUUsVUFBVTtnQkFDaEIsYUFBYSxFQUFFLEtBQUs7Z0JBQ3BCLFdBQVcsRUFBRSxHQUFHO2dCQUNoQixTQUFTLEVBQUUsS0FBSztnQkFDaEIsZUFBZSxFQUFFLEVBQUU7Z0JBQ25CLFdBQVcsRUFBRSxvREFBb0Q7YUFDbEUsQ0FBQztRQUNKLENBQUM7UUFFRCxxQkFBcUI7UUFDckIsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUVoRCx3QkFBd0I7UUFDeEIsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBRXJELG1CQUFtQjtRQUNuQixJQUFJLFFBQVEsR0FBc0IsSUFBSSxDQUFDO1FBQ3ZDLElBQUksU0FBUyxHQUFHLENBQUMsQ0FBQyxD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
|