@agentshouse/mdruntime 0.1.0-cacb4c3f6734e → 0.1.0-cae97ad2e9142
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 +23 -10
- package/dist/adapter.d.ts +126 -30
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +5 -1
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +36 -6
- package/dist/attempt.d.ts +16 -0
- package/dist/attempt.d.ts.map +1 -0
- package/dist/attempt.js +70 -0
- package/dist/binary.d.ts +18 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +8 -0
- package/dist/bootstrap.d.ts +40 -6
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +186 -45
- package/dist/chain.d.ts +11 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +64 -0
- package/dist/change-set.d.ts +9 -14
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +11 -56
- package/dist/change.d.ts +45 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +34 -0
- package/dist/client.d.ts +35 -14
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +107 -17
- package/dist/collection.d.ts +9 -13
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +46 -56
- package/dist/compile.d.ts +16 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +767 -0
- package/dist/connection.d.ts +58 -19
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +3659 -726
- package/dist/conversation.d.ts +41 -12
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +588 -180
- package/dist/digest.d.ts +7 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +113 -0
- package/dist/durability.d.ts +5 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +5 -0
- package/dist/entry.d.ts +19 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +151 -0
- package/dist/expectation.d.ts +21 -0
- package/dist/expectation.d.ts.map +1 -0
- package/dist/expectation.js +70 -0
- package/dist/failure.d.ts +13 -0
- package/dist/failure.d.ts.map +1 -0
- package/dist/failure.js +19 -0
- package/dist/fence.d.ts +6 -0
- package/dist/fence.d.ts.map +1 -0
- package/dist/fence.js +10 -0
- package/dist/file.d.ts +2 -10
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +3 -120
- package/dist/fulltext.d.ts +27 -0
- package/dist/fulltext.d.ts.map +1 -0
- package/dist/fulltext.js +368 -0
- package/dist/host.d.ts +15 -2
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +132 -21
- package/dist/identity.d.ts +4 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +16 -1
- package/dist/index.d.ts +20 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/live.d.ts +22 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +124 -0
- package/dist/memory.d.ts +13 -5
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +99 -31
- package/dist/messages.d.ts +28 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +90 -0
- package/dist/model.d.ts +14 -4
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +13 -2
- package/dist/name.d.ts +4 -0
- package/dist/name.d.ts.map +1 -0
- package/dist/name.js +122 -0
- package/dist/observation.d.ts +19 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +113 -12
- package/dist/open.d.ts +2 -0
- package/dist/open.d.ts.map +1 -0
- package/dist/open.js +1 -0
- package/dist/opened.d.ts +22 -0
- package/dist/opened.d.ts.map +1 -0
- package/dist/opened.js +107 -0
- package/dist/outcome.d.ts +62 -3
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +67 -1
- package/dist/overlay.d.ts +15 -5
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +136 -180
- package/dist/query.d.ts +70 -4
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +53 -38
- package/dist/reach.d.ts +17 -4
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +226 -65
- package/dist/react.d.ts +31 -20
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +13 -21
- package/dist/readiness.d.ts +64 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +33 -0
- package/dist/reads.d.ts +11 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +100 -0
- package/dist/recovery.d.ts +25 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +198 -0
- package/dist/refresh.d.ts +19 -8
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +125 -57
- package/dist/replica.d.ts +59 -33
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +321 -155
- package/dist/room-draft.d.ts +41 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +186 -37
- package/dist/rows.d.ts +37 -35
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +168 -100
- package/dist/save.d.ts +71 -4
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +815 -146
- package/dist/search.d.ts +41 -29
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +37 -105
- package/dist/seed.d.ts +16 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +75 -0
- package/dist/shared.d.ts +16 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +431 -0
- package/dist/status.d.ts +6 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +0 -8
- package/dist/store.d.ts +124 -58
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +362 -344
- package/dist/stored.d.ts +150 -0
- package/dist/stored.d.ts.map +1 -0
- package/dist/stored.js +564 -0
- package/dist/tables.d.ts +354 -0
- package/dist/tables.d.ts.map +1 -0
- package/dist/tables.js +624 -0
- package/dist/turn.d.ts +9 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +75 -0
- package/dist/validation.d.ts +30 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +162 -0
- package/dist/windows.d.ts +22 -0
- package/dist/windows.d.ts.map +1 -0
- package/dist/windows.js +89 -0
- package/dist/wire.d.ts +169 -33
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +68 -31
- package/dist/words.d.ts +19 -0
- package/dist/words.d.ts.map +1 -0
- package/dist/words.js +150 -0
- package/package.json +26 -15
- package/AGENTS.md +0 -244
- package/dist/attachment.d.ts +0 -3
- package/dist/attachment.d.ts.map +0 -1
- package/dist/attachment.js +0 -8
- package/dist/read-side.d.ts +0 -18
- package/dist/read-side.d.ts.map +0 -1
- package/dist/read-side.js +0 -433
package/dist/compile.js
ADDED
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
import { ARRAY_INDEX, parse, pointerSegments } from '@agentshouse/mdmodel';
|
|
2
|
+
import { binaryRecord } from './binary.js';
|
|
3
|
+
import { documentKey, kindOf, pathOfKey, placedOf, roomOfKey, settingsTextPath, } from './rows.js';
|
|
4
|
+
import { byCodePoint, compared, cursorOf, FROM_START, MAXIMUM_WINDOW, } from './windows.js';
|
|
5
|
+
const VISIBLE = (alias) => `${alias}.visible = 1 AND ${alias}.present = 1 AND ${alias}.queried = 1`;
|
|
6
|
+
function fragment(sql, bind = []) {
|
|
7
|
+
return { sql, bind };
|
|
8
|
+
}
|
|
9
|
+
function joined(parts, separator) {
|
|
10
|
+
return {
|
|
11
|
+
sql: parts.map((part) => part.sql).join(separator),
|
|
12
|
+
bind: parts.flatMap((part) => part.bind),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function all(parts) {
|
|
16
|
+
return parts.length === 0 ? fragment('1') : joined(parts.map(grouped), ' AND ');
|
|
17
|
+
}
|
|
18
|
+
function any(parts) {
|
|
19
|
+
return parts.length === 0 ? fragment('0') : joined(parts.map(grouped), ' OR ');
|
|
20
|
+
}
|
|
21
|
+
function grouped(part) {
|
|
22
|
+
return { sql: `(${part.sql})`, bind: part.bind };
|
|
23
|
+
}
|
|
24
|
+
function list(values) {
|
|
25
|
+
return fragment(`(${values.map(() => '?').join(', ')})`, values);
|
|
26
|
+
}
|
|
27
|
+
function sqlValue(value) {
|
|
28
|
+
if (value === null)
|
|
29
|
+
return { kind: 'null', value: null };
|
|
30
|
+
if (typeof value === 'boolean')
|
|
31
|
+
return { kind: 'boolean', value: value ? 1 : 0 };
|
|
32
|
+
if (typeof value === 'number')
|
|
33
|
+
return { kind: 'number', value };
|
|
34
|
+
return { kind: 'string', value };
|
|
35
|
+
}
|
|
36
|
+
function same(alias, value) {
|
|
37
|
+
const held = sqlValue(value);
|
|
38
|
+
return held.kind === 'null'
|
|
39
|
+
? fragment(`${alias}.kind = 'null'`)
|
|
40
|
+
: fragment(`${alias}.kind = ? AND ${alias}.value = ?`, [held.kind, held.value]);
|
|
41
|
+
}
|
|
42
|
+
const BOUNDS = { gt: '>', gte: '>=', lt: '<', lte: '<=' };
|
|
43
|
+
function pointed(alias, field, reach) {
|
|
44
|
+
const { keys, places, glob } = placedOf(field, reach);
|
|
45
|
+
return fragment(`${alias}.keys = ? AND ${alias}.places ${glob ? 'GLOB' : '='} ?`, [keys, places]);
|
|
46
|
+
}
|
|
47
|
+
function heldValues(doc, field, alias, where) {
|
|
48
|
+
return joined([
|
|
49
|
+
fragment(`SELECT 1 FROM replica_values ${alias} WHERE ${alias}.doc = ${doc}.id AND `),
|
|
50
|
+
grouped(pointed(alias, field, 'value')),
|
|
51
|
+
fragment(' AND '),
|
|
52
|
+
grouped(where),
|
|
53
|
+
], '');
|
|
54
|
+
}
|
|
55
|
+
function exists(part) {
|
|
56
|
+
return { sql: `EXISTS (${part.sql})`, bind: part.bind };
|
|
57
|
+
}
|
|
58
|
+
function itemOf(held, where) {
|
|
59
|
+
return exists(joined([
|
|
60
|
+
fragment(`SELECT 1 FROM replica_values i WHERE i.doc = ${held}.doc AND i.keys = ${held}.keys AND i.places = ${held}.places AND i.item >= 0 AND `),
|
|
61
|
+
grouped(where),
|
|
62
|
+
], ''));
|
|
63
|
+
}
|
|
64
|
+
function matching(held, values) {
|
|
65
|
+
return any(values.map((value) => any([
|
|
66
|
+
same(held, value),
|
|
67
|
+
all([fragment(`${held}.kind = 'array'`), itemOf(held, same('i', value))]),
|
|
68
|
+
])));
|
|
69
|
+
}
|
|
70
|
+
function tested(doc, field, test) {
|
|
71
|
+
const reached = exists(heldValues(doc, field, 'r', fragment('r.item = -1')));
|
|
72
|
+
if (test.op === 'exists')
|
|
73
|
+
return test.exists ? reached : fragment(`NOT ${reached.sql}`, reached.bind);
|
|
74
|
+
if (test.op === 'eq' || test.op === 'in') {
|
|
75
|
+
const values = test.op === 'eq' ? [test.value] : test.values;
|
|
76
|
+
return exists(heldValues(doc, field, 'v', any(values.map((value) => same('v', value)))));
|
|
77
|
+
}
|
|
78
|
+
if (test.op === 'ne' || test.op === 'nin') {
|
|
79
|
+
const values = test.op === 'ne' ? [test.value] : test.values;
|
|
80
|
+
const unmatched = matching('h', values);
|
|
81
|
+
return any([
|
|
82
|
+
fragment(`NOT ${reached.sql}`, reached.bind),
|
|
83
|
+
exists(heldValues(doc, field, 'h', all([fragment('h.item = -1'), fragment(`NOT (${unmatched.sql})`, unmatched.bind)]))),
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
if (test.op === 'range') {
|
|
87
|
+
return exists(heldValues(doc, field, 'v', all([
|
|
88
|
+
fragment('v.item = -1'),
|
|
89
|
+
...test.bounds.map(([operator, bound]) => {
|
|
90
|
+
const held = sqlValue(bound);
|
|
91
|
+
return fragment(`v.kind = ? AND v.value ${BOUNDS[operator]} ?`, [
|
|
92
|
+
held.kind,
|
|
93
|
+
held.value,
|
|
94
|
+
]);
|
|
95
|
+
}),
|
|
96
|
+
])));
|
|
97
|
+
}
|
|
98
|
+
if (test.op === 'all') {
|
|
99
|
+
return exists(heldValues(doc, field, 'h', all([
|
|
100
|
+
fragment("h.item = -1 AND h.kind = 'array'"),
|
|
101
|
+
...test.values.map((value) => itemOf('h', same('i', value))),
|
|
102
|
+
])));
|
|
103
|
+
}
|
|
104
|
+
return exists(heldValues(doc, field, 'v', fragment("v.item = -1 AND v.kind = 'array' AND v.size = ?", [test.size])));
|
|
105
|
+
}
|
|
106
|
+
function conditioned(doc, condition) {
|
|
107
|
+
if ('every' in condition)
|
|
108
|
+
return all(condition.every.map((one) => conditioned(doc, one)));
|
|
109
|
+
if ('some' in condition)
|
|
110
|
+
return any(condition.some.map((one) => conditioned(doc, one)));
|
|
111
|
+
return tested(doc, condition.field, condition.test);
|
|
112
|
+
}
|
|
113
|
+
function located(bound, alias) {
|
|
114
|
+
if (bound.rooms.length === 0)
|
|
115
|
+
return fragment("''");
|
|
116
|
+
return fragment(`CASE ${alias}.key ${bound.rooms.map(() => 'WHEN ? THEN ?').join(' ')} END`, bound.rooms.flatMap(({ key, locator }) => [key, locator]));
|
|
117
|
+
}
|
|
118
|
+
function reached(bound, alias) {
|
|
119
|
+
const reach = bound.reach;
|
|
120
|
+
if (reach === null)
|
|
121
|
+
return fragment('1');
|
|
122
|
+
const rooms = reach.rooms.flatMap((locator) => {
|
|
123
|
+
const key = bound.keys.get(locator);
|
|
124
|
+
return key === undefined ? [] : [key];
|
|
125
|
+
});
|
|
126
|
+
const documents = reach.documents.flatMap((held) => {
|
|
127
|
+
const key = bound.keys.get(roomOfKey(held));
|
|
128
|
+
return key === undefined ? [] : [[key, pathOfKey(held)]];
|
|
129
|
+
});
|
|
130
|
+
const areas = reach.areas.flatMap((area) => {
|
|
131
|
+
const key = bound.keys.get(area.room);
|
|
132
|
+
return key === undefined ? [] : [{ key, path: area.path, types: area.types }];
|
|
133
|
+
});
|
|
134
|
+
const beneath = (area) => fragment(`${alias}.key = ? AND (${alias}.path = ? OR (${alias}.path >= ? AND ${alias}.path < ?))`, [area.key, area.path, `${area.path}/`, `${area.path}0`]);
|
|
135
|
+
const typed = (types) => fragment(`${alias}.type IN ${list(types).sql}`, types);
|
|
136
|
+
const outside = any(areas.map((area) => area.types.length === 0
|
|
137
|
+
? beneath(area)
|
|
138
|
+
: all([beneath(area), fragment(`NOT ${grouped(typed(area.types)).sql}`, area.types)])));
|
|
139
|
+
return all([
|
|
140
|
+
any([
|
|
141
|
+
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${list(rooms).sql}`, rooms)]),
|
|
142
|
+
...(documents.length === 0
|
|
143
|
+
? []
|
|
144
|
+
: [
|
|
145
|
+
fragment(`EXISTS (SELECT 1 FROM json_each(?) j WHERE j.value ->> 0 = ${alias}.key AND j.value ->> 1 = ${alias}.path)`, [JSON.stringify(documents)]),
|
|
146
|
+
]),
|
|
147
|
+
...areas.flatMap((area) => area.types.length === 0 ? [] : [all([beneath(area), typed(area.types)])]),
|
|
148
|
+
]),
|
|
149
|
+
...(areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
|
|
150
|
+
]);
|
|
151
|
+
}
|
|
152
|
+
function scoped(alias, scope) {
|
|
153
|
+
return all([
|
|
154
|
+
...(scope.types === null
|
|
155
|
+
? []
|
|
156
|
+
: [fragment(`${alias}.type IN ${list(scope.types).sql}`, scope.types)]),
|
|
157
|
+
...(scope.pathPrefix === null || scope.pathPrefix === ''
|
|
158
|
+
? []
|
|
159
|
+
: [
|
|
160
|
+
fragment(`${alias}.path >= ? AND ${alias}.path < ?`, [
|
|
161
|
+
`${scope.pathPrefix}/`,
|
|
162
|
+
`${scope.pathPrefix}0`,
|
|
163
|
+
]),
|
|
164
|
+
]),
|
|
165
|
+
...(scope.where === null ? [] : [conditioned(alias, scope.where)]),
|
|
166
|
+
]);
|
|
167
|
+
}
|
|
168
|
+
function related(bound, plan) {
|
|
169
|
+
const parts = [];
|
|
170
|
+
if (plan.hop !== null) {
|
|
171
|
+
parts.push(exists(joined([
|
|
172
|
+
fragment(`SELECT 1 FROM replica_links l JOIN replica_entries m ON m.key = d.key AND m.path = l.target AND ${VISIBLE('m')} WHERE l.doc = d.id AND l.kind = 'field' AND`),
|
|
173
|
+
grouped(pointed('l', plan.hop.field, 'link')),
|
|
174
|
+
fragment('AND'),
|
|
175
|
+
grouped(all([scoped('m', plan.hop), reached(bound, 'm')])),
|
|
176
|
+
], ' ')));
|
|
177
|
+
}
|
|
178
|
+
if (plan.backlinks !== null) {
|
|
179
|
+
const field = plan.backlinks.field;
|
|
180
|
+
parts.push(exists(joined([
|
|
181
|
+
fragment(`SELECT 1 FROM replica_links l JOIN replica_entries s ON s.id = l.doc AND ${VISIBLE('s')} WHERE l.key = d.key AND l.target = d.path AND l.kind = 'field' AND`),
|
|
182
|
+
...(field === null ? [] : [grouped(pointed('l', field, 'link')), fragment('AND')]),
|
|
183
|
+
grouped(all([scoped('s', plan.backlinks), reached(bound, 's')])),
|
|
184
|
+
], ' ')));
|
|
185
|
+
}
|
|
186
|
+
return parts;
|
|
187
|
+
}
|
|
188
|
+
function filtered(bound, plan) {
|
|
189
|
+
const keys = bound.rooms.map(({ key }) => key);
|
|
190
|
+
return all([
|
|
191
|
+
fragment(`d.key IN ${list(keys).sql} AND ${VISIBLE('d')}`, keys),
|
|
192
|
+
...(plan.paths === null ? [] : [fragment(`d.path IN ${list(plan.paths).sql}`, plan.paths)]),
|
|
193
|
+
...(plan.folder === null ? [] : [fragment('d.folder = ?', [plan.folder])]),
|
|
194
|
+
reached(bound, 'd'),
|
|
195
|
+
scoped('d', plan.root),
|
|
196
|
+
...related(bound, plan),
|
|
197
|
+
]);
|
|
198
|
+
}
|
|
199
|
+
function orderingOf(options) {
|
|
200
|
+
const stringSort = options.stringSort ?? 'locale';
|
|
201
|
+
const collated = stringSort === 'locale';
|
|
202
|
+
return {
|
|
203
|
+
direction: options.direction,
|
|
204
|
+
nulls: options.nulls ?? 'first',
|
|
205
|
+
stringSort,
|
|
206
|
+
locale: collated ? options.locale : undefined,
|
|
207
|
+
localeOptions: collated
|
|
208
|
+
? options.localeOptions
|
|
209
|
+
: undefined,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const GROUP_ORDER = {
|
|
213
|
+
direction: 'asc',
|
|
214
|
+
nulls: 'first',
|
|
215
|
+
stringSort: 'locale',
|
|
216
|
+
locale: undefined,
|
|
217
|
+
localeOptions: undefined,
|
|
218
|
+
};
|
|
219
|
+
const GROUP_TIES = {
|
|
220
|
+
direction: 'asc',
|
|
221
|
+
nulls: 'last',
|
|
222
|
+
stringSort: 'lexical',
|
|
223
|
+
locale: undefined,
|
|
224
|
+
localeOptions: undefined,
|
|
225
|
+
};
|
|
226
|
+
function classOf(value, ordering) {
|
|
227
|
+
if (value === null || value === undefined) {
|
|
228
|
+
return { placement: ordering.nulls === 'first' ? 0 : 2, klass: 0, scalar: null };
|
|
229
|
+
}
|
|
230
|
+
if (typeof value === 'boolean')
|
|
231
|
+
return { placement: 1, klass: 1, scalar: value ? 1 : 0 };
|
|
232
|
+
if (typeof value === 'number')
|
|
233
|
+
return { placement: 1, klass: 1, scalar: value };
|
|
234
|
+
if (typeof value === 'string')
|
|
235
|
+
return { placement: 1, klass: 2, scalar: value };
|
|
236
|
+
return { placement: 1, klass: 3, scalar: JSON.stringify(value) };
|
|
237
|
+
}
|
|
238
|
+
function placement(term) {
|
|
239
|
+
const nulls = term.ordering.nulls === 'first' ? 0 : 2;
|
|
240
|
+
return fragment(`CASE WHEN ${term.kind.sql} IS NULL OR ${term.kind.sql} = 'null' THEN ${nulls} ELSE 1 END`, [...term.kind.bind, ...term.kind.bind]);
|
|
241
|
+
}
|
|
242
|
+
function klass(term) {
|
|
243
|
+
return fragment(`CASE ${term.kind.sql} WHEN 'boolean' THEN 1 WHEN 'number' THEN 1 WHEN 'string' THEN 2 WHEN 'array' THEN 3 ELSE 4 END`, term.kind.bind);
|
|
244
|
+
}
|
|
245
|
+
function classed(term, which) {
|
|
246
|
+
const kinds = which === 1 ? "('boolean', 'number')" : which === 2 ? "('string')" : "('array')";
|
|
247
|
+
return fragment(`(CASE WHEN ${term.kind.sql} IN ${kinds} THEN ${term.value.sql} END)`, [
|
|
248
|
+
...term.kind.bind,
|
|
249
|
+
...term.value.bind,
|
|
250
|
+
]);
|
|
251
|
+
}
|
|
252
|
+
function collated(bound, term, which) {
|
|
253
|
+
if (which === 1)
|
|
254
|
+
return '';
|
|
255
|
+
const name = bound.collate(term.ordering, which === 3, term.total === true);
|
|
256
|
+
return name === null ? '' : ` COLLATE ${name}`;
|
|
257
|
+
}
|
|
258
|
+
function sorted(bound, term) {
|
|
259
|
+
const direction = term.ordering.direction === 'desc' ? 'DESC' : 'ASC';
|
|
260
|
+
return joined([
|
|
261
|
+
placement(term),
|
|
262
|
+
fragment(`${klass(term).sql} ${direction}`, klass(term).bind),
|
|
263
|
+
...[1, 2, 3].map((which) => {
|
|
264
|
+
const value = classed(term, which);
|
|
265
|
+
return fragment(`${value.sql}${collated(bound, term, which)} ${direction}`, value.bind);
|
|
266
|
+
}),
|
|
267
|
+
], ', ');
|
|
268
|
+
}
|
|
269
|
+
function beyond(bound, term, value) {
|
|
270
|
+
const held = classOf(value, term.ordering);
|
|
271
|
+
const place = placement(term);
|
|
272
|
+
if (held.placement !== 1) {
|
|
273
|
+
return {
|
|
274
|
+
beyond: fragment(`${place.sql} > ${held.placement}`, place.bind),
|
|
275
|
+
equal: fragment(`${place.sql} = ${held.placement}`, place.bind),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
const after = term.ordering.direction === 'desc' ? '<' : '>';
|
|
279
|
+
const kind = klass(term);
|
|
280
|
+
const scalar = classed(term, held.klass);
|
|
281
|
+
const collation = collated(bound, term, held.klass);
|
|
282
|
+
return {
|
|
283
|
+
beyond: any([
|
|
284
|
+
fragment(`${place.sql} > 1`, place.bind),
|
|
285
|
+
all([
|
|
286
|
+
fragment(`${place.sql} = 1`, place.bind),
|
|
287
|
+
any([
|
|
288
|
+
fragment(`${kind.sql} ${after} ${held.klass}`, kind.bind),
|
|
289
|
+
all([
|
|
290
|
+
fragment(`${kind.sql} = ${held.klass}`, kind.bind),
|
|
291
|
+
fragment(`${scalar.sql} ${after} ?${collation}`, [...scalar.bind, held.scalar]),
|
|
292
|
+
]),
|
|
293
|
+
]),
|
|
294
|
+
]),
|
|
295
|
+
]),
|
|
296
|
+
equal: all([
|
|
297
|
+
fragment(`${place.sql} = 1 AND ${kind.sql} = ${held.klass}`, [...place.bind, ...kind.bind]),
|
|
298
|
+
fragment(`${scalar.sql} = ?${collation}`, [...scalar.bind, held.scalar]),
|
|
299
|
+
]),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function following(parts, tie) {
|
|
303
|
+
let condition = tie;
|
|
304
|
+
for (const part of [...parts].reverse()) {
|
|
305
|
+
condition = any([part.beyond, all([part.equal, condition])]);
|
|
306
|
+
}
|
|
307
|
+
return condition;
|
|
308
|
+
}
|
|
309
|
+
function keyTerm(bound, key, ordering, index) {
|
|
310
|
+
if (key.of === 'field') {
|
|
311
|
+
const alias = `o${index}`;
|
|
312
|
+
return {
|
|
313
|
+
term: { ordering, kind: fragment(`${alias}.kind`), value: fragment(`${alias}.value`) },
|
|
314
|
+
join: joined([
|
|
315
|
+
fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
|
|
316
|
+
grouped(pointed(alias, key.field, 'exact')),
|
|
317
|
+
fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
|
|
318
|
+
], ''),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
const expression = key.of === 'room'
|
|
322
|
+
? located(bound, 'd')
|
|
323
|
+
: key.of === 'path'
|
|
324
|
+
? fragment('d.path')
|
|
325
|
+
: key.of === 'type'
|
|
326
|
+
? fragment('d.type')
|
|
327
|
+
: fragment('d.folder');
|
|
328
|
+
return {
|
|
329
|
+
term: {
|
|
330
|
+
ordering,
|
|
331
|
+
kind: fragment(`(CASE WHEN ${expression.sql} IS NULL THEN 'null' ELSE 'string' END)`, expression.bind),
|
|
332
|
+
value: expression,
|
|
333
|
+
},
|
|
334
|
+
join: null,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
function decoded(kind, value) {
|
|
338
|
+
if (kind === 'string')
|
|
339
|
+
return value;
|
|
340
|
+
if (kind === 'number')
|
|
341
|
+
return typeof value === 'bigint' ? Number(value) : value;
|
|
342
|
+
if (kind === 'boolean')
|
|
343
|
+
return Number(value) === 1;
|
|
344
|
+
if (kind === 'array')
|
|
345
|
+
return JSON.parse(value);
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
function decodedKey(key, row, index) {
|
|
349
|
+
if (key.of === 'field') {
|
|
350
|
+
const kind = row[`k${index}`];
|
|
351
|
+
return kind === null || kind === undefined ? undefined : decoded(kind, row[`v${index}`]);
|
|
352
|
+
}
|
|
353
|
+
return row[`v${index}`] ?? null;
|
|
354
|
+
}
|
|
355
|
+
const registered = new WeakMap();
|
|
356
|
+
function hashed(text) {
|
|
357
|
+
let hash = 0x811c9dc5;
|
|
358
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
359
|
+
hash ^= text.charCodeAt(index);
|
|
360
|
+
hash = Math.imul(hash, 0x01000193);
|
|
361
|
+
}
|
|
362
|
+
return (hash >>> 0).toString(36);
|
|
363
|
+
}
|
|
364
|
+
function collating(connection) {
|
|
365
|
+
let held = registered.get(connection);
|
|
366
|
+
if (held === undefined) {
|
|
367
|
+
held = new Set();
|
|
368
|
+
registered.set(connection, held);
|
|
369
|
+
}
|
|
370
|
+
const names = held;
|
|
371
|
+
return (ordering, lists, total) => {
|
|
372
|
+
if (!lists && ordering.stringSort !== 'locale')
|
|
373
|
+
return null;
|
|
374
|
+
const inner = lists
|
|
375
|
+
? {
|
|
376
|
+
...ordering,
|
|
377
|
+
direction: 'asc',
|
|
378
|
+
nulls: ordering.direction === 'desc'
|
|
379
|
+
? ordering.nulls === 'first'
|
|
380
|
+
? 'last'
|
|
381
|
+
: 'first'
|
|
382
|
+
: ordering.nulls,
|
|
383
|
+
}
|
|
384
|
+
: ordering;
|
|
385
|
+
const spec = JSON.stringify([
|
|
386
|
+
lists,
|
|
387
|
+
total,
|
|
388
|
+
inner.nulls,
|
|
389
|
+
inner.stringSort,
|
|
390
|
+
inner.locale ?? null,
|
|
391
|
+
inner.localeOptions ?? null,
|
|
392
|
+
]);
|
|
393
|
+
const name = `mdr_${lists ? 'l' : 's'}${hashed(spec)}`;
|
|
394
|
+
if (!names.has(name)) {
|
|
395
|
+
const collator = new Intl.Collator(inner.locale, inner.localeOptions);
|
|
396
|
+
connection.collate(name, lists
|
|
397
|
+
? (left, right) => Math.sign(compared(JSON.parse(left), JSON.parse(right), inner)) || (total ? Math.sign(byCodePoint(left, right)) : 0)
|
|
398
|
+
: (left, right) => Math.sign(collator.compare(left, right)));
|
|
399
|
+
names.add(name);
|
|
400
|
+
}
|
|
401
|
+
return name;
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function bodied(shape) {
|
|
405
|
+
return shape.sections !== null || shape.preamble || shape.markdown;
|
|
406
|
+
}
|
|
407
|
+
function masked(pointers) {
|
|
408
|
+
const root = { whole: false, members: new Map() };
|
|
409
|
+
for (const pointer of pointers) {
|
|
410
|
+
let mask = root;
|
|
411
|
+
for (const token of pointerSegments(pointer)) {
|
|
412
|
+
const next = mask.members.get(token) ?? { whole: false, members: new Map() };
|
|
413
|
+
mask.members.set(token, next);
|
|
414
|
+
mask = next;
|
|
415
|
+
}
|
|
416
|
+
mask.whole = true;
|
|
417
|
+
}
|
|
418
|
+
return root;
|
|
419
|
+
}
|
|
420
|
+
function merged(left, right) {
|
|
421
|
+
if (left === undefined)
|
|
422
|
+
return right;
|
|
423
|
+
const members = new Map(left.members);
|
|
424
|
+
for (const [token, mask] of right.members)
|
|
425
|
+
members.set(token, merged(members.get(token), mask));
|
|
426
|
+
return { whole: left.whole || right.whole, members };
|
|
427
|
+
}
|
|
428
|
+
function cut(value, mask) {
|
|
429
|
+
if (mask.whole)
|
|
430
|
+
return value;
|
|
431
|
+
if (Array.isArray(value)) {
|
|
432
|
+
const each = {
|
|
433
|
+
whole: false,
|
|
434
|
+
members: new Map([...mask.members].filter(([token]) => !ARRAY_INDEX.test(token))),
|
|
435
|
+
};
|
|
436
|
+
const kept = [];
|
|
437
|
+
value.forEach((item, index) => {
|
|
438
|
+
const part = cut(item, merged(mask.members.get(String(index)), each));
|
|
439
|
+
if (part !== undefined)
|
|
440
|
+
kept[index] = part;
|
|
441
|
+
});
|
|
442
|
+
return kept.length === 0 ? undefined : kept;
|
|
443
|
+
}
|
|
444
|
+
if (typeof value !== 'object' || value === null)
|
|
445
|
+
return undefined;
|
|
446
|
+
const kept = Object.entries(value).flatMap(([key, held]) => {
|
|
447
|
+
const member = mask.members.get(key);
|
|
448
|
+
const part = member === undefined ? undefined : cut(held, member);
|
|
449
|
+
return part === undefined ? [] : [[key, part]];
|
|
450
|
+
});
|
|
451
|
+
return kept.length === 0 ? undefined : Object.fromEntries(kept);
|
|
452
|
+
}
|
|
453
|
+
function sectionsOf(sections, names) {
|
|
454
|
+
if (names === 'all')
|
|
455
|
+
return sections;
|
|
456
|
+
return Object.fromEntries(names.flatMap((name) => (Object.hasOwn(sections, name) ? [[name, sections[name]]] : [])));
|
|
457
|
+
}
|
|
458
|
+
function documentOf(shape, room, row, linked) {
|
|
459
|
+
const path = row.path;
|
|
460
|
+
const type = row.type;
|
|
461
|
+
const fileId = row.file_id ?? null;
|
|
462
|
+
const revision = row.revision ?? null;
|
|
463
|
+
if (type === null) {
|
|
464
|
+
const binary = row.binary === null ? null : JSON.parse(row.binary);
|
|
465
|
+
const content = row.content ?? null;
|
|
466
|
+
return {
|
|
467
|
+
room,
|
|
468
|
+
path,
|
|
469
|
+
...(binary === null
|
|
470
|
+
? {}
|
|
471
|
+
: {
|
|
472
|
+
binaryFile: binaryRecord(path, binary, revision ?? undefined),
|
|
473
|
+
}),
|
|
474
|
+
...(shape.markdown && content !== null && settingsTextPath(path)
|
|
475
|
+
? { markdown: content }
|
|
476
|
+
: {}),
|
|
477
|
+
...(shape.fileId ? { fileId } : {}),
|
|
478
|
+
...(shape.revision ? { revision } : {}),
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
const read = bodied(shape) ? parse(row.content, path) : null;
|
|
482
|
+
const body = read?.ok === true ? read.document : null;
|
|
483
|
+
const fields = JSON.parse(row.fields ?? '{}');
|
|
484
|
+
return {
|
|
485
|
+
room,
|
|
486
|
+
path,
|
|
487
|
+
type,
|
|
488
|
+
...(shape.fields === null
|
|
489
|
+
? {}
|
|
490
|
+
: {
|
|
491
|
+
fields: shape.fields === 'all'
|
|
492
|
+
? fields
|
|
493
|
+
: (cut(fields, masked(shape.fields)) ?? {}),
|
|
494
|
+
}),
|
|
495
|
+
...(shape.sections === null || body === null
|
|
496
|
+
? {}
|
|
497
|
+
: { sections: sectionsOf(body.sections, shape.sections) }),
|
|
498
|
+
...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
|
|
499
|
+
...(shape.markdown && body !== null ? { markdown: body.content } : {}),
|
|
500
|
+
...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
|
|
501
|
+
...(shape.change
|
|
502
|
+
? {
|
|
503
|
+
change: row.change === null ? null : JSON.parse(row.change),
|
|
504
|
+
}
|
|
505
|
+
: {}),
|
|
506
|
+
...(shape.fileId ? { fileId } : {}),
|
|
507
|
+
...(shape.revision ? { revision } : {}),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function backlinksOf(connection, bound, rows) {
|
|
511
|
+
const linked = new Map();
|
|
512
|
+
const byKey = new Map();
|
|
513
|
+
for (const row of rows) {
|
|
514
|
+
const key = Number(row.key);
|
|
515
|
+
const held = byKey.get(key) ?? [];
|
|
516
|
+
held.push(row.path);
|
|
517
|
+
byKey.set(key, held);
|
|
518
|
+
}
|
|
519
|
+
for (const [key, targets] of byKey) {
|
|
520
|
+
const reach = reached(bound, 's');
|
|
521
|
+
const room = bound.rooms.find((one) => one.key === key).locator;
|
|
522
|
+
const found = connection.rows(`SELECT target, path, type, field FROM (SELECT l.target, s.path, s.type, l.field, ROW_NUMBER() OVER (PARTITION BY l.target ORDER BY s.path, l.field) AS rank FROM (SELECT DISTINCT doc, target, field FROM replica_links WHERE key = ? AND kind = 'field' AND target IN (SELECT value FROM json_each(?))) l JOIN replica_entries s ON s.id = l.doc AND ${VISIBLE('s')} WHERE ${reach.sql}) WHERE rank <= ? ORDER BY target, path, field`, [key, JSON.stringify(targets), ...reach.bind, MAXIMUM_WINDOW]);
|
|
523
|
+
for (const row of found) {
|
|
524
|
+
const at = documentKey(room, row.target);
|
|
525
|
+
const held = linked.get(at) ?? [];
|
|
526
|
+
held.push({ path: row.path, type: row.type, field: row.field });
|
|
527
|
+
linked.set(at, held);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return linked;
|
|
531
|
+
}
|
|
532
|
+
function documents(connection, bound, plan, shape, budget) {
|
|
533
|
+
const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(bound, key, orderingOf(options), index));
|
|
534
|
+
const filter = filtered(bound, plan);
|
|
535
|
+
const cursor = plan.after?.values ?? null;
|
|
536
|
+
const room = located(bound, 'd');
|
|
537
|
+
const tie = cursor === null
|
|
538
|
+
? null
|
|
539
|
+
: (() => {
|
|
540
|
+
const key = cursor[shape.orderBy.length];
|
|
541
|
+
return any([
|
|
542
|
+
fragment(`${room.sql} > ?`, [...room.bind, roomOfKey(key)]),
|
|
543
|
+
fragment(`${room.sql} = ? AND d.path > ?`, [
|
|
544
|
+
...room.bind,
|
|
545
|
+
roomOfKey(key),
|
|
546
|
+
pathOfKey(key),
|
|
547
|
+
]),
|
|
548
|
+
]);
|
|
549
|
+
})();
|
|
550
|
+
const after = cursor === null || tie === null
|
|
551
|
+
? null
|
|
552
|
+
: following(terms.map(({ term }, index) => beyond(bound, term, cursor[index])), tie);
|
|
553
|
+
const selected = joined([
|
|
554
|
+
fragment('SELECT d.id, d.key, d.path, d.type, d.binary, d.revision, d.fields, d.change, d.file_id'),
|
|
555
|
+
...terms.map(({ term }, index) => joined([
|
|
556
|
+
fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
557
|
+
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
558
|
+
], '')),
|
|
559
|
+
fragment(' FROM replica_entries d'),
|
|
560
|
+
...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
|
|
561
|
+
fragment(' WHERE '),
|
|
562
|
+
after === null ? filter : all([filter, after]),
|
|
563
|
+
fragment(' ORDER BY '),
|
|
564
|
+
joined([...terms.map(({ term }) => sorted(bound, term)), room, fragment('d.path')], ', '),
|
|
565
|
+
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
566
|
+
], '');
|
|
567
|
+
const rows = connection.rows(selected.sql, selected.bind);
|
|
568
|
+
const windowed = plan.limit === null ? rows : rows.slice(0, plan.limit);
|
|
569
|
+
const kept = bodied(shape) ? withContent(connection, windowed, budget) : windowed;
|
|
570
|
+
const cut = kept.length;
|
|
571
|
+
const roomOf = new Map(bound.rooms.map(({ key, locator }) => [key, locator]));
|
|
572
|
+
const linked = shape.backlinks ? backlinksOf(connection, bound, kept) : new Map();
|
|
573
|
+
const found = kept.map((row) => documentOf(shape, roomOf.get(Number(row.key)), row, linked));
|
|
574
|
+
const last = kept.at(-1);
|
|
575
|
+
const cursorAt = (row) => cursorOf([
|
|
576
|
+
...shape.orderBy.map(({ key }, index) => decodedKey(key, row, index)),
|
|
577
|
+
documentKey(roomOf.get(Number(row.key)), row.path),
|
|
578
|
+
]);
|
|
579
|
+
const more = cut < windowed.length && last !== undefined ? cursorAt(last) : undefined;
|
|
580
|
+
const next = more !== undefined || plan.limit === null || rows.length <= plan.limit
|
|
581
|
+
? undefined
|
|
582
|
+
: last === undefined
|
|
583
|
+
? (plan.after?.text ?? FROM_START)
|
|
584
|
+
: cursorAt(last);
|
|
585
|
+
const total = plan.total
|
|
586
|
+
? (() => {
|
|
587
|
+
const counted = joined([fragment('SELECT count(*) AS held FROM replica_entries d WHERE '), filter], '');
|
|
588
|
+
return Number(connection.rows(counted.sql, counted.bind)[0].held);
|
|
589
|
+
})()
|
|
590
|
+
: undefined;
|
|
591
|
+
return {
|
|
592
|
+
documents: found,
|
|
593
|
+
...(next === undefined ? {} : { next }),
|
|
594
|
+
...(more === undefined ? {} : { more }),
|
|
595
|
+
...(total === undefined ? {} : { total }),
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
function withContent(connection, rows, budget) {
|
|
599
|
+
if (rows.length === 0)
|
|
600
|
+
return rows;
|
|
601
|
+
const ids = rows.map((row) => row.id);
|
|
602
|
+
const sizes = new Map(connection
|
|
603
|
+
.rows(`SELECT id, octet_length(content) AS size FROM replica_entries WHERE id IN ${list(ids).sql}`, ids)
|
|
604
|
+
.map((row) => [row.id, Number(row.size ?? 0)]));
|
|
605
|
+
let cut = rows.length;
|
|
606
|
+
let spent = 0;
|
|
607
|
+
for (let at = 0; budget !== null && at < rows.length; at += 1) {
|
|
608
|
+
const size = sizes.get(rows[at].id) ?? 0;
|
|
609
|
+
if (at > 0 && spent + size > budget) {
|
|
610
|
+
cut = at;
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
spent += size;
|
|
614
|
+
}
|
|
615
|
+
const kept = rows.slice(0, cut);
|
|
616
|
+
const read = kept.map((row) => row.id);
|
|
617
|
+
const held = new Map(connection
|
|
618
|
+
.rows(`SELECT id, content FROM replica_entries WHERE id IN ${list(read).sql}`, read)
|
|
619
|
+
.map((row) => [row.id, row.content]));
|
|
620
|
+
return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
|
|
621
|
+
}
|
|
622
|
+
function storedFolders(bound, plan) {
|
|
623
|
+
if (plan.paths !== null || plan.hop !== null || plan.backlinks !== null)
|
|
624
|
+
return null;
|
|
625
|
+
const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
|
|
626
|
+
if (whole.length === 0)
|
|
627
|
+
return null;
|
|
628
|
+
const keys = whole.map(({ key }) => key);
|
|
629
|
+
const prefix = plan.root.pathPrefix;
|
|
630
|
+
const fenced = (bound.reach?.areas ?? []).flatMap((area) => {
|
|
631
|
+
const key = bound.keys.get(area.room);
|
|
632
|
+
return key === undefined
|
|
633
|
+
? []
|
|
634
|
+
: [
|
|
635
|
+
fragment('NOT (f.key = ? AND (f.path = ? OR (f.path >= ? AND f.path < ?)))', [
|
|
636
|
+
key,
|
|
637
|
+
area.path,
|
|
638
|
+
`${area.path}/`,
|
|
639
|
+
`${area.path}0`,
|
|
640
|
+
]),
|
|
641
|
+
];
|
|
642
|
+
});
|
|
643
|
+
return all([
|
|
644
|
+
fragment(`f.key IN ${list(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
|
|
645
|
+
...fenced,
|
|
646
|
+
...(prefix === null || prefix === ''
|
|
647
|
+
? []
|
|
648
|
+
: [
|
|
649
|
+
fragment('(f.path = ? OR (f.path >= ? AND f.path < ?))', [
|
|
650
|
+
prefix,
|
|
651
|
+
`${prefix}/`,
|
|
652
|
+
`${prefix}0`,
|
|
653
|
+
]),
|
|
654
|
+
]),
|
|
655
|
+
...(plan.folder === null ? [] : [fragment('f.path = ?', [plan.folder])]),
|
|
656
|
+
]);
|
|
657
|
+
}
|
|
658
|
+
function groups(connection, bound, plan, shape) {
|
|
659
|
+
const keyed = shape.groupBy.map((key, index) => {
|
|
660
|
+
const { term, join } = keyTerm(bound, key, GROUP_ORDER, index);
|
|
661
|
+
const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
|
|
662
|
+
return { term: { ...term, kind }, join };
|
|
663
|
+
});
|
|
664
|
+
const filter = filtered(bound, plan);
|
|
665
|
+
const inner = joined([
|
|
666
|
+
fragment('SELECT '),
|
|
667
|
+
joined(keyed.map(({ term }, index) => joined([
|
|
668
|
+
fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
669
|
+
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
670
|
+
], '')), ', '),
|
|
671
|
+
fragment(', 1 AS n FROM replica_entries d'),
|
|
672
|
+
...keyed.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
|
|
673
|
+
fragment(' WHERE '),
|
|
674
|
+
filter,
|
|
675
|
+
], '');
|
|
676
|
+
const folders = shape.groupBy.length === 1 && shape.groupBy[0].of === 'folder'
|
|
677
|
+
? storedFolders(bound, plan)
|
|
678
|
+
: null;
|
|
679
|
+
const source = folders === null
|
|
680
|
+
? inner
|
|
681
|
+
: joined([
|
|
682
|
+
inner,
|
|
683
|
+
fragment(" UNION ALL SELECT 'string' AS k0, f.path AS v0, 0 AS n FROM replica_folders f WHERE "),
|
|
684
|
+
folders,
|
|
685
|
+
], '');
|
|
686
|
+
const columns = keyed.map((_, index) => `k${index}, v${index}`).join(', ');
|
|
687
|
+
const counted = {
|
|
688
|
+
ordering: { ...GROUP_ORDER },
|
|
689
|
+
kind: fragment("'number'"),
|
|
690
|
+
value: fragment('n'),
|
|
691
|
+
};
|
|
692
|
+
const outer = (index, ordering) => ({
|
|
693
|
+
ordering,
|
|
694
|
+
kind: fragment(`k${index}`),
|
|
695
|
+
value: fragment(`v${index}`),
|
|
696
|
+
});
|
|
697
|
+
const ordered = shape.orderBy.map(({ by, direction }) => by === 'count'
|
|
698
|
+
? { ...counted, ordering: { ...GROUP_ORDER, direction } }
|
|
699
|
+
: outer(by, { ...GROUP_ORDER, direction }));
|
|
700
|
+
const ties = keyed.flatMap((_, index) => [
|
|
701
|
+
{ ...outer(index, GROUP_TIES), total: true },
|
|
702
|
+
{ ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
|
|
703
|
+
]);
|
|
704
|
+
const cursor = plan.after?.values ?? null;
|
|
705
|
+
const tieValues = cursor === null
|
|
706
|
+
? null
|
|
707
|
+
: cursor[shape.orderBy.length].flatMap((value) => [
|
|
708
|
+
value,
|
|
709
|
+
kindOf(value ?? null),
|
|
710
|
+
]);
|
|
711
|
+
const after = cursor === null || tieValues === null
|
|
712
|
+
? null
|
|
713
|
+
: following(ordered.map((term, index) => beyond(bound, term, cursor[index])), following(ties.slice(0, -1).map((term, index) => beyond(bound, term, tieValues[index])), beyond(bound, ties.at(-1), tieValues.at(-1)).beyond));
|
|
714
|
+
const grouping = joined([fragment(`SELECT ${columns}, sum(n) AS n FROM (`), source, fragment(`) GROUP BY ${columns}`)], '');
|
|
715
|
+
const windowed = joined([
|
|
716
|
+
fragment('SELECT * FROM ('),
|
|
717
|
+
grouping,
|
|
718
|
+
fragment(')'),
|
|
719
|
+
...(after === null ? [] : [fragment(' WHERE '), after]),
|
|
720
|
+
fragment(' ORDER BY '),
|
|
721
|
+
joined([...ordered, ...ties].map((term) => sorted(bound, term)), ', '),
|
|
722
|
+
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
723
|
+
], '');
|
|
724
|
+
const rows = connection.rows(windowed.sql, windowed.bind);
|
|
725
|
+
const kept = plan.limit === null ? rows : rows.slice(0, plan.limit);
|
|
726
|
+
const keyOf = (row) => keyed.map((_, index) => decoded(row[`k${index}`], row[`v${index}`]));
|
|
727
|
+
const found = kept.map((row) => ({ key: keyOf(row), count: Number(row.n) }));
|
|
728
|
+
const last = kept.at(-1);
|
|
729
|
+
const next = plan.limit === null || rows.length <= plan.limit
|
|
730
|
+
? undefined
|
|
731
|
+
: last === undefined
|
|
732
|
+
? (plan.after?.text ?? FROM_START)
|
|
733
|
+
: cursorOf([
|
|
734
|
+
...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : decoded(last[`k${by}`], last[`v${by}`])),
|
|
735
|
+
keyOf(last),
|
|
736
|
+
]);
|
|
737
|
+
const total = plan.total
|
|
738
|
+
? Number(connection.rows(joined([fragment('SELECT count(*) AS held FROM ('), grouping, fragment(')')], '').sql, grouping.bind)[0].held)
|
|
739
|
+
: undefined;
|
|
740
|
+
return {
|
|
741
|
+
groups: found,
|
|
742
|
+
...(next === undefined ? {} : { next }),
|
|
743
|
+
...(total === undefined ? {} : { total }),
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
function boundOf(connection, plan, rooms) {
|
|
747
|
+
return {
|
|
748
|
+
rooms: [...rooms].sort((left, right) => left.locator < right.locator ? -1 : left.locator > right.locator ? 1 : 0),
|
|
749
|
+
keys: new Map(rooms.map(({ locator, key }) => [locator, key])),
|
|
750
|
+
reach: plan.reach,
|
|
751
|
+
collate: collating(connection),
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
export function selected(connection, plan, rooms) {
|
|
755
|
+
return filtered(boundOf(connection, plan, rooms), plan);
|
|
756
|
+
}
|
|
757
|
+
export function answerQuery(connection, plan, rooms, budget = null) {
|
|
758
|
+
const bound = boundOf(connection, plan, rooms);
|
|
759
|
+
if (rooms.length === 0) {
|
|
760
|
+
return plan.shape.kind === 'groups'
|
|
761
|
+
? { groups: [], ...(plan.total ? { total: 0 } : {}) }
|
|
762
|
+
: { documents: [], ...(plan.total ? { total: 0 } : {}) };
|
|
763
|
+
}
|
|
764
|
+
return plan.shape.kind === 'groups'
|
|
765
|
+
? groups(connection, bound, plan, plan.shape)
|
|
766
|
+
: documents(connection, bound, plan, plan.shape, budget);
|
|
767
|
+
}
|