@agentshouse/mdruntime 0.1.0-c588e78cf6975 → 0.1.0-c59a5a6bd2f31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/acceptance.d.ts +26 -0
- package/dist/acceptance.d.ts.map +1 -0
- package/dist/acceptance.js +384 -0
- package/dist/adapter.d.ts +38 -25
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +3 -10
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +21 -56
- package/dist/bootstrap.d.ts +8 -4
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +209 -78
- package/dist/chain.d.ts +2 -1
- package/dist/chain.d.ts.map +1 -1
- package/dist/change.d.ts +1 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/chat-types.d.ts +3 -0
- package/dist/chat-types.d.ts.map +1 -0
- package/dist/chat-types.js +2 -0
- package/dist/client.d.ts +14 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -0
- package/dist/collection.d.ts +2 -1
- package/dist/collection.d.ts.map +1 -1
- package/dist/compile.d.ts +7 -7
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +229 -297
- package/dist/connection.d.ts +19 -43
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1325 -3586
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +7 -0
- package/dist/conversation-row.d.ts +33 -0
- package/dist/conversation-row.d.ts.map +1 -0
- package/dist/conversation-row.js +47 -0
- package/dist/conversation.d.ts +18 -9
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +375 -224
- package/dist/durability.d.ts +6 -0
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +2 -2
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +17 -8
- package/dist/figures.d.ts +48 -0
- package/dist/figures.d.ts.map +1 -0
- package/dist/figures.js +197 -0
- package/dist/host.d.ts +4 -5
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +22 -21
- package/dist/identity.d.ts +0 -1
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +0 -3
- package/dist/index.d.ts +18 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/install.d.ts +86 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +537 -0
- package/dist/job.d.ts +33 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +54 -0
- package/dist/live.d.ts +5 -4
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +62 -20
- package/dist/log.d.ts +1 -0
- package/dist/log.d.ts.map +1 -1
- package/dist/memory.d.ts +4 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +43 -4
- package/dist/messages.d.ts +27 -3
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +97 -19
- package/dist/model.d.ts +2 -9
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +0 -9
- package/dist/observation.d.ts +2 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +7 -2
- package/dist/outcome.d.ts +1 -0
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +3 -0
- package/dist/overlay.d.ts +1 -0
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +11 -0
- package/dist/parts.d.ts +59 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +118 -0
- package/dist/query.d.ts +13 -210
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -508
- package/dist/questions.d.ts +32 -0
- package/dist/questions.d.ts.map +1 -0
- package/dist/questions.js +118 -0
- package/dist/reach.d.ts +11 -11
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +166 -113
- package/dist/react.d.ts +4 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/read.d.ts +7 -0
- package/dist/read.d.ts.map +1 -0
- package/dist/read.js +17 -0
- package/dist/readiness.d.ts +0 -2
- package/dist/readiness.d.ts.map +1 -1
- package/dist/reads.d.ts +35 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +128 -0
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +9 -1
- package/dist/refresh.d.ts +1 -1
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +8 -20
- package/dist/replica.d.ts +8 -8
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +50 -18
- package/dist/room-draft.d.ts +3 -2
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +4 -4
- package/dist/room-drafts.d.ts +29 -0
- package/dist/room-drafts.d.ts.map +1 -0
- package/dist/room-drafts.js +216 -0
- package/dist/rows.d.ts +14 -22
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +48 -189
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +11 -8
- package/dist/saves.d.ts +96 -0
- package/dist/saves.d.ts.map +1 -0
- package/dist/saves.js +999 -0
- package/dist/search.d.ts +12 -63
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +9 -52
- package/dist/seed.d.ts +16 -2
- package/dist/seed.d.ts.map +1 -1
- package/dist/seed.js +66 -0
- package/dist/socket/admission.d.ts +7 -0
- package/dist/socket/admission.d.ts.map +1 -0
- package/dist/socket/admission.js +39 -0
- package/dist/socket/answers.d.ts +6 -0
- package/dist/socket/answers.d.ts.map +1 -0
- package/dist/socket/answers.js +20 -0
- package/dist/socket/reads.d.ts +10 -0
- package/dist/socket/reads.d.ts.map +1 -0
- package/dist/socket/reads.js +31 -0
- package/dist/socket.d.ts +43 -0
- package/dist/socket.d.ts.map +1 -0
- package/dist/socket.js +96 -0
- package/dist/source.d.ts +61 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +81 -0
- package/dist/status.d.ts +0 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +0 -6
- package/dist/store/fences.d.ts +16 -0
- package/dist/store/fences.d.ts.map +1 -0
- package/dist/store/fences.js +26 -0
- package/dist/store/installs.d.ts +30 -0
- package/dist/store/installs.d.ts.map +1 -0
- package/dist/store/installs.js +34 -0
- package/dist/store/records.d.ts +212 -0
- package/dist/store/records.d.ts.map +1 -0
- package/dist/store/records.js +408 -0
- package/dist/store/room-held.d.ts +8 -0
- package/dist/store/room-held.d.ts.map +1 -0
- package/dist/store/room-held.js +17 -0
- package/dist/store/watchers.d.ts +15 -0
- package/dist/store/watchers.d.ts.map +1 -0
- package/dist/store/watchers.js +36 -0
- package/dist/store/writes.d.ts +24 -0
- package/dist/store/writes.d.ts.map +1 -0
- package/dist/store/writes.js +125 -0
- package/dist/store.d.ts +78 -156
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +117 -401
- package/dist/stored.d.ts +58 -65
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +264 -243
- package/dist/tables.d.ts +107 -51
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +193 -59
- package/dist/tallies.d.ts +18 -0
- package/dist/tallies.d.ts.map +1 -0
- package/dist/tallies.js +129 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +7 -4
- package/dist/windows.d.ts +1 -12
- package/dist/windows.d.ts.map +1 -1
- package/dist/windows.js +2 -75
- package/dist/wire.d.ts +14 -0
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +5 -0
- package/package.json +2 -2
- package/dist/fence.d.ts +0 -6
- package/dist/fence.d.ts.map +0 -1
- package/dist/fence.js +0 -10
- package/dist/fulltext.d.ts +0 -27
- package/dist/fulltext.d.ts.map +0 -1
- package/dist/fulltext.js +0 -370
- package/dist/words.d.ts +0 -19
- package/dist/words.d.ts.map +0 -1
- package/dist/words.js +0 -156
package/dist/compile.js
CHANGED
|
@@ -1,126 +1,13 @@
|
|
|
1
|
-
import { ARRAY_INDEX, parse, pointerSegments } from '@agentshouse/mdmodel';
|
|
1
|
+
import { ARRAY_INDEX, bodied, byCodePoint, compared, cursorOf, heldKind, parse, pointerSegments, sql, } from '@agentshouse/mdmodel';
|
|
2
2
|
import { binaryRecord } from './binary.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
const VISIBLE = (alias) => `${alias}.visible = 1 AND ${alias}.present = 1 AND ${alias}.queried = 1`;
|
|
7
|
-
function fragment(sql, bind = []) {
|
|
8
|
-
return { sql, bind };
|
|
9
|
-
}
|
|
10
|
-
function joined(parts, separator) {
|
|
11
|
-
return {
|
|
12
|
-
sql: parts.map((part) => part.sql).join(separator),
|
|
13
|
-
bind: parts.flatMap((part) => part.bind),
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
function balanced(parts, separator) {
|
|
17
|
-
if (parts.length === 1)
|
|
18
|
-
return grouped(parts[0]);
|
|
19
|
-
const half = Math.ceil(parts.length / 2);
|
|
20
|
-
return grouped(joined([balanced(parts.slice(0, half), separator), balanced(parts.slice(half), separator)], separator));
|
|
21
|
-
}
|
|
22
|
-
function all(parts) {
|
|
23
|
-
return parts.length === 0 ? fragment('1') : balanced(parts, ' AND ');
|
|
24
|
-
}
|
|
25
|
-
function any(parts) {
|
|
26
|
-
return parts.length === 0 ? fragment('0') : balanced(parts, ' OR ');
|
|
27
|
-
}
|
|
28
|
-
function grouped(part) {
|
|
29
|
-
return { sql: `(${part.sql})`, bind: part.bind };
|
|
30
|
-
}
|
|
31
|
-
function list(values) {
|
|
32
|
-
return fragment(`(${values.map(() => '?').join(', ')})`, values);
|
|
33
|
-
}
|
|
34
|
-
function sqlValue(value) {
|
|
35
|
-
if (value === null)
|
|
36
|
-
return { kind: 'null', value: null };
|
|
37
|
-
if (typeof value === 'boolean')
|
|
38
|
-
return { kind: 'boolean', value: value ? 1 : 0 };
|
|
39
|
-
if (typeof value === 'number')
|
|
40
|
-
return { kind: 'number', value };
|
|
41
|
-
return { kind: 'string', value };
|
|
42
|
-
}
|
|
43
|
-
function same(alias, value) {
|
|
44
|
-
const held = sqlValue(value);
|
|
45
|
-
return held.kind === 'null'
|
|
46
|
-
? fragment(`${alias}.kind = 'null'`)
|
|
47
|
-
: fragment(`${alias}.kind = ? AND ${alias}.value = ?`, [held.kind, held.value]);
|
|
48
|
-
}
|
|
49
|
-
const BOUNDS = { gt: '>', gte: '>=', lt: '<', lte: '<=' };
|
|
50
|
-
function pointed(alias, field, reach) {
|
|
51
|
-
const { keys, places, glob } = placedOf(field, reach);
|
|
52
|
-
return fragment(`${alias}.keys = ? AND ${alias}.places ${glob ? 'GLOB' : '='} ?`, [keys, places]);
|
|
53
|
-
}
|
|
54
|
-
function heldValues(doc, field, alias, where) {
|
|
55
|
-
return joined([
|
|
56
|
-
fragment(`SELECT 1 FROM replica_values ${alias} WHERE ${alias}.doc = ${doc}.id AND `),
|
|
57
|
-
grouped(pointed(alias, field, 'value')),
|
|
58
|
-
fragment(' AND '),
|
|
59
|
-
grouped(where),
|
|
60
|
-
], '');
|
|
61
|
-
}
|
|
62
|
-
function exists(part) {
|
|
63
|
-
return { sql: `EXISTS (${part.sql})`, bind: part.bind };
|
|
64
|
-
}
|
|
65
|
-
function itemOf(held, where) {
|
|
66
|
-
return exists(joined([
|
|
67
|
-
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 `),
|
|
68
|
-
grouped(where),
|
|
69
|
-
], ''));
|
|
70
|
-
}
|
|
71
|
-
function matching(held, values) {
|
|
72
|
-
return any(values.map((value) => any([
|
|
73
|
-
same(held, value),
|
|
74
|
-
all([fragment(`${held}.kind = 'array'`), itemOf(held, same('i', value))]),
|
|
75
|
-
])));
|
|
76
|
-
}
|
|
77
|
-
function tested(doc, field, test) {
|
|
78
|
-
const reached = exists(heldValues(doc, field, 'r', fragment('r.item = -1')));
|
|
79
|
-
if (test.op === 'exists')
|
|
80
|
-
return test.exists ? reached : fragment(`NOT ${reached.sql}`, reached.bind);
|
|
81
|
-
if (test.op === 'eq' || test.op === 'in') {
|
|
82
|
-
const values = test.op === 'eq' ? [test.value] : test.values;
|
|
83
|
-
return exists(heldValues(doc, field, 'v', any(values.map((value) => same('v', value)))));
|
|
84
|
-
}
|
|
85
|
-
if (test.op === 'ne' || test.op === 'nin') {
|
|
86
|
-
const values = test.op === 'ne' ? [test.value] : test.values;
|
|
87
|
-
const unmatched = matching('h', values);
|
|
88
|
-
return any([
|
|
89
|
-
fragment(`NOT ${reached.sql}`, reached.bind),
|
|
90
|
-
exists(heldValues(doc, field, 'h', all([fragment('h.item = -1'), fragment(`NOT (${unmatched.sql})`, unmatched.bind)]))),
|
|
91
|
-
]);
|
|
92
|
-
}
|
|
93
|
-
if (test.op === 'range') {
|
|
94
|
-
return exists(heldValues(doc, field, 'v', all([
|
|
95
|
-
fragment('v.item = -1'),
|
|
96
|
-
...test.bounds.map(([operator, bound]) => {
|
|
97
|
-
const held = sqlValue(bound);
|
|
98
|
-
return fragment(`v.kind = ? AND v.value ${BOUNDS[operator]} ?`, [
|
|
99
|
-
held.kind,
|
|
100
|
-
held.value,
|
|
101
|
-
]);
|
|
102
|
-
}),
|
|
103
|
-
])));
|
|
104
|
-
}
|
|
105
|
-
if (test.op === 'all') {
|
|
106
|
-
return exists(heldValues(doc, field, 'h', all([
|
|
107
|
-
fragment("h.item = -1 AND h.kind = 'array'"),
|
|
108
|
-
...test.values.map((value) => itemOf('h', same('i', value))),
|
|
109
|
-
])));
|
|
110
|
-
}
|
|
111
|
-
return exists(heldValues(doc, field, 'v', fragment("v.item = -1 AND v.kind = 'array' AND v.size = ?", [test.size])));
|
|
112
|
-
}
|
|
113
|
-
function conditioned(doc, condition) {
|
|
114
|
-
if ('every' in condition)
|
|
115
|
-
return all(condition.every.map((one) => conditioned(doc, one)));
|
|
116
|
-
if ('some' in condition)
|
|
117
|
-
return any(condition.some.map((one) => conditioned(doc, one)));
|
|
118
|
-
return tested(doc, condition.field, condition.test);
|
|
119
|
-
}
|
|
3
|
+
import { documentKey, pathOfKey, roomOfKey } from './rows.js';
|
|
4
|
+
import { FROM_START, MAXIMUM_WINDOW } from './windows.js';
|
|
5
|
+
const { all, any, exists, fragment, grouped, joined, listed } = sql;
|
|
6
|
+
export const VISIBLE = (alias) => `${alias}.visible = 1 AND ${alias}.present = 1 AND ${alias}.queried = 1`;
|
|
120
7
|
function located(alias) {
|
|
121
8
|
return fragment(`(SELECT k.locator FROM replica_keys k WHERE k.key = ${alias}.key)`);
|
|
122
9
|
}
|
|
123
|
-
function within(alias, path) {
|
|
10
|
+
export function within(alias, path) {
|
|
124
11
|
return fragment(`(${alias}.path = ? OR (${alias}.path >= ? AND ${alias}.path < ?))`, [
|
|
125
12
|
path,
|
|
126
13
|
`${path}/`,
|
|
@@ -163,7 +50,7 @@ function reached(bound, alias) {
|
|
|
163
50
|
}));
|
|
164
51
|
return all([
|
|
165
52
|
any([
|
|
166
|
-
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${
|
|
53
|
+
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${listed(rooms).sql}`, rooms)]),
|
|
167
54
|
...(documents.length === 0
|
|
168
55
|
? []
|
|
169
56
|
: [
|
|
@@ -173,40 +60,22 @@ function reached(bound, alias) {
|
|
|
173
60
|
...(reach.areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
|
|
174
61
|
]);
|
|
175
62
|
}
|
|
176
|
-
function unindexed(column) {
|
|
177
|
-
return `+${column}`;
|
|
178
|
-
}
|
|
179
|
-
function scoped(alias, scope, byPaths = false) {
|
|
180
|
-
const type = byPaths ? unindexed(`${alias}.type`) : `${alias}.type`;
|
|
181
|
-
return all([
|
|
182
|
-
...(scope.types === null ? [] : [fragment(`${type} IN ${list(scope.types).sql}`, scope.types)]),
|
|
183
|
-
...(scope.pathPrefix === null || scope.pathPrefix === ''
|
|
184
|
-
? []
|
|
185
|
-
: [
|
|
186
|
-
fragment(`${alias}.path >= ? AND ${alias}.path < ?`, [
|
|
187
|
-
`${scope.pathPrefix}/`,
|
|
188
|
-
`${scope.pathPrefix}0`,
|
|
189
|
-
]),
|
|
190
|
-
]),
|
|
191
|
-
...(scope.where === null ? [] : [conditioned(alias, scope.where)]),
|
|
192
|
-
]);
|
|
193
|
-
}
|
|
194
63
|
function related(bound, plan) {
|
|
195
64
|
const parts = [];
|
|
196
65
|
if (plan.hop !== null) {
|
|
197
66
|
parts.push(exists(joined([
|
|
198
67
|
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`),
|
|
199
|
-
grouped(
|
|
68
|
+
grouped(sql.placed('l', plan.hop.field, 'link')),
|
|
200
69
|
fragment('AND'),
|
|
201
|
-
grouped(all([scoped('m', plan.hop), reached(bound, 'm')])),
|
|
70
|
+
grouped(all([sql.scoped('m', plan.hop), reached(bound, 'm')])),
|
|
202
71
|
], ' ')));
|
|
203
72
|
}
|
|
204
73
|
if (plan.backlinks !== null) {
|
|
205
74
|
const field = plan.backlinks.field;
|
|
206
75
|
parts.push(exists(joined([
|
|
207
76
|
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`),
|
|
208
|
-
...(field === null ? [] : [grouped(
|
|
209
|
-
grouped(all([scoped('s', plan.backlinks), reached(bound, 's')])),
|
|
77
|
+
...(field === null ? [] : [grouped(sql.placed('l', field, 'link')), fragment('AND')]),
|
|
78
|
+
grouped(all([sql.scoped('s', plan.backlinks), reached(bound, 's')])),
|
|
210
79
|
], ' ')));
|
|
211
80
|
}
|
|
212
81
|
return parts;
|
|
@@ -214,11 +83,9 @@ function related(bound, plan) {
|
|
|
214
83
|
function filtered(bound, plan) {
|
|
215
84
|
const keys = bound.rooms.map(({ key }) => key);
|
|
216
85
|
return all([
|
|
217
|
-
fragment(`d.key IN ${
|
|
218
|
-
...(plan.paths === null ? [] : [fragment(`d.path IN ${list(plan.paths).sql}`, plan.paths)]),
|
|
219
|
-
...(plan.folder === null ? [] : [fragment('d.folder = ?', [plan.folder])]),
|
|
86
|
+
fragment(`d.key IN ${listed(keys).sql} AND ${VISIBLE('d')}`, keys),
|
|
220
87
|
reached(bound, 'd'),
|
|
221
|
-
|
|
88
|
+
sql.tests('d', plan),
|
|
222
89
|
...related(bound, plan),
|
|
223
90
|
]);
|
|
224
91
|
}
|
|
@@ -249,108 +116,65 @@ const GROUP_TIES = {
|
|
|
249
116
|
locale: undefined,
|
|
250
117
|
localeOptions: undefined,
|
|
251
118
|
};
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
return { placement: 1, klass: 3, scalar: JSON.stringify(value) };
|
|
263
|
-
}
|
|
264
|
-
function placement(term) {
|
|
265
|
-
const nulls = term.ordering.nulls === 'first' ? 0 : 2;
|
|
266
|
-
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]);
|
|
267
|
-
}
|
|
268
|
-
function klass(term) {
|
|
269
|
-
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);
|
|
270
|
-
}
|
|
271
|
-
function classed(term, which) {
|
|
272
|
-
const kinds = which === 1 ? "('boolean', 'number')" : which === 2 ? "('string')" : "('array')";
|
|
273
|
-
return fragment(`(CASE WHEN ${term.kind.sql} IN ${kinds} THEN ${term.value.sql} END)`, [
|
|
274
|
-
...term.kind.bind,
|
|
275
|
-
...term.value.bind,
|
|
276
|
-
]);
|
|
277
|
-
}
|
|
278
|
-
function collated(bound, term, which) {
|
|
279
|
-
if (which === 1)
|
|
280
|
-
return '';
|
|
281
|
-
const name = bound.collate(term.ordering, which === 3, term.total === true);
|
|
282
|
-
return name === null ? '' : ` COLLATE ${name}`;
|
|
283
|
-
}
|
|
284
|
-
function sorted(bound, term) {
|
|
285
|
-
const direction = term.ordering.direction === 'desc' ? 'DESC' : 'ASC';
|
|
286
|
-
return joined([
|
|
287
|
-
placement(term),
|
|
288
|
-
fragment(`${klass(term).sql} ${direction}`, klass(term).bind),
|
|
289
|
-
...[1, 2, 3].map((which) => {
|
|
290
|
-
const value = classed(term, which);
|
|
291
|
-
return fragment(`${value.sql}${collated(bound, term, which)} ${direction}`, value.bind);
|
|
292
|
-
}),
|
|
293
|
-
], ', ');
|
|
294
|
-
}
|
|
295
|
-
function beyond(bound, term, value) {
|
|
296
|
-
const held = classOf(value, term.ordering);
|
|
297
|
-
const place = placement(term);
|
|
298
|
-
if (held.placement !== 1) {
|
|
299
|
-
return {
|
|
300
|
-
beyond: fragment(`${place.sql} > ${held.placement}`, place.bind),
|
|
301
|
-
equal: fragment(`${place.sql} = ${held.placement}`, place.bind),
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
const after = term.ordering.direction === 'desc' ? '<' : '>';
|
|
305
|
-
const kind = klass(term);
|
|
306
|
-
const scalar = classed(term, held.klass);
|
|
307
|
-
const collation = collated(bound, term, held.klass);
|
|
308
|
-
return {
|
|
309
|
-
beyond: any([
|
|
310
|
-
fragment(`${place.sql} > 1`, place.bind),
|
|
311
|
-
all([
|
|
312
|
-
fragment(`${place.sql} = 1`, place.bind),
|
|
313
|
-
any([
|
|
314
|
-
fragment(`${kind.sql} ${after} ${held.klass}`, kind.bind),
|
|
315
|
-
all([
|
|
316
|
-
fragment(`${kind.sql} = ${held.klass}`, kind.bind),
|
|
317
|
-
fragment(`${scalar.sql} ${after} ?${collation}`, [...scalar.bind, held.scalar]),
|
|
318
|
-
]),
|
|
319
|
-
]),
|
|
320
|
-
]),
|
|
321
|
-
]),
|
|
322
|
-
equal: all([
|
|
323
|
-
fragment(`${place.sql} = 1 AND ${kind.sql} = ${held.klass}`, [...place.bind, ...kind.bind]),
|
|
324
|
-
fragment(`${scalar.sql} = ?${collation}`, [...scalar.bind, held.scalar]),
|
|
325
|
-
]),
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
function following(parts, tie) {
|
|
329
|
-
let condition = tie;
|
|
330
|
-
for (const part of [...parts].reverse()) {
|
|
331
|
-
condition = any([part.beyond, all([part.equal, condition])]);
|
|
332
|
-
}
|
|
333
|
-
return condition;
|
|
119
|
+
const GREATEST = { ...GROUP_ORDER, direction: 'desc', nulls: 'last' };
|
|
120
|
+
const TIMES = {
|
|
121
|
+
created: { value: 'd.created', instant: 'd.created_instant' },
|
|
122
|
+
updated: { value: "(d.change ->> '$.time')", instant: 'd.changed_instant' },
|
|
123
|
+
};
|
|
124
|
+
function subfolderOf(column, prefix) {
|
|
125
|
+
const root = prefix ?? '';
|
|
126
|
+
const base = root === '' ? '' : `${root}/`;
|
|
127
|
+
const rest = `substr(${column}, length(?) + 1)`;
|
|
128
|
+
return fragment(`(CASE WHEN ${column} = ? THEN NULL WHEN instr(${rest}, '/') = 0 THEN ${column} ELSE ? || substr(${rest}, 1, instr(${rest}, '/') - 1) END)`, [root, base, base, base, base]);
|
|
334
129
|
}
|
|
335
|
-
function keyTerm(key, ordering, index) {
|
|
130
|
+
function keyTerm(key, ordering, index, prefix) {
|
|
336
131
|
if (key.of === 'field') {
|
|
337
132
|
const alias = `o${index}`;
|
|
338
133
|
return {
|
|
339
|
-
term: {
|
|
134
|
+
term: {
|
|
135
|
+
ordering,
|
|
136
|
+
kind: fragment(`${alias}.kind`),
|
|
137
|
+
value: fragment(`${alias}.value`),
|
|
138
|
+
instant: fragment(`${alias}.instant`),
|
|
139
|
+
},
|
|
340
140
|
join: joined([
|
|
341
141
|
fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
|
|
342
|
-
grouped(
|
|
142
|
+
grouped(sql.placed(alias, key.field, 'exact')),
|
|
343
143
|
fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
|
|
344
144
|
], ''),
|
|
345
145
|
};
|
|
346
146
|
}
|
|
147
|
+
if (key.of === 'size') {
|
|
148
|
+
return {
|
|
149
|
+
term: {
|
|
150
|
+
ordering,
|
|
151
|
+
kind: fragment("(CASE WHEN d.bytes IS NULL THEN 'null' ELSE 'number' END)"),
|
|
152
|
+
value: fragment('d.bytes'),
|
|
153
|
+
},
|
|
154
|
+
join: null,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (key.of === 'created' || key.of === 'updated') {
|
|
158
|
+
const { value, instant } = TIMES[key.of];
|
|
159
|
+
return {
|
|
160
|
+
term: {
|
|
161
|
+
ordering,
|
|
162
|
+
kind: fragment(`(CASE WHEN ${value} IS NULL THEN 'null' ELSE 'string' END)`),
|
|
163
|
+
value: fragment(value),
|
|
164
|
+
instant: fragment(instant),
|
|
165
|
+
},
|
|
166
|
+
join: null,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
347
169
|
const expression = key.of === 'room'
|
|
348
170
|
? located('d')
|
|
349
171
|
: key.of === 'path'
|
|
350
172
|
? fragment('d.path')
|
|
351
173
|
: key.of === 'type'
|
|
352
174
|
? fragment('d.type')
|
|
353
|
-
:
|
|
175
|
+
: key.of === 'subfolder'
|
|
176
|
+
? subfolderOf('d.folder', prefix)
|
|
177
|
+
: fragment('d.folder');
|
|
354
178
|
return {
|
|
355
179
|
term: {
|
|
356
180
|
ordering,
|
|
@@ -371,12 +195,20 @@ function decoded(kind, value) {
|
|
|
371
195
|
return JSON.parse(value);
|
|
372
196
|
return null;
|
|
373
197
|
}
|
|
198
|
+
function sortedOf(kind, value, instant) {
|
|
199
|
+
return sql.sortValue(kind, decoded(kind, value), instant);
|
|
200
|
+
}
|
|
201
|
+
function sortedValue(row, index) {
|
|
202
|
+
return sortedOf(row[`k${index}`], row[`v${index}`], row[`i${index}`]);
|
|
203
|
+
}
|
|
374
204
|
function decodedKey(key, row, index) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
return
|
|
378
|
-
|
|
379
|
-
|
|
205
|
+
const kind = row[`k${index}`];
|
|
206
|
+
if (key.of === 'field' && (kind === null || kind === undefined))
|
|
207
|
+
return undefined;
|
|
208
|
+
return sortedValue(row, index);
|
|
209
|
+
}
|
|
210
|
+
function instantOf(term) {
|
|
211
|
+
return term.instant ?? fragment('NULL');
|
|
380
212
|
}
|
|
381
213
|
const registered = new WeakMap();
|
|
382
214
|
function hashed(text) {
|
|
@@ -483,10 +315,9 @@ function documentOf(shape, room, row, linked) {
|
|
|
483
315
|
const type = row.type;
|
|
484
316
|
const fileId = row.file_id ?? null;
|
|
485
317
|
const revision = row.revision ?? null;
|
|
486
|
-
const content = row.content ?? null;
|
|
487
|
-
const unheld = Number(row.unheld) === 1 ? { textHeld: false } : {};
|
|
488
318
|
if (type === null) {
|
|
489
319
|
const binary = row.binary === null ? null : JSON.parse(row.binary);
|
|
320
|
+
const content = row.content ?? null;
|
|
490
321
|
return {
|
|
491
322
|
room,
|
|
492
323
|
path,
|
|
@@ -496,12 +327,13 @@ function documentOf(shape, room, row, linked) {
|
|
|
496
327
|
binaryFile: binaryRecord(path, binary, revision ?? undefined),
|
|
497
328
|
}),
|
|
498
329
|
...(shape.markdown && content !== null ? { markdown: content } : {}),
|
|
499
|
-
...
|
|
330
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
500
331
|
...(shape.fileId ? { fileId } : {}),
|
|
501
332
|
...(shape.revision ? { revision } : {}),
|
|
333
|
+
...heldFacts(shape, row),
|
|
502
334
|
};
|
|
503
335
|
}
|
|
504
|
-
const read = bodied(shape)
|
|
336
|
+
const read = bodied(shape) ? parse(row.content, path) : null;
|
|
505
337
|
const body = read?.ok === true ? read.document : null;
|
|
506
338
|
const fields = JSON.parse(row.fields ?? '{}');
|
|
507
339
|
return {
|
|
@@ -519,16 +351,21 @@ function documentOf(shape, room, row, linked) {
|
|
|
519
351
|
? {}
|
|
520
352
|
: { sections: sectionsOf(body.sections, shape.sections) }),
|
|
521
353
|
...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
|
|
522
|
-
...(shape.markdown
|
|
523
|
-
...unheld,
|
|
354
|
+
...(shape.markdown ? { markdown: row.content } : {}),
|
|
524
355
|
...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
|
|
525
|
-
...(shape.change
|
|
526
|
-
? {
|
|
527
|
-
change: row.change === null ? null : JSON.parse(row.change),
|
|
528
|
-
}
|
|
529
|
-
: {}),
|
|
356
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
530
357
|
...(shape.fileId ? { fileId } : {}),
|
|
531
358
|
...(shape.revision ? { revision } : {}),
|
|
359
|
+
...heldFacts(shape, row),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function changeOf(row) {
|
|
363
|
+
return row.change === null ? null : JSON.parse(row.change);
|
|
364
|
+
}
|
|
365
|
+
function heldFacts(shape, row) {
|
|
366
|
+
return {
|
|
367
|
+
...(shape.size ? { size: row.bytes === null ? null : Number(row.bytes) } : {}),
|
|
368
|
+
...(shape.created ? { created: row.created ?? null } : {}),
|
|
532
369
|
};
|
|
533
370
|
}
|
|
534
371
|
function backlinksOf(connection, bound, rows) {
|
|
@@ -554,7 +391,7 @@ function backlinksOf(connection, bound, rows) {
|
|
|
554
391
|
return linked;
|
|
555
392
|
}
|
|
556
393
|
function documents(connection, bound, plan, shape, budget) {
|
|
557
|
-
const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(key, orderingOf(options), index));
|
|
394
|
+
const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(key, orderingOf(options), index, plan.root.pathPrefix));
|
|
558
395
|
const filter = filtered(bound, plan);
|
|
559
396
|
const cursor = plan.after?.values ?? null;
|
|
560
397
|
const room = located('d');
|
|
@@ -573,19 +410,20 @@ function documents(connection, bound, plan, shape, budget) {
|
|
|
573
410
|
})();
|
|
574
411
|
const after = cursor === null || tie === null
|
|
575
412
|
? null
|
|
576
|
-
: following(terms.map(({ term }, index) => beyond(bound, term, cursor[index])), tie);
|
|
413
|
+
: sql.following(terms.map(({ term }, index) => sql.beyond(bound.collate, term, cursor[index])), tie);
|
|
577
414
|
const selected = joined([
|
|
578
|
-
fragment('SELECT d.id, d.key, d.path, d.type, d.binary, d.revision, d.fields, d.change, d.file_id, d.
|
|
415
|
+
fragment('SELECT d.id, d.key, d.path, d.type, d.binary, d.revision, d.fields, d.change, d.file_id, d.bytes, d.created'),
|
|
579
416
|
...terms.map(({ term }, index) => joined([
|
|
580
417
|
fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
581
418
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
419
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
582
420
|
], '')),
|
|
583
421
|
fragment(' FROM replica_entries d'),
|
|
584
422
|
...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
|
|
585
423
|
fragment(' WHERE '),
|
|
586
424
|
after === null ? filter : all([filter, after]),
|
|
587
425
|
fragment(' ORDER BY '),
|
|
588
|
-
joined([...terms.map(({ term }) => sorted(bound, term)), room, fragment('d.path')], ', '),
|
|
426
|
+
joined([...terms.map(({ term }) => sql.sorted(bound.collate, term)), room, fragment('d.path')], ', '),
|
|
589
427
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
590
428
|
], '');
|
|
591
429
|
const rows = connection.rows(selected.sql, selected.bind);
|
|
@@ -624,7 +462,7 @@ function withContent(connection, rows, budget) {
|
|
|
624
462
|
return rows;
|
|
625
463
|
const ids = rows.map((row) => row.id);
|
|
626
464
|
const sizes = new Map(connection
|
|
627
|
-
.rows(`SELECT id, octet_length(content) AS size FROM replica_entries WHERE id IN ${
|
|
465
|
+
.rows(`SELECT id, octet_length(content) AS size FROM replica_entries WHERE id IN ${listed(ids).sql}`, ids)
|
|
628
466
|
.map((row) => [row.id, Number(row.size ?? 0)]));
|
|
629
467
|
let cut = rows.length;
|
|
630
468
|
let spent = 0;
|
|
@@ -639,18 +477,22 @@ function withContent(connection, rows, budget) {
|
|
|
639
477
|
const kept = rows.slice(0, cut);
|
|
640
478
|
const read = kept.map((row) => row.id);
|
|
641
479
|
const held = new Map(connection
|
|
642
|
-
.rows(`SELECT id, content FROM replica_entries WHERE id IN ${
|
|
480
|
+
.rows(`SELECT id, content FROM replica_entries WHERE id IN ${listed(read).sql}`, read)
|
|
643
481
|
.map((row) => [row.id, row.content]));
|
|
644
482
|
return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
|
|
645
483
|
}
|
|
646
|
-
function storedFolders(bound, plan) {
|
|
647
|
-
if (plan.paths !== null || plan.
|
|
484
|
+
function storedFolders(bound, plan, subfolder) {
|
|
485
|
+
if (plan.paths !== null || plan.fileIds !== null)
|
|
486
|
+
return null;
|
|
487
|
+
if (plan.hop !== null || plan.backlinks !== null)
|
|
488
|
+
return null;
|
|
489
|
+
if (subfolder && plan.folder !== null)
|
|
648
490
|
return null;
|
|
649
491
|
const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
|
|
650
492
|
if (whole.length === 0)
|
|
651
493
|
return null;
|
|
652
494
|
const keys = whole.map(({ key }) => key);
|
|
653
|
-
const prefix = plan.root.pathPrefix;
|
|
495
|
+
const prefix = plan.root.pathPrefix ?? '';
|
|
654
496
|
const outside = (bound.reach?.areas ?? []).flatMap((area) => {
|
|
655
497
|
const key = bound.keys.get(area.room);
|
|
656
498
|
if (key === undefined)
|
|
@@ -658,63 +500,146 @@ function storedFolders(bound, plan) {
|
|
|
658
500
|
const fence = fenced('f', key, area, true);
|
|
659
501
|
return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
|
|
660
502
|
});
|
|
503
|
+
const under = prefix === ''
|
|
504
|
+
? subfolder
|
|
505
|
+
? [fragment("f.path <> ''")]
|
|
506
|
+
: []
|
|
507
|
+
: [
|
|
508
|
+
subfolder
|
|
509
|
+
? fragment('(f.path >= ? AND f.path < ?)', [`${prefix}/`, `${prefix}0`])
|
|
510
|
+
: within('f', prefix),
|
|
511
|
+
];
|
|
661
512
|
return all([
|
|
662
|
-
fragment(`f.key IN ${
|
|
513
|
+
fragment(`f.key IN ${listed(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
|
|
663
514
|
...outside,
|
|
664
|
-
...
|
|
665
|
-
|
|
666
|
-
: [
|
|
667
|
-
fragment('(f.path = ? OR (f.path >= ? AND f.path < ?))', [
|
|
668
|
-
prefix,
|
|
669
|
-
`${prefix}/`,
|
|
670
|
-
`${prefix}0`,
|
|
671
|
-
]),
|
|
672
|
-
]),
|
|
515
|
+
...under,
|
|
516
|
+
...(plan.outside ?? []).map((folder) => fragment(`NOT ${within('f', folder).sql}`, within('f', folder).bind)),
|
|
673
517
|
...(plan.folder === null ? [] : [fragment('f.path = ?', [plan.folder])]),
|
|
674
518
|
]);
|
|
675
519
|
}
|
|
520
|
+
const SIZES = [
|
|
521
|
+
'CASE WHEN d.binary IS NULL THEN 1 ELSE 0 END AS dn',
|
|
522
|
+
'CASE WHEN d.binary IS NULL THEN d.bytes ELSE 0 END AS ds',
|
|
523
|
+
'CASE WHEN d.binary IS NULL THEN 0 ELSE 1 END AS fn',
|
|
524
|
+
'CASE WHEN d.binary IS NULL THEN 0 ELSE d.bytes END AS fs',
|
|
525
|
+
];
|
|
526
|
+
const LATEST = "d.changed_instant || ' ' || (d.change ->> '$.time') AS u";
|
|
527
|
+
function greatestTerm(ordering) {
|
|
528
|
+
return { ordering, kind: fragment('gk'), value: fragment('gv'), instant: fragment('gi') };
|
|
529
|
+
}
|
|
530
|
+
function latestOf(held) {
|
|
531
|
+
if (typeof held !== 'string')
|
|
532
|
+
return null;
|
|
533
|
+
return held.slice(held.indexOf(' ') + 1);
|
|
534
|
+
}
|
|
676
535
|
function groups(connection, bound, plan, shape) {
|
|
536
|
+
const prefix = plan.root.pathPrefix;
|
|
677
537
|
const keyed = shape.groupBy.map((key, index) => {
|
|
678
|
-
const { term, join } = keyTerm(key, GROUP_ORDER, index);
|
|
538
|
+
const { term, join } = keyTerm(key, GROUP_ORDER, index, prefix);
|
|
679
539
|
const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
|
|
680
540
|
return { term: { ...term, kind }, join };
|
|
681
541
|
});
|
|
542
|
+
const ordering = shape.orderBy.map(({ by }) => by);
|
|
543
|
+
const sized = shape.size || ordering.includes('size');
|
|
544
|
+
const dated = shape.updated || ordering.includes('updated');
|
|
545
|
+
const greatest = shape.greatest === null
|
|
546
|
+
? null
|
|
547
|
+
: keyTerm({ of: 'field', field: shape.greatest }, GREATEST, keyed.length, prefix);
|
|
682
548
|
const filter = filtered(bound, plan);
|
|
683
549
|
const inner = joined([
|
|
684
550
|
fragment('SELECT '),
|
|
685
551
|
joined(keyed.map(({ term }, index) => joined([
|
|
686
552
|
fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
687
553
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
554
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
688
555
|
], '')), ', '),
|
|
689
|
-
fragment(', 1 AS n
|
|
690
|
-
...
|
|
556
|
+
fragment(['', '1 AS n', ...(sized ? SIZES : []), ...(dated ? [LATEST] : [])].join(', ')),
|
|
557
|
+
...(greatest === null
|
|
558
|
+
? []
|
|
559
|
+
: [
|
|
560
|
+
fragment(`, ${greatest.term.kind.sql} AS gk`, greatest.term.kind.bind),
|
|
561
|
+
fragment(`, ${greatest.term.value.sql} AS gv`, greatest.term.value.bind),
|
|
562
|
+
fragment(`, ${instantOf(greatest.term).sql} AS gi`, instantOf(greatest.term).bind),
|
|
563
|
+
]),
|
|
564
|
+
fragment(' FROM replica_entries d'),
|
|
565
|
+
...[...keyed, ...(greatest === null ? [] : [greatest])].flatMap(({ join }) => join === null ? [] : [fragment(' '), join]),
|
|
691
566
|
fragment(' WHERE '),
|
|
692
567
|
filter,
|
|
693
568
|
], '');
|
|
694
|
-
const
|
|
695
|
-
|
|
696
|
-
|
|
569
|
+
const only = shape.groupBy.length === 1 ? shape.groupBy[0].of : null;
|
|
570
|
+
const subfolder = only === 'subfolder';
|
|
571
|
+
const folders = only === 'folder' || subfolder ? storedFolders(bound, plan, subfolder) : null;
|
|
572
|
+
const folderKey = subfolder ? subfolderOf('f.path', prefix) : fragment('f.path');
|
|
573
|
+
const zeros = [
|
|
574
|
+
...(sized ? ['0', '0', '0', '0'] : []),
|
|
575
|
+
...(dated ? ['NULL'] : []),
|
|
576
|
+
...(greatest === null ? [] : ['NULL', 'NULL', 'NULL']),
|
|
577
|
+
];
|
|
697
578
|
const source = folders === null
|
|
698
579
|
? inner
|
|
699
580
|
: joined([
|
|
700
581
|
inner,
|
|
701
|
-
fragment(
|
|
582
|
+
fragment(` UNION ALL SELECT 'string' AS k0, ${folderKey.sql} AS v0, NULL AS i0, 0 AS n${zeros.map((one) => `, ${one}`).join('')} FROM replica_folders f WHERE `, folderKey.bind),
|
|
702
583
|
folders,
|
|
703
584
|
], '');
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
585
|
+
const held = keyed
|
|
586
|
+
.map((_, index) => `k${index}, i${index}, CASE WHEN i${index} IS NULL THEN v${index} END`)
|
|
587
|
+
.join(', ');
|
|
588
|
+
const ranked = greatest === null
|
|
589
|
+
? source
|
|
590
|
+
: joined([
|
|
591
|
+
fragment(`SELECT *, ROW_NUMBER() OVER (PARTITION BY ${held} ORDER BY `),
|
|
592
|
+
sql.sorted(bound.collate, greatestTerm(GREATEST)),
|
|
593
|
+
fragment(', gv) AS gr FROM ('),
|
|
594
|
+
source,
|
|
595
|
+
fragment(')'),
|
|
596
|
+
], '');
|
|
597
|
+
const columns = [
|
|
598
|
+
...keyed.map((_, index) => `k${index}, min(v${index}) AS v${index}, i${index}`),
|
|
599
|
+
'sum(n) AS n',
|
|
600
|
+
...(sized ? ['sum(dn) AS dn', 'sum(ds) AS ds', 'sum(fn) AS fn', 'sum(fs) AS fs'] : []),
|
|
601
|
+
...(dated ? ['max(u) AS u'] : []),
|
|
602
|
+
...(greatest === null
|
|
603
|
+
? []
|
|
604
|
+
: ['gk', 'gv', 'gi'].map((column) => `min(CASE WHEN gr = 1 THEN ${column} END) AS ${column}`)),
|
|
605
|
+
].join(', ');
|
|
606
|
+
const outer = (index, order) => ({
|
|
607
|
+
ordering: order,
|
|
712
608
|
kind: fragment(`k${index}`),
|
|
713
609
|
value: fragment(`v${index}`),
|
|
714
610
|
});
|
|
715
|
-
const
|
|
716
|
-
|
|
717
|
-
|
|
611
|
+
const termOf = (by, order) => {
|
|
612
|
+
if (by === 'count')
|
|
613
|
+
return { ordering: order, kind: fragment("'number'"), value: fragment('n') };
|
|
614
|
+
if (by === 'size') {
|
|
615
|
+
return { ordering: order, kind: fragment("'number'"), value: fragment('(ds + fs)') };
|
|
616
|
+
}
|
|
617
|
+
if (by === 'updated') {
|
|
618
|
+
return {
|
|
619
|
+
ordering: order,
|
|
620
|
+
kind: fragment("(CASE WHEN u IS NULL THEN 'null' ELSE 'string' END)"),
|
|
621
|
+
value: fragment("substr(u, instr(u, ' ') + 1)"),
|
|
622
|
+
instant: fragment("substr(u, 1, instr(u, ' ') - 1)"),
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
if (by === 'greatest')
|
|
626
|
+
return greatestTerm(order);
|
|
627
|
+
return { ...outer(by, order), instant: fragment(`i${by}`) };
|
|
628
|
+
};
|
|
629
|
+
const valueOf = (row, by) => {
|
|
630
|
+
if (by === 'count')
|
|
631
|
+
return Number(row.n);
|
|
632
|
+
if (by === 'size')
|
|
633
|
+
return Number(row.ds) + Number(row.fs);
|
|
634
|
+
if (by === 'updated') {
|
|
635
|
+
const text = latestOf(row.u);
|
|
636
|
+
return text === null ? null : sql.sortValue('string', text, row.u.split(' ')[0]);
|
|
637
|
+
}
|
|
638
|
+
if (by === 'greatest')
|
|
639
|
+
return sortedOf(row.gk, row.gv, row.gi);
|
|
640
|
+
return sortedValue(row, by);
|
|
641
|
+
};
|
|
642
|
+
const ordered = shape.orderBy.map(({ by, direction, nulls }) => termOf(by, { ...GROUP_ORDER, direction, nulls: nulls ?? GROUP_ORDER.nulls }));
|
|
718
643
|
const ties = keyed.flatMap((_, index) => [
|
|
719
644
|
{ ...outer(index, GROUP_TIES), total: true },
|
|
720
645
|
{ ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
|
|
@@ -724,34 +649,44 @@ function groups(connection, bound, plan, shape) {
|
|
|
724
649
|
? null
|
|
725
650
|
: cursor[shape.orderBy.length].flatMap((value) => [
|
|
726
651
|
value,
|
|
727
|
-
|
|
652
|
+
heldKind(value ?? null),
|
|
728
653
|
]);
|
|
729
654
|
const after = cursor === null || tieValues === null
|
|
730
655
|
? null
|
|
731
|
-
: following(ordered.map((term, index) => beyond(bound, term, cursor[index])), following(ties
|
|
732
|
-
|
|
656
|
+
: sql.following(ordered.map((term, index) => sql.beyond(bound.collate, term, cursor[index])), sql.following(ties
|
|
657
|
+
.slice(0, -1)
|
|
658
|
+
.map((term, index) => sql.beyond(bound.collate, term, tieValues[index])), sql.beyond(bound.collate, ties.at(-1), tieValues.at(-1)).beyond));
|
|
659
|
+
const grouping = joined([fragment(`SELECT ${columns} FROM (`), ranked, fragment(`) GROUP BY ${held}`)], '');
|
|
733
660
|
const windowed = joined([
|
|
734
661
|
fragment('SELECT * FROM ('),
|
|
735
662
|
grouping,
|
|
736
663
|
fragment(')'),
|
|
737
664
|
...(after === null ? [] : [fragment(' WHERE '), after]),
|
|
738
665
|
fragment(' ORDER BY '),
|
|
739
|
-
joined([...ordered, ...ties].map((term) => sorted(bound, term)), ', '),
|
|
666
|
+
joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
|
|
740
667
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
741
668
|
], '');
|
|
742
669
|
const rows = connection.rows(windowed.sql, windowed.bind);
|
|
743
670
|
const kept = plan.limit === null ? rows : rows.slice(0, plan.limit);
|
|
744
671
|
const keyOf = (row) => keyed.map((_, index) => decoded(row[`k${index}`], row[`v${index}`]));
|
|
745
|
-
const found = kept.map((row) => ({
|
|
672
|
+
const found = kept.map((row) => ({
|
|
673
|
+
key: keyOf(row),
|
|
674
|
+
count: Number(row.n),
|
|
675
|
+
...(shape.size
|
|
676
|
+
? {
|
|
677
|
+
documents: { count: Number(row.dn), size: Number(row.ds) },
|
|
678
|
+
files: { count: Number(row.fn), size: Number(row.fs) },
|
|
679
|
+
}
|
|
680
|
+
: {}),
|
|
681
|
+
...(shape.updated ? { updated: latestOf(row.u) } : {}),
|
|
682
|
+
...(shape.greatest === null ? {} : { greatest: decoded(row.gk, row.gv) }),
|
|
683
|
+
}));
|
|
746
684
|
const last = kept.at(-1);
|
|
747
685
|
const next = plan.limit === null || rows.length <= plan.limit
|
|
748
686
|
? undefined
|
|
749
687
|
: last === undefined
|
|
750
688
|
? (plan.after?.text ?? FROM_START)
|
|
751
|
-
: cursorOf([
|
|
752
|
-
...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : decoded(last[`k${by}`], last[`v${by}`])),
|
|
753
|
-
keyOf(last),
|
|
754
|
-
]);
|
|
689
|
+
: cursorOf([...shape.orderBy.map(({ by }) => valueOf(last, by)), keyOf(last)]);
|
|
755
690
|
const total = plan.total
|
|
756
691
|
? Number(connection.rows(joined([fragment('SELECT count(*) AS held FROM ('), grouping, fragment(')')], '').sql, grouping.bind)[0].held)
|
|
757
692
|
: undefined;
|
|
@@ -769,9 +704,6 @@ function boundOf(connection, plan, rooms) {
|
|
|
769
704
|
collate: collating(connection),
|
|
770
705
|
};
|
|
771
706
|
}
|
|
772
|
-
export function selected(connection, plan, rooms) {
|
|
773
|
-
return filtered(boundOf(connection, plan, rooms), plan);
|
|
774
|
-
}
|
|
775
707
|
export function answerQuery(connection, plan, rooms, budget = null) {
|
|
776
708
|
const bound = boundOf(connection, plan, rooms);
|
|
777
709
|
if (rooms.length === 0) {
|