@routier/core 0.2.1 → 0.4.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/LICENSE +21 -0
- package/{readme.md → README.md} +3 -3
- package/dist/assertions/index.cjs +191 -0
- package/dist/assertions/index.cjs.map +1 -0
- package/dist/assertions/index.js +43 -63
- package/dist/assertions/index.js.map +1 -1
- package/dist/capabilities/index.cjs +820 -0
- package/dist/capabilities/index.cjs.map +1 -0
- package/dist/capabilities/index.js +584 -558
- package/dist/capabilities/index.js.map +1 -1
- package/dist/codegen/handlers/CloneHandlerBuilder.d.ts +10 -1
- package/dist/codegen/handlers/clone/CloneArrayHandler.d.ts +3 -0
- package/dist/codegen/handlers/clone/CloneDateHandler.d.ts +13 -0
- package/dist/codegen/handlers/clone/CloneValueHandler.d.ts +16 -0
- package/dist/codegen/handlers/compare/CompareComputedHandler.d.ts +10 -0
- package/dist/codegen/handlers/compare/CompareFunctionHandler.d.ts +10 -0
- package/dist/codegen/handlers/deserialize/DeserializeArrayHandler.d.ts +14 -0
- package/dist/codegen/handlers/enableChangeTracking/EnableChangeTrackingArrayHandler.d.ts +11 -0
- package/dist/codegen/handlers/enrichment/EnrichmentArrayHandler.d.ts +11 -0
- package/dist/codegen/handlers/freeze/FreezeArrayHandler.d.ts +6 -0
- package/dist/codegen/handlers/hash/HashArrayHandler.d.ts +10 -0
- package/dist/codegen/handlers/hash/HashFileHandler.d.ts +24 -0
- package/dist/codegen/handlers/hash/HashObjectHandler.d.ts +10 -0
- package/dist/codegen/handlers/hash/HashTransformHandler.d.ts +23 -0
- package/dist/codegen/handlers/merge/MergeArrayHandler.d.ts +20 -0
- package/dist/codegen/handlers/merge/MergeObjectHandler.d.ts +10 -0
- package/dist/codegen/handlers/serialize/SerializeArrayHandler.d.ts +19 -0
- package/dist/codegen/handlers/strip/StripComputedHandler.d.ts +10 -0
- package/dist/codegen/handlers/strip/StripFunctionHandler.d.ts +9 -0
- package/dist/codegen/handlers/types.d.ts +55 -1
- package/dist/codegen/index.cjs +618 -0
- package/dist/codegen/index.cjs.map +1 -0
- package/dist/codegen/index.js +99 -127
- package/dist/codegen/index.js.map +1 -1
- package/dist/collections/MemoryDataCollection.d.ts +15 -1
- package/dist/collections/TagCollection.d.ts +0 -1
- package/dist/collections/index.cjs +702 -0
- package/dist/collections/index.cjs.map +1 -0
- package/dist/collections/index.js +282 -276
- package/dist/collections/index.js.map +1 -1
- package/dist/errors/OptimisticConcurrencyError.d.ts +18 -0
- package/dist/errors/PluginDestroyedError.d.ts +10 -0
- package/dist/errors/index.cjs +132 -0
- package/dist/errors/index.cjs.map +1 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.js +61 -19
- package/dist/errors/index.js.map +1 -1
- package/dist/expressions/evaluate.d.ts +53 -0
- package/dist/expressions/index.cjs +2087 -0
- package/dist/expressions/index.cjs.map +1 -0
- package/dist/expressions/index.d.ts +1 -1
- package/dist/expressions/index.js +1727 -1082
- package/dist/expressions/index.js.map +1 -1
- package/dist/expressions/parser.d.ts +19 -0
- package/dist/expressions/types.d.ts +87 -2
- package/dist/index.cjs +14377 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +12374 -7931
- package/dist/index.js.map +1 -1
- package/dist/performance/index.cjs +217 -0
- package/dist/performance/index.cjs.map +1 -0
- package/dist/performance/index.js +120 -55
- package/dist/performance/index.js.map +1 -1
- package/dist/pipeline/index.cjs +576 -0
- package/dist/pipeline/index.cjs.map +1 -0
- package/dist/pipeline/index.js +223 -161
- package/dist/pipeline/index.js.map +1 -1
- package/dist/plugins/BatchingDbPlugin.d.ts +139 -0
- package/dist/plugins/CacheDbPlugin.d.ts +70 -0
- package/dist/plugins/ConcurrencyDbPlugin.d.ts +78 -0
- package/dist/plugins/EphemeralDataPlugin.d.ts +57 -1
- package/dist/plugins/RetryDbPlugin.d.ts +77 -0
- package/dist/plugins/index.cjs +6551 -0
- package/dist/plugins/index.cjs.map +1 -0
- package/dist/plugins/index.d.ts +5 -0
- package/dist/plugins/index.js +6271 -1170
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/query/QueryOptionsCollection.d.ts +34 -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 +4 -0
- package/dist/plugins/query/join.d.ts +210 -0
- package/dist/plugins/query/similarity.d.ts +38 -0
- package/dist/plugins/query/types.d.ts +75 -1
- package/dist/plugins/translators/DataTranslator.d.ts +28 -0
- package/dist/plugins/translators/JsonTranslator.d.ts +27 -0
- package/dist/plugins/translators/SqlTranslator.d.ts +45 -0
- package/dist/plugins/translators/TupleTranslator.d.ts +45 -0
- package/dist/plugins/translators/index.d.ts +1 -0
- package/dist/plugins/types.d.ts +125 -3
- package/dist/plugins/wire/handler.d.ts +122 -0
- package/dist/plugins/wire/index.d.ts +4 -0
- package/dist/plugins/wire/persist.d.ts +43 -0
- package/dist/plugins/wire/query.d.ts +49 -0
- package/dist/plugins/wire/types.d.ts +139 -0
- package/dist/results/index.cjs +174 -0
- package/dist/results/index.cjs.map +1 -0
- package/dist/results/index.js +29 -44
- package/dist/results/index.js.map +1 -1
- package/dist/schema/PropertyInfo.d.ts +31 -2
- package/dist/schema/SchemaDefinition.d.ts +23 -1
- package/dist/schema/builder.d.ts +35 -1
- package/dist/schema/communication/broadcast.d.ts +17 -1
- package/dist/schema/index.cjs +6911 -0
- package/dist/schema/index.cjs.map +1 -0
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.js +5330 -4541
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/property/base/SchemaBase.d.ts +48 -2
- package/dist/schema/property/modifiers/SchemaNullable.d.ts +10 -0
- package/dist/schema/property/modifiers/SchemaOptional.d.ts +19 -0
- package/dist/schema/property/modifiers/SchemaSearchable.d.ts +34 -0
- package/dist/schema/property/modifiers/index.d.ts +1 -0
- package/dist/schema/property/types/SchemaFile.d.ts +60 -0
- package/dist/schema/property/types/SchemaString.d.ts +21 -1
- package/dist/schema/property/types/SchemaVector.d.ts +61 -0
- package/dist/schema/property/types/index.d.ts +2 -0
- package/dist/schema/table/SchemaTransform.d.ts +40 -0
- package/dist/schema/table/index.d.ts +1 -0
- package/dist/schema/types.d.ts +166 -8
- package/dist/schema/utils/propertyKind.d.ts +4 -0
- package/dist/types/index.cjs +23 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.js +0 -14
- package/dist/types/index.js.map +1 -1
- package/dist/utilities/index.cjs +1013 -0
- package/dist/utilities/index.cjs.map +1 -0
- package/dist/utilities/index.js +541 -379
- package/dist/utilities/index.js.map +1 -1
- package/dist/utilities/logger.d.ts +48 -0
- package/package.json +156 -98
- package/dist/codegen/SlotPath.test.d.ts +0 -1
- package/dist/codegen/blocks.test.d.ts +0 -1
- package/dist/codegen/utils.test.d.ts +0 -1
- package/dist/collections/Changes.test.d.ts +0 -1
- package/dist/collections/IdSet.test.d.ts +0 -1
- package/dist/collections/MemoryDataCollection.test.d.ts +0 -1
- package/dist/collections/TagCollection.contract.test.d.ts +0 -1
- package/dist/collections/TagCollection.test.d.ts +0 -1
- package/dist/expressions/parser.test.d.ts +0 -1
- package/dist/expressions/sql.d.ts +0 -28
- package/dist/expressions/sql.test.d.ts +0 -1
- package/dist/expressions/utils.test.d.ts +0 -1
- package/dist/pipeline/SyncronousQueue.test.d.ts +0 -1
- package/dist/plugins/EphemeralDataPlugin.test.d.ts +0 -1
- package/dist/plugins/query/Query.test.d.ts +0 -1
- package/dist/plugins/query/QueryOptionsCollection.test.d.ts +0 -1
- package/dist/plugins/translators/JsonTranslator.test.d.ts +0 -1
- package/dist/plugins/translators/SqlTranslator.test.d.ts +0 -1
- package/dist/plugins/translators/TranslatedValues.test.d.ts +0 -1
- package/dist/results/Result.test.d.ts +0 -1
- package/dist/results/utils.test.d.ts +0 -1
- package/dist/schema/PropertyInfo.test.d.ts +0 -1
- package/dist/schema/communication/broadcast.test.d.ts +0 -1
- package/dist/schema/schemaGeneration.test.d.ts +0 -1
- package/dist/schema/utils/standardJsonSchema.test.d.ts +0 -1
- package/dist/utilities/arrays.test.d.ts +0 -1
- package/dist/utilities/dates.test.d.ts +0 -1
- package/dist/utilities/runtime.test.d.ts +0 -1
- package/dist/utilities/strings.test.d.ts +0 -1
- package/dist/utilities/uuid.test.d.ts +0 -1
|
@@ -6,13 +6,47 @@ 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>;
|
|
14
27
|
static isEmpty<T>(options: QueryOptionsCollection<T>): boolean;
|
|
15
28
|
add<K extends QueryOptionName>(name: K, value: QueryOption<T, K>["value"]): void;
|
|
29
|
+
/**
|
|
30
|
+
* Splits the collection around the FIRST occurrence of `name`, preserving order.
|
|
31
|
+
*
|
|
32
|
+
* For a join: the options recorded before it operate on entity rows, the option itself
|
|
33
|
+
* produces tuples, and the ones after it operate on tuples. Three different shapes, so the
|
|
34
|
+
* caller has to run them in three steps rather than one pass.
|
|
35
|
+
*/
|
|
36
|
+
splitAt<K extends QueryOptionName>(name: K): {
|
|
37
|
+
before: QueryOptionsCollection<T>;
|
|
38
|
+
at: QueryOption<T, K> | null;
|
|
39
|
+
after: QueryOptionsCollection<T>;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Captures the collection's current state and returns a function that restores it.
|
|
43
|
+
*
|
|
44
|
+
* Terminal queryable operations (count, first, aggregates, …) record their option on
|
|
45
|
+
* the shared collection before executing. Without restoring, a re-executed terminal —
|
|
46
|
+
* the whole point of a subscribed queryable — stacks its option a second time and
|
|
47
|
+
* runs it over the first execution's scalar result.
|
|
48
|
+
*/
|
|
49
|
+
snapshot(): () => void;
|
|
16
50
|
split(): {
|
|
17
51
|
memory: QueryOptionsCollection<T>;
|
|
18
52
|
database: QueryOptionsCollection<T>;
|
|
@@ -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;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { CompiledSchemaCore, PropertyInfo } from "../../schema";
|
|
2
|
+
import { UnknownRecord } from "../../utilities";
|
|
3
|
+
import { PluginEventCallbackResult } from "../../results";
|
|
4
|
+
import type { DbPluginQueryEvent } from "../types";
|
|
5
|
+
import type { ITranslatedValue } from "../translators/types";
|
|
6
|
+
import { QueryOptionsCollection } from "./QueryOptionsCollection";
|
|
7
|
+
import { QueryOptionValueMap } from "./types";
|
|
8
|
+
export type JoinKind = "inner" | "left";
|
|
9
|
+
/**
|
|
10
|
+
* One side's join key, as a property path plus the resolved property.
|
|
11
|
+
*
|
|
12
|
+
* The path is what survives serialization; the `PropertyInfo` is the live handle that reads
|
|
13
|
+
* the value and resolves a `from`-renamed storage name. Both, because a key read has to work
|
|
14
|
+
* on either side of the wire.
|
|
15
|
+
*/
|
|
16
|
+
export type JoinKeyReference = {
|
|
17
|
+
propertyName: string;
|
|
18
|
+
property: PropertyInfo<any> | null;
|
|
19
|
+
};
|
|
20
|
+
export type JoinQueryOptionValue = QueryOptionValueMap<any>["join"];
|
|
21
|
+
/**
|
|
22
|
+
* The inner side of a join, as the PLUGIN sees it.
|
|
23
|
+
*
|
|
24
|
+
* Supplied by the plugin rather than fetched by the translator, because loading rows is the one
|
|
25
|
+
* part of a join that is backend-specific: a memory plugin resolves a collection, Dexie opens a
|
|
26
|
+
* store, Mongo reads a collection. Everything after that — deserializing, applying the inner
|
|
27
|
+
* scopes, pairing — is identical, so it lives here and runs once.
|
|
28
|
+
*
|
|
29
|
+
* Rows arrive in STORAGE shape, exactly as the plugin holds them; the translator deserializes
|
|
30
|
+
* them with `innerSchema`, which the outer query's own deserialization would never do.
|
|
31
|
+
*/
|
|
32
|
+
export type JoinInnerSide = {
|
|
33
|
+
innerSchema: CompiledSchemaCore<any>;
|
|
34
|
+
innerRows: readonly unknown[];
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* `undefined` when the query has no join at all, which is the common case and not an error.
|
|
38
|
+
*/
|
|
39
|
+
export type JoinInnerSideResult = {
|
|
40
|
+
ok: "success";
|
|
41
|
+
innerSide?: JoinInnerSide;
|
|
42
|
+
} | {
|
|
43
|
+
ok: "error";
|
|
44
|
+
error: unknown;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* A joined pair, each half fully deserialized into its own schema's ENTITY shape.
|
|
48
|
+
*
|
|
49
|
+
* The wire contract for every interpretation of a join: a native SQL join, an in-plugin hash
|
|
50
|
+
* join, and the datastore's cross-plugin join all produce exactly this. Flat combined rows
|
|
51
|
+
* never leave a translator — see `specs/joins.md`.
|
|
52
|
+
*/
|
|
53
|
+
export type JoinTuple = [UnknownRecord, UnknownRecord | undefined];
|
|
54
|
+
/**
|
|
55
|
+
* Turns storage-shape records into entity-shape values, one side of a join at a time.
|
|
56
|
+
*
|
|
57
|
+
* The inner side does NOT pass through the outer query's normal deserialization —
|
|
58
|
+
* `DatabaseDataAccessStrategy.query` transforms against the outer schema only — so each side
|
|
59
|
+
* is deserialized with its own schema here, which is what makes the two halves of a tuple
|
|
60
|
+
* readable by the same property names the caller wrote in the selectors.
|
|
61
|
+
*
|
|
62
|
+
* `"diff"` rather than `"proxy"`: join results are read-only projections and never attach to
|
|
63
|
+
* the change tracker, so there is nothing for a tracking proxy to record.
|
|
64
|
+
*/
|
|
65
|
+
export declare const toEntityShape: (schema: CompiledSchemaCore<any>, rows: readonly unknown[]) => UnknownRecord[];
|
|
66
|
+
/**
|
|
67
|
+
* Reads a join key off an entity-shape row.
|
|
68
|
+
*
|
|
69
|
+
* Through the `PropertyInfo` when there is one, so a nested path (`a.b.id`) resolves the same
|
|
70
|
+
* way every other option resolves it. The string fallback exists for an option that crossed a
|
|
71
|
+
* wire without its schema; it walks the same path by name.
|
|
72
|
+
*/
|
|
73
|
+
export declare const readJoinKey: (row: UnknownRecord | undefined, reference: JoinKeyReference) => unknown;
|
|
74
|
+
/**
|
|
75
|
+
* Applies an inner side's own filters to its rows.
|
|
76
|
+
*
|
|
77
|
+
* **This is the correctness trap of the whole feature.** Every interpretation of a join
|
|
78
|
+
* bypasses the inner collection's normal datastore read path, so the inner side's soft-delete
|
|
79
|
+
* scope and `.scope()` filters exist ONLY because `innerOptions` carries them. An interpreter
|
|
80
|
+
* that skips this returns soft-deleted rows.
|
|
81
|
+
*
|
|
82
|
+
* Filters only. Nothing else reaches `innerOptions` today — scopes are filters — and applying
|
|
83
|
+
* a `skip`/`take` recorded against the inner collection to the rows feeding a join would
|
|
84
|
+
* change which pairs exist rather than which rows are visible.
|
|
85
|
+
*/
|
|
86
|
+
export declare const applyInnerOptions: (rows: UnknownRecord[], innerOptions: QueryOptionsCollection<any>) => UnknownRecord[];
|
|
87
|
+
/**
|
|
88
|
+
* The join itself: one hash join, written once, called from every interpreter.
|
|
89
|
+
*
|
|
90
|
+
* O(n + m) rather than a nested loop, which is the whole reason the API takes explicit key
|
|
91
|
+
* selectors instead of a free-form predicate. Both key properties are `string` or `number` by
|
|
92
|
+
* a build-time rule, so the keys are hashable and compare the same way in JS and in SQL.
|
|
93
|
+
*
|
|
94
|
+
* Semantics, exactly as `specs/joins.md` states them:
|
|
95
|
+
*
|
|
96
|
+
* - **Null keys** never match. Under `left` the outer row still appears, paired with
|
|
97
|
+
* `undefined`.
|
|
98
|
+
* - **Duplicates** produce every pair: the full cross product per key group.
|
|
99
|
+
* - **Ordering** is outer order, then inner order within a key group. Undefined by contract —
|
|
100
|
+
* a caller who cares sorts.
|
|
101
|
+
*/
|
|
102
|
+
export declare const hashJoin: (options: {
|
|
103
|
+
kind: JoinKind;
|
|
104
|
+
outerRows: UnknownRecord[];
|
|
105
|
+
innerRows: UnknownRecord[];
|
|
106
|
+
outerKey: JoinKeyReference;
|
|
107
|
+
innerKey: JoinKeyReference;
|
|
108
|
+
}) => JoinTuple[];
|
|
109
|
+
/**
|
|
110
|
+
* How many distinct outer keys are still worth turning into an `IN (...)` prefilter.
|
|
111
|
+
*
|
|
112
|
+
* A cost decision, never a correctness one: above the threshold the inner side is read under its
|
|
113
|
+
* own scopes and the hash join discards the surplus, which is the same answer by a slower route.
|
|
114
|
+
* 500 because a bound-parameter list is cheap in the hundreds and starts costing more than the
|
|
115
|
+
* scan it saves in the thousands — and some engines refuse a list that long outright.
|
|
116
|
+
*/
|
|
117
|
+
export declare const DEFAULT_SEMI_JOIN_KEY_THRESHOLD = 500;
|
|
118
|
+
/**
|
|
119
|
+
* The distinct join keys of the outer rows, or `null` when there are too many to be worth sending.
|
|
120
|
+
*
|
|
121
|
+
* `null` means "do not prefilter", not "no keys" — an empty SET is a real answer meaning the inner
|
|
122
|
+
* side cannot match anything.
|
|
123
|
+
*
|
|
124
|
+
* @param column The key's STORAGE column name when `rows` are storage-shaped, or its property name
|
|
125
|
+
* when they are entity-shaped. The caller knows which it holds.
|
|
126
|
+
*/
|
|
127
|
+
export declare const distinctJoinKeys: (rows: readonly UnknownRecord[], reference: JoinKeyReference, threshold?: number, options?: {
|
|
128
|
+
storageShape?: boolean;
|
|
129
|
+
}) => Set<unknown> | null;
|
|
130
|
+
/**
|
|
131
|
+
* A filter restricting the inner side to rows whose key is one the outer side actually has.
|
|
132
|
+
*
|
|
133
|
+
* Built as an expression tree by hand rather than parsed from generated source, for the reason
|
|
134
|
+
* `softDeleteScope` gives: generating source needs `new Function`, which a Content-Security-Policy
|
|
135
|
+
* blocks, and the shape is known here so there is nothing to parse. An `includes` comparator over
|
|
136
|
+
* an array value is what every translator already turns into `IN (...)` or `$in`, so this pushes
|
|
137
|
+
* down on the backends that can take it and runs as the closure on the ones that cannot.
|
|
138
|
+
*
|
|
139
|
+
* Cost only. Every pair it removes from the inner read is one the hash join would have discarded.
|
|
140
|
+
*/
|
|
141
|
+
export declare const semiJoinFilter: (reference: JoinKeyReference, keys: ReadonlySet<unknown>) => QueryOptionValueMap<any>["filter"];
|
|
142
|
+
/**
|
|
143
|
+
* Loads a join's inner side by asking the plugin to run an ORDINARY query for it.
|
|
144
|
+
*
|
|
145
|
+
* The generic way for a plugin to interpret a join — one call, no join-specific reading code.
|
|
146
|
+
* The inner side is just "this collection, under these filters", which is a query every plugin
|
|
147
|
+
* already knows how to answer, through whatever indexes and scoping it normally applies.
|
|
148
|
+
*
|
|
149
|
+
* Only the DATABASE half of `innerOptions` is sent. The memory half would mean nothing to the
|
|
150
|
+
* plugin, and `executeJoin` re-applies every filter regardless — filters are pure, so the second
|
|
151
|
+
* pass costs a walk over the survivors and guarantees the inner scopes are honoured even if the
|
|
152
|
+
* plugin silently ignored them.
|
|
153
|
+
*
|
|
154
|
+
* The inner query carries no `join` option of its own, so this cannot recurse.
|
|
155
|
+
*
|
|
156
|
+
* @param query How this plugin runs a query. **Not necessarily `plugin.query`**: a plugin that
|
|
157
|
+
* serializes queries through a work queue must pass its UN-QUEUED path, or this call waits behind
|
|
158
|
+
* the outer query that is still holding the queue and the plugin deadlocks.
|
|
159
|
+
*/
|
|
160
|
+
export declare const loadJoinInnerSide: <TRoot extends {}, TShape>(event: DbPluginQueryEvent<TRoot, TShape>, query: (innerEvent: DbPluginQueryEvent<UnknownRecord, UnknownRecord>, done: PluginEventCallbackResult<ITranslatedValue<UnknownRecord>>) => void, done: (result: JoinInnerSideResult) => void,
|
|
161
|
+
/**
|
|
162
|
+
* The outer side's distinct keys, when the caller already has them.
|
|
163
|
+
*
|
|
164
|
+
* Only a plugin that runs its outer query FIRST can supply these, and most run this loader
|
|
165
|
+
* before anything else — so it is optional, and its absence costs a wider inner read rather
|
|
166
|
+
* than a wrong one.
|
|
167
|
+
*/
|
|
168
|
+
outerKeys?: ReadonlySet<unknown> | null) => void;
|
|
169
|
+
/**
|
|
170
|
+
* Interprets a join by running TWO ordinary queries through the plugin's own read path.
|
|
171
|
+
*
|
|
172
|
+
* The whole of interpretation 2 for a plugin that has no reason to do anything cleverer, and the
|
|
173
|
+
* shape every non-SQL backend should prefer:
|
|
174
|
+
*
|
|
175
|
+
* ```ts
|
|
176
|
+
* query(event, done) {
|
|
177
|
+
* if (event.operation.options.has("join")) {
|
|
178
|
+
* joinInPlugin(event, (e, d) => this.query(e, d), done);
|
|
179
|
+
* return;
|
|
180
|
+
* }
|
|
181
|
+
* // ...the ordinary single-collection path
|
|
182
|
+
* }
|
|
183
|
+
* ```
|
|
184
|
+
*
|
|
185
|
+
* **The outer side runs FIRST, and that ordering is the optimization.** Its keys are what narrow
|
|
186
|
+
* the inner read to rows that can actually pair — and they do not exist until the outer filters
|
|
187
|
+
* have run. Loading the inner side first, which is what a naive implementation does, means reading
|
|
188
|
+
* and materializing a whole collection to pair it with three rows.
|
|
189
|
+
*
|
|
190
|
+
* Neither query carries the join option, so both take the plugin's normal path: its indexes, its
|
|
191
|
+
* pushdown decisions, its retries. Nothing recurses, because the option is stripped before either
|
|
192
|
+
* goes out.
|
|
193
|
+
*
|
|
194
|
+
* @param query How this plugin runs a query. NOT necessarily `plugin.query` — a plugin that
|
|
195
|
+
* serializes queries through a work queue must pass its UN-QUEUED path, or the two reads below
|
|
196
|
+
* wait on the slot this one is holding.
|
|
197
|
+
*/
|
|
198
|
+
export declare const joinInPlugin: <TRoot extends {}, TShape>(event: DbPluginQueryEvent<TRoot, TShape>, query: (innerEvent: DbPluginQueryEvent<UnknownRecord, UnknownRecord>, done: PluginEventCallbackResult<ITranslatedValue<UnknownRecord>>) => void, done: PluginEventCallbackResult<ITranslatedValue<TShape>>) => void;
|
|
199
|
+
/**
|
|
200
|
+
* A join over rows both sides have already deserialized, with the inner side's scopes applied.
|
|
201
|
+
*
|
|
202
|
+
* The one entry point every interpreter uses: `JsonTranslator.join` inside a plugin, and the
|
|
203
|
+
* datastore's own memory half for a cross-plugin join. Callers hand over ENTITY-shape rows —
|
|
204
|
+
* `toEntityShape` is separate because a caller may already have paid for it.
|
|
205
|
+
*/
|
|
206
|
+
export declare const executeJoin: (options: {
|
|
207
|
+
option: JoinQueryOptionValue;
|
|
208
|
+
outerRows: UnknownRecord[];
|
|
209
|
+
innerRows: UnknownRecord[];
|
|
210
|
+
}) => JoinTuple[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cosine distance, and the ordering rules that go with it.
|
|
3
|
+
*
|
|
4
|
+
* One implementation, shared by every in-memory path, because the promise the feature makes
|
|
5
|
+
* is that a backend with no vector support returns the SAME ROWS IN THE SAME ORDER as one
|
|
6
|
+
* that pushes the search down. Two copies of this arithmetic would drift, and the drift would
|
|
7
|
+
* only ever show up as a conformance failure nobody could localise.
|
|
8
|
+
*
|
|
9
|
+
* The tie-breaking and degenerate cases below are chosen to match pgvector rather than to be
|
|
10
|
+
* independently reasonable — parity is the point.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Distance in `[0, 2]`, or `Infinity` for a value that cannot be compared.
|
|
14
|
+
*
|
|
15
|
+
* `Infinity` covers three cases, and they all sort LAST, which is what PostgreSQL does too:
|
|
16
|
+
* a missing value (`NULL` sorts last under `ASC` by default), a zero-magnitude vector
|
|
17
|
+
* (pgvector's `<=>` yields `NaN`, which PostgreSQL orders after every real number), and a
|
|
18
|
+
* stored vector of the wrong width.
|
|
19
|
+
*
|
|
20
|
+
* The width case cannot happen on a native `vector(n)` column — the engine rejects the write —
|
|
21
|
+
* so it only arises on a backend storing JSON, where the data was written by something other
|
|
22
|
+
* than this schema. Sorting it last rather than throwing keeps one corrupt row from failing a
|
|
23
|
+
* query that is otherwise answerable.
|
|
24
|
+
*/
|
|
25
|
+
export declare const cosineDistance: (left: readonly number[] | null | undefined, right: readonly number[]) => number;
|
|
26
|
+
/**
|
|
27
|
+
* The `count` rows closest to `vector`, nearest first.
|
|
28
|
+
*
|
|
29
|
+
* Distances are computed once per row rather than inside the comparator: a comparison-time
|
|
30
|
+
* computation runs O(n log n) times over vectors that are commonly 1536 wide, which turns an
|
|
31
|
+
* ordering into the dominant cost of the query.
|
|
32
|
+
*
|
|
33
|
+
* The sort is stable, so rows at equal distance keep the order the backend returned them in.
|
|
34
|
+
* That is not a guarantee worth relying on across backends — two engines can hand back the
|
|
35
|
+
* same rows in different orders — but it does mean this function never introduces a
|
|
36
|
+
* difference of its own.
|
|
37
|
+
*/
|
|
38
|
+
export declare const nearestBy: <T>(rows: readonly T[], vector: number[], count: number, select: (row: T) => readonly number[] | null | undefined) => T[];
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Expression, Filter, ParamsFilter } from "../../expressions";
|
|
2
|
-
import { PropertyInfo } from "../../schema";
|
|
2
|
+
import { PropertyInfo, SchemaId } from "../../schema";
|
|
3
3
|
import { GenericFunction } from "../../types";
|
|
4
|
+
import type { JoinKeyReference, JoinKind } from "./join";
|
|
5
|
+
import type { QueryOptionsCollection } from "./QueryOptionsCollection";
|
|
4
6
|
export declare enum QueryOrdering {
|
|
5
7
|
Descending = "desc",
|
|
6
8
|
Ascending = "asc"
|
|
@@ -17,10 +19,21 @@ export type QueryField = {
|
|
|
17
19
|
};
|
|
18
20
|
export type QueryOptionExecutionTarget = "database" | "memory";
|
|
19
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";
|
|
20
31
|
export type QueryOption<T, K extends QueryOptionName> = {
|
|
21
32
|
name: QueryOptionName;
|
|
22
33
|
value: QueryOptionValueMap<T>[K];
|
|
23
34
|
target: QueryOptionExecutionTarget;
|
|
35
|
+
/** Set only when `target` is `"memory"`. */
|
|
36
|
+
reason?: MemoryExecutionReason;
|
|
24
37
|
};
|
|
25
38
|
export type QueryOptionValueMap<T extends {}> = {
|
|
26
39
|
skip: number;
|
|
@@ -29,6 +42,7 @@ export type QueryOptionValueMap<T extends {}> = {
|
|
|
29
42
|
selector: GenericFunction<T, T[keyof T]>;
|
|
30
43
|
direction: QueryOrdering;
|
|
31
44
|
propertyName: string;
|
|
45
|
+
property?: PropertyInfo<T> | null;
|
|
32
46
|
};
|
|
33
47
|
map: {
|
|
34
48
|
selector: GenericFunction<T, any>;
|
|
@@ -44,6 +58,66 @@ export type QueryOptionValueMap<T extends {}> = {
|
|
|
44
58
|
filter: ParamsFilter<T, {}> | Filter<T>;
|
|
45
59
|
expression: Expression;
|
|
46
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* Similarity search: an ordering plus a limit, never a filter.
|
|
63
|
+
*
|
|
64
|
+
* `count` is part of the option rather than a separate `take` because the two are one
|
|
65
|
+
* operation to a backend that can push this down — `ORDER BY ... LIMIT n` is what makes an
|
|
66
|
+
* approximate index usable, and splitting them would order every row before limiting.
|
|
67
|
+
*/
|
|
68
|
+
nearest: {
|
|
69
|
+
selector: GenericFunction<T, T[keyof T]>;
|
|
70
|
+
propertyName: string;
|
|
71
|
+
property?: PropertyInfo<T> | null;
|
|
72
|
+
vector: number[];
|
|
73
|
+
count: number;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* An equi-join against a second collection, interpreted by whoever receives it.
|
|
77
|
+
*
|
|
78
|
+
* A first-class query option rather than a datastore side-path: a SQL backend emits a real
|
|
79
|
+
* `INNER JOIN`/`LEFT JOIN`, every other backend loads the rows it needs and the shared hash
|
|
80
|
+
* join runs inside the plugin, and a cross-plugin join runs in the datastore's memory half.
|
|
81
|
+
* All three produce the same pairs — see `specs/joins.md`.
|
|
82
|
+
*
|
|
83
|
+
* Serializable by construction: property paths and a schema id, never live rows, with any
|
|
84
|
+
* filter's values travelling in its params object. That is what lets the whole option be
|
|
85
|
+
* forwarded to a server once expression-tree serialization lands.
|
|
86
|
+
*/
|
|
87
|
+
join: {
|
|
88
|
+
kind: JoinKind;
|
|
89
|
+
/** Resolved through `event.schemas`, which already carries every schema in the store. */
|
|
90
|
+
innerSchemaId: SchemaId;
|
|
91
|
+
outerKey: JoinKeyReference;
|
|
92
|
+
innerKey: JoinKeyReference;
|
|
93
|
+
/**
|
|
94
|
+
* The inner side's own filters — INCLUDING its soft-delete scope and `.scope()`
|
|
95
|
+
* filters. Every interpreter must apply these: it is the only place they exist, because
|
|
96
|
+
* a join bypasses the inner collection's normal read path.
|
|
97
|
+
*/
|
|
98
|
+
innerOptions: QueryOptionsCollection<any>;
|
|
99
|
+
/**
|
|
100
|
+
* Whether the two sides live on DIFFERENT plugin instances, in which case no plugin can
|
|
101
|
+
* receive the option and the datastore is the interpreter.
|
|
102
|
+
*
|
|
103
|
+
* Decided by plugin instance identity at build time, never by comparing database names —
|
|
104
|
+
* two plugins over one database are still two interpreters, and one name can front two
|
|
105
|
+
* databases.
|
|
106
|
+
*/
|
|
107
|
+
crossPlugin: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* How many distinct outer keys are still worth turning into an `IN (...)` prefilter on the
|
|
110
|
+
* inner read — the datastore's `semiJoinKeyThreshold`, default 500.
|
|
111
|
+
*
|
|
112
|
+
* Carried in the option because the decision is made where the join executes, which is
|
|
113
|
+
* usually inside a plugin, and a plugin cannot see a datastore's configuration. A number
|
|
114
|
+
* serializes; a reference to the store would not.
|
|
115
|
+
*
|
|
116
|
+
* Cost only. Above the threshold the inner side is read under its own scopes and the hash
|
|
117
|
+
* join discards the surplus — the same answer by a slower route.
|
|
118
|
+
*/
|
|
119
|
+
semiJoinKeyThreshold: number;
|
|
120
|
+
};
|
|
47
121
|
min: true;
|
|
48
122
|
max: true;
|
|
49
123
|
count: true;
|
|
@@ -16,5 +16,33 @@ export declare abstract class DataTranslator<TRoot extends {}, TShape> {
|
|
|
16
16
|
abstract sort(data: unknown, option: QueryOption<TShape, "sort">): TShape;
|
|
17
17
|
abstract map(data: unknown, option: QueryOption<TShape, "map">): TShape;
|
|
18
18
|
abstract group(data: unknown, option: QueryOption<TShape, "group">): TShape;
|
|
19
|
+
/**
|
|
20
|
+
* Abstract on purpose, unlike the pass-throughs a storage translator can usually inherit.
|
|
21
|
+
*
|
|
22
|
+
* Every other shaper degrades safely when a backend ignores it — an unsorted result is
|
|
23
|
+
* still the right rows. A similarity search is not: it is the only option whose value is
|
|
24
|
+
* ENTIRELY in the ordering and the limit, so a translator that quietly passes the data
|
|
25
|
+
* through returns every row in insertion order and calls it the ten nearest.
|
|
26
|
+
*
|
|
27
|
+
* Requiring an answer here means a new translator cannot be written without deciding
|
|
28
|
+
* whether its backend performed the search, and the compiler asks the question.
|
|
29
|
+
*/
|
|
30
|
+
abstract nearest(data: unknown, option: QueryOption<TShape, "nearest">): TShape;
|
|
31
|
+
/**
|
|
32
|
+
* Abstract for the same reason as `nearest`, one step further.
|
|
33
|
+
*
|
|
34
|
+
* A translator that quietly passed a join through would not return an unsorted or unlimited
|
|
35
|
+
* result — it would return the OUTER rows, one object each where the contract says tuples,
|
|
36
|
+
* and every `([outer, inner]) => ...` lambda downstream would destructure the outer entity
|
|
37
|
+
* instead. Nothing errors; the answer is simply a different query's answer.
|
|
38
|
+
*
|
|
39
|
+
* So the compiler asks the question. Every translator must state how its backend joins:
|
|
40
|
+
* natively (pass through the rows the SQL already paired), in memory (the shared hash join
|
|
41
|
+
* over rows the plugin supplies), or not at all (throw, loudly, naming the backend).
|
|
42
|
+
*
|
|
43
|
+
* The output contract, whichever answer: an array of `[outer, inner]` tuples, each half
|
|
44
|
+
* fully deserialized into its OWN schema's entity shape.
|
|
45
|
+
*/
|
|
46
|
+
abstract join(data: unknown, option: QueryOption<TShape, "join">): TShape;
|
|
19
47
|
translate(data: unknown): ITranslatedValue<TShape>;
|
|
20
48
|
}
|
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import { DataTranslator } from "./DataTranslator";
|
|
2
2
|
import { QueryOption } from "../query/types";
|
|
3
|
+
import { JoinInnerSide } from "../query/join";
|
|
4
|
+
import { IQuery } from "../types";
|
|
3
5
|
export declare class JsonTranslator<TRoot extends {}, TShape> extends DataTranslator<TRoot, TShape> {
|
|
6
|
+
private readonly innerSide?;
|
|
7
|
+
/**
|
|
8
|
+
* @param innerSide The inner collection's rows, when this query carries a `join` option.
|
|
9
|
+
* A plugin that omits it for a query that HAS a join gets a throw from `join()` rather than
|
|
10
|
+
* a silently un-joined result.
|
|
11
|
+
*/
|
|
12
|
+
constructor(query: IQuery<TRoot, TShape>, innerSide?: JoinInnerSide);
|
|
13
|
+
/**
|
|
14
|
+
* The hash join itself, over rows already in memory — the floor every non-SQL backend
|
|
15
|
+
* stands on.
|
|
16
|
+
*
|
|
17
|
+
* Both halves are deserialized here, each with its own schema, because that is where the
|
|
18
|
+
* `===` on key values is specified to happen: in entity shape, by the property names the
|
|
19
|
+
* caller wrote in the key selectors. A `from`-renamed column reads correctly for free.
|
|
20
|
+
*/
|
|
21
|
+
join<TResult>(data: unknown, option: QueryOption<TShape, "join">): TResult;
|
|
4
22
|
filter<TResult>(data: unknown, option: QueryOption<TShape, "filter">): TResult;
|
|
5
23
|
map<T>(data: unknown, option: QueryOption<T, "map">): T;
|
|
6
24
|
group<T>(data: unknown, option: QueryOption<T, "group">): T;
|
|
@@ -8,6 +26,15 @@ export declare class JsonTranslator<TRoot extends {}, TShape> extends DataTransl
|
|
|
8
26
|
min<TResult extends string | number | Date>(data: unknown, _: QueryOption<TShape, "min">): TResult;
|
|
9
27
|
max<TResult extends string | number | Date>(data: unknown, _: QueryOption<TShape, "max">): TResult;
|
|
10
28
|
sort<TResult>(data: unknown, option: QueryOption<TShape, "sort">): TResult;
|
|
29
|
+
/**
|
|
30
|
+
* The similarity search itself, over values already in memory.
|
|
31
|
+
*
|
|
32
|
+
* This is the floor the whole feature stands on: it is reached whenever the backend did
|
|
33
|
+
* not do the search, which is every backend except the ones with a native vector index.
|
|
34
|
+
* It reads the property through the option's selector, so it works on any shape the rows
|
|
35
|
+
* arrive in.
|
|
36
|
+
*/
|
|
37
|
+
nearest<TResult>(data: unknown, option: QueryOption<TShape, "nearest">): TResult;
|
|
11
38
|
sum<TResult extends number>(data: unknown, _: QueryOption<TShape, "sum">): TResult;
|
|
12
39
|
distinct<TResult>(data: unknown, _: QueryOption<TShape, "distinct">): TResult;
|
|
13
40
|
skip<TResult>(data: unknown, option: QueryOption<TShape, "skip">): TResult;
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { QueryOption } from "../query/types";
|
|
2
|
+
import { IQuery } from "../types";
|
|
2
3
|
import { DataTranslator } from "./DataTranslator";
|
|
4
|
+
/**
|
|
5
|
+
* What the statement that produced these rows actually did.
|
|
6
|
+
*
|
|
7
|
+
* Supplied by the plugin because only its query builder knows: an extension may be missing, a
|
|
8
|
+
* window may have made a pushdown unsafe, an inner filter may have had no column to compare
|
|
9
|
+
* against. Every flag defaults to false — the safe direction, since doing the work twice is slow
|
|
10
|
+
* and skipping it is wrong.
|
|
11
|
+
*/
|
|
12
|
+
export type SqlPushdown = {
|
|
13
|
+
/** The statement contained a real `INNER JOIN`/`LEFT JOIN` and its rows are already tuples. */
|
|
14
|
+
join?: boolean;
|
|
15
|
+
};
|
|
3
16
|
export declare class SqlTranslator<TRoot extends {}, TShape> extends DataTranslator<TRoot, TShape> {
|
|
17
|
+
protected readonly pushedDown: SqlPushdown;
|
|
18
|
+
constructor(query: IQuery<TRoot, TShape>, pushedDown?: SqlPushdown);
|
|
4
19
|
count<TResult extends number>(data: unknown, _: QueryOption<TShape, "count">): TResult;
|
|
5
20
|
min<TResult extends string | number | Date>(data: unknown, _: QueryOption<TShape, "min">): TResult;
|
|
6
21
|
max<TResult extends string | number | Date>(data: unknown, _: QueryOption<TShape, "max">): TResult;
|
|
@@ -11,6 +26,36 @@ export declare class SqlTranslator<TRoot extends {}, TShape> extends DataTransla
|
|
|
11
26
|
skip(data: unknown, _: QueryOption<TShape, "skip">): TShape;
|
|
12
27
|
take(data: unknown, _: QueryOption<TShape, "take">): TShape;
|
|
13
28
|
sort(data: unknown, _: QueryOption<TShape, "sort">): TShape;
|
|
29
|
+
/**
|
|
30
|
+
* Scores in memory, unlike every other shaper here.
|
|
31
|
+
*
|
|
32
|
+
* The pass-throughs above are safe because the SQL that produced these rows contained the
|
|
33
|
+
* corresponding clause. No `sql-core` statement contains a similarity ordering — engines
|
|
34
|
+
* that can express one are the exception, not the rule — so passing the data through
|
|
35
|
+
* would return whatever order the engine happened to produce.
|
|
36
|
+
*
|
|
37
|
+
* A plugin whose engine DID push the search down overrides this with a pass-through,
|
|
38
|
+
* gated on `option.target`. Postgres is the only one today.
|
|
39
|
+
*
|
|
40
|
+
* Rows arrive keyed by storage column name and are read that way rather than through the
|
|
41
|
+
* option's selector, because the selector is written against the entity shape and these
|
|
42
|
+
* rows have not been deserialized into it yet.
|
|
43
|
+
*/
|
|
44
|
+
nearest(data: unknown, option: QueryOption<TShape, "nearest">): TShape;
|
|
45
|
+
/**
|
|
46
|
+
* Passes through only when the statement really did contain the `JOIN`, and refuses otherwise.
|
|
47
|
+
*
|
|
48
|
+
* The pass-throughs above are safe unconditionally because the SQL that produced these rows
|
|
49
|
+
* contained the corresponding clause. A join is not like that: if the plugin did not emit one,
|
|
50
|
+
* these rows are the outer side alone, and passing them through hands the caller entities
|
|
51
|
+
* where the contract says tuples — every `([outer, inner]) => ...` lambda downstream then
|
|
52
|
+
* destructures the wrong object, and nothing errors.
|
|
53
|
+
*
|
|
54
|
+
* So the plugin has to say, and the default is to refuse. A plugin that DID emit the join has
|
|
55
|
+
* already split each flat row into its two deserialized halves (`splitJoinRows` in
|
|
56
|
+
* `@routier/sql-plugin-core`), so by the time the option is walked the work is done.
|
|
57
|
+
*/
|
|
58
|
+
join(data: unknown, option: QueryOption<TShape, "join">): TShape;
|
|
14
59
|
group<T>(data: unknown, option: QueryOption<T, "group">): T;
|
|
15
60
|
map(data: unknown, option: QueryOption<TShape, "map">): TShape;
|
|
16
61
|
}
|