@rocicorp/zero 1.11.0-canary.1 → 1.11.0-canary.2
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/out/replicache/src/btree/read.d.ts +12 -1
- package/out/replicache/src/btree/read.d.ts.map +1 -1
- package/out/replicache/src/btree/read.js +24 -12
- package/out/replicache/src/btree/read.js.map +1 -1
- package/out/replicache/src/db/write.js.map +1 -1
- package/out/replicache/src/replicache-impl.d.ts.map +1 -1
- package/out/replicache/src/replicache-impl.js +10 -0
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/replicache/src/transactions.d.ts.map +1 -1
- package/out/replicache/src/transactions.js +1 -1
- package/out/replicache/src/transactions.js.map +1 -1
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +2 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +12 -4
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/server/replicator.js +1 -1
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.js +2 -39
- package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +2 -2
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +2 -2
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +6 -6
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +3 -1
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/litestream/config-v5.yml +1 -4
- package/out/zero-cache/src/services/replicator/change-log-db.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +22 -5
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +4 -4
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.js +2 -2
- package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker.js +10 -4
- package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
- package/out/zero-cache/src/types/streams.d.ts +6 -6
- package/out/zero-cache/src/types/streams.d.ts.map +1 -1
- package/out/zero-cache/src/types/streams.js +6 -11
- package/out/zero-cache/src/types/streams.js.map +1 -1
- package/out/zero-client/src/client/context.d.ts +15 -0
- package/out/zero-client/src/client/context.d.ts.map +1 -1
- package/out/zero-client/src/client/context.js +44 -0
- package/out/zero-client/src/client/context.js.map +1 -1
- package/out/zero-client/src/client/inspector/lazy-inspector.js +2 -2
- package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
- package/out/zero-client/src/client/ivm-branch.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero-rep.d.ts.map +1 -1
- package/out/zero-client/src/client/zero-rep.js +2 -1
- package/out/zero-client/src/client/zero-rep.js.map +1 -1
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +48 -11
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-protocol/src/down.d.ts +29 -29
- package/out/zero-protocol/src/inspect-down.d.ts +81 -81
- package/out/zero-protocol/src/poke.d.ts +6 -6
- package/out/zero-react/src/use-query.d.ts.map +1 -1
- package/out/zero-react/src/use-query.js +24 -5
- package/out/zero-react/src/use-query.js.map +1 -1
- package/out/zero-solid/src/use-query.js +4 -1
- package/out/zero-solid/src/use-query.js.map +1 -1
- package/out/zql/src/ivm/array-view.d.ts.map +1 -1
- package/out/zql/src/ivm/array-view.js +5 -3
- package/out/zql/src/ivm/array-view.js.map +1 -1
- package/out/zql/src/ivm/deferred-input.d.ts +44 -0
- package/out/zql/src/ivm/deferred-input.d.ts.map +1 -0
- package/out/zql/src/ivm/deferred-input.js +88 -0
- package/out/zql/src/ivm/deferred-input.js.map +1 -0
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +5 -10
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/query/query-delegate-base.d.ts +6 -0
- package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
- package/out/zql/src/query/query-delegate-base.js +73 -10
- package/out/zql/src/query/query-delegate-base.js.map +1 -1
- package/out/zql/src/query/query-delegate.d.ts +22 -0
- package/out/zql/src/query/query-delegate.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.d.ts +12 -11
- package/out/zql/src/query/query-impl.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.js +383 -60
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query-transitions.d.ts +171 -0
- package/out/zql/src/query/query-transitions.d.ts.map +1 -0
- package/out/zql/src/query/query-transitions.js +207 -0
- package/out/zql/src/query/query-transitions.js.map +1 -0
- package/out/zql/src/query/runnable-query-impl.d.ts.map +1 -1
- package/out/zql/src/query/runnable-query-impl.js +21 -1
- package/out/zql/src/query/runnable-query-impl.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { assert } from "../../../shared/src/asserts.js";
|
|
2
|
+
import { deepEqual } from "../../../shared/src/json.js";
|
|
2
3
|
import { SUBQ_PREFIX, insertRelated, normalizeAST, normalizeCondition, normalizedRelated, tableAST } from "../../../zero-protocol/src/ast.js";
|
|
3
4
|
import { defaultFormat } from "../../../zero-types/src/format.js";
|
|
4
5
|
import { hashOfQueryInternals } from "../../../zero-protocol/src/query-hash-visitor.js";
|
|
5
6
|
import { NotImplementedError } from "../error.js";
|
|
6
7
|
import { ExpressionBuilder, and, cmp, simplifyCondition } from "./expression.js";
|
|
7
8
|
import { queryInternalsTag } from "./query-internals.js";
|
|
9
|
+
import { HashIndex, Transitions } from "./query-transitions.js";
|
|
8
10
|
//#region ../zql/src/query/query-impl.ts
|
|
9
11
|
function newQuery(schema, table) {
|
|
10
12
|
return newQueryInternal(schema, table, tableAST(table), defaultFormat, "client");
|
|
@@ -12,9 +14,40 @@ function newQuery(schema, table) {
|
|
|
12
14
|
function newQueryImpl(schema, tableName, ast, format, system) {
|
|
13
15
|
return newQueryInternal(schema, tableName, normalizeAST(ast), format, system);
|
|
14
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Root queries are interned per schema so that two `createBuilder(schema)` calls
|
|
19
|
+
* hand out the same starting point, and therefore share the whole transition
|
|
20
|
+
* tree below it. Only cleared entries accumulate here, at most one per
|
|
21
|
+
* system/table pair, so no sweeping is needed.
|
|
22
|
+
*/
|
|
23
|
+
var rootsBySchema = /* @__PURE__ */ new WeakMap();
|
|
24
|
+
/**
|
|
25
|
+
* Whether this is the AST `tableAST(tableName)` produces -- the whole table,
|
|
26
|
+
* nothing else. A normalized AST carries every field, so this cannot be a key
|
|
27
|
+
* count; each one has to be checked.
|
|
28
|
+
*/
|
|
29
|
+
function isRootAST(ast, tableName) {
|
|
30
|
+
return ast.table === tableName && ast.schema === void 0 && ast.alias === void 0 && ast.where === void 0 && ast.related === void 0 && ast.start === void 0 && ast.limit === void 0 && ast.orderBy === void 0;
|
|
31
|
+
}
|
|
15
32
|
function newQueryInternal(schema, tableName, ast, format, system) {
|
|
33
|
+
const interned = format === defaultFormat && isRootAST(ast, tableName);
|
|
34
|
+
const key = `${system}:${tableName}`;
|
|
35
|
+
let roots;
|
|
36
|
+
if (interned) {
|
|
37
|
+
roots = rootsBySchema.get(schema);
|
|
38
|
+
const existing = roots?.get(key)?.deref();
|
|
39
|
+
if (existing) return existing;
|
|
40
|
+
}
|
|
16
41
|
const inner = (tableName, ast, format, customQueryID, currentJunction) => new QueryImpl(schema, tableName, ast, format, system, customQueryID, currentJunction, inner);
|
|
17
|
-
|
|
42
|
+
const created = inner(tableName, ast, format, void 0, void 0);
|
|
43
|
+
if (interned) {
|
|
44
|
+
if (!roots) {
|
|
45
|
+
roots = /* @__PURE__ */ new Map();
|
|
46
|
+
rootsBySchema.set(schema, roots);
|
|
47
|
+
}
|
|
48
|
+
roots.set(key, new WeakRef(created));
|
|
49
|
+
}
|
|
50
|
+
return created;
|
|
18
51
|
}
|
|
19
52
|
/**
|
|
20
53
|
* The AST of a QueryImpl is always normalized. Each step builds its AST in
|
|
@@ -36,6 +69,26 @@ var QueryImpl = class {
|
|
|
36
69
|
#currentJunction;
|
|
37
70
|
customQueryID;
|
|
38
71
|
#newQuery;
|
|
72
|
+
/**
|
|
73
|
+
* The query this one was derived from, held *strongly*, and the queries
|
|
74
|
+
* derived from this one, held *weakly*. This is the arrangement V8 uses for
|
|
75
|
+
* hidden-class transitions, and it is what makes interning stable: as long as
|
|
76
|
+
* a query is reachable its whole ancestor spine is too, so re-deriving it from
|
|
77
|
+
* the root yields the same instance. See `query-transitions.ts`.
|
|
78
|
+
*/
|
|
79
|
+
#parent;
|
|
80
|
+
#transitions;
|
|
81
|
+
/**
|
|
82
|
+
* The second level of interning, keyed by hash; see `HashIndex`. Set on the
|
|
83
|
+
* root only, which is what scopes it to one schema and one delegate.
|
|
84
|
+
*/
|
|
85
|
+
#byHash;
|
|
86
|
+
/**
|
|
87
|
+
* Memoized because a query is interned: the same node serves every rebuild of
|
|
88
|
+
* a chain, so building this once rather than per `where(fn)` call takes the
|
|
89
|
+
* whole thing -- the builder and the bound `#exists` -- off the repeat path.
|
|
90
|
+
*/
|
|
91
|
+
#expressionBuilder;
|
|
39
92
|
constructor(schema, tableName, ast, format, system, customQueryID, currentJunction, newQuery) {
|
|
40
93
|
this.#schema = schema;
|
|
41
94
|
this.#tableName = tableName;
|
|
@@ -46,6 +99,44 @@ var QueryImpl = class {
|
|
|
46
99
|
this.customQueryID = customQueryID;
|
|
47
100
|
this.#newQuery = newQuery;
|
|
48
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Interns a transition whose key space is finite and schema-derived; see
|
|
104
|
+
* {@linkcode Transitions.storeBounded}. Used for the base sub-query handed to
|
|
105
|
+
* a `related`/`exists` callback, which is determined entirely by the
|
|
106
|
+
* relationship name.
|
|
107
|
+
*/
|
|
108
|
+
#deriveBounded(key, build) {
|
|
109
|
+
const existing = this.#transitions?.lookupBounded(key);
|
|
110
|
+
if (existing !== void 0) return existing;
|
|
111
|
+
return this.#record(key, void 0, void 0, build(), true);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Interns the result of applying a builder operation to this query.
|
|
115
|
+
*
|
|
116
|
+
* `key` names the operation and `value` carries its varying argument, kept
|
|
117
|
+
* apart so the key stays a string V8 has already hashed. When the two do not
|
|
118
|
+
* settle it, `delta` carries the rest and is compared with `deepEqual`. Only
|
|
119
|
+
* the delta is compared, never the whole AST: this query is already canonical,
|
|
120
|
+
* so two of its children are equal exactly when the operations that produced
|
|
121
|
+
* them are.
|
|
122
|
+
*
|
|
123
|
+
* `T` is deliberately unconstrained so that the contextual return type at each
|
|
124
|
+
* call site still drives `#newQuery`'s inference, exactly as it did when the
|
|
125
|
+
* call sites returned `this.#newQuery(...)` directly.
|
|
126
|
+
*/
|
|
127
|
+
#derive(key, value, delta, build) {
|
|
128
|
+
const existing = this.#transitions?.lookup(key, value, delta);
|
|
129
|
+
if (existing !== void 0) return existing;
|
|
130
|
+
return this.#record(key, value, delta, build(), false);
|
|
131
|
+
}
|
|
132
|
+
#record(key, value, delta, created, bounded) {
|
|
133
|
+
const q = created;
|
|
134
|
+
q.#parent = this;
|
|
135
|
+
const transitions = this.#transitions ??= new Transitions();
|
|
136
|
+
if (bounded) transitions.storeBounded(key, q);
|
|
137
|
+
else transitions.store(key, value, delta, q);
|
|
138
|
+
return created;
|
|
139
|
+
}
|
|
49
140
|
run(_options) {
|
|
50
141
|
throwQueryNotRunnable();
|
|
51
142
|
}
|
|
@@ -56,43 +147,76 @@ var QueryImpl = class {
|
|
|
56
147
|
throwQueryNotRunnable();
|
|
57
148
|
}
|
|
58
149
|
nameAndArgs(name, args) {
|
|
59
|
-
return this.#newQuery(this.#tableName, this.#ast, this.format, {
|
|
150
|
+
return this.#derive("nameAndArgs", name, args, () => this.#newQuery(this.#tableName, this.#ast, this.format, {
|
|
60
151
|
name,
|
|
61
152
|
args
|
|
62
|
-
}, this.#currentJunction);
|
|
153
|
+
}, this.#currentJunction));
|
|
63
154
|
}
|
|
64
155
|
hash() {
|
|
65
|
-
if (!this.#hash)
|
|
156
|
+
if (!this.#hash) {
|
|
157
|
+
this.#hash = hashOfQueryInternals(this.#ast, this.format, this.#system, this.#currentJunction, this.customQueryID?.name, this.customQueryID?.args);
|
|
158
|
+
this.#canonicalize();
|
|
159
|
+
}
|
|
66
160
|
return this.#hash;
|
|
67
161
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Folds this query into its root's hash index, now that its hash is known.
|
|
164
|
+
*
|
|
165
|
+
* If an equal query is already indexed, the transition that produced this
|
|
166
|
+
* one is re-pointed at it, so the next rebuild along this path yields that
|
|
167
|
+
* instance rather than a second copy. This query itself is left alone: its
|
|
168
|
+
* identity has already been handed out. Content is verified with `#sameAs`
|
|
169
|
+
* before anything is redirected, so a hash collision costs nothing worse
|
|
170
|
+
* than a missed unification.
|
|
171
|
+
*/
|
|
172
|
+
#canonicalize() {
|
|
173
|
+
const parent = this.#parent;
|
|
174
|
+
if (parent === void 0) return;
|
|
175
|
+
let root = parent;
|
|
176
|
+
while (root.#parent !== void 0) root = root.#parent;
|
|
177
|
+
const index = root.#byHash ??= new HashIndex();
|
|
178
|
+
const existing = index.get(this.#hash);
|
|
179
|
+
if (existing === void 0) index.set(this.#hash, this);
|
|
180
|
+
else if (existing !== this && this.#sameAs(existing)) parent.#transitions?.replace(this, existing);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Whether `other` is interchangeable with this query: everything `hash()`
|
|
184
|
+
* covers, compared structurally. Schema and delegate are not compared. Both
|
|
185
|
+
* are fixed by the root, and this only ever compares queries under one root.
|
|
186
|
+
*/
|
|
187
|
+
#sameAs(other) {
|
|
188
|
+
return this.#system === other.#system && this.#currentJunction === other.#currentJunction && this.customQueryID?.name === other.customQueryID?.name && deepEqual(this.customQueryID?.args, other.customQueryID?.args) && deepEqual(this.format, other.format) && deepEqual(this.#ast, other.#ast);
|
|
189
|
+
}
|
|
190
|
+
one() {
|
|
191
|
+
return this.#derive("one", void 0, void 0, () => this.#newQuery(this.#tableName, {
|
|
192
|
+
...this.#ast,
|
|
193
|
+
limit: 1
|
|
194
|
+
}, {
|
|
195
|
+
...this.format,
|
|
196
|
+
singular: true
|
|
197
|
+
}, this.customQueryID, this.#currentJunction));
|
|
198
|
+
}
|
|
199
|
+
whereExists(relationship, cbOrOptions, options) {
|
|
76
200
|
const cb = typeof cbOrOptions === "function" ? cbOrOptions : void 0;
|
|
77
201
|
const opts = typeof cbOrOptions === "function" ? options : cbOrOptions;
|
|
78
202
|
return this.where(({ exists }) => exists(relationship, cb, opts));
|
|
79
|
-
}
|
|
80
|
-
related
|
|
203
|
+
}
|
|
204
|
+
related(relationship, cb) {
|
|
81
205
|
if (relationship.startsWith("zsubq_")) throw new Error(`Relationship names may not start with "${SUBQ_PREFIX}". That is a reserved prefix.`);
|
|
82
206
|
cb = cb ?? ((q) => q);
|
|
83
207
|
const related = this.#schema.relationships[this.#tableName][relationship];
|
|
84
208
|
assert(related, "Invalid relationship");
|
|
85
209
|
if (isOneHop(related)) {
|
|
86
210
|
const { destSchema, destField, sourceField, cardinality } = related[0];
|
|
87
|
-
const q = this.#newQuery(destSchema, tableAST(destSchema, relationship), {
|
|
211
|
+
const q = this.#deriveBounded(`relatedBase:${relationship}`, () => this.#newQuery(destSchema, tableAST(destSchema, relationship), {
|
|
88
212
|
relationships: {},
|
|
89
213
|
singular: cardinality === "one"
|
|
90
|
-
}, this.customQueryID, void 0);
|
|
214
|
+
}, this.customQueryID, void 0));
|
|
91
215
|
const subQuery = asQueryImpl(cb(q));
|
|
92
216
|
assert(isCompoundKey(sourceField), "The source of a relationship must specify at last 1 field");
|
|
93
217
|
assert(isCompoundKey(destField), "The destination of a relationship must specify at last 1 field");
|
|
94
218
|
assert(sourceField.length === destField.length, "The source and destination of a relationship must have the same number of fields");
|
|
95
|
-
return this.#newQuery(this.#tableName, {
|
|
219
|
+
return this.#derive(relatedKey(relationship), astID(subQuery.#ast), void 0, () => this.#newQuery(this.#tableName, {
|
|
96
220
|
...this.#ast,
|
|
97
221
|
related: insertRelated(this.#ast.related, {
|
|
98
222
|
correlation: {
|
|
@@ -108,34 +232,36 @@ var QueryImpl = class {
|
|
|
108
232
|
...this.format.relationships,
|
|
109
233
|
[relationship]: subQuery.format
|
|
110
234
|
}
|
|
111
|
-
}, this.customQueryID, this.#currentJunction);
|
|
235
|
+
}, this.customQueryID, this.#currentJunction));
|
|
112
236
|
}
|
|
113
237
|
if (isTwoHop(related)) {
|
|
114
238
|
const [firstRelation, secondRelation] = related;
|
|
115
239
|
const { destSchema } = secondRelation;
|
|
116
240
|
const junctionSchema = firstRelation.destSchema;
|
|
117
|
-
const sq = asQueryImpl(cb(this.#newQuery(destSchema, tableAST(destSchema, relationship), {
|
|
241
|
+
const sq = asQueryImpl(cb(this.#deriveBounded(`relatedBase:${relationship}`, () => this.#newQuery(destSchema, tableAST(destSchema, relationship), {
|
|
118
242
|
relationships: {},
|
|
119
243
|
singular: secondRelation.cardinality === "one"
|
|
120
|
-
}, this.customQueryID, relationship)));
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
assert(isCompoundKey(
|
|
124
|
-
assert(isCompoundKey(
|
|
125
|
-
|
|
244
|
+
}, this.customQueryID, relationship))));
|
|
245
|
+
const { sourceField: firstSource, destField: firstDest } = firstRelation;
|
|
246
|
+
const { sourceField: secondSource, destField: secondDest } = secondRelation;
|
|
247
|
+
assert(isCompoundKey(firstSource), "Invalid relationship");
|
|
248
|
+
assert(isCompoundKey(firstDest), "Invalid relationship");
|
|
249
|
+
assert(isCompoundKey(secondSource), "Invalid relationship");
|
|
250
|
+
assert(isCompoundKey(secondDest), "Invalid relationship");
|
|
251
|
+
return this.#derive(relatedKey(relationship), astID(sq.#ast), void 0, () => this.#newQuery(this.#tableName, {
|
|
126
252
|
...this.#ast,
|
|
127
253
|
related: insertRelated(this.#ast.related, {
|
|
128
254
|
correlation: {
|
|
129
|
-
parentField:
|
|
130
|
-
childField:
|
|
255
|
+
parentField: firstSource,
|
|
256
|
+
childField: firstDest
|
|
131
257
|
},
|
|
132
258
|
hidden: true,
|
|
133
259
|
subquery: {
|
|
134
260
|
...tableAST(junctionSchema, relationship),
|
|
135
261
|
related: [normalizedRelated({
|
|
136
262
|
correlation: {
|
|
137
|
-
parentField:
|
|
138
|
-
childField:
|
|
263
|
+
parentField: secondSource,
|
|
264
|
+
childField: secondDest
|
|
139
265
|
},
|
|
140
266
|
subquery: sq.#ast,
|
|
141
267
|
system: this.#system
|
|
@@ -149,49 +275,76 @@ var QueryImpl = class {
|
|
|
149
275
|
...this.format.relationships,
|
|
150
276
|
[relationship]: sq.format
|
|
151
277
|
}
|
|
152
|
-
}, this.customQueryID, this.#currentJunction);
|
|
278
|
+
}, this.customQueryID, this.#currentJunction));
|
|
153
279
|
}
|
|
154
280
|
throw new Error(`Invalid relationship ${relationship}`);
|
|
155
|
-
}
|
|
156
|
-
where
|
|
281
|
+
}
|
|
282
|
+
where(fieldOrExpressionFactory, opOrValue, value) {
|
|
157
283
|
let cond;
|
|
158
284
|
if (typeof fieldOrExpressionFactory === "function") cond = fieldOrExpressionFactory(this.expressionBuilder());
|
|
159
285
|
else {
|
|
160
286
|
assert(arguments.length >= 2, "Invalid condition. Too few arguments.");
|
|
161
|
-
|
|
162
|
-
|
|
287
|
+
const twoArg = arguments.length === 2;
|
|
288
|
+
const op = twoArg ? "=" : opOrValue;
|
|
289
|
+
const raw = twoArg ? opOrValue : value;
|
|
290
|
+
if (raw === null || typeof raw !== "object") {
|
|
291
|
+
const hit = this.#transitions?.lookup(whereKey(fieldOrExpressionFactory, op), raw ?? null, void 0);
|
|
292
|
+
if (hit !== void 0) return hit;
|
|
293
|
+
}
|
|
294
|
+
cond = twoArg ? cmp(fieldOrExpressionFactory, opOrValue) : cmp(fieldOrExpressionFactory, opOrValue, value);
|
|
163
295
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
296
|
+
let key;
|
|
297
|
+
let tValue;
|
|
298
|
+
let delta = cond;
|
|
299
|
+
if (cond.type === "simple" && cond.left.type === "column") {
|
|
300
|
+
key = whereKey(cond.left.name, cond.op);
|
|
301
|
+
const right = cond.right;
|
|
302
|
+
tValue = right.type === "literal" && isPrimitive(right.value) ? right.value : rightTag(right);
|
|
303
|
+
delta = void 0;
|
|
304
|
+
} else {
|
|
305
|
+
const exact = condKey(cond);
|
|
306
|
+
if (exact === void 0) key = treeKey(cond);
|
|
307
|
+
else {
|
|
308
|
+
key = "where:tree";
|
|
309
|
+
tValue = exact;
|
|
310
|
+
delta = void 0;
|
|
311
|
+
}
|
|
176
312
|
}
|
|
177
|
-
|
|
178
|
-
|
|
313
|
+
return this.#derive(key, tValue, delta, () => {
|
|
314
|
+
const existingWhere = this.#ast.where;
|
|
315
|
+
const merged = existingWhere ? and(existingWhere, cond) : cond;
|
|
316
|
+
return this.#newQuery(this.#tableName, {
|
|
317
|
+
...this.#ast,
|
|
318
|
+
where: normalizeCondition(simplifyCondition(merged))
|
|
319
|
+
}, this.format, this.customQueryID, this.#currentJunction);
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
start(row, opts) {
|
|
323
|
+
return this.#derive(opts?.inclusive ? "start:inclusive" : "start:exclusive", valueTag(row), void 0, () => this.#newQuery(this.#tableName, {
|
|
324
|
+
...this.#ast,
|
|
325
|
+
start: {
|
|
326
|
+
row,
|
|
327
|
+
exclusive: !opts?.inclusive
|
|
328
|
+
}
|
|
329
|
+
}, this.format, this.customQueryID, this.#currentJunction));
|
|
330
|
+
}
|
|
331
|
+
limit(limit) {
|
|
179
332
|
if (limit < 0) throw new Error("Limit must be non-negative");
|
|
180
333
|
if ((limit | 0) !== limit) throw new Error("Limit must be an integer");
|
|
181
334
|
if (this.#currentJunction) throw new NotImplementedError("Limit is not supported in junction relationships yet. Junction relationship being limited: " + this.#currentJunction);
|
|
182
|
-
return this.#newQuery(this.#tableName, {
|
|
335
|
+
return this.#derive("limit", limit, void 0, () => this.#newQuery(this.#tableName, {
|
|
183
336
|
...this.#ast,
|
|
184
337
|
limit
|
|
185
|
-
}, this.format, this.customQueryID, this.#currentJunction);
|
|
186
|
-
}
|
|
187
|
-
orderBy
|
|
338
|
+
}, this.format, this.customQueryID, this.#currentJunction));
|
|
339
|
+
}
|
|
340
|
+
orderBy(field, direction) {
|
|
188
341
|
if (this.#currentJunction) throw new NotImplementedError("Order by is not supported in junction relationships yet. Junction relationship being ordered: " + this.#currentJunction);
|
|
189
|
-
return this.#newQuery(this.#tableName, {
|
|
342
|
+
return this.#derive(direction === "asc" ? "orderBy:asc" : "orderBy:desc", field, void 0, () => this.#newQuery(this.#tableName, {
|
|
190
343
|
...this.#ast,
|
|
191
344
|
orderBy: [...this.#ast.orderBy ?? [], [field, direction]]
|
|
192
|
-
}, this.format, this.customQueryID, this.#currentJunction);
|
|
193
|
-
}
|
|
194
|
-
#exists
|
|
345
|
+
}, this.format, this.customQueryID, this.#currentJunction));
|
|
346
|
+
}
|
|
347
|
+
#exists(relationship, cb, options) {
|
|
195
348
|
cb = cb ?? ((q) => q);
|
|
196
349
|
const flip = options?.flip;
|
|
197
350
|
const scalar = options?.scalar;
|
|
@@ -201,7 +354,8 @@ var QueryImpl = class {
|
|
|
201
354
|
const { destSchema: destTableName, sourceField, destField } = related[0];
|
|
202
355
|
assert(isCompoundKey(sourceField), "Invalid relationship");
|
|
203
356
|
assert(isCompoundKey(destField), "Invalid relationship");
|
|
204
|
-
const subQuery = asQueryImpl(cb(this.#newQuery(destTableName, tableAST(destTableName, `${SUBQ_PREFIX}${relationship}`), defaultFormat, this.customQueryID, void 0)));
|
|
357
|
+
const subQuery = asQueryImpl(cb(this.#deriveBounded(`existsBase:${relationship}`, () => this.#newQuery(destTableName, tableAST(destTableName, `${SUBQ_PREFIX}${relationship}`), defaultFormat, this.customQueryID, void 0))));
|
|
358
|
+
astID(subQuery.#ast);
|
|
205
359
|
return {
|
|
206
360
|
type: "correlatedSubquery",
|
|
207
361
|
related: {
|
|
@@ -225,7 +379,7 @@ var QueryImpl = class {
|
|
|
225
379
|
assert(isCompoundKey(secondRelation.destField), "Invalid relationship");
|
|
226
380
|
const { destSchema } = secondRelation;
|
|
227
381
|
const junctionSchema = firstRelation.destSchema;
|
|
228
|
-
const queryToDest = cb(this.#newQuery(destSchema, tableAST(destSchema, `${SUBQ_PREFIX}zhidden_${relationship}`), defaultFormat, this.customQueryID, relationship));
|
|
382
|
+
const queryToDest = cb(this.#deriveBounded(`existsJunctionBase:${relationship}`, () => this.#newQuery(destSchema, tableAST(destSchema, `${SUBQ_PREFIX}zhidden_${relationship}`), defaultFormat, this.customQueryID, relationship)));
|
|
229
383
|
return {
|
|
230
384
|
type: "correlatedSubquery",
|
|
231
385
|
related: {
|
|
@@ -257,18 +411,187 @@ var QueryImpl = class {
|
|
|
257
411
|
};
|
|
258
412
|
}
|
|
259
413
|
throw new Error(`Invalid relationship ${relationship}`);
|
|
260
|
-
}
|
|
414
|
+
}
|
|
261
415
|
get ast() {
|
|
262
416
|
return this.#ast;
|
|
263
417
|
}
|
|
264
418
|
expressionBuilder() {
|
|
265
|
-
return new ExpressionBuilder(this.#exists);
|
|
419
|
+
return this.#expressionBuilder ??= new ExpressionBuilder(this.#exists.bind(this));
|
|
266
420
|
}
|
|
267
421
|
};
|
|
268
422
|
function asQueryImpl(q) {
|
|
269
423
|
assert(q instanceof QueryImpl, "Expected QueryImpl instance");
|
|
270
424
|
return q;
|
|
271
425
|
}
|
|
426
|
+
/**
|
|
427
|
+
* A stable id per query AST.
|
|
428
|
+
*
|
|
429
|
+
* Every `QueryImpl` allocates its own AST object, so these are one-to-one with
|
|
430
|
+
* queries, and interning makes the object identity stable across rebuilds. That
|
|
431
|
+
* lets a `related` or `exists` transition key on *which* sub-query it points at
|
|
432
|
+
* rather than comparing the sub-tree.
|
|
433
|
+
*
|
|
434
|
+
* Assigned only where it is needed. A `WeakMap.set` costs on the order of a
|
|
435
|
+
* hundred nanoseconds, so doing this for every query rather than for the few
|
|
436
|
+
* that are sub-queries was one of the largest single costs of building a query
|
|
437
|
+
* from cold. ASTs that never pass through here -- including the junction AST a
|
|
438
|
+
* two-hop `exists` builds inline -- are absent, and fall back to a delta.
|
|
439
|
+
*/
|
|
440
|
+
var astIDs = /* @__PURE__ */ new WeakMap();
|
|
441
|
+
var nextASTID = 0;
|
|
442
|
+
function astID(ast) {
|
|
443
|
+
let id = astIDs.get(ast);
|
|
444
|
+
if (id === void 0) {
|
|
445
|
+
id = ++nextASTID;
|
|
446
|
+
astIDs.set(ast, id);
|
|
447
|
+
}
|
|
448
|
+
return id;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Memoized transition keys.
|
|
452
|
+
*
|
|
453
|
+
* A transition key must be a string V8 has already hashed, or the `Map` lookup
|
|
454
|
+
* has to hash it from scratch -- measured at ~148ns against ~10ns, which dwarfs
|
|
455
|
+
* everything else a lookup does. Both tables are keyed by schema-derived names,
|
|
456
|
+
* so they are bounded by the schema.
|
|
457
|
+
*/
|
|
458
|
+
var whereKeys = /* @__PURE__ */ new Map();
|
|
459
|
+
function whereKey(column, op) {
|
|
460
|
+
let byOp = whereKeys.get(column);
|
|
461
|
+
if (byOp === void 0) {
|
|
462
|
+
byOp = /* @__PURE__ */ new Map();
|
|
463
|
+
whereKeys.set(column, byOp);
|
|
464
|
+
}
|
|
465
|
+
let key = byOp.get(op);
|
|
466
|
+
if (key === void 0) {
|
|
467
|
+
key = `where:${column}:${op}`;
|
|
468
|
+
byOp.set(op, key);
|
|
469
|
+
}
|
|
470
|
+
return key;
|
|
471
|
+
}
|
|
472
|
+
var relatedKeys = /* @__PURE__ */ new Map();
|
|
473
|
+
function relatedKey(relationship) {
|
|
474
|
+
let key = relatedKeys.get(relationship);
|
|
475
|
+
if (key === void 0) {
|
|
476
|
+
key = `related:${relationship}`;
|
|
477
|
+
relatedKeys.set(relationship, key);
|
|
478
|
+
}
|
|
479
|
+
return key;
|
|
480
|
+
}
|
|
481
|
+
function isPrimitive(v) {
|
|
482
|
+
return v === null || typeof v !== "object";
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Folds a JSON value into a self-delimiting token: a string is
|
|
486
|
+
* length-prefixed, a number is terminated, an array or object is
|
|
487
|
+
* count-prefixed and encodes its members recursively, and the rest are fixed
|
|
488
|
+
* width. Tokens can therefore be concatenated into a key with no way for one
|
|
489
|
+
* to run into the next, and the leading tag keeps `'1'` and `1` apart.
|
|
490
|
+
*
|
|
491
|
+
* Not `JSON.stringify`: it writes `Infinity`, `NaN` and `null` all as `null`,
|
|
492
|
+
* and this token is trusted as exact, so a nested value that serialized like
|
|
493
|
+
* another's would hand back a query with the wrong value in its AST. Numbers
|
|
494
|
+
* are written the way `String` writes them, which keeps those apart. An
|
|
495
|
+
* `undefined` property is skipped, as `deepEqual` would skip it; property
|
|
496
|
+
* order is part of the encoding, so an object spelled in another order
|
|
497
|
+
* interns separately.
|
|
498
|
+
*/
|
|
499
|
+
function valueTag(v) {
|
|
500
|
+
switch (typeof v) {
|
|
501
|
+
case "string": return `:s${enc(v)}`;
|
|
502
|
+
case "number": return `:n${v};`;
|
|
503
|
+
case "boolean": return v ? ":t" : ":f";
|
|
504
|
+
default: {
|
|
505
|
+
if (v === null) return ":z";
|
|
506
|
+
if (Array.isArray(v)) {
|
|
507
|
+
let out = `:a${v.length}:`;
|
|
508
|
+
for (const e of v) out += valueTag(e);
|
|
509
|
+
return out;
|
|
510
|
+
}
|
|
511
|
+
const o = v;
|
|
512
|
+
let body = "";
|
|
513
|
+
let n = 0;
|
|
514
|
+
for (const key in o) {
|
|
515
|
+
const e = o[key];
|
|
516
|
+
if (e !== void 0 && Object.hasOwn(o, key)) {
|
|
517
|
+
body += enc(key) + valueTag(e);
|
|
518
|
+
n++;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return `:o${n}:${body}`;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Folds the right-hand side of a simple condition into a transition value, so
|
|
527
|
+
* that the transition is exact and the lookup needs no comparison at all.
|
|
528
|
+
*
|
|
529
|
+
* Arrays (`IN`) and parameter references are encoded in full: that is one
|
|
530
|
+
* pass over a small value, where leaving them in the delta would instead cost
|
|
531
|
+
* a `deepEqual` over the whole condition for every sibling a lookup walks
|
|
532
|
+
* past.
|
|
533
|
+
*/
|
|
534
|
+
function rightTag(right) {
|
|
535
|
+
if (right.type !== "literal") return `:p${valueTag(right)}`;
|
|
536
|
+
return valueTag(right.value);
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Length-prefixes a free-form string so it can be concatenated into a key
|
|
540
|
+
* without a separator being ambiguous with the content.
|
|
541
|
+
*/
|
|
542
|
+
function enc(s) {
|
|
543
|
+
return `${s.length}:${s}`;
|
|
544
|
+
}
|
|
545
|
+
/** Encodes an optional boolean without collapsing `undefined` into `false`. */
|
|
546
|
+
function tri(b) {
|
|
547
|
+
return b === void 0 ? "-" : b ? "t" : "f";
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* An exact key for a whole condition tree, or `undefined` for a shape we cannot
|
|
551
|
+
* encode (which falls back to {@linkcode treeKey} plus a delta).
|
|
552
|
+
*
|
|
553
|
+
* Everything variable-length is length-prefixed, so distinct trees cannot
|
|
554
|
+
* produce the same string and the key needs no `deepEqual` to back it up. That
|
|
555
|
+
* matters because the alternative -- one coarse bucket per shape -- turns N
|
|
556
|
+
* sibling conditions off the same parent into an N-deep scan of tree
|
|
557
|
+
* comparisons, which measured far slower than not interning at all.
|
|
558
|
+
*/
|
|
559
|
+
function condKey(cond) {
|
|
560
|
+
switch (cond.type) {
|
|
561
|
+
case "simple":
|
|
562
|
+
if (cond.left.type !== "column") return;
|
|
563
|
+
return `S${enc(cond.left.name)}${enc(cond.op)}${rightTag(cond.right)}`;
|
|
564
|
+
case "correlatedSubquery": {
|
|
565
|
+
const id = astIDs.get(cond.related.subquery);
|
|
566
|
+
return id === void 0 ? void 0 : `E${id}:${enc(cond.op)}${tri(cond.flip)}${tri(cond.scalar)}`;
|
|
567
|
+
}
|
|
568
|
+
case "and":
|
|
569
|
+
case "or": {
|
|
570
|
+
let out = `${cond.type === "and" ? "A" : "O"}${cond.conditions.length}:`;
|
|
571
|
+
for (const c of cond.conditions) {
|
|
572
|
+
const k = condKey(c);
|
|
573
|
+
if (k === void 0) return;
|
|
574
|
+
out += k;
|
|
575
|
+
}
|
|
576
|
+
return out;
|
|
577
|
+
}
|
|
578
|
+
default: return;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* A coarse bucket key for the conditions {@linkcode condKey} cannot encode --
|
|
583
|
+
* a two-hop `exists`, whose junction AST is built inline and so has no identity
|
|
584
|
+
* of its own. Reads only the top of the tree, so it is cheap; the delta tells
|
|
585
|
+
* apart what shares it.
|
|
586
|
+
*/
|
|
587
|
+
function treeKey(cond) {
|
|
588
|
+
switch (cond.type) {
|
|
589
|
+
case "correlatedSubquery": return `where:e:${cond.related.subquery.alias}:${cond.op}`;
|
|
590
|
+
case "and":
|
|
591
|
+
case "or": return `where:${cond.type}:${cond.conditions.length}`;
|
|
592
|
+
default: return "where:*";
|
|
593
|
+
}
|
|
594
|
+
}
|
|
272
595
|
function throwQueryNotRunnable() {
|
|
273
596
|
throw new Error("Query is not runnable");
|
|
274
597
|
}
|