@agentshouse/mdruntime 0.1.0-c08b314dc5105 → 0.1.0-c0b4d0492dec3
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/adapter.d.ts +85 -70
- 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 +29 -47
- package/dist/bootstrap.d.ts +14 -17
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +258 -152
- 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 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -1
- package/dist/collection.d.ts +2 -4
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +1 -17
- package/dist/compile.d.ts +6 -6
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +263 -311
- package/dist/connection.d.ts +16 -10
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +2178 -1917
- 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 +27 -16
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +484 -248
- package/dist/durability.d.ts +6 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +3 -2
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +18 -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 +3 -7
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +25 -30
- 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 +16 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- 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 +63 -22
- package/dist/media.d.ts +1 -1
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js +27 -14
- package/dist/memory.d.ts +12 -5
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +174 -83
- 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 -10
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +0 -18
- 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 -3
- 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/paths.d.ts +1 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +4 -0
- package/dist/query.d.ts +14 -209
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -505
- 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 +13 -12
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +185 -139
- package/dist/react.d.ts +3 -1
- 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 +3 -17
- package/dist/readiness.d.ts.map +1 -1
- package/dist/readiness.js +4 -10
- package/dist/reads.d.ts +34 -10
- package/dist/reads.d.ts.map +1 -1
- package/dist/reads.js +114 -86
- package/dist/recovery.d.ts +1 -0
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +35 -1
- package/dist/refresh.d.ts +7 -5
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +33 -5
- package/dist/replica.d.ts +9 -7
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +50 -19
- package/dist/room-draft.d.ts +21 -5
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +98 -51
- package/dist/rows.d.ts +14 -21
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +48 -184
- package/dist/save.d.ts +2 -0
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +22 -13
- package/dist/search.d.ts +12 -63
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +9 -52
- package/dist/shared.d.ts +0 -1
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +0 -10
- 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 +4 -5
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -7
- package/dist/store.d.ts +99 -60
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +151 -168
- package/dist/stored.d.ts +61 -57
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +299 -233
- package/dist/tables.d.ts +113 -60
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +199 -99
- package/dist/tallies.d.ts +18 -0
- package/dist/tallies.d.ts.map +1 -0
- package/dist/tallies.js +129 -0
- package/dist/transfer.d.ts +0 -5
- package/dist/transfer.d.ts.map +1 -1
- package/dist/validation.d.ts +3 -4
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +42 -100
- 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 +15 -4
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +6 -1
- 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 -368
- package/dist/words.d.ts +0 -19
- package/dist/words.d.ts.map +0 -1
- package/dist/words.js +0 -150
package/dist/compile.js
CHANGED
|
@@ -1,119 +1,36 @@
|
|
|
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 { documentKey,
|
|
4
|
-
import {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
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])));
|
|
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`;
|
|
7
|
+
function located(alias) {
|
|
8
|
+
return fragment(`(SELECT k.locator FROM replica_keys k WHERE k.key = ${alias}.key)`);
|
|
9
|
+
}
|
|
10
|
+
export function within(alias, path) {
|
|
11
|
+
return fragment(`(${alias}.path = ? OR (${alias}.path >= ? AND ${alias}.path < ?))`, [
|
|
12
|
+
path,
|
|
13
|
+
`${path}/`,
|
|
14
|
+
`${path}0`,
|
|
15
|
+
]);
|
|
105
16
|
}
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return
|
|
111
|
-
|
|
17
|
+
function ancestors(area) {
|
|
18
|
+
const depth = area.path.split('/').length;
|
|
19
|
+
return area.open.flatMap((open) => {
|
|
20
|
+
const segments = open.split('/');
|
|
21
|
+
return segments.slice(depth - 1, -1).map((_, at) => segments.slice(0, depth + at).join('/'));
|
|
22
|
+
});
|
|
112
23
|
}
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
24
|
+
function fenced(alias, key, area, folders) {
|
|
25
|
+
const opened = any([
|
|
26
|
+
...area.open.map((open) => within(alias, open)),
|
|
27
|
+
...(folders ? ancestors(area).map((path) => fragment(`${alias}.path = ?`, [path])) : []),
|
|
28
|
+
]);
|
|
29
|
+
return all([
|
|
30
|
+
fragment(`${alias}.key = ?`, [key]),
|
|
31
|
+
within(alias, area.path),
|
|
32
|
+
fragment(`NOT ${grouped(opened).sql}`, opened.bind),
|
|
33
|
+
]);
|
|
117
34
|
}
|
|
118
35
|
function reached(bound, alias) {
|
|
119
36
|
const reach = bound.reach;
|
|
@@ -127,42 +44,20 @@ function reached(bound, alias) {
|
|
|
127
44
|
const key = bound.keys.get(roomOfKey(held));
|
|
128
45
|
return key === undefined ? [] : [[key, pathOfKey(held)]];
|
|
129
46
|
});
|
|
130
|
-
const
|
|
47
|
+
const outside = any(reach.areas.flatMap((area) => {
|
|
131
48
|
const key = bound.keys.get(area.room);
|
|
132
|
-
return key === undefined ? [] : [
|
|
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)])));
|
|
49
|
+
return key === undefined ? [] : [fenced(alias, key, area, false)];
|
|
50
|
+
}));
|
|
139
51
|
return all([
|
|
140
52
|
any([
|
|
141
|
-
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${
|
|
53
|
+
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${listed(rooms).sql}`, rooms)]),
|
|
142
54
|
...(documents.length === 0
|
|
143
55
|
? []
|
|
144
56
|
: [
|
|
145
57
|
fragment(`EXISTS (SELECT 1 FROM json_each(?) j WHERE j.value ->> 0 = ${alias}.key AND j.value ->> 1 = ${alias}.path)`, [JSON.stringify(documents)]),
|
|
146
58
|
]),
|
|
147
|
-
...areas.flatMap((area) => area.types.length === 0 ? [] : [all([beneath(area), typed(area.types)])]),
|
|
148
59
|
]),
|
|
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)]),
|
|
60
|
+
...(reach.areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
|
|
166
61
|
]);
|
|
167
62
|
}
|
|
168
63
|
function related(bound, plan) {
|
|
@@ -170,17 +65,17 @@ function related(bound, plan) {
|
|
|
170
65
|
if (plan.hop !== null) {
|
|
171
66
|
parts.push(exists(joined([
|
|
172
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`),
|
|
173
|
-
grouped(
|
|
68
|
+
grouped(sql.placed('l', plan.hop.field, 'link')),
|
|
174
69
|
fragment('AND'),
|
|
175
|
-
grouped(all([scoped('m', plan.hop), reached(bound, 'm')])),
|
|
70
|
+
grouped(all([sql.scoped('m', plan.hop), reached(bound, 'm')])),
|
|
176
71
|
], ' ')));
|
|
177
72
|
}
|
|
178
73
|
if (plan.backlinks !== null) {
|
|
179
74
|
const field = plan.backlinks.field;
|
|
180
75
|
parts.push(exists(joined([
|
|
181
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`),
|
|
182
|
-
...(field === null ? [] : [grouped(
|
|
183
|
-
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')])),
|
|
184
79
|
], ' ')));
|
|
185
80
|
}
|
|
186
81
|
return parts;
|
|
@@ -188,11 +83,9 @@ function related(bound, plan) {
|
|
|
188
83
|
function filtered(bound, plan) {
|
|
189
84
|
const keys = bound.rooms.map(({ key }) => key);
|
|
190
85
|
return all([
|
|
191
|
-
fragment(`d.key IN ${
|
|
192
|
-
...(plan.paths === null ? [] : [fragment(`d.path IN ${list(plan.paths).sql}`, plan.paths)]),
|
|
193
|
-
...(plan.folder === null ? [] : [fragment('d.folder = ?', [plan.folder])]),
|
|
86
|
+
fragment(`d.key IN ${listed(keys).sql} AND ${VISIBLE('d')}`, keys),
|
|
194
87
|
reached(bound, 'd'),
|
|
195
|
-
|
|
88
|
+
sql.tests('d', plan),
|
|
196
89
|
...related(bound, plan),
|
|
197
90
|
]);
|
|
198
91
|
}
|
|
@@ -223,108 +116,65 @@ const GROUP_TIES = {
|
|
|
223
116
|
locale: undefined,
|
|
224
117
|
localeOptions: undefined,
|
|
225
118
|
};
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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;
|
|
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]);
|
|
308
129
|
}
|
|
309
|
-
function keyTerm(
|
|
130
|
+
function keyTerm(key, ordering, index, prefix) {
|
|
310
131
|
if (key.of === 'field') {
|
|
311
132
|
const alias = `o${index}`;
|
|
312
133
|
return {
|
|
313
|
-
term: {
|
|
134
|
+
term: {
|
|
135
|
+
ordering,
|
|
136
|
+
kind: fragment(`${alias}.kind`),
|
|
137
|
+
value: fragment(`${alias}.value`),
|
|
138
|
+
instant: fragment(`${alias}.instant`),
|
|
139
|
+
},
|
|
314
140
|
join: joined([
|
|
315
141
|
fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
|
|
316
|
-
grouped(
|
|
142
|
+
grouped(sql.placed(alias, key.field, 'exact')),
|
|
317
143
|
fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
|
|
318
144
|
], ''),
|
|
319
145
|
};
|
|
320
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
|
+
}
|
|
321
169
|
const expression = key.of === 'room'
|
|
322
|
-
? located(
|
|
170
|
+
? located('d')
|
|
323
171
|
: key.of === 'path'
|
|
324
172
|
? fragment('d.path')
|
|
325
173
|
: key.of === 'type'
|
|
326
174
|
? fragment('d.type')
|
|
327
|
-
:
|
|
175
|
+
: key.of === 'subfolder'
|
|
176
|
+
? subfolderOf('d.folder', prefix)
|
|
177
|
+
: fragment('d.folder');
|
|
328
178
|
return {
|
|
329
179
|
term: {
|
|
330
180
|
ordering,
|
|
@@ -345,12 +195,20 @@ function decoded(kind, value) {
|
|
|
345
195
|
return JSON.parse(value);
|
|
346
196
|
return null;
|
|
347
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
|
+
}
|
|
348
204
|
function decodedKey(key, row, index) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
return
|
|
352
|
-
|
|
353
|
-
|
|
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');
|
|
354
212
|
}
|
|
355
213
|
const registered = new WeakMap();
|
|
356
214
|
function hashed(text) {
|
|
@@ -401,9 +259,6 @@ function collating(connection) {
|
|
|
401
259
|
return name;
|
|
402
260
|
};
|
|
403
261
|
}
|
|
404
|
-
function bodied(shape) {
|
|
405
|
-
return shape.sections !== null || shape.preamble || shape.markdown;
|
|
406
|
-
}
|
|
407
262
|
function masked(pointers) {
|
|
408
263
|
const root = { whole: false, members: new Map() };
|
|
409
264
|
for (const pointer of pointers) {
|
|
@@ -472,8 +327,10 @@ function documentOf(shape, room, row, linked) {
|
|
|
472
327
|
binaryFile: binaryRecord(path, binary, revision ?? undefined),
|
|
473
328
|
}),
|
|
474
329
|
...(shape.markdown && content !== null ? { markdown: content } : {}),
|
|
330
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
475
331
|
...(shape.fileId ? { fileId } : {}),
|
|
476
332
|
...(shape.revision ? { revision } : {}),
|
|
333
|
+
...heldFacts(shape, row),
|
|
477
334
|
};
|
|
478
335
|
}
|
|
479
336
|
const read = bodied(shape) ? parse(row.content, path) : null;
|
|
@@ -494,15 +351,21 @@ function documentOf(shape, room, row, linked) {
|
|
|
494
351
|
? {}
|
|
495
352
|
: { sections: sectionsOf(body.sections, shape.sections) }),
|
|
496
353
|
...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
|
|
497
|
-
...(shape.markdown
|
|
354
|
+
...(shape.markdown ? { markdown: row.content } : {}),
|
|
498
355
|
...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
|
|
499
|
-
...(shape.change
|
|
500
|
-
? {
|
|
501
|
-
change: row.change === null ? null : JSON.parse(row.change),
|
|
502
|
-
}
|
|
503
|
-
: {}),
|
|
356
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
504
357
|
...(shape.fileId ? { fileId } : {}),
|
|
505
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 } : {}),
|
|
506
369
|
};
|
|
507
370
|
}
|
|
508
371
|
function backlinksOf(connection, bound, rows) {
|
|
@@ -528,10 +391,10 @@ function backlinksOf(connection, bound, rows) {
|
|
|
528
391
|
return linked;
|
|
529
392
|
}
|
|
530
393
|
function documents(connection, bound, plan, shape, budget) {
|
|
531
|
-
const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(
|
|
394
|
+
const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(key, orderingOf(options), index, plan.root.pathPrefix));
|
|
532
395
|
const filter = filtered(bound, plan);
|
|
533
396
|
const cursor = plan.after?.values ?? null;
|
|
534
|
-
const room = located(
|
|
397
|
+
const room = located('d');
|
|
535
398
|
const tie = cursor === null
|
|
536
399
|
? null
|
|
537
400
|
: (() => {
|
|
@@ -547,19 +410,20 @@ function documents(connection, bound, plan, shape, budget) {
|
|
|
547
410
|
})();
|
|
548
411
|
const after = cursor === null || tie === null
|
|
549
412
|
? null
|
|
550
|
-
: 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);
|
|
551
414
|
const selected = joined([
|
|
552
|
-
fragment('SELECT d.id, d.key, d.path, d.type, d.binary, d.revision, d.fields, d.change, d.file_id'),
|
|
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'),
|
|
553
416
|
...terms.map(({ term }, index) => joined([
|
|
554
417
|
fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
555
418
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
419
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
556
420
|
], '')),
|
|
557
421
|
fragment(' FROM replica_entries d'),
|
|
558
422
|
...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
|
|
559
423
|
fragment(' WHERE '),
|
|
560
424
|
after === null ? filter : all([filter, after]),
|
|
561
425
|
fragment(' ORDER BY '),
|
|
562
|
-
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')], ', '),
|
|
563
427
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
564
428
|
], '');
|
|
565
429
|
const rows = connection.rows(selected.sql, selected.bind);
|
|
@@ -598,7 +462,7 @@ function withContent(connection, rows, budget) {
|
|
|
598
462
|
return rows;
|
|
599
463
|
const ids = rows.map((row) => row.id);
|
|
600
464
|
const sizes = new Map(connection
|
|
601
|
-
.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)
|
|
602
466
|
.map((row) => [row.id, Number(row.size ?? 0)]));
|
|
603
467
|
let cut = rows.length;
|
|
604
468
|
let spent = 0;
|
|
@@ -613,88 +477,169 @@ function withContent(connection, rows, budget) {
|
|
|
613
477
|
const kept = rows.slice(0, cut);
|
|
614
478
|
const read = kept.map((row) => row.id);
|
|
615
479
|
const held = new Map(connection
|
|
616
|
-
.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)
|
|
617
481
|
.map((row) => [row.id, row.content]));
|
|
618
482
|
return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
|
|
619
483
|
}
|
|
620
|
-
function storedFolders(bound, plan) {
|
|
621
|
-
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)
|
|
622
490
|
return null;
|
|
623
491
|
const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
|
|
624
492
|
if (whole.length === 0)
|
|
625
493
|
return null;
|
|
626
494
|
const keys = whole.map(({ key }) => key);
|
|
627
|
-
const prefix = plan.root.pathPrefix;
|
|
628
|
-
const
|
|
495
|
+
const prefix = plan.root.pathPrefix ?? '';
|
|
496
|
+
const outside = (bound.reach?.areas ?? []).flatMap((area) => {
|
|
629
497
|
const key = bound.keys.get(area.room);
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
key,
|
|
635
|
-
area.path,
|
|
636
|
-
`${area.path}/`,
|
|
637
|
-
`${area.path}0`,
|
|
638
|
-
]),
|
|
639
|
-
];
|
|
498
|
+
if (key === undefined)
|
|
499
|
+
return [];
|
|
500
|
+
const fence = fenced('f', key, area, true);
|
|
501
|
+
return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
|
|
640
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
|
+
];
|
|
641
512
|
return all([
|
|
642
|
-
fragment(`f.key IN ${
|
|
643
|
-
...
|
|
644
|
-
...
|
|
645
|
-
|
|
646
|
-
: [
|
|
647
|
-
fragment('(f.path = ? OR (f.path >= ? AND f.path < ?))', [
|
|
648
|
-
prefix,
|
|
649
|
-
`${prefix}/`,
|
|
650
|
-
`${prefix}0`,
|
|
651
|
-
]),
|
|
652
|
-
]),
|
|
513
|
+
fragment(`f.key IN ${listed(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
|
|
514
|
+
...outside,
|
|
515
|
+
...under,
|
|
516
|
+
...(plan.outside ?? []).map((folder) => fragment(`NOT ${within('f', folder).sql}`, within('f', folder).bind)),
|
|
653
517
|
...(plan.folder === null ? [] : [fragment('f.path = ?', [plan.folder])]),
|
|
654
518
|
]);
|
|
655
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
|
+
}
|
|
656
535
|
function groups(connection, bound, plan, shape) {
|
|
536
|
+
const prefix = plan.root.pathPrefix;
|
|
657
537
|
const keyed = shape.groupBy.map((key, index) => {
|
|
658
|
-
const { term, join } = keyTerm(
|
|
538
|
+
const { term, join } = keyTerm(key, GROUP_ORDER, index, prefix);
|
|
659
539
|
const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
|
|
660
540
|
return { term: { ...term, kind }, join };
|
|
661
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);
|
|
662
548
|
const filter = filtered(bound, plan);
|
|
663
549
|
const inner = joined([
|
|
664
550
|
fragment('SELECT '),
|
|
665
551
|
joined(keyed.map(({ term }, index) => joined([
|
|
666
552
|
fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
667
553
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
554
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
668
555
|
], '')), ', '),
|
|
669
|
-
fragment(', 1 AS n
|
|
670
|
-
...
|
|
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]),
|
|
671
566
|
fragment(' WHERE '),
|
|
672
567
|
filter,
|
|
673
568
|
], '');
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
|
|
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
|
+
];
|
|
677
578
|
const source = folders === null
|
|
678
579
|
? inner
|
|
679
580
|
: joined([
|
|
680
581
|
inner,
|
|
681
|
-
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),
|
|
682
583
|
folders,
|
|
683
584
|
], '');
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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,
|
|
692
608
|
kind: fragment(`k${index}`),
|
|
693
609
|
value: fragment(`v${index}`),
|
|
694
610
|
});
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
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 }));
|
|
698
643
|
const ties = keyed.flatMap((_, index) => [
|
|
699
644
|
{ ...outer(index, GROUP_TIES), total: true },
|
|
700
645
|
{ ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
|
|
@@ -704,34 +649,44 @@ function groups(connection, bound, plan, shape) {
|
|
|
704
649
|
? null
|
|
705
650
|
: cursor[shape.orderBy.length].flatMap((value) => [
|
|
706
651
|
value,
|
|
707
|
-
|
|
652
|
+
heldKind(value ?? null),
|
|
708
653
|
]);
|
|
709
654
|
const after = cursor === null || tieValues === null
|
|
710
655
|
? null
|
|
711
|
-
: following(ordered.map((term, index) => beyond(bound, term, cursor[index])), following(ties
|
|
712
|
-
|
|
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}`)], '');
|
|
713
660
|
const windowed = joined([
|
|
714
661
|
fragment('SELECT * FROM ('),
|
|
715
662
|
grouping,
|
|
716
663
|
fragment(')'),
|
|
717
664
|
...(after === null ? [] : [fragment(' WHERE '), after]),
|
|
718
665
|
fragment(' ORDER BY '),
|
|
719
|
-
joined([...ordered, ...ties].map((term) => sorted(bound, term)), ', '),
|
|
666
|
+
joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
|
|
720
667
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
721
668
|
], '');
|
|
722
669
|
const rows = connection.rows(windowed.sql, windowed.bind);
|
|
723
670
|
const kept = plan.limit === null ? rows : rows.slice(0, plan.limit);
|
|
724
671
|
const keyOf = (row) => keyed.map((_, index) => decoded(row[`k${index}`], row[`v${index}`]));
|
|
725
|
-
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
|
+
}));
|
|
726
684
|
const last = kept.at(-1);
|
|
727
685
|
const next = plan.limit === null || rows.length <= plan.limit
|
|
728
686
|
? undefined
|
|
729
687
|
: last === undefined
|
|
730
688
|
? (plan.after?.text ?? FROM_START)
|
|
731
|
-
: cursorOf([
|
|
732
|
-
...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : decoded(last[`k${by}`], last[`v${by}`])),
|
|
733
|
-
keyOf(last),
|
|
734
|
-
]);
|
|
689
|
+
: cursorOf([...shape.orderBy.map(({ by }) => valueOf(last, by)), keyOf(last)]);
|
|
735
690
|
const total = plan.total
|
|
736
691
|
? Number(connection.rows(joined([fragment('SELECT count(*) AS held FROM ('), grouping, fragment(')')], '').sql, grouping.bind)[0].held)
|
|
737
692
|
: undefined;
|
|
@@ -749,9 +704,6 @@ function boundOf(connection, plan, rooms) {
|
|
|
749
704
|
collate: collating(connection),
|
|
750
705
|
};
|
|
751
706
|
}
|
|
752
|
-
export function selected(connection, plan, rooms) {
|
|
753
|
-
return filtered(boundOf(connection, plan, rooms), plan);
|
|
754
|
-
}
|
|
755
707
|
export function answerQuery(connection, plan, rooms, budget = null) {
|
|
756
708
|
const bound = boundOf(connection, plan, rooms);
|
|
757
709
|
if (rooms.length === 0) {
|