@agentshouse/mdruntime 0.1.0-ca6edc1abd618 → 0.1.0-ca8707258fdde
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 +102 -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 +16 -14
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +272 -129
- 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 +20 -10
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +55 -6
- package/dist/collection.d.ts +7 -4
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +23 -17
- package/dist/compile.d.ts +6 -6
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +278 -282
- package/dist/connection.d.ts +22 -44
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +1600 -3577
- 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 -18
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +484 -258
- 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/file.d.ts +2 -11
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +6 -4
- package/dist/host.d.ts +3 -7
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +29 -32
- 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 +20 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -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/media.d.ts +8 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +218 -0
- package/dist/memory.d.ts +12 -5
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +216 -85
- 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 +8 -10
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +5 -17
- package/dist/observation.d.ts +3 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +8 -3
- package/dist/outcome.d.ts +14 -5
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +19 -2
- 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 +18 -207
- 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 +18 -11
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +206 -126
- package/dist/react.d.ts +5 -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 +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 +10 -11
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +52 -20
- 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/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 +18 -16
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +45 -153
- package/dist/save.d.ts +2 -0
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +22 -13
- 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/shared.d.ts +0 -1
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +0 -10
- 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 +4 -5
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -7
- 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 +76 -158
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +117 -403
- package/dist/stored.d.ts +66 -55
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +343 -273
- package/dist/tables.d.ts +118 -65
- 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 +23 -8
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +8 -2
- 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,107 +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 heldValues(doc, field, alias, where) {
|
|
44
|
-
return fragment(`SELECT 1 FROM replica_values ${alias} WHERE ${alias}.doc = ${doc}.id AND ${alias}.form = ? AND (${where.sql})`, [field, ...where.bind]);
|
|
45
|
-
}
|
|
46
|
-
function exists(part) {
|
|
47
|
-
return { sql: `EXISTS (${part.sql})`, bind: part.bind };
|
|
48
|
-
}
|
|
49
|
-
function matching(held, doc, field, values) {
|
|
50
|
-
return any(values.map((value) => any([
|
|
51
|
-
same(held, value),
|
|
52
|
-
all([
|
|
53
|
-
fragment(`${held}.kind = 'array'`),
|
|
54
|
-
exists(heldValues(doc, field, 'i', all([fragment(`i.held = ${held}.held AND i.item >= 0`), same('i', value)]))),
|
|
55
|
-
]),
|
|
56
|
-
])));
|
|
57
|
-
}
|
|
58
|
-
function tested(doc, field, test) {
|
|
59
|
-
const reached = exists(heldValues(doc, field, 'r', fragment('r.item = -1')));
|
|
60
|
-
if (test.op === 'exists')
|
|
61
|
-
return test.exists ? reached : fragment(`NOT ${reached.sql}`, reached.bind);
|
|
62
|
-
if (test.op === 'eq' || test.op === 'in') {
|
|
63
|
-
const values = test.op === 'eq' ? [test.value] : test.values;
|
|
64
|
-
return exists(heldValues(doc, field, 'v', any(values.map((value) => same('v', value)))));
|
|
65
|
-
}
|
|
66
|
-
if (test.op === 'ne' || test.op === 'nin') {
|
|
67
|
-
const values = test.op === 'ne' ? [test.value] : test.values;
|
|
68
|
-
const unmatched = matching('h', doc, field, values);
|
|
69
|
-
return any([
|
|
70
|
-
fragment(`NOT ${reached.sql}`, reached.bind),
|
|
71
|
-
exists(heldValues(doc, field, 'h', all([fragment('h.item = -1'), fragment(`NOT (${unmatched.sql})`, unmatched.bind)]))),
|
|
72
|
-
]);
|
|
73
|
-
}
|
|
74
|
-
if (test.op === 'range') {
|
|
75
|
-
return exists(heldValues(doc, field, 'v', all([
|
|
76
|
-
fragment('v.item = -1'),
|
|
77
|
-
...test.bounds.map(([operator, bound]) => {
|
|
78
|
-
const held = sqlValue(bound);
|
|
79
|
-
return fragment(`v.kind = ? AND v.value ${BOUNDS[operator]} ?`, [
|
|
80
|
-
held.kind,
|
|
81
|
-
held.value,
|
|
82
|
-
]);
|
|
83
|
-
}),
|
|
84
|
-
])));
|
|
85
|
-
}
|
|
86
|
-
if (test.op === 'all') {
|
|
87
|
-
return exists(heldValues(doc, field, 'h', all([
|
|
88
|
-
fragment("h.item = -1 AND h.kind = 'array'"),
|
|
89
|
-
...test.values.map((value) => exists(heldValues(doc, field, 'i', all([fragment('i.held = h.held AND i.item >= 0'), same('i', value)])))),
|
|
90
|
-
])));
|
|
91
|
-
}
|
|
92
|
-
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
|
+
]);
|
|
93
16
|
}
|
|
94
|
-
function
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return
|
|
99
|
-
|
|
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
|
+
});
|
|
100
23
|
}
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
+
]);
|
|
105
34
|
}
|
|
106
35
|
function reached(bound, alias) {
|
|
107
36
|
const reach = bound.reach;
|
|
@@ -115,44 +44,38 @@ function reached(bound, alias) {
|
|
|
115
44
|
const key = bound.keys.get(roomOfKey(held));
|
|
116
45
|
return key === undefined ? [] : [[key, pathOfKey(held)]];
|
|
117
46
|
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
: [
|
|
123
|
-
fragment(`EXISTS (SELECT 1 FROM json_each(?) j WHERE j.value ->> 0 = ${alias}.key AND j.value ->> 1 = ${alias}.path)`, [JSON.stringify(documents)]),
|
|
124
|
-
]),
|
|
125
|
-
]);
|
|
126
|
-
}
|
|
127
|
-
function scoped(alias, scope) {
|
|
47
|
+
const outside = any(reach.areas.flatMap((area) => {
|
|
48
|
+
const key = bound.keys.get(area.room);
|
|
49
|
+
return key === undefined ? [] : [fenced(alias, key, area, false)];
|
|
50
|
+
}));
|
|
128
51
|
return all([
|
|
129
|
-
|
|
130
|
-
? []
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
fragment(`${alias}.path >= ? AND ${alias}.path < ?`, [
|
|
136
|
-
`${scope.pathPrefix}/`,
|
|
137
|
-
`${scope.pathPrefix}0`,
|
|
52
|
+
any([
|
|
53
|
+
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${listed(rooms).sql}`, rooms)]),
|
|
54
|
+
...(documents.length === 0
|
|
55
|
+
? []
|
|
56
|
+
: [
|
|
57
|
+
fragment(`EXISTS (SELECT 1 FROM json_each(?) j WHERE j.value ->> 0 = ${alias}.key AND j.value ->> 1 = ${alias}.path)`, [JSON.stringify(documents)]),
|
|
138
58
|
]),
|
|
139
|
-
|
|
140
|
-
...(
|
|
59
|
+
]),
|
|
60
|
+
...(reach.areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
|
|
141
61
|
]);
|
|
142
62
|
}
|
|
143
63
|
function related(bound, plan) {
|
|
144
64
|
const parts = [];
|
|
145
65
|
if (plan.hop !== null) {
|
|
146
66
|
parts.push(exists(joined([
|
|
147
|
-
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
|
|
148
|
-
grouped(
|
|
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`),
|
|
68
|
+
grouped(sql.placed('l', plan.hop.field, 'link')),
|
|
69
|
+
fragment('AND'),
|
|
70
|
+
grouped(all([sql.scoped('m', plan.hop), reached(bound, 'm')])),
|
|
149
71
|
], ' ')));
|
|
150
72
|
}
|
|
151
73
|
if (plan.backlinks !== null) {
|
|
152
74
|
const field = plan.backlinks.field;
|
|
153
75
|
parts.push(exists(joined([
|
|
154
|
-
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'
|
|
155
|
-
grouped(
|
|
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`),
|
|
77
|
+
...(field === null ? [] : [grouped(sql.placed('l', field, 'link')), fragment('AND')]),
|
|
78
|
+
grouped(all([sql.scoped('s', plan.backlinks), reached(bound, 's')])),
|
|
156
79
|
], ' ')));
|
|
157
80
|
}
|
|
158
81
|
return parts;
|
|
@@ -160,11 +83,9 @@ function related(bound, plan) {
|
|
|
160
83
|
function filtered(bound, plan) {
|
|
161
84
|
const keys = bound.rooms.map(({ key }) => key);
|
|
162
85
|
return all([
|
|
163
|
-
fragment(`d.key IN ${
|
|
164
|
-
...(plan.paths === null ? [] : [fragment(`d.path IN ${list(plan.paths).sql}`, plan.paths)]),
|
|
165
|
-
...(plan.folder === null ? [] : [fragment('d.folder = ?', [plan.folder])]),
|
|
86
|
+
fragment(`d.key IN ${listed(keys).sql} AND ${VISIBLE('d')}`, keys),
|
|
166
87
|
reached(bound, 'd'),
|
|
167
|
-
|
|
88
|
+
sql.tests('d', plan),
|
|
168
89
|
...related(bound, plan),
|
|
169
90
|
]);
|
|
170
91
|
}
|
|
@@ -195,104 +116,65 @@ const GROUP_TIES = {
|
|
|
195
116
|
locale: undefined,
|
|
196
117
|
localeOptions: undefined,
|
|
197
118
|
};
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
return { placement: 1, klass: 3, scalar: JSON.stringify(value) };
|
|
209
|
-
}
|
|
210
|
-
function placement(term) {
|
|
211
|
-
const nulls = term.ordering.nulls === 'first' ? 0 : 2;
|
|
212
|
-
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]);
|
|
213
|
-
}
|
|
214
|
-
function klass(term) {
|
|
215
|
-
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);
|
|
216
|
-
}
|
|
217
|
-
function classed(term, which) {
|
|
218
|
-
const kinds = which === 1 ? "('boolean', 'number')" : which === 2 ? "('string')" : "('array')";
|
|
219
|
-
return fragment(`(CASE WHEN ${term.kind.sql} IN ${kinds} THEN ${term.value.sql} END)`, [
|
|
220
|
-
...term.kind.bind,
|
|
221
|
-
...term.value.bind,
|
|
222
|
-
]);
|
|
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]);
|
|
223
129
|
}
|
|
224
|
-
function
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
const name = bound.collate(term.ordering, which === 3, term.total === true);
|
|
228
|
-
return name === null ? '' : ` COLLATE ${name}`;
|
|
229
|
-
}
|
|
230
|
-
function sorted(bound, term) {
|
|
231
|
-
const direction = term.ordering.direction === 'desc' ? 'DESC' : 'ASC';
|
|
232
|
-
return joined([
|
|
233
|
-
placement(term),
|
|
234
|
-
fragment(`${klass(term).sql} ${direction}`, klass(term).bind),
|
|
235
|
-
...[1, 2, 3].map((which) => {
|
|
236
|
-
const value = classed(term, which);
|
|
237
|
-
return fragment(`${value.sql}${collated(bound, term, which)} ${direction}`, value.bind);
|
|
238
|
-
}),
|
|
239
|
-
], ', ');
|
|
240
|
-
}
|
|
241
|
-
function beyond(bound, term, value) {
|
|
242
|
-
const held = classOf(value, term.ordering);
|
|
243
|
-
const place = placement(term);
|
|
244
|
-
if (held.placement !== 1) {
|
|
130
|
+
function keyTerm(key, ordering, index, prefix) {
|
|
131
|
+
if (key.of === 'field') {
|
|
132
|
+
const alias = `o${index}`;
|
|
245
133
|
return {
|
|
246
|
-
|
|
247
|
-
|
|
134
|
+
term: {
|
|
135
|
+
ordering,
|
|
136
|
+
kind: fragment(`${alias}.kind`),
|
|
137
|
+
value: fragment(`${alias}.value`),
|
|
138
|
+
instant: fragment(`${alias}.instant`),
|
|
139
|
+
},
|
|
140
|
+
join: joined([
|
|
141
|
+
fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
|
|
142
|
+
grouped(sql.placed(alias, key.field, 'exact')),
|
|
143
|
+
fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
|
|
144
|
+
], ''),
|
|
248
145
|
};
|
|
249
146
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
any([
|
|
260
|
-
fragment(`${kind.sql} ${after} ${held.klass}`, kind.bind),
|
|
261
|
-
all([
|
|
262
|
-
fragment(`${kind.sql} = ${held.klass}`, kind.bind),
|
|
263
|
-
fragment(`${scalar.sql} ${after} ?${collation}`, [...scalar.bind, held.scalar]),
|
|
264
|
-
]),
|
|
265
|
-
]),
|
|
266
|
-
]),
|
|
267
|
-
]),
|
|
268
|
-
equal: all([
|
|
269
|
-
fragment(`${place.sql} = 1 AND ${kind.sql} = ${held.klass}`, [...place.bind, ...kind.bind]),
|
|
270
|
-
fragment(`${scalar.sql} = ?${collation}`, [...scalar.bind, held.scalar]),
|
|
271
|
-
]),
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
function following(parts, tie) {
|
|
275
|
-
let condition = tie;
|
|
276
|
-
for (const part of [...parts].reverse()) {
|
|
277
|
-
condition = any([part.beyond, all([part.equal, condition])]);
|
|
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
|
+
};
|
|
278
156
|
}
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
function keyTerm(bound, key, ordering, index) {
|
|
282
|
-
if (key.of === 'field') {
|
|
283
|
-
const alias = `o${index}`;
|
|
157
|
+
if (key.of === 'created' || key.of === 'updated') {
|
|
158
|
+
const { value, instant } = TIMES[key.of];
|
|
284
159
|
return {
|
|
285
|
-
term: {
|
|
286
|
-
|
|
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,
|
|
287
167
|
};
|
|
288
168
|
}
|
|
289
169
|
const expression = key.of === 'room'
|
|
290
|
-
? located(
|
|
170
|
+
? located('d')
|
|
291
171
|
: key.of === 'path'
|
|
292
172
|
? fragment('d.path')
|
|
293
173
|
: key.of === 'type'
|
|
294
174
|
? fragment('d.type')
|
|
295
|
-
:
|
|
175
|
+
: key.of === 'subfolder'
|
|
176
|
+
? subfolderOf('d.folder', prefix)
|
|
177
|
+
: fragment('d.folder');
|
|
296
178
|
return {
|
|
297
179
|
term: {
|
|
298
180
|
ordering,
|
|
@@ -313,12 +195,20 @@ function decoded(kind, value) {
|
|
|
313
195
|
return JSON.parse(value);
|
|
314
196
|
return null;
|
|
315
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
|
+
}
|
|
316
204
|
function decodedKey(key, row, index) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
return
|
|
320
|
-
|
|
321
|
-
|
|
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');
|
|
322
212
|
}
|
|
323
213
|
const registered = new WeakMap();
|
|
324
214
|
function hashed(text) {
|
|
@@ -369,9 +259,6 @@ function collating(connection) {
|
|
|
369
259
|
return name;
|
|
370
260
|
};
|
|
371
261
|
}
|
|
372
|
-
function bodied(shape) {
|
|
373
|
-
return shape.sections !== null || shape.preamble || shape.markdown;
|
|
374
|
-
}
|
|
375
262
|
function masked(pointers) {
|
|
376
263
|
const root = { whole: false, members: new Map() };
|
|
377
264
|
for (const pointer of pointers) {
|
|
@@ -439,11 +326,11 @@ function documentOf(shape, room, row, linked) {
|
|
|
439
326
|
: {
|
|
440
327
|
binaryFile: binaryRecord(path, binary, revision ?? undefined),
|
|
441
328
|
}),
|
|
442
|
-
...(shape.markdown && content !== null
|
|
443
|
-
|
|
444
|
-
: {}),
|
|
329
|
+
...(shape.markdown && content !== null ? { markdown: content } : {}),
|
|
330
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
445
331
|
...(shape.fileId ? { fileId } : {}),
|
|
446
332
|
...(shape.revision ? { revision } : {}),
|
|
333
|
+
...heldFacts(shape, row),
|
|
447
334
|
};
|
|
448
335
|
}
|
|
449
336
|
const read = bodied(shape) ? parse(row.content, path) : null;
|
|
@@ -464,15 +351,21 @@ function documentOf(shape, room, row, linked) {
|
|
|
464
351
|
? {}
|
|
465
352
|
: { sections: sectionsOf(body.sections, shape.sections) }),
|
|
466
353
|
...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
|
|
467
|
-
...(shape.markdown
|
|
354
|
+
...(shape.markdown ? { markdown: row.content } : {}),
|
|
468
355
|
...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
|
|
469
|
-
...(shape.change
|
|
470
|
-
? {
|
|
471
|
-
change: row.change === null ? null : JSON.parse(row.change),
|
|
472
|
-
}
|
|
473
|
-
: {}),
|
|
356
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
474
357
|
...(shape.fileId ? { fileId } : {}),
|
|
475
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 } : {}),
|
|
476
369
|
};
|
|
477
370
|
}
|
|
478
371
|
function backlinksOf(connection, bound, rows) {
|
|
@@ -498,10 +391,10 @@ function backlinksOf(connection, bound, rows) {
|
|
|
498
391
|
return linked;
|
|
499
392
|
}
|
|
500
393
|
function documents(connection, bound, plan, shape, budget) {
|
|
501
|
-
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));
|
|
502
395
|
const filter = filtered(bound, plan);
|
|
503
396
|
const cursor = plan.after?.values ?? null;
|
|
504
|
-
const room = located(
|
|
397
|
+
const room = located('d');
|
|
505
398
|
const tie = cursor === null
|
|
506
399
|
? null
|
|
507
400
|
: (() => {
|
|
@@ -517,19 +410,20 @@ function documents(connection, bound, plan, shape, budget) {
|
|
|
517
410
|
})();
|
|
518
411
|
const after = cursor === null || tie === null
|
|
519
412
|
? null
|
|
520
|
-
: 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);
|
|
521
414
|
const selected = joined([
|
|
522
|
-
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'),
|
|
523
416
|
...terms.map(({ term }, index) => joined([
|
|
524
417
|
fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
525
418
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
419
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
526
420
|
], '')),
|
|
527
421
|
fragment(' FROM replica_entries d'),
|
|
528
422
|
...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
|
|
529
423
|
fragment(' WHERE '),
|
|
530
424
|
after === null ? filter : all([filter, after]),
|
|
531
425
|
fragment(' ORDER BY '),
|
|
532
|
-
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')], ', '),
|
|
533
427
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
534
428
|
], '');
|
|
535
429
|
const rows = connection.rows(selected.sql, selected.bind);
|
|
@@ -568,7 +462,7 @@ function withContent(connection, rows, budget) {
|
|
|
568
462
|
return rows;
|
|
569
463
|
const ids = rows.map((row) => row.id);
|
|
570
464
|
const sizes = new Map(connection
|
|
571
|
-
.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)
|
|
572
466
|
.map((row) => [row.id, Number(row.size ?? 0)]));
|
|
573
467
|
let cut = rows.length;
|
|
574
468
|
let spent = 0;
|
|
@@ -583,74 +477,169 @@ function withContent(connection, rows, budget) {
|
|
|
583
477
|
const kept = rows.slice(0, cut);
|
|
584
478
|
const read = kept.map((row) => row.id);
|
|
585
479
|
const held = new Map(connection
|
|
586
|
-
.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)
|
|
587
481
|
.map((row) => [row.id, row.content]));
|
|
588
482
|
return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
|
|
589
483
|
}
|
|
590
|
-
function storedFolders(bound, plan) {
|
|
591
|
-
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)
|
|
592
490
|
return null;
|
|
593
491
|
const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
|
|
594
492
|
if (whole.length === 0)
|
|
595
493
|
return null;
|
|
596
494
|
const keys = whole.map(({ key }) => key);
|
|
597
|
-
const prefix = plan.root.pathPrefix;
|
|
495
|
+
const prefix = plan.root.pathPrefix ?? '';
|
|
496
|
+
const outside = (bound.reach?.areas ?? []).flatMap((area) => {
|
|
497
|
+
const key = bound.keys.get(area.room);
|
|
498
|
+
if (key === undefined)
|
|
499
|
+
return [];
|
|
500
|
+
const fence = fenced('f', key, area, true);
|
|
501
|
+
return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
|
|
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
|
+
];
|
|
598
512
|
return all([
|
|
599
|
-
fragment(`f.key IN ${
|
|
600
|
-
...
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
fragment('(f.path = ? OR (f.path >= ? AND f.path < ?))', [
|
|
604
|
-
prefix,
|
|
605
|
-
`${prefix}/`,
|
|
606
|
-
`${prefix}0`,
|
|
607
|
-
]),
|
|
608
|
-
]),
|
|
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)),
|
|
609
517
|
...(plan.folder === null ? [] : [fragment('f.path = ?', [plan.folder])]),
|
|
610
518
|
]);
|
|
611
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
|
+
}
|
|
612
535
|
function groups(connection, bound, plan, shape) {
|
|
536
|
+
const prefix = plan.root.pathPrefix;
|
|
613
537
|
const keyed = shape.groupBy.map((key, index) => {
|
|
614
|
-
const { term, join } = keyTerm(
|
|
538
|
+
const { term, join } = keyTerm(key, GROUP_ORDER, index, prefix);
|
|
615
539
|
const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
|
|
616
540
|
return { term: { ...term, kind }, join };
|
|
617
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);
|
|
618
548
|
const filter = filtered(bound, plan);
|
|
619
549
|
const inner = joined([
|
|
620
550
|
fragment('SELECT '),
|
|
621
551
|
joined(keyed.map(({ term }, index) => joined([
|
|
622
552
|
fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
623
553
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
554
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
624
555
|
], '')), ', '),
|
|
625
|
-
fragment(', 1 AS n
|
|
626
|
-
...
|
|
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]),
|
|
627
566
|
fragment(' WHERE '),
|
|
628
567
|
filter,
|
|
629
568
|
], '');
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
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
|
+
];
|
|
633
578
|
const source = folders === null
|
|
634
579
|
? inner
|
|
635
580
|
: joined([
|
|
636
581
|
inner,
|
|
637
|
-
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),
|
|
638
583
|
folders,
|
|
639
584
|
], '');
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
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,
|
|
648
608
|
kind: fragment(`k${index}`),
|
|
649
609
|
value: fragment(`v${index}`),
|
|
650
610
|
});
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
|
|
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 }));
|
|
654
643
|
const ties = keyed.flatMap((_, index) => [
|
|
655
644
|
{ ...outer(index, GROUP_TIES), total: true },
|
|
656
645
|
{ ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
|
|
@@ -660,34 +649,44 @@ function groups(connection, bound, plan, shape) {
|
|
|
660
649
|
? null
|
|
661
650
|
: cursor[shape.orderBy.length].flatMap((value) => [
|
|
662
651
|
value,
|
|
663
|
-
|
|
652
|
+
heldKind(value ?? null),
|
|
664
653
|
]);
|
|
665
654
|
const after = cursor === null || tieValues === null
|
|
666
655
|
? null
|
|
667
|
-
: following(ordered.map((term, index) => beyond(bound, term, cursor[index])), following(ties
|
|
668
|
-
|
|
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}`)], '');
|
|
669
660
|
const windowed = joined([
|
|
670
661
|
fragment('SELECT * FROM ('),
|
|
671
662
|
grouping,
|
|
672
663
|
fragment(')'),
|
|
673
664
|
...(after === null ? [] : [fragment(' WHERE '), after]),
|
|
674
665
|
fragment(' ORDER BY '),
|
|
675
|
-
joined([...ordered, ...ties].map((term) => sorted(bound, term)), ', '),
|
|
666
|
+
joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
|
|
676
667
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
677
668
|
], '');
|
|
678
669
|
const rows = connection.rows(windowed.sql, windowed.bind);
|
|
679
670
|
const kept = plan.limit === null ? rows : rows.slice(0, plan.limit);
|
|
680
671
|
const keyOf = (row) => keyed.map((_, index) => decoded(row[`k${index}`], row[`v${index}`]));
|
|
681
|
-
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
|
+
}));
|
|
682
684
|
const last = kept.at(-1);
|
|
683
685
|
const next = plan.limit === null || rows.length <= plan.limit
|
|
684
686
|
? undefined
|
|
685
687
|
: last === undefined
|
|
686
688
|
? (plan.after?.text ?? FROM_START)
|
|
687
|
-
: cursorOf([
|
|
688
|
-
...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : decoded(last[`k${by}`], last[`v${by}`])),
|
|
689
|
-
keyOf(last),
|
|
690
|
-
]);
|
|
689
|
+
: cursorOf([...shape.orderBy.map(({ by }) => valueOf(last, by)), keyOf(last)]);
|
|
691
690
|
const total = plan.total
|
|
692
691
|
? Number(connection.rows(joined([fragment('SELECT count(*) AS held FROM ('), grouping, fragment(')')], '').sql, grouping.bind)[0].held)
|
|
693
692
|
: undefined;
|
|
@@ -705,9 +704,6 @@ function boundOf(connection, plan, rooms) {
|
|
|
705
704
|
collate: collating(connection),
|
|
706
705
|
};
|
|
707
706
|
}
|
|
708
|
-
export function selected(connection, plan, rooms) {
|
|
709
|
-
return filtered(boundOf(connection, plan, rooms), plan);
|
|
710
|
-
}
|
|
711
707
|
export function answerQuery(connection, plan, rooms, budget = null) {
|
|
712
708
|
const bound = boundOf(connection, plan, rooms);
|
|
713
709
|
if (rooms.length === 0) {
|