@routier/core 0.3.0 → 0.5.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 +2 -2
- package/dist/index.cjs +549 -462
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +554 -462
- package/dist/index.js.map +1 -1
- package/dist/plugins/TelemetryDbPlugin.d.ts +43 -0
- package/dist/plugins/index.cjs +538 -24
- package/dist/plugins/index.cjs.map +1 -1
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/index.js +544 -22
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/query/QueryOptionsCollection.d.ts +13 -0
- package/dist/plugins/query/explain.d.ts +82 -0
- package/dist/plugins/query/formatExplanation.d.ts +9 -0
- package/dist/plugins/query/index.d.ts +2 -0
- package/dist/plugins/query/types.d.ts +11 -0
- package/dist/plugins/types.d.ts +43 -1
- package/dist/plugins/wire/types.d.ts +17 -1
- package/dist/utilities/index.cjs +43 -12
- package/dist/utilities/index.cjs.map +1 -1
- package/dist/utilities/index.js +43 -12
- package/dist/utilities/index.js.map +1 -1
- package/package.json +6 -10
- package/dist/capabilities/Capability.d.ts +0 -11
- package/dist/capabilities/PerformanceCapability.d.ts +0 -13
- package/dist/capabilities/TracingCapability.d.ts +0 -11
- package/dist/capabilities/index.cjs +0 -820
- package/dist/capabilities/index.cjs.map +0 -1
- package/dist/capabilities/index.d.ts +0 -4
- package/dist/capabilities/index.js +0 -808
- package/dist/capabilities/index.js.map +0 -1
- package/dist/capabilities/performance/PerformanceTracker.d.ts +0 -11
- package/dist/capabilities/tracing/CallTraceManager.d.ts +0 -12
- package/dist/capabilities/types.d.ts +0 -17
|
@@ -6,8 +6,21 @@ export type QueryCollectionItem<T, K extends QueryOptionName> = {
|
|
|
6
6
|
export declare class QueryOptionsCollection<T> {
|
|
7
7
|
private options;
|
|
8
8
|
private nextExecutionTarget;
|
|
9
|
+
private nextExecutionReason;
|
|
9
10
|
private nextIndex;
|
|
10
11
|
private enumeratedItems;
|
|
12
|
+
/** Cuts over to memory execution, keeping the first cause. See `MemoryExecutionReason`. */
|
|
13
|
+
private cutOverToMemory;
|
|
14
|
+
/**
|
|
15
|
+
* True when `split()` or `splitAt()` produced this collection.
|
|
16
|
+
*
|
|
17
|
+
* Those rebuild each half by re-adding its options, which re-derives execution targets
|
|
18
|
+
* without the options that caused them — a post-join filter alone in the memory half
|
|
19
|
+
* derives back to `"database"`. Anything reading `target` as a report of where work runs
|
|
20
|
+
* has to reject a derived collection; see `explainQuery`.
|
|
21
|
+
*/
|
|
22
|
+
private derived;
|
|
23
|
+
get isDerived(): boolean;
|
|
11
24
|
get items(): Map<keyof QueryOptionValueMap<unknown>, QueryCollectionItem<any, any>[]>;
|
|
12
25
|
get isEmpty(): boolean;
|
|
13
26
|
static EMPTY<R>(): QueryOptionsCollection<R>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { MemoryExecutionReason, QueryOptionExecutionTarget, QueryOptionName } from "./types";
|
|
2
|
+
import { QueryOptionsCollection } from "./QueryOptionsCollection";
|
|
3
|
+
/**
|
|
4
|
+
* One sentence per reason code, written for someone meeting pushdown for the first time.
|
|
5
|
+
*
|
|
6
|
+
* Beside the codes rather than in the formatter, so console output, a failing test and the
|
|
7
|
+
* docs all say the same thing.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MEMORY_EXECUTION_EXPLANATIONS: Record<MemoryExecutionReason, string>;
|
|
10
|
+
/**
|
|
11
|
+
* One thing a backend actually executed, in the backend's own language.
|
|
12
|
+
*
|
|
13
|
+
* A plugin pushes these onto `DbPluginQueryEvent.executedQueries` as it runs them, so a join —
|
|
14
|
+
* which reads twice — reports both, in execution order. `text` is not required to be SQL: a
|
|
15
|
+
* key-value store describes what it did in whatever terms it has.
|
|
16
|
+
*/
|
|
17
|
+
export type ExecutedQuery = {
|
|
18
|
+
text: string;
|
|
19
|
+
parameters?: unknown[];
|
|
20
|
+
};
|
|
21
|
+
export type ExplainedOption = {
|
|
22
|
+
index: number;
|
|
23
|
+
name: QueryOptionName;
|
|
24
|
+
detail?: Record<string, unknown>;
|
|
25
|
+
};
|
|
26
|
+
export declare const EXECUTED_QUERIES_UNSUPPORTED = "This plugin did not report what it executed. It may not support explain.";
|
|
27
|
+
export type ExecutionStep = {
|
|
28
|
+
step: number;
|
|
29
|
+
of: number;
|
|
30
|
+
executedIn: QueryOptionExecutionTarget;
|
|
31
|
+
description: string;
|
|
32
|
+
options: ExplainedOption[];
|
|
33
|
+
/** Set on database steps once the plugin has reported. */
|
|
34
|
+
executedQueries?: ExecutedQuery[];
|
|
35
|
+
/** Set on the first database step instead, when the plugin reported nothing. */
|
|
36
|
+
executedQueriesUnsupported?: string;
|
|
37
|
+
/** Set on memory steps only. */
|
|
38
|
+
reason?: MemoryExecutionReason;
|
|
39
|
+
explanation?: string;
|
|
40
|
+
};
|
|
41
|
+
export type QueryExplanationSummary = {
|
|
42
|
+
database: number;
|
|
43
|
+
memory: number;
|
|
44
|
+
/** Deduped, in first-seen order. Empty when the whole query pushed down. */
|
|
45
|
+
reasons: MemoryExecutionReason[];
|
|
46
|
+
explanation: string;
|
|
47
|
+
};
|
|
48
|
+
export type QueryExplanation = {
|
|
49
|
+
collection: string;
|
|
50
|
+
database: string;
|
|
51
|
+
summary: QueryExplanationSummary;
|
|
52
|
+
executionSteps: ExecutionStep[];
|
|
53
|
+
plugin: {
|
|
54
|
+
kind: string;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
export type ExplainContext = {
|
|
58
|
+
collection: string;
|
|
59
|
+
database: string;
|
|
60
|
+
pluginKind: string;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Builds the explanation from the resolved options, with no plugin involvement.
|
|
64
|
+
*
|
|
65
|
+
* Takes the collection BEFORE `split()`, and throws otherwise. Splitting re-adds each half
|
|
66
|
+
* into a fresh collection, which re-derives targets without the options that caused them — a
|
|
67
|
+
* post-join filter alone in the memory half derives back to `"database"`, and the document
|
|
68
|
+
* would report memory work as having run in the database.
|
|
69
|
+
*/
|
|
70
|
+
export declare const explainQuery: (options: QueryOptionsCollection<any>, context: ExplainContext) => QueryExplanation;
|
|
71
|
+
/**
|
|
72
|
+
* Attaches what the backend reported to the step that was sent to it.
|
|
73
|
+
*
|
|
74
|
+
* Reporting is optional for a plugin, so an empty report is not an error: the step is marked
|
|
75
|
+
* `executedQueriesUnsupported` instead, and the rest of the explanation stands — the pushdown
|
|
76
|
+
* analysis comes from the options and is correct with or without the plugin's statements.
|
|
77
|
+
*
|
|
78
|
+
* Copies the steps rather than writing into them, so the explanation a caller already holds
|
|
79
|
+
* does not gain statements after the fact. Options and their details are shared with the
|
|
80
|
+
* original — nothing mutates them, and copying deeper would only look safer than it is.
|
|
81
|
+
*/
|
|
82
|
+
export declare const withExecutedQueries: (explanation: QueryExplanation, executedQueries: ExecutedQuery[]) => QueryExplanation;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { QueryExplanation } from "./explain";
|
|
2
|
+
/**
|
|
3
|
+
* Renders an explanation for a terminal.
|
|
4
|
+
*
|
|
5
|
+
* The STEP headers carry the whole lesson: a reader who has never heard of pushdown still sees
|
|
6
|
+
* that the statement in step 1 is not the entire query. Nobody should have to notice a missing
|
|
7
|
+
* ORDER BY to work that out.
|
|
8
|
+
*/
|
|
9
|
+
export declare const formatExplanation: (explanation: QueryExplanation) => string;
|
|
@@ -19,10 +19,21 @@ export type QueryField = {
|
|
|
19
19
|
};
|
|
20
20
|
export type QueryOptionExecutionTarget = "database" | "memory";
|
|
21
21
|
export type QueryOptionName = keyof QueryOptionValueMap<unknown>;
|
|
22
|
+
/**
|
|
23
|
+
* Why an option runs in memory rather than in the database.
|
|
24
|
+
*
|
|
25
|
+
* A code rather than a sentence, so a test can assert on it — the sentences live in
|
|
26
|
+
* `MEMORY_EXECUTION_EXPLANATIONS`. Every cause is a ratchet, because `nextExecutionTarget`
|
|
27
|
+
* never returns to `"database"`, so the code recorded is the FIRST cause and it stays on every
|
|
28
|
+
* option after it. Reporting a later one would name a symptom of this one.
|
|
29
|
+
*/
|
|
30
|
+
export type MemoryExecutionReason = "not-parsable" | "unmapped-property" | "renamed-property" | "map-rename" | "after-nearest" | "after-join" | "cross-plugin-join";
|
|
22
31
|
export type QueryOption<T, K extends QueryOptionName> = {
|
|
23
32
|
name: QueryOptionName;
|
|
24
33
|
value: QueryOptionValueMap<T>[K];
|
|
25
34
|
target: QueryOptionExecutionTarget;
|
|
35
|
+
/** Set only when `target` is `"memory"`. */
|
|
36
|
+
reason?: MemoryExecutionReason;
|
|
26
37
|
};
|
|
27
38
|
export type QueryOptionValueMap<T extends {}> = {
|
|
28
39
|
skip: number;
|
package/dist/plugins/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PluginEventCallbackPartialResult, PluginEventCallbackResult } from "../results";
|
|
2
|
+
import { ExecutedQuery } from "./query/explain";
|
|
2
3
|
import { QueryOptionsCollection } from "./query/QueryOptionsCollection";
|
|
3
4
|
import { CompiledSchema, InferType } from '../schema';
|
|
4
5
|
import { BulkPersistChanges, BulkPersistResult, SchemaCollection } from "../collections";
|
|
@@ -50,7 +51,48 @@ export interface IDbPlugin {
|
|
|
50
51
|
/**
|
|
51
52
|
* Event for a query operation, including schema, parent, and the query operation.
|
|
52
53
|
*/
|
|
53
|
-
export type DbPluginQueryEvent<TRoot extends {}, TShape> = DbPluginOperationEvent<IQuery<TRoot, TShape
|
|
54
|
+
export type DbPluginQueryEvent<TRoot extends {}, TShape> = DbPluginOperationEvent<IQuery<TRoot, TShape>> & {
|
|
55
|
+
/**
|
|
56
|
+
* Whether the caller asked for an explanation. Required, never optional: a query is
|
|
57
|
+
* either explained or it is not, and "unset" is not a third state.
|
|
58
|
+
*
|
|
59
|
+
* A plugin is free to ignore it. One that reports unconditionally is correct; one that
|
|
60
|
+
* checks the flag to skip building report strings is also correct. What a plugin must
|
|
61
|
+
* NOT do is treat `true` as an instruction it has to obey — a plugin that cannot report
|
|
62
|
+
* simply doesn't, and the datastore marks the step as not reported.
|
|
63
|
+
*/
|
|
64
|
+
explain: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Where a plugin reports what it executed. Pushing to it is how a plugin supports
|
|
67
|
+
* `.explain()` — a plugin that never pushes still answers queries, and its explanations
|
|
68
|
+
* mark the database step as not reported (`executedQueriesUnsupported`) instead of
|
|
69
|
+
* showing statements.
|
|
70
|
+
*
|
|
71
|
+
* The datastore decides whether anyone sees it: with `explain` on it reads the array and
|
|
72
|
+
* an empty one means "not supported"; with `explain` off it takes no action either way.
|
|
73
|
+
*
|
|
74
|
+
* An array the DATASTORE creates and the plugin pushes into, rather than a value the plugin
|
|
75
|
+
* returns. The result envelope is rebuilt in at least six places between a plugin and the
|
|
76
|
+
* caller — the memory half re-translates, joins build fresh tuple values, the cache
|
|
77
|
+
* reconstructs from stored entries — so anything carried on it is discarded before arrival.
|
|
78
|
+
* The event is not rebuilt, and an array survives the shallow spread in `ConcurrencyDbPlugin`
|
|
79
|
+
* because both sides then hold the same array. Assigning a new one would not.
|
|
80
|
+
*
|
|
81
|
+
* Push once per query actually executed, in execution order, so a join reports both reads.
|
|
82
|
+
* `text` is whatever the backend runs — SQL for a SQL engine, a description of the access
|
|
83
|
+
* path for a store that has no statement. A plugin that answered without touching its
|
|
84
|
+
* backend pushes a description of that instead — `CacheDbPlugin` pushes "cache hit" —
|
|
85
|
+
* because pushing nothing reads as "this plugin does not report".
|
|
86
|
+
*
|
|
87
|
+
* Push AFTER the query runs, not before. `RetryDbPlugin` re-invokes with the same event, so
|
|
88
|
+
* a plugin that pushes first reports an entry per failed attempt.
|
|
89
|
+
*
|
|
90
|
+
* The array accumulates for as long as the event lives. That is why `.explain()` is not
|
|
91
|
+
* offered on a subscribed queryable: `subscribeQuery` builds its event once and re-issues it
|
|
92
|
+
* on every change notification, which would grow this without bound.
|
|
93
|
+
*/
|
|
94
|
+
executedQueries: ExecutedQuery[];
|
|
95
|
+
};
|
|
54
96
|
/**
|
|
55
97
|
* Event for bulk operations, including schema, parent, and the entity changes.
|
|
56
98
|
*/
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SerializedExpression } from "../../expressions";
|
|
2
2
|
import { JoinKind } from "../query/join";
|
|
3
|
+
import { ExecutedQuery } from "../query/explain";
|
|
3
4
|
import { QueryOrdering } from "../query/types";
|
|
4
5
|
/**
|
|
5
6
|
* The wire format for a whole Routier operation.
|
|
@@ -75,6 +76,12 @@ export type SerializedQueryRequest = {
|
|
|
75
76
|
kind: "query";
|
|
76
77
|
collectionName: string;
|
|
77
78
|
options: SerializedQueryOption[];
|
|
79
|
+
/**
|
|
80
|
+
* Whether the caller wants the response to say what the server ran. Required — a query is
|
|
81
|
+
* either explained or it is not. A server whose plugin does not report answers `true` the
|
|
82
|
+
* same as `false`, and the caller's explanation marks the remote step as not reported.
|
|
83
|
+
*/
|
|
84
|
+
explain: boolean;
|
|
78
85
|
};
|
|
79
86
|
/** One entity update, as `EntityUpdateInfo` minus nothing — every field of it is already JSON. */
|
|
80
87
|
export type SerializedUpdate = {
|
|
@@ -101,10 +108,19 @@ export type SerializedDestroyRequest = {
|
|
|
101
108
|
};
|
|
102
109
|
export type SerializedRequest = SerializedQueryRequest | SerializedPersistRequest | SerializedDestroyRequest;
|
|
103
110
|
/** What a receiver sends back. Errors are a value, not a transport status. */
|
|
104
|
-
export type SerializedResponse =
|
|
111
|
+
export type SerializedResponse =
|
|
112
|
+
/**
|
|
113
|
+
* `executedQueries` carries what the SERVER's plugin ran, so `.explain()` on a client sees
|
|
114
|
+
* through the wire rather than reporting a blank. Optional on the response, unlike on a
|
|
115
|
+
* local event: a plugin that does not report has nothing to send, and the client's
|
|
116
|
+
* explanation then marks the remote step as not reported. There is no flag on either end —
|
|
117
|
+
* the wire forwards whatever the plugin pushed, or nothing.
|
|
118
|
+
*/
|
|
119
|
+
{
|
|
105
120
|
ok: true;
|
|
106
121
|
kind: "query";
|
|
107
122
|
value: unknown;
|
|
123
|
+
executedQueries?: ExecutedQuery[];
|
|
108
124
|
} | {
|
|
109
125
|
ok: true;
|
|
110
126
|
kind: "persist";
|
package/dist/utilities/index.cjs
CHANGED
|
@@ -150,8 +150,26 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
150
150
|
class QueryOptionsCollection {
|
|
151
151
|
options = new Map();
|
|
152
152
|
nextExecutionTarget = "database";
|
|
153
|
+
nextExecutionReason = null;
|
|
153
154
|
nextIndex = 0;
|
|
154
155
|
enumeratedItems = [];
|
|
156
|
+
/** Cuts over to memory execution, keeping the first cause. See `MemoryExecutionReason`. */ cutOverToMemory(reason) {
|
|
157
|
+
this.nextExecutionTarget = "memory";
|
|
158
|
+
if (this.nextExecutionReason == null) {
|
|
159
|
+
this.nextExecutionReason = reason;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* True when `split()` or `splitAt()` produced this collection.
|
|
164
|
+
*
|
|
165
|
+
* Those rebuild each half by re-adding its options, which re-derives execution targets
|
|
166
|
+
* without the options that caused them — a post-join filter alone in the memory half
|
|
167
|
+
* derives back to `"database"`. Anything reading `target` as a report of where work runs
|
|
168
|
+
* has to reject a derived collection; see `explainQuery`.
|
|
169
|
+
*/ derived = false;
|
|
170
|
+
get isDerived() {
|
|
171
|
+
return this.derived;
|
|
172
|
+
}
|
|
155
173
|
get items() {
|
|
156
174
|
return this.options;
|
|
157
175
|
}
|
|
@@ -172,7 +190,7 @@ class QueryOptionsCollection {
|
|
|
172
190
|
if (mapValue.fields.some((x)=>x.isRename === true) || mapValue.fields.some((x)=>x.property?.isUnmapped === true)) {
|
|
173
191
|
// Cut over to memory execution since we are renaming a property with .map
|
|
174
192
|
// We do not want to figure out how the new name flows through the entire query
|
|
175
|
-
this.
|
|
193
|
+
this.cutOverToMemory("map-rename");
|
|
176
194
|
}
|
|
177
195
|
}
|
|
178
196
|
if (name === "filter") {
|
|
@@ -184,13 +202,13 @@ class QueryOptionsCollection {
|
|
|
184
202
|
return;
|
|
185
203
|
}
|
|
186
204
|
if (filterValue.expression.type === "not-parsable") {
|
|
187
|
-
this.
|
|
205
|
+
this.cutOverToMemory("not-parsable");
|
|
188
206
|
} else {
|
|
189
207
|
(0,_expressions_utils__rspack_import_0/* .forEach */.j)(filterValue.expression, (expression)=>{
|
|
190
208
|
if ((0,_assertions__rspack_import_1.isPropertyExpression)(expression) && expression.property.isUnmapped) {
|
|
191
209
|
// Cut over to memory execution, unmapped properties are not in the database and
|
|
192
210
|
// cannot be queried
|
|
193
|
-
this.
|
|
211
|
+
this.cutOverToMemory("unmapped-property");
|
|
194
212
|
return false;
|
|
195
213
|
}
|
|
196
214
|
if ((0,_assertions__rspack_import_1.isPropertyExpression)(expression) && expression.property.hasRenamedSegments) {
|
|
@@ -198,7 +216,7 @@ class QueryOptionsCollection {
|
|
|
198
216
|
// `from` (storage) names, but filter selectors reference the
|
|
199
217
|
// in-memory names. Memory execution runs after deserialization,
|
|
200
218
|
// where the in-memory names exist
|
|
201
|
-
this.
|
|
219
|
+
this.cutOverToMemory("renamed-property");
|
|
202
220
|
return false;
|
|
203
221
|
}
|
|
204
222
|
return true;
|
|
@@ -209,8 +227,10 @@ class QueryOptionsCollection {
|
|
|
209
227
|
const sortValue = value;
|
|
210
228
|
// Same rule as filters: sort selectors reference in-memory names, which
|
|
211
229
|
// only exist after deserialization when the property is renamed or unmapped
|
|
212
|
-
if (sortValue.property != null &&
|
|
213
|
-
this.
|
|
230
|
+
if (sortValue.property != null && sortValue.property.isUnmapped) {
|
|
231
|
+
this.cutOverToMemory("unmapped-property");
|
|
232
|
+
} else if (sortValue.property != null && sortValue.property.hasRenamedSegments) {
|
|
233
|
+
this.cutOverToMemory("renamed-property");
|
|
214
234
|
}
|
|
215
235
|
}
|
|
216
236
|
if (name === "nearest") {
|
|
@@ -221,8 +241,10 @@ class QueryOptionsCollection {
|
|
|
221
241
|
//
|
|
222
242
|
// This is also what lets every translator's in-memory fallback read the column by
|
|
223
243
|
// its resolved name — anything whose storage name differs never reaches them.
|
|
224
|
-
if (nearestValue.property != null &&
|
|
225
|
-
this.
|
|
244
|
+
if (nearestValue.property != null && nearestValue.property.isUnmapped) {
|
|
245
|
+
this.cutOverToMemory("unmapped-property");
|
|
246
|
+
} else if (nearestValue.property != null && nearestValue.property.hasRenamedSegments) {
|
|
247
|
+
this.cutOverToMemory("renamed-property");
|
|
226
248
|
}
|
|
227
249
|
}
|
|
228
250
|
if (name === "join") {
|
|
@@ -234,7 +256,7 @@ class QueryOptionsCollection {
|
|
|
234
256
|
// Set BEFORE the item is created, unlike `nearest`'s ratchet below, because this
|
|
235
257
|
// moves the join option itself rather than everything after it.
|
|
236
258
|
if (joinValue.crossPlugin === true) {
|
|
237
|
-
this.
|
|
259
|
+
this.cutOverToMemory("cross-plugin-join");
|
|
238
260
|
}
|
|
239
261
|
}
|
|
240
262
|
const item = {
|
|
@@ -242,7 +264,10 @@ class QueryOptionsCollection {
|
|
|
242
264
|
option: {
|
|
243
265
|
name,
|
|
244
266
|
target: this.nextExecutionTarget,
|
|
245
|
-
value
|
|
267
|
+
value,
|
|
268
|
+
...this.nextExecutionReason == null ? {} : {
|
|
269
|
+
reason: this.nextExecutionReason
|
|
270
|
+
}
|
|
246
271
|
}
|
|
247
272
|
};
|
|
248
273
|
this.nextIndex++;
|
|
@@ -265,7 +290,7 @@ class QueryOptionsCollection {
|
|
|
265
290
|
//
|
|
266
291
|
// A plugin that DID push the search down loses nothing but the chance to also
|
|
267
292
|
// push down what follows it, which is a limit over ten rows.
|
|
268
|
-
this.
|
|
293
|
+
this.cutOverToMemory("after-nearest");
|
|
269
294
|
}
|
|
270
295
|
if (name === "join") {
|
|
271
296
|
// Everything AFTER a join runs in memory, for the same reason as `nearest`: this
|
|
@@ -276,7 +301,7 @@ class QueryOptionsCollection {
|
|
|
276
301
|
// OUTER rows read, not the pairs produced — a plausible-looking result with the
|
|
277
302
|
// wrong number of rows in it. Conjuncts that can safely run earlier are split off
|
|
278
303
|
// by the query builder BEFORE dispatch, which is the only exception.
|
|
279
|
-
this.
|
|
304
|
+
this.cutOverToMemory("after-join");
|
|
280
305
|
}
|
|
281
306
|
}
|
|
282
307
|
/**
|
|
@@ -290,6 +315,8 @@ class QueryOptionsCollection {
|
|
|
290
315
|
const sortedItems = this.enumeratedItems.toSorted((a, b)=>a.index - b.index);
|
|
291
316
|
const before = new QueryOptionsCollection();
|
|
292
317
|
const after = new QueryOptionsCollection();
|
|
318
|
+
before.derived = true;
|
|
319
|
+
after.derived = true;
|
|
293
320
|
let at = null;
|
|
294
321
|
for(let i = 0, length = sortedItems.length; i < length; i++){
|
|
295
322
|
const { option } = sortedItems[i];
|
|
@@ -323,10 +350,12 @@ class QueryOptionsCollection {
|
|
|
323
350
|
]
|
|
324
351
|
]));
|
|
325
352
|
const nextExecutionTarget = this.nextExecutionTarget;
|
|
353
|
+
const nextExecutionReason = this.nextExecutionReason;
|
|
326
354
|
const nextIndex = this.nextIndex;
|
|
327
355
|
return ()=>{
|
|
328
356
|
this.options = new Map(options);
|
|
329
357
|
this.nextExecutionTarget = nextExecutionTarget;
|
|
358
|
+
this.nextExecutionReason = nextExecutionReason;
|
|
330
359
|
this.nextIndex = nextIndex;
|
|
331
360
|
this.enumeratedItems = [];
|
|
332
361
|
};
|
|
@@ -336,6 +365,8 @@ class QueryOptionsCollection {
|
|
|
336
365
|
const sortedItems = this.enumeratedItems.toSorted((a, b)=>a.index - b.index);
|
|
337
366
|
const memoryQueryOptionsCollection = new QueryOptionsCollection();
|
|
338
367
|
const databaseQueryOptionsCollection = new QueryOptionsCollection();
|
|
368
|
+
memoryQueryOptionsCollection.derived = true;
|
|
369
|
+
databaseQueryOptionsCollection.derived = true;
|
|
339
370
|
for(let i = 0, length = sortedItems.length; i < length; i++){
|
|
340
371
|
const sortedItem = sortedItems[i];
|
|
341
372
|
if (sortedItem.option.target === "database") {
|