@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
package/dist/plugins/index.cjs
CHANGED
|
@@ -2398,8 +2398,26 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
2398
2398
|
class QueryOptionsCollection {
|
|
2399
2399
|
options = new Map();
|
|
2400
2400
|
nextExecutionTarget = "database";
|
|
2401
|
+
nextExecutionReason = null;
|
|
2401
2402
|
nextIndex = 0;
|
|
2402
2403
|
enumeratedItems = [];
|
|
2404
|
+
/** Cuts over to memory execution, keeping the first cause. See `MemoryExecutionReason`. */ cutOverToMemory(reason) {
|
|
2405
|
+
this.nextExecutionTarget = "memory";
|
|
2406
|
+
if (this.nextExecutionReason == null) {
|
|
2407
|
+
this.nextExecutionReason = reason;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
/**
|
|
2411
|
+
* True when `split()` or `splitAt()` produced this collection.
|
|
2412
|
+
*
|
|
2413
|
+
* Those rebuild each half by re-adding its options, which re-derives execution targets
|
|
2414
|
+
* without the options that caused them — a post-join filter alone in the memory half
|
|
2415
|
+
* derives back to `"database"`. Anything reading `target` as a report of where work runs
|
|
2416
|
+
* has to reject a derived collection; see `explainQuery`.
|
|
2417
|
+
*/ derived = false;
|
|
2418
|
+
get isDerived() {
|
|
2419
|
+
return this.derived;
|
|
2420
|
+
}
|
|
2403
2421
|
get items() {
|
|
2404
2422
|
return this.options;
|
|
2405
2423
|
}
|
|
@@ -2420,7 +2438,7 @@ class QueryOptionsCollection {
|
|
|
2420
2438
|
if (mapValue.fields.some((x)=>x.isRename === true) || mapValue.fields.some((x)=>x.property?.isUnmapped === true)) {
|
|
2421
2439
|
// Cut over to memory execution since we are renaming a property with .map
|
|
2422
2440
|
// We do not want to figure out how the new name flows through the entire query
|
|
2423
|
-
this.
|
|
2441
|
+
this.cutOverToMemory("map-rename");
|
|
2424
2442
|
}
|
|
2425
2443
|
}
|
|
2426
2444
|
if (name === "filter") {
|
|
@@ -2432,13 +2450,13 @@ class QueryOptionsCollection {
|
|
|
2432
2450
|
return;
|
|
2433
2451
|
}
|
|
2434
2452
|
if (filterValue.expression.type === "not-parsable") {
|
|
2435
|
-
this.
|
|
2453
|
+
this.cutOverToMemory("not-parsable");
|
|
2436
2454
|
} else {
|
|
2437
2455
|
(0,_expressions_utils__rspack_import_0/* .forEach */.j)(filterValue.expression, (expression)=>{
|
|
2438
2456
|
if ((0,_assertions__rspack_import_1.isPropertyExpression)(expression) && expression.property.isUnmapped) {
|
|
2439
2457
|
// Cut over to memory execution, unmapped properties are not in the database and
|
|
2440
2458
|
// cannot be queried
|
|
2441
|
-
this.
|
|
2459
|
+
this.cutOverToMemory("unmapped-property");
|
|
2442
2460
|
return false;
|
|
2443
2461
|
}
|
|
2444
2462
|
if ((0,_assertions__rspack_import_1.isPropertyExpression)(expression) && expression.property.hasRenamedSegments) {
|
|
@@ -2446,7 +2464,7 @@ class QueryOptionsCollection {
|
|
|
2446
2464
|
// `from` (storage) names, but filter selectors reference the
|
|
2447
2465
|
// in-memory names. Memory execution runs after deserialization,
|
|
2448
2466
|
// where the in-memory names exist
|
|
2449
|
-
this.
|
|
2467
|
+
this.cutOverToMemory("renamed-property");
|
|
2450
2468
|
return false;
|
|
2451
2469
|
}
|
|
2452
2470
|
return true;
|
|
@@ -2457,8 +2475,10 @@ class QueryOptionsCollection {
|
|
|
2457
2475
|
const sortValue = value;
|
|
2458
2476
|
// Same rule as filters: sort selectors reference in-memory names, which
|
|
2459
2477
|
// only exist after deserialization when the property is renamed or unmapped
|
|
2460
|
-
if (sortValue.property != null &&
|
|
2461
|
-
this.
|
|
2478
|
+
if (sortValue.property != null && sortValue.property.isUnmapped) {
|
|
2479
|
+
this.cutOverToMemory("unmapped-property");
|
|
2480
|
+
} else if (sortValue.property != null && sortValue.property.hasRenamedSegments) {
|
|
2481
|
+
this.cutOverToMemory("renamed-property");
|
|
2462
2482
|
}
|
|
2463
2483
|
}
|
|
2464
2484
|
if (name === "nearest") {
|
|
@@ -2469,8 +2489,10 @@ class QueryOptionsCollection {
|
|
|
2469
2489
|
//
|
|
2470
2490
|
// This is also what lets every translator's in-memory fallback read the column by
|
|
2471
2491
|
// its resolved name — anything whose storage name differs never reaches them.
|
|
2472
|
-
if (nearestValue.property != null &&
|
|
2473
|
-
this.
|
|
2492
|
+
if (nearestValue.property != null && nearestValue.property.isUnmapped) {
|
|
2493
|
+
this.cutOverToMemory("unmapped-property");
|
|
2494
|
+
} else if (nearestValue.property != null && nearestValue.property.hasRenamedSegments) {
|
|
2495
|
+
this.cutOverToMemory("renamed-property");
|
|
2474
2496
|
}
|
|
2475
2497
|
}
|
|
2476
2498
|
if (name === "join") {
|
|
@@ -2482,7 +2504,7 @@ class QueryOptionsCollection {
|
|
|
2482
2504
|
// Set BEFORE the item is created, unlike `nearest`'s ratchet below, because this
|
|
2483
2505
|
// moves the join option itself rather than everything after it.
|
|
2484
2506
|
if (joinValue.crossPlugin === true) {
|
|
2485
|
-
this.
|
|
2507
|
+
this.cutOverToMemory("cross-plugin-join");
|
|
2486
2508
|
}
|
|
2487
2509
|
}
|
|
2488
2510
|
const item = {
|
|
@@ -2490,7 +2512,10 @@ class QueryOptionsCollection {
|
|
|
2490
2512
|
option: {
|
|
2491
2513
|
name,
|
|
2492
2514
|
target: this.nextExecutionTarget,
|
|
2493
|
-
value
|
|
2515
|
+
value,
|
|
2516
|
+
...this.nextExecutionReason == null ? {} : {
|
|
2517
|
+
reason: this.nextExecutionReason
|
|
2518
|
+
}
|
|
2494
2519
|
}
|
|
2495
2520
|
};
|
|
2496
2521
|
this.nextIndex++;
|
|
@@ -2513,7 +2538,7 @@ class QueryOptionsCollection {
|
|
|
2513
2538
|
//
|
|
2514
2539
|
// A plugin that DID push the search down loses nothing but the chance to also
|
|
2515
2540
|
// push down what follows it, which is a limit over ten rows.
|
|
2516
|
-
this.
|
|
2541
|
+
this.cutOverToMemory("after-nearest");
|
|
2517
2542
|
}
|
|
2518
2543
|
if (name === "join") {
|
|
2519
2544
|
// Everything AFTER a join runs in memory, for the same reason as `nearest`: this
|
|
@@ -2524,7 +2549,7 @@ class QueryOptionsCollection {
|
|
|
2524
2549
|
// OUTER rows read, not the pairs produced — a plausible-looking result with the
|
|
2525
2550
|
// wrong number of rows in it. Conjuncts that can safely run earlier are split off
|
|
2526
2551
|
// by the query builder BEFORE dispatch, which is the only exception.
|
|
2527
|
-
this.
|
|
2552
|
+
this.cutOverToMemory("after-join");
|
|
2528
2553
|
}
|
|
2529
2554
|
}
|
|
2530
2555
|
/**
|
|
@@ -2538,6 +2563,8 @@ class QueryOptionsCollection {
|
|
|
2538
2563
|
const sortedItems = this.enumeratedItems.toSorted((a, b)=>a.index - b.index);
|
|
2539
2564
|
const before = new QueryOptionsCollection();
|
|
2540
2565
|
const after = new QueryOptionsCollection();
|
|
2566
|
+
before.derived = true;
|
|
2567
|
+
after.derived = true;
|
|
2541
2568
|
let at = null;
|
|
2542
2569
|
for(let i = 0, length = sortedItems.length; i < length; i++){
|
|
2543
2570
|
const { option } = sortedItems[i];
|
|
@@ -2571,10 +2598,12 @@ class QueryOptionsCollection {
|
|
|
2571
2598
|
]
|
|
2572
2599
|
]));
|
|
2573
2600
|
const nextExecutionTarget = this.nextExecutionTarget;
|
|
2601
|
+
const nextExecutionReason = this.nextExecutionReason;
|
|
2574
2602
|
const nextIndex = this.nextIndex;
|
|
2575
2603
|
return ()=>{
|
|
2576
2604
|
this.options = new Map(options);
|
|
2577
2605
|
this.nextExecutionTarget = nextExecutionTarget;
|
|
2606
|
+
this.nextExecutionReason = nextExecutionReason;
|
|
2578
2607
|
this.nextIndex = nextIndex;
|
|
2579
2608
|
this.enumeratedItems = [];
|
|
2580
2609
|
};
|
|
@@ -2584,6 +2613,8 @@ class QueryOptionsCollection {
|
|
|
2584
2613
|
const sortedItems = this.enumeratedItems.toSorted((a, b)=>a.index - b.index);
|
|
2585
2614
|
const memoryQueryOptionsCollection = new QueryOptionsCollection();
|
|
2586
2615
|
const databaseQueryOptionsCollection = new QueryOptionsCollection();
|
|
2616
|
+
memoryQueryOptionsCollection.derived = true;
|
|
2617
|
+
databaseQueryOptionsCollection.derived = true;
|
|
2587
2618
|
for(let i = 0, length = sortedItems.length; i < length; i++){
|
|
2588
2619
|
const sortedItem = sortedItems[i];
|
|
2589
2620
|
if (sortedItem.option.target === "database") {
|
|
@@ -3028,32 +3059,40 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
3028
3059
|
__webpack_require__.d(__webpack_exports__, {
|
|
3029
3060
|
DEFAULT_SEMI_JOIN_KEY_THRESHOLD: () => (/* reexport */ DEFAULT_SEMI_JOIN_KEY_THRESHOLD),
|
|
3030
3061
|
TranslatedArrayValue: () => (/* reexport */ TranslatedArrayValue),
|
|
3062
|
+
collectingSink: () => (/* reexport */ collectingSink),
|
|
3031
3063
|
semiJoinFilter: () => (/* reexport */ semiJoinFilter),
|
|
3032
3064
|
CacheDbPlugin: () => (/* reexport */ CacheDbPlugin),
|
|
3033
3065
|
Query: () => (/* reexport */ Query),
|
|
3034
3066
|
splitSendableOptions: () => (/* reexport */ splitSendableOptions),
|
|
3035
3067
|
executeJoin: () => (/* reexport */ executeJoin),
|
|
3068
|
+
formatExplanation: () => (/* reexport */ formatExplanation),
|
|
3036
3069
|
serializePersistResult: () => (/* reexport */ serializePersistResult),
|
|
3070
|
+
explainQuery: () => (/* reexport */ explainQuery),
|
|
3071
|
+
cosineDistance: () => (/* reexport */ cosineDistance),
|
|
3037
3072
|
RetryDbPlugin: () => (/* reexport */ RetryDbPlugin),
|
|
3038
|
-
nearestBy: () => (/* reexport */ nearestBy),
|
|
3039
3073
|
BatchingDbPlugin: () => (/* reexport */ BatchingDbPlugin),
|
|
3040
3074
|
applyInnerOptions: () => (/* reexport */ applyInnerOptions),
|
|
3041
|
-
|
|
3075
|
+
MEMORY_EXECUTION_EXPLANATIONS: () => (/* reexport */ MEMORY_EXECUTION_EXPLANATIONS),
|
|
3042
3076
|
TupleTranslator: () => (/* reexport */ TupleTranslator),
|
|
3043
|
-
|
|
3077
|
+
TranslatedSingleValue: () => (/* reexport */ TranslatedSingleValue),
|
|
3044
3078
|
ConcurrencyDbPlugin: () => (/* reexport */ ConcurrencyDbPlugin),
|
|
3045
3079
|
TranslatedGroupValue: () => (/* reexport */ TranslatedGroupValue),
|
|
3046
3080
|
deserializeBulkPersist: () => (/* reexport */ deserializeBulkPersist),
|
|
3081
|
+
EXECUTED_QUERIES_UNSUPPORTED: () => (/* reexport */ EXECUTED_QUERIES_UNSUPPORTED),
|
|
3047
3082
|
joinInPlugin: () => (/* reexport */ joinInPlugin),
|
|
3048
|
-
readJoinKey: () => (/* reexport */ readJoinKey),
|
|
3049
3083
|
deserializePersistResult: () => (/* reexport */ deserializePersistResult),
|
|
3050
|
-
|
|
3051
|
-
|
|
3084
|
+
loggerSink: () => (/* reexport */ loggerSink),
|
|
3085
|
+
nearestBy: () => (/* reexport */ nearestBy),
|
|
3086
|
+
readJoinKey: () => (/* reexport */ readJoinKey),
|
|
3052
3087
|
hashJoin: () => (/* reexport */ hashJoin),
|
|
3053
3088
|
EphemeralDataPlugin: () => (/* reexport */ EphemeralDataPlugin),
|
|
3054
3089
|
QueryOptionsCollection: () => (/* reexport */ QueryOptionsCollection/* .QueryOptionsCollection */.H),
|
|
3090
|
+
serializeBulkPersist: () => (/* reexport */ serializeBulkPersist),
|
|
3091
|
+
toEntityShape: () => (/* reexport */ toEntityShape),
|
|
3055
3092
|
loadJoinInnerSide: () => (/* reexport */ loadJoinInnerSide),
|
|
3056
3093
|
DataTranslator: () => (/* reexport */ DataTranslator),
|
|
3094
|
+
withExecutedQueries: () => (/* reexport */ withExecutedQueries),
|
|
3095
|
+
TelemetryDbPlugin: () => (/* reexport */ TelemetryDbPlugin),
|
|
3057
3096
|
createRequestHandler: () => (/* reexport */ createRequestHandler),
|
|
3058
3097
|
SqlTranslator: () => (/* reexport */ SqlTranslator),
|
|
3059
3098
|
QueryOrdering: () => (/* reexport */ types_QueryOrdering),
|
|
@@ -3595,7 +3634,11 @@ class Query {
|
|
|
3595
3634
|
id: `${event.id}-inner`,
|
|
3596
3635
|
source: event.source,
|
|
3597
3636
|
action: "query",
|
|
3598
|
-
reason: "join inner side"
|
|
3637
|
+
reason: "join inner side",
|
|
3638
|
+
explain: event.explain,
|
|
3639
|
+
// The same array the outer read pushes into, so a join reports BOTH reads in execution
|
|
3640
|
+
// order. Built fresh rather than spread, so this has to be carried explicitly.
|
|
3641
|
+
executedQueries: event.executedQueries
|
|
3599
3642
|
};
|
|
3600
3643
|
query(innerEvent, (result)=>{
|
|
3601
3644
|
if (result.ok === Result/* .PluginEventResult.ERROR */.D.ERROR) {
|
|
@@ -4295,6 +4338,386 @@ class SqlTranslator extends DataTranslator {
|
|
|
4295
4338
|
|
|
4296
4339
|
|
|
4297
4340
|
|
|
4341
|
+
;// CONCATENATED MODULE: ./src/plugins/query/explain.ts
|
|
4342
|
+
|
|
4343
|
+
/**
|
|
4344
|
+
* One sentence per reason code, written for someone meeting pushdown for the first time.
|
|
4345
|
+
*
|
|
4346
|
+
* Beside the codes rather than in the formatter, so console output, a failing test and the
|
|
4347
|
+
* docs all say the same thing.
|
|
4348
|
+
*/ const MEMORY_EXECUTION_EXPLANATIONS = {
|
|
4349
|
+
"not-parsable": "A filter could not be parsed into an expression tree, so it and every option after it run in memory.",
|
|
4350
|
+
"unmapped-property": "The property is not stored in the database, so it can only be read after deserialization.",
|
|
4351
|
+
"renamed-property": "The property is stored under a different name, and selectors use the in-memory name, so it can only be read after deserialization.",
|
|
4352
|
+
"map-rename": "A map renames or drops properties, so every option after it refers to names the database does not have.",
|
|
4353
|
+
"after-nearest": "A similarity search orders and limits rows, and the plugin cannot report whether it performed the search, so every option after it runs in memory.",
|
|
4354
|
+
"after-join": "A join produces [outer, inner] tuples rather than entities, and the plugin cannot report how it joined, so every option after it runs in memory.",
|
|
4355
|
+
"cross-plugin-join": "The two sides of this join live on different plugins, so neither can read the other's rows and the join runs in the datastore."
|
|
4356
|
+
};
|
|
4357
|
+
const EXECUTED_QUERIES_UNSUPPORTED = "This plugin did not report what it executed. It may not support explain.";
|
|
4358
|
+
const DATABASE_STEP_DESCRIPTION = "These options are sent to the plugin.";
|
|
4359
|
+
const MEMORY_STEP_DESCRIPTION = "Routier runs these over the rows the database returned, after deserializing them.";
|
|
4360
|
+
const UNNARROWED_READ_DESCRIPTION = "No option could be pushed down, so the plugin reads the whole collection.";
|
|
4361
|
+
/**
|
|
4362
|
+
* The reportable shape of one option's value.
|
|
4363
|
+
*
|
|
4364
|
+
* Serializable facts only, never the live selector functions — an explanation is a document a
|
|
4365
|
+
* caller may log, diff in a test, or send to a server, and a closure survives none of that.
|
|
4366
|
+
*/ const detailOf = (option)=>{
|
|
4367
|
+
if (option.name === "filter") {
|
|
4368
|
+
const value = option.value;
|
|
4369
|
+
if (value.expression == null) {
|
|
4370
|
+
return undefined;
|
|
4371
|
+
}
|
|
4372
|
+
try {
|
|
4373
|
+
return {
|
|
4374
|
+
expression: types/* .Expression.toJson */.r4.toJson(value.expression)
|
|
4375
|
+
};
|
|
4376
|
+
} catch {
|
|
4377
|
+
// `valueToJson` rejects a value no wire can carry. Reporting the rest of the
|
|
4378
|
+
// explanation beats taking the diagnostic down with the query it describes.
|
|
4379
|
+
return {
|
|
4380
|
+
expressionUnavailable: "This filter holds a value that cannot be serialized."
|
|
4381
|
+
};
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
4384
|
+
if (option.name === "sort") {
|
|
4385
|
+
const value = option.value;
|
|
4386
|
+
return {
|
|
4387
|
+
propertyName: value.propertyName,
|
|
4388
|
+
direction: value.direction
|
|
4389
|
+
};
|
|
4390
|
+
}
|
|
4391
|
+
if (option.name === "skip" || option.name === "take") {
|
|
4392
|
+
return {
|
|
4393
|
+
value: option.value
|
|
4394
|
+
};
|
|
4395
|
+
}
|
|
4396
|
+
if (option.name === "nearest") {
|
|
4397
|
+
const value = option.value;
|
|
4398
|
+
return {
|
|
4399
|
+
propertyName: value.propertyName,
|
|
4400
|
+
dimensions: value.vector.length,
|
|
4401
|
+
count: value.count
|
|
4402
|
+
};
|
|
4403
|
+
}
|
|
4404
|
+
if (option.name === "join") {
|
|
4405
|
+
const value = option.value;
|
|
4406
|
+
return {
|
|
4407
|
+
kind: value.kind,
|
|
4408
|
+
outerKey: value.outerKey.propertyName,
|
|
4409
|
+
innerKey: value.innerKey.propertyName,
|
|
4410
|
+
crossPlugin: value.crossPlugin,
|
|
4411
|
+
innerOptions: explainedOptionsOf(value.innerOptions)
|
|
4412
|
+
};
|
|
4413
|
+
}
|
|
4414
|
+
if (option.name === "map" || option.name === "group") {
|
|
4415
|
+
const value = option.value;
|
|
4416
|
+
return {
|
|
4417
|
+
fields: value.fields.map((x)=>({
|
|
4418
|
+
from: x.sourceName,
|
|
4419
|
+
to: x.destinationName
|
|
4420
|
+
}))
|
|
4421
|
+
};
|
|
4422
|
+
}
|
|
4423
|
+
return undefined;
|
|
4424
|
+
};
|
|
4425
|
+
const explainedOptionOf = (option, index)=>{
|
|
4426
|
+
const detail = detailOf(option);
|
|
4427
|
+
return {
|
|
4428
|
+
index,
|
|
4429
|
+
name: option.name,
|
|
4430
|
+
...detail == null ? {} : {
|
|
4431
|
+
detail
|
|
4432
|
+
}
|
|
4433
|
+
};
|
|
4434
|
+
};
|
|
4435
|
+
const explainedOptionsOf = (options)=>{
|
|
4436
|
+
const explained = [];
|
|
4437
|
+
let index = 0;
|
|
4438
|
+
options.forEach((option)=>explained.push(explainedOptionOf(option, index++)));
|
|
4439
|
+
return explained;
|
|
4440
|
+
};
|
|
4441
|
+
const summarize = (steps)=>{
|
|
4442
|
+
const reasons = [];
|
|
4443
|
+
let database = 0;
|
|
4444
|
+
let memory = 0;
|
|
4445
|
+
for (const step of steps){
|
|
4446
|
+
if (step.executedIn === "database") {
|
|
4447
|
+
database += step.options.length;
|
|
4448
|
+
continue;
|
|
4449
|
+
}
|
|
4450
|
+
memory += step.options.length;
|
|
4451
|
+
if (step.reason != null && reasons.includes(step.reason) === false) {
|
|
4452
|
+
reasons.push(step.reason);
|
|
4453
|
+
}
|
|
4454
|
+
}
|
|
4455
|
+
const counts = `${database} ${database === 1 ? "option ran" : "options ran"} in the database, ${memory} ran in memory.`;
|
|
4456
|
+
const causes = reasons.map((reason)=>MEMORY_EXECUTION_EXPLANATIONS[reason]).join(" ");
|
|
4457
|
+
return {
|
|
4458
|
+
database,
|
|
4459
|
+
memory,
|
|
4460
|
+
reasons,
|
|
4461
|
+
explanation: causes.length === 0 ? counts : `${counts} ${causes}`
|
|
4462
|
+
};
|
|
4463
|
+
};
|
|
4464
|
+
/**
|
|
4465
|
+
* Groups options into consecutive runs that execute in the same place.
|
|
4466
|
+
*
|
|
4467
|
+
* A step boundary is where execution moves, and a reader has to see the statement as step 1 OF
|
|
4468
|
+
* 2 to understand it is not the whole query. Cutting over to memory is a ratchet, so the
|
|
4469
|
+
* database options are always a prefix and there are at most two steps.
|
|
4470
|
+
*
|
|
4471
|
+
* A database step is emitted even when NO option pushed down, because the plugin is dispatched
|
|
4472
|
+
* either way — `createQueryPayload` always builds a database event. Without it, the worst case
|
|
4473
|
+
* the feature exists to expose reports "0 in the database" while the backend reads the whole
|
|
4474
|
+
* table, which is the opposite of the truth.
|
|
4475
|
+
*/ const toExecutionSteps = (options)=>{
|
|
4476
|
+
const steps = [];
|
|
4477
|
+
let index = 0;
|
|
4478
|
+
options.forEach((option)=>{
|
|
4479
|
+
const explained = explainedOptionOf(option, index++);
|
|
4480
|
+
const current = steps[steps.length - 1];
|
|
4481
|
+
if (current != null && current.executedIn === option.target) {
|
|
4482
|
+
current.options.push(explained);
|
|
4483
|
+
return;
|
|
4484
|
+
}
|
|
4485
|
+
steps.push({
|
|
4486
|
+
step: steps.length + 1,
|
|
4487
|
+
of: 0,
|
|
4488
|
+
executedIn: option.target,
|
|
4489
|
+
description: option.target === "database" ? DATABASE_STEP_DESCRIPTION : MEMORY_STEP_DESCRIPTION,
|
|
4490
|
+
options: [
|
|
4491
|
+
explained
|
|
4492
|
+
],
|
|
4493
|
+
...option.reason == null ? {} : {
|
|
4494
|
+
reason: option.reason,
|
|
4495
|
+
explanation: MEMORY_EXECUTION_EXPLANATIONS[option.reason]
|
|
4496
|
+
}
|
|
4497
|
+
});
|
|
4498
|
+
});
|
|
4499
|
+
if (steps[0]?.executedIn !== "database") {
|
|
4500
|
+
steps.unshift({
|
|
4501
|
+
step: 0,
|
|
4502
|
+
of: 0,
|
|
4503
|
+
executedIn: "database",
|
|
4504
|
+
description: UNNARROWED_READ_DESCRIPTION,
|
|
4505
|
+
options: []
|
|
4506
|
+
});
|
|
4507
|
+
}
|
|
4508
|
+
for(let i = 0; i < steps.length; i++){
|
|
4509
|
+
steps[i].step = i + 1;
|
|
4510
|
+
steps[i].of = steps.length;
|
|
4511
|
+
}
|
|
4512
|
+
return steps;
|
|
4513
|
+
};
|
|
4514
|
+
/**
|
|
4515
|
+
* Builds the explanation from the resolved options, with no plugin involvement.
|
|
4516
|
+
*
|
|
4517
|
+
* Takes the collection BEFORE `split()`, and throws otherwise. Splitting re-adds each half
|
|
4518
|
+
* into a fresh collection, which re-derives targets without the options that caused them — a
|
|
4519
|
+
* post-join filter alone in the memory half derives back to `"database"`, and the document
|
|
4520
|
+
* would report memory work as having run in the database.
|
|
4521
|
+
*/ const explainQuery = (options, context)=>{
|
|
4522
|
+
if (options.isDerived === true) {
|
|
4523
|
+
throw new Error("explainQuery was given a collection produced by split() or splitAt(). Those re-derive " + "execution targets without the options that caused them, so the explanation would report " + "memory work as having run in the database. Pass the collection as it was resolved, " + "before splitting.");
|
|
4524
|
+
}
|
|
4525
|
+
const executionSteps = toExecutionSteps(options);
|
|
4526
|
+
return {
|
|
4527
|
+
collection: context.collection,
|
|
4528
|
+
database: context.database,
|
|
4529
|
+
summary: summarize(executionSteps),
|
|
4530
|
+
executionSteps,
|
|
4531
|
+
plugin: {
|
|
4532
|
+
kind: context.pluginKind
|
|
4533
|
+
}
|
|
4534
|
+
};
|
|
4535
|
+
};
|
|
4536
|
+
/**
|
|
4537
|
+
* Attaches what the backend reported to the step that was sent to it.
|
|
4538
|
+
*
|
|
4539
|
+
* Reporting is optional for a plugin, so an empty report is not an error: the step is marked
|
|
4540
|
+
* `executedQueriesUnsupported` instead, and the rest of the explanation stands — the pushdown
|
|
4541
|
+
* analysis comes from the options and is correct with or without the plugin's statements.
|
|
4542
|
+
*
|
|
4543
|
+
* Copies the steps rather than writing into them, so the explanation a caller already holds
|
|
4544
|
+
* does not gain statements after the fact. Options and their details are shared with the
|
|
4545
|
+
* original — nothing mutates them, and copying deeper would only look safer than it is.
|
|
4546
|
+
*/ const withExecutedQueries = (explanation, executedQueries)=>{
|
|
4547
|
+
let attached = false;
|
|
4548
|
+
const executionSteps = explanation.executionSteps.map((step)=>{
|
|
4549
|
+
// Only the first database step: a plugin reports what IT ran, and everything it ran
|
|
4550
|
+
// was sent as one dispatch. Stamping the same statements onto a second database step
|
|
4551
|
+
// would claim they ran twice.
|
|
4552
|
+
if (step.executedIn !== "database" || attached === true) {
|
|
4553
|
+
return {
|
|
4554
|
+
...step,
|
|
4555
|
+
options: [
|
|
4556
|
+
...step.options
|
|
4557
|
+
]
|
|
4558
|
+
};
|
|
4559
|
+
}
|
|
4560
|
+
attached = true;
|
|
4561
|
+
if (executedQueries.length === 0) {
|
|
4562
|
+
return {
|
|
4563
|
+
...step,
|
|
4564
|
+
options: [
|
|
4565
|
+
...step.options
|
|
4566
|
+
],
|
|
4567
|
+
executedQueriesUnsupported: EXECUTED_QUERIES_UNSUPPORTED
|
|
4568
|
+
};
|
|
4569
|
+
}
|
|
4570
|
+
return {
|
|
4571
|
+
...step,
|
|
4572
|
+
options: [
|
|
4573
|
+
...step.options
|
|
4574
|
+
],
|
|
4575
|
+
executedQueries: [
|
|
4576
|
+
...executedQueries
|
|
4577
|
+
]
|
|
4578
|
+
};
|
|
4579
|
+
});
|
|
4580
|
+
return {
|
|
4581
|
+
...explanation,
|
|
4582
|
+
executionSteps
|
|
4583
|
+
};
|
|
4584
|
+
};
|
|
4585
|
+
|
|
4586
|
+
;// CONCATENATED MODULE: ./src/plugins/query/formatExplanation.ts
|
|
4587
|
+
const OPTION_LABEL_WIDTH = 8;
|
|
4588
|
+
const WRAP_WIDTH = 68;
|
|
4589
|
+
/** Wraps `text` to `WRAP_WIDTH`, prefixing every line with `indent`. */ const wrap = (text, indent)=>{
|
|
4590
|
+
const lines = [];
|
|
4591
|
+
let line = "";
|
|
4592
|
+
for (const word of text.split(" ")){
|
|
4593
|
+
if (line.length > 0 && line.length + word.length + 1 > WRAP_WIDTH) {
|
|
4594
|
+
lines.push(indent + line);
|
|
4595
|
+
line = word;
|
|
4596
|
+
continue;
|
|
4597
|
+
}
|
|
4598
|
+
line = line.length === 0 ? word : `${line} ${word}`;
|
|
4599
|
+
}
|
|
4600
|
+
if (line.length > 0) {
|
|
4601
|
+
lines.push(indent + line);
|
|
4602
|
+
}
|
|
4603
|
+
return lines;
|
|
4604
|
+
};
|
|
4605
|
+
const COMPARATOR_SYMBOLS = {
|
|
4606
|
+
"equals": "===",
|
|
4607
|
+
"greater-than": ">",
|
|
4608
|
+
"greater-than-equals": ">=",
|
|
4609
|
+
"less-than": "<",
|
|
4610
|
+
"less-than-equals": "<="
|
|
4611
|
+
};
|
|
4612
|
+
const describeValue = (value)=>{
|
|
4613
|
+
if (value == null) {
|
|
4614
|
+
return "?";
|
|
4615
|
+
}
|
|
4616
|
+
if (value.k === "raw") {
|
|
4617
|
+
return typeof value.v === "string" ? `"${value.v}"` : String(value.v);
|
|
4618
|
+
}
|
|
4619
|
+
if (value.k === "date") {
|
|
4620
|
+
return value.v;
|
|
4621
|
+
}
|
|
4622
|
+
if (value.k === "array") {
|
|
4623
|
+
return `[${value.v.map(describeValue).join(", ")}]`;
|
|
4624
|
+
}
|
|
4625
|
+
return value.k === "undefined" ? "undefined" : String(value.v);
|
|
4626
|
+
};
|
|
4627
|
+
/** Renders a serialized expression back to something close to the source predicate. */ const describeExpression = (expression)=>{
|
|
4628
|
+
if (expression == null) {
|
|
4629
|
+
return "?";
|
|
4630
|
+
}
|
|
4631
|
+
if (expression.t === "operator") {
|
|
4632
|
+
return `${describeExpression(expression.left)} ${expression.operator} ${describeExpression(expression.right)}`;
|
|
4633
|
+
}
|
|
4634
|
+
if (expression.t === "comparator") {
|
|
4635
|
+
const left = describeExpression(expression.left);
|
|
4636
|
+
const right = describeExpression(expression.right);
|
|
4637
|
+
const symbol = COMPARATOR_SYMBOLS[expression.comparator];
|
|
4638
|
+
if (symbol == null) {
|
|
4639
|
+
return `${left}.${expression.comparator}(${right})${expression.negated === true ? " === false" : ""}`;
|
|
4640
|
+
}
|
|
4641
|
+
return `${left} ${expression.negated === true ? "!==" : symbol} ${right}`;
|
|
4642
|
+
}
|
|
4643
|
+
if (expression.t === "property") {
|
|
4644
|
+
return expression.path;
|
|
4645
|
+
}
|
|
4646
|
+
if (expression.t === "value") {
|
|
4647
|
+
return describeValue(expression.value);
|
|
4648
|
+
}
|
|
4649
|
+
return expression.t === "empty" ? "(no filter)" : "(not parsable)";
|
|
4650
|
+
};
|
|
4651
|
+
const describeOption = (option)=>{
|
|
4652
|
+
const detail = option.detail;
|
|
4653
|
+
if (detail == null) {
|
|
4654
|
+
return "";
|
|
4655
|
+
}
|
|
4656
|
+
if (option.name === "filter") {
|
|
4657
|
+
return detail.expression == null ? String(detail.expressionUnavailable ?? "") : describeExpression(detail.expression);
|
|
4658
|
+
}
|
|
4659
|
+
if (option.name === "sort") {
|
|
4660
|
+
return `${detail.propertyName} ${detail.direction}`;
|
|
4661
|
+
}
|
|
4662
|
+
if (option.name === "skip" || option.name === "take") {
|
|
4663
|
+
return String(detail.value);
|
|
4664
|
+
}
|
|
4665
|
+
if (option.name === "join") {
|
|
4666
|
+
return `${detail.kind} → ${detail.outerKey} = ${detail.innerKey}`;
|
|
4667
|
+
}
|
|
4668
|
+
if (option.name === "nearest") {
|
|
4669
|
+
return `${detail.propertyName}, ${detail.count} nearest`;
|
|
4670
|
+
}
|
|
4671
|
+
if (option.name === "map" || option.name === "group") {
|
|
4672
|
+
const fields = detail.fields;
|
|
4673
|
+
return fields.map((x)=>x.from === x.to ? x.from : `${x.from} → ${x.to}`).join(", ");
|
|
4674
|
+
}
|
|
4675
|
+
return "";
|
|
4676
|
+
};
|
|
4677
|
+
const formatStep = (step, lines)=>{
|
|
4678
|
+
const reason = step.reason == null ? "" : ` [${step.reason}]`;
|
|
4679
|
+
lines.push(` STEP ${step.step} of ${step.of} — ${step.executedIn}${reason}`);
|
|
4680
|
+
lines.push(...wrap(step.description, " "));
|
|
4681
|
+
if (step.explanation != null) {
|
|
4682
|
+
lines.push(...wrap(step.explanation, " "));
|
|
4683
|
+
}
|
|
4684
|
+
lines.push("");
|
|
4685
|
+
for (const option of step.options){
|
|
4686
|
+
lines.push(` ${option.name.padEnd(OPTION_LABEL_WIDTH)} ${describeOption(option)}`.trimEnd());
|
|
4687
|
+
}
|
|
4688
|
+
for (const executed of step.executedQueries ?? []){
|
|
4689
|
+
lines.push("");
|
|
4690
|
+
lines.push(...executed.text.split("\n").map((line)=>` ${line}`));
|
|
4691
|
+
if (executed.parameters != null && executed.parameters.length > 0) {
|
|
4692
|
+
lines.push(` parameters: ${JSON.stringify(executed.parameters)}`);
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4695
|
+
if (step.executedQueriesUnsupported != null) {
|
|
4696
|
+
lines.push("");
|
|
4697
|
+
lines.push(...wrap(step.executedQueriesUnsupported, " "));
|
|
4698
|
+
}
|
|
4699
|
+
lines.push("");
|
|
4700
|
+
};
|
|
4701
|
+
/**
|
|
4702
|
+
* Renders an explanation for a terminal.
|
|
4703
|
+
*
|
|
4704
|
+
* The STEP headers carry the whole lesson: a reader who has never heard of pushdown still sees
|
|
4705
|
+
* that the statement in step 1 is not the entire query. Nobody should have to notice a missing
|
|
4706
|
+
* ORDER BY to work that out.
|
|
4707
|
+
*/ const formatExplanation = (explanation)=>{
|
|
4708
|
+
const { collection, database, summary, executionSteps } = explanation;
|
|
4709
|
+
const stepCount = `${executionSteps.length} ${executionSteps.length === 1 ? "step" : "steps"}`;
|
|
4710
|
+
const lines = [
|
|
4711
|
+
`${collection} · ${database} · ${stepCount}`,
|
|
4712
|
+
""
|
|
4713
|
+
];
|
|
4714
|
+
for (const step of executionSteps){
|
|
4715
|
+
formatStep(step, lines);
|
|
4716
|
+
}
|
|
4717
|
+
lines.push(...wrap(summary.explanation, " "));
|
|
4718
|
+
return lines.join("\n");
|
|
4719
|
+
};
|
|
4720
|
+
|
|
4298
4721
|
;// CONCATENATED MODULE: ./src/plugins/query/types.ts
|
|
4299
4722
|
var types_QueryOrdering = /*#__PURE__*/ function(QueryOrdering) {
|
|
4300
4723
|
QueryOrdering["Descending"] = "desc";
|
|
@@ -4309,6 +4732,8 @@ var types_QueryOrdering = /*#__PURE__*/ function(QueryOrdering) {
|
|
|
4309
4732
|
|
|
4310
4733
|
|
|
4311
4734
|
|
|
4735
|
+
|
|
4736
|
+
|
|
4312
4737
|
// EXTERNAL MODULE: ./src/expressions/evaluate.ts
|
|
4313
4738
|
var evaluate = __webpack_require__(379);
|
|
4314
4739
|
;// CONCATENATED MODULE: ./src/plugins/wire/query.ts
|
|
@@ -4780,6 +5205,7 @@ const createRequestHandler = (options)=>{
|
|
|
4780
5205
|
const queryOptions = deserializeQueryOptions(request.options, schema, resolveSchema, // Applied to the outer collection AND to every collection a join reaches, so a
|
|
4781
5206
|
// join cannot be used to read around a scope
|
|
4782
5207
|
(target)=>scopeExpressionFor(target, context, "query"));
|
|
5208
|
+
const executedQueries = [];
|
|
4783
5209
|
return await new Promise((resolve)=>{
|
|
4784
5210
|
plugin.query({
|
|
4785
5211
|
// `false`: nothing here attaches to a change tracker — the tracker lives on
|
|
@@ -4788,7 +5214,9 @@ const createRequestHandler = (options)=>{
|
|
|
4788
5214
|
schemas: schemas,
|
|
4789
5215
|
id: (0,uuid/* .uuid */.u)(8),
|
|
4790
5216
|
source: "RequestHandler",
|
|
4791
|
-
action: "query"
|
|
5217
|
+
action: "query",
|
|
5218
|
+
explain: request.explain,
|
|
5219
|
+
executedQueries
|
|
4792
5220
|
}, (result)=>{
|
|
4793
5221
|
if (result.ok === Result/* .PluginEventResult.ERROR */.D.ERROR) {
|
|
4794
5222
|
resolve(failed(result.error));
|
|
@@ -4797,7 +5225,13 @@ const createRequestHandler = (options)=>{
|
|
|
4797
5225
|
resolve({
|
|
4798
5226
|
ok: true,
|
|
4799
5227
|
kind: "query",
|
|
4800
|
-
value: result.data.value
|
|
5228
|
+
value: result.data.value,
|
|
5229
|
+
// Only when asked, and only what the plugin reported. A plugin
|
|
5230
|
+
// that reported nothing sends nothing, and the caller marks the
|
|
5231
|
+
// remote step as not reported.
|
|
5232
|
+
...request.explain === true && executedQueries.length > 0 ? {
|
|
5233
|
+
executedQueries
|
|
5234
|
+
} : {}
|
|
4801
5235
|
});
|
|
4802
5236
|
});
|
|
4803
5237
|
});
|
|
@@ -5446,6 +5880,10 @@ class EphemeralDataPlugin {
|
|
|
5446
5880
|
}
|
|
5447
5881
|
innerRows.push(cloneRecord(record));
|
|
5448
5882
|
}
|
|
5883
|
+
const narrowing = outerKeys == null ? "full scan" : `narrowed by ${outerKeys.size} outer ${outerKeys.size === 1 ? "key" : "keys"}`;
|
|
5884
|
+
event.executedQueries.push({
|
|
5885
|
+
text: `${innerSchema.collectionName}: scanned ${innerRows.length} in-memory ${innerRows.length === 1 ? "record" : "records"} for join inner side (${narrowing})`
|
|
5886
|
+
});
|
|
5449
5887
|
done({
|
|
5450
5888
|
ok: "success",
|
|
5451
5889
|
innerSide: {
|
|
@@ -5555,7 +5993,13 @@ class EphemeralDataPlugin {
|
|
|
5555
5993
|
* collection to pair it with three rows.
|
|
5556
5994
|
*
|
|
5557
5995
|
* `cloned` is in storage shape, so the keys are read by resolved column name.
|
|
5558
|
-
*/
|
|
5996
|
+
*/ // No statement to quote — an ephemeral store walks its own records. Said
|
|
5997
|
+
// plainly so `.explain()` does not leave a reader wondering whether the
|
|
5998
|
+
// plugin simply failed to report. Before the inner side, to match execution order.
|
|
5999
|
+
event.executedQueries.push({
|
|
6000
|
+
text: `${operation.schema.collectionName}: scanned ${cloned.length} in-memory ${cloned.length === 1 ? "record" : "records"}`
|
|
6001
|
+
});
|
|
6002
|
+
const joinOption = operation.options.getLast("join");
|
|
5559
6003
|
const outerKeys = joinOption == null ? null : distinctJoinKeys(cloned, joinOption.value.outerKey, joinOption.value.semiJoinKeyThreshold, {
|
|
5560
6004
|
storageShape: true
|
|
5561
6005
|
});
|
|
@@ -5639,6 +6083,69 @@ class RetryDbPlugin {
|
|
|
5639
6083
|
}
|
|
5640
6084
|
}
|
|
5641
6085
|
|
|
6086
|
+
;// CONCATENATED MODULE: ./src/plugins/TelemetryDbPlugin.ts
|
|
6087
|
+
|
|
6088
|
+
/** Default sink: writes through the levelled logger, so ROUTIER_LOG_LEVEL governs it. */ const loggerSink = ()=>(e)=>{
|
|
6089
|
+
const line = `[routier] ${e.operation} ${e.schemas.join(",")} ${e.durationMs.toFixed(1)}ms`;
|
|
6090
|
+
if (e.ok === "error") {
|
|
6091
|
+
logger/* .logger.error */.vF.error(line, e.error);
|
|
6092
|
+
return;
|
|
6093
|
+
}
|
|
6094
|
+
logger/* .logger.info */.vF.info(line);
|
|
6095
|
+
};
|
|
6096
|
+
/** Pushes every event into `into`. For tests and custom buffering. */ const collectingSink = (into)=>(e)=>{
|
|
6097
|
+
into.push(e);
|
|
6098
|
+
};
|
|
6099
|
+
class TelemetryDbPlugin {
|
|
6100
|
+
plugin;
|
|
6101
|
+
onEvent;
|
|
6102
|
+
constructor(plugin, options = {}){
|
|
6103
|
+
this.plugin = plugin;
|
|
6104
|
+
this.onEvent = options.onEvent ?? loggerSink();
|
|
6105
|
+
}
|
|
6106
|
+
get databaseName() {
|
|
6107
|
+
return this.plugin.databaseName;
|
|
6108
|
+
}
|
|
6109
|
+
query(event, done) {
|
|
6110
|
+
const start = performance.now();
|
|
6111
|
+
this.plugin.query(event, (result)=>{
|
|
6112
|
+
this.emit("query", event, result, start);
|
|
6113
|
+
done(result);
|
|
6114
|
+
});
|
|
6115
|
+
}
|
|
6116
|
+
bulkPersist(event, done) {
|
|
6117
|
+
const start = performance.now();
|
|
6118
|
+
this.plugin.bulkPersist(event, (result)=>{
|
|
6119
|
+
this.emit("bulkPersist", event, result, start);
|
|
6120
|
+
done(result);
|
|
6121
|
+
});
|
|
6122
|
+
}
|
|
6123
|
+
destroy(event, done) {
|
|
6124
|
+
const start = performance.now();
|
|
6125
|
+
this.plugin.destroy(event, (result)=>{
|
|
6126
|
+
this.emit("destroy", event, result, start);
|
|
6127
|
+
done(result);
|
|
6128
|
+
});
|
|
6129
|
+
}
|
|
6130
|
+
emit(operation, event, result, start) {
|
|
6131
|
+
try {
|
|
6132
|
+
this.onEvent({
|
|
6133
|
+
operation,
|
|
6134
|
+
eventId: event.id,
|
|
6135
|
+
source: event.source,
|
|
6136
|
+
schemas: [
|
|
6137
|
+
...event.schemas.values()
|
|
6138
|
+
].map((s)=>s.collectionName),
|
|
6139
|
+
durationMs: performance.now() - start,
|
|
6140
|
+
ok: result.ok,
|
|
6141
|
+
error: result.ok === "success" ? undefined : result.error
|
|
6142
|
+
});
|
|
6143
|
+
} catch {
|
|
6144
|
+
// A broken sink must never fail the data operation.
|
|
6145
|
+
}
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
|
|
5642
6149
|
;// CONCATENATED MODULE: ./src/plugins/CacheDbPlugin.ts
|
|
5643
6150
|
|
|
5644
6151
|
/**
|
|
@@ -5648,7 +6155,7 @@ class RetryDbPlugin {
|
|
|
5648
6155
|
* `PropertyInfo` objects carrying functions and would not survive `JSON.stringify`. A filter is
|
|
5649
6156
|
* identified by its source text plus its params, which is what the expression is derived from
|
|
5650
6157
|
* anyway — two queries with the same source and params are the same query.
|
|
5651
|
-
*/ const
|
|
6158
|
+
*/ const CacheDbPlugin_describeOption = (option)=>{
|
|
5652
6159
|
const value = option.value;
|
|
5653
6160
|
switch(option.name){
|
|
5654
6161
|
case "filter":
|
|
@@ -5682,7 +6189,7 @@ class CacheDbPlugin {
|
|
|
5682
6189
|
/** `schemaId` first, so invalidating a schema is a prefix match. */ keyFor(event) {
|
|
5683
6190
|
const parts = [];
|
|
5684
6191
|
event.operation.options.forEach((option)=>{
|
|
5685
|
-
parts.push(`${option.name}:${
|
|
6192
|
+
parts.push(`${option.name}:${CacheDbPlugin_describeOption(option)}`);
|
|
5686
6193
|
});
|
|
5687
6194
|
return `${String(event.operation.schema.id)}\u0000${parts.join("\u0001")}`;
|
|
5688
6195
|
}
|
|
@@ -5712,6 +6219,12 @@ class CacheDbPlugin {
|
|
|
5712
6219
|
// Re-set to move it to the end of the insertion order: most recently used.
|
|
5713
6220
|
this.entries.delete(key);
|
|
5714
6221
|
this.entries.set(key, cached);
|
|
6222
|
+
// Said rather than left blank. A hit means no database was touched, which is a fact
|
|
6223
|
+
// worth reporting — and an empty report would otherwise read as a plugin that failed
|
|
6224
|
+
// to say what it ran.
|
|
6225
|
+
event.executedQueries.push({
|
|
6226
|
+
text: "cache hit — no query was executed"
|
|
6227
|
+
});
|
|
5715
6228
|
done(Result/* .PluginEventResult.success */.D.success(event.id, this.rebuild(cached)));
|
|
5716
6229
|
return;
|
|
5717
6230
|
}
|
|
@@ -6096,6 +6609,7 @@ const DEFAULT_MAX_BATCH_SIZE = 100;
|
|
|
6096
6609
|
|
|
6097
6610
|
|
|
6098
6611
|
|
|
6612
|
+
|
|
6099
6613
|
})();
|
|
6100
6614
|
|
|
6101
6615
|
module.exports = __webpack_exports__;
|