@agentshouse/mdruntime 0.1.0-ce41fec66d91b → 0.1.0-ce5b3c1e6ea87
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 +6 -0
- package/dist/acceptance.d.ts.map +1 -0
- package/dist/acceptance.js +389 -0
- package/dist/actions.d.ts +20 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +251 -0
- package/dist/adapter.d.ts +44 -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/cell.d.ts +6 -0
- package/dist/cell.d.ts.map +1 -0
- package/dist/cell.js +9 -0
- 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 +5 -0
- package/dist/chat-types.d.ts.map +1 -0
- package/dist/chat-types.js +13 -0
- package/dist/client.d.ts +20 -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 +6 -6
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +231 -300
- package/dist/connection.d.ts +5 -141
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +101 -5058
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +7 -0
- package/dist/conversation-live.d.ts +136 -0
- package/dist/conversation-live.d.ts.map +1 -0
- package/dist/conversation-live.js +248 -0
- package/dist/conversation-row.d.ts +33 -0
- package/dist/conversation-row.d.ts.map +1 -0
- package/dist/conversation-row.js +53 -0
- package/dist/conversation.d.ts +34 -38
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +856 -850
- package/dist/discovery.d.ts +10 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +175 -0
- package/dist/durability.d.ts +15 -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 -7
- package/dist/figures.d.ts +48 -0
- package/dist/figures.d.ts.map +1 -0
- package/dist/figures.js +197 -0
- package/dist/holdings.d.ts +29 -0
- package/dist/holdings.d.ts.map +1 -0
- package/dist/holdings.js +95 -0
- package/dist/host.d.ts +4 -5
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +23 -22
- 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 +23 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/install.d.ts +56 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +560 -0
- package/dist/job.d.ts +33 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +54 -0
- package/dist/lifecycle.d.ts +7 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +268 -0
- package/dist/listing.d.ts +53 -0
- package/dist/listing.d.ts.map +1 -0
- package/dist/listing.js +175 -0
- package/dist/live.d.ts +5 -4
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +62 -20
- package/dist/locks.d.ts +9 -0
- package/dist/locks.d.ts.map +1 -0
- package/dist/locks.js +61 -0
- package/dist/log.d.ts +1 -0
- package/dist/log.d.ts.map +1 -1
- package/dist/lost.d.ts +5 -0
- package/dist/lost.d.ts.map +1 -0
- package/dist/lost.js +61 -0
- package/dist/memory.d.ts +5 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +47 -8
- 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/models.d.ts +14 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +193 -0
- package/dist/observation.d.ts +2 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +7 -2
- package/dist/opening.d.ts +4 -0
- package/dist/opening.d.ts.map +1 -0
- package/dist/opening.js +57 -0
- package/dist/outcome.d.ts +4 -1
- 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/preparation.d.ts +20 -0
- package/dist/preparation.d.ts.map +1 -0
- package/dist/preparation.js +226 -0
- package/dist/queries.d.ts +16 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/queries.js +272 -0
- package/dist/query.d.ts +13 -208
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -505
- package/dist/questions.d.ts +34 -0
- package/dist/questions.d.ts.map +1 -0
- package/dist/questions.js +122 -0
- package/dist/reach.d.ts +14 -12
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +167 -114
- package/dist/react.d.ts +10 -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 +37 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +171 -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 +7 -0
- package/dist/refusal.d.ts +5 -0
- package/dist/refusal.d.ts.map +1 -0
- package/dist/refusal.js +56 -0
- package/dist/replica.d.ts +8 -8
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +52 -16
- package/dist/room-draft.d.ts +4 -3
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +65 -52
- package/dist/room-drafts.d.ts +24 -0
- package/dist/room-drafts.d.ts.map +1 -0
- package/dist/room-drafts.js +216 -0
- package/dist/rows.d.ts +14 -21
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +48 -184
- package/dist/runtime.d.ts +43 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1 -0
- package/dist/save.d.ts +3 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +16 -11
- package/dist/saves.d.ts +70 -0
- package/dist/saves.d.ts.map +1 -0
- package/dist/saves.js +1006 -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 +11 -2
- package/dist/seed.d.ts.map +1 -1
- package/dist/seed.js +71 -0
- package/dist/shared.d.ts +1 -1
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +9 -5
- package/dist/shown.d.ts +31 -0
- package/dist/shown.d.ts.map +1 -0
- package/dist/shown.js +295 -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/lanes.d.ts +20 -0
- package/dist/socket/lanes.d.ts.map +1 -0
- package/dist/socket/lanes.js +77 -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/rooms.d.ts +43 -0
- package/dist/socket/rooms.d.ts.map +1 -0
- package/dist/socket/rooms.js +90 -0
- package/dist/socket/session.d.ts +39 -0
- package/dist/socket/session.d.ts.map +1 -0
- package/dist/socket/session.js +23 -0
- package/dist/socket.d.ts +70 -0
- package/dist/socket.d.ts.map +1 -0
- package/dist/socket.js +175 -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/readiness.d.ts +10 -0
- package/dist/store/readiness.d.ts.map +1 -0
- package/dist/store/readiness.js +52 -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/space.d.ts +11 -0
- package/dist/store/space.d.ts.map +1 -0
- package/dist/store/space.js +43 -0
- package/dist/store/watchers.d.ts +17 -0
- package/dist/store/watchers.d.ts.map +1 -0
- package/dist/store/watchers.js +57 -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 +107 -145
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +143 -366
- package/dist/stored.d.ts +58 -32
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +264 -187
- package/dist/surface.d.ts +88 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +223 -0
- package/dist/sync.d.ts +20 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +532 -0
- package/dist/tables.d.ts +117 -27
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +211 -49
- 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 +17 -1
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +7 -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,127 +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 { documentKey,
|
|
4
|
-
import {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
sql: parts.map((part) => part.sql).join(separator),
|
|
12
|
-
bind: parts.flatMap((part) => part.bind),
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
function balanced(parts, separator) {
|
|
16
|
-
if (parts.length === 1)
|
|
17
|
-
return grouped(parts[0]);
|
|
18
|
-
const half = Math.ceil(parts.length / 2);
|
|
19
|
-
return grouped(joined([balanced(parts.slice(0, half), separator), balanced(parts.slice(half), separator)], separator));
|
|
20
|
-
}
|
|
21
|
-
function all(parts) {
|
|
22
|
-
return parts.length === 0 ? fragment('1') : balanced(parts, ' AND ');
|
|
23
|
-
}
|
|
24
|
-
function any(parts) {
|
|
25
|
-
return parts.length === 0 ? fragment('0') : balanced(parts, ' OR ');
|
|
26
|
-
}
|
|
27
|
-
function grouped(part) {
|
|
28
|
-
return { sql: `(${part.sql})`, bind: part.bind };
|
|
29
|
-
}
|
|
30
|
-
function list(values) {
|
|
31
|
-
return fragment(`(${values.map(() => '?').join(', ')})`, values);
|
|
32
|
-
}
|
|
33
|
-
function sqlValue(value) {
|
|
34
|
-
if (value === null)
|
|
35
|
-
return { kind: 'null', value: null };
|
|
36
|
-
if (typeof value === 'boolean')
|
|
37
|
-
return { kind: 'boolean', value: value ? 1 : 0 };
|
|
38
|
-
if (typeof value === 'number')
|
|
39
|
-
return { kind: 'number', value };
|
|
40
|
-
return { kind: 'string', value };
|
|
41
|
-
}
|
|
42
|
-
function same(alias, value) {
|
|
43
|
-
const held = sqlValue(value);
|
|
44
|
-
return held.kind === 'null'
|
|
45
|
-
? fragment(`${alias}.kind = 'null'`)
|
|
46
|
-
: fragment(`${alias}.kind = ? AND ${alias}.value = ?`, [held.kind, held.value]);
|
|
47
|
-
}
|
|
48
|
-
const BOUNDS = { gt: '>', gte: '>=', lt: '<', lte: '<=' };
|
|
49
|
-
function pointed(alias, field, reach) {
|
|
50
|
-
const { keys, places, glob } = placedOf(field, reach);
|
|
51
|
-
return fragment(`${alias}.keys = ? AND ${alias}.places ${glob ? 'GLOB' : '='} ?`, [keys, places]);
|
|
52
|
-
}
|
|
53
|
-
function heldValues(doc, field, alias, where) {
|
|
54
|
-
return joined([
|
|
55
|
-
fragment(`SELECT 1 FROM replica_values ${alias} WHERE ${alias}.doc = ${doc}.id AND `),
|
|
56
|
-
grouped(pointed(alias, field, 'value')),
|
|
57
|
-
fragment(' AND '),
|
|
58
|
-
grouped(where),
|
|
59
|
-
], '');
|
|
60
|
-
}
|
|
61
|
-
function exists(part) {
|
|
62
|
-
return { sql: `EXISTS (${part.sql})`, bind: part.bind };
|
|
63
|
-
}
|
|
64
|
-
function itemOf(held, where) {
|
|
65
|
-
return exists(joined([
|
|
66
|
-
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 `),
|
|
67
|
-
grouped(where),
|
|
68
|
-
], ''));
|
|
69
|
-
}
|
|
70
|
-
function matching(held, values) {
|
|
71
|
-
return any(values.map((value) => any([
|
|
72
|
-
same(held, value),
|
|
73
|
-
all([fragment(`${held}.kind = 'array'`), itemOf(held, same('i', value))]),
|
|
74
|
-
])));
|
|
75
|
-
}
|
|
76
|
-
function tested(doc, field, test) {
|
|
77
|
-
const reached = exists(heldValues(doc, field, 'r', fragment('r.item = -1')));
|
|
78
|
-
if (test.op === 'exists')
|
|
79
|
-
return test.exists ? reached : fragment(`NOT ${reached.sql}`, reached.bind);
|
|
80
|
-
if (test.op === 'eq' || test.op === 'in') {
|
|
81
|
-
const values = test.op === 'eq' ? [test.value] : test.values;
|
|
82
|
-
return exists(heldValues(doc, field, 'v', any(values.map((value) => same('v', value)))));
|
|
83
|
-
}
|
|
84
|
-
if (test.op === 'ne' || test.op === 'nin') {
|
|
85
|
-
const values = test.op === 'ne' ? [test.value] : test.values;
|
|
86
|
-
const unmatched = matching('h', values);
|
|
87
|
-
return any([
|
|
88
|
-
fragment(`NOT ${reached.sql}`, reached.bind),
|
|
89
|
-
exists(heldValues(doc, field, 'h', all([fragment('h.item = -1'), fragment(`NOT (${unmatched.sql})`, unmatched.bind)]))),
|
|
90
|
-
]);
|
|
91
|
-
}
|
|
92
|
-
if (test.op === 'range') {
|
|
93
|
-
return exists(heldValues(doc, field, 'v', all([
|
|
94
|
-
fragment('v.item = -1'),
|
|
95
|
-
...test.bounds.map(([operator, bound]) => {
|
|
96
|
-
const held = sqlValue(bound);
|
|
97
|
-
return fragment(`v.kind = ? AND v.value ${BOUNDS[operator]} ?`, [
|
|
98
|
-
held.kind,
|
|
99
|
-
held.value,
|
|
100
|
-
]);
|
|
101
|
-
}),
|
|
102
|
-
])));
|
|
103
|
-
}
|
|
104
|
-
if (test.op === 'all') {
|
|
105
|
-
return exists(heldValues(doc, field, 'h', all([
|
|
106
|
-
fragment("h.item = -1 AND h.kind = 'array'"),
|
|
107
|
-
...test.values.map((value) => itemOf('h', same('i', value))),
|
|
108
|
-
])));
|
|
109
|
-
}
|
|
110
|
-
return exists(heldValues(doc, field, 'v', fragment("v.item = -1 AND v.kind = 'array' AND v.size = ?", [test.size])));
|
|
111
|
-
}
|
|
112
|
-
function conditioned(doc, condition) {
|
|
113
|
-
if ('every' in condition)
|
|
114
|
-
return all(condition.every.map((one) => conditioned(doc, one)));
|
|
115
|
-
if ('some' in condition)
|
|
116
|
-
return any(condition.some.map((one) => conditioned(doc, one)));
|
|
117
|
-
return tested(doc, condition.field, condition.test);
|
|
118
|
-
}
|
|
119
|
-
function located(bound, alias) {
|
|
120
|
-
if (bound.rooms.length === 0)
|
|
121
|
-
return fragment("''");
|
|
122
|
-
return fragment(`CASE ${alias}.key ${bound.rooms.map(() => 'WHEN ? THEN ?').join(' ')} END`, bound.rooms.flatMap(({ key, locator }) => [key, locator]));
|
|
123
|
-
}
|
|
124
|
-
function within(alias, path) {
|
|
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) {
|
|
125
11
|
return fragment(`(${alias}.path = ? OR (${alias}.path >= ? AND ${alias}.path < ?))`, [
|
|
126
12
|
path,
|
|
127
13
|
`${path}/`,
|
|
@@ -164,7 +50,7 @@ function reached(bound, alias) {
|
|
|
164
50
|
}));
|
|
165
51
|
return all([
|
|
166
52
|
any([
|
|
167
|
-
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${
|
|
53
|
+
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${listed(rooms).sql}`, rooms)]),
|
|
168
54
|
...(documents.length === 0
|
|
169
55
|
? []
|
|
170
56
|
: [
|
|
@@ -174,40 +60,22 @@ function reached(bound, alias) {
|
|
|
174
60
|
...(reach.areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
|
|
175
61
|
]);
|
|
176
62
|
}
|
|
177
|
-
function unindexed(column) {
|
|
178
|
-
return `+${column}`;
|
|
179
|
-
}
|
|
180
|
-
function scoped(alias, scope, byPaths = false) {
|
|
181
|
-
const type = byPaths ? unindexed(`${alias}.type`) : `${alias}.type`;
|
|
182
|
-
return all([
|
|
183
|
-
...(scope.types === null ? [] : [fragment(`${type} IN ${list(scope.types).sql}`, scope.types)]),
|
|
184
|
-
...(scope.pathPrefix === null || scope.pathPrefix === ''
|
|
185
|
-
? []
|
|
186
|
-
: [
|
|
187
|
-
fragment(`${alias}.path >= ? AND ${alias}.path < ?`, [
|
|
188
|
-
`${scope.pathPrefix}/`,
|
|
189
|
-
`${scope.pathPrefix}0`,
|
|
190
|
-
]),
|
|
191
|
-
]),
|
|
192
|
-
...(scope.where === null ? [] : [conditioned(alias, scope.where)]),
|
|
193
|
-
]);
|
|
194
|
-
}
|
|
195
63
|
function related(bound, plan) {
|
|
196
64
|
const parts = [];
|
|
197
65
|
if (plan.hop !== null) {
|
|
198
66
|
parts.push(exists(joined([
|
|
199
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`),
|
|
200
|
-
grouped(
|
|
68
|
+
grouped(sql.placed('l', plan.hop.field, 'link')),
|
|
201
69
|
fragment('AND'),
|
|
202
|
-
grouped(all([scoped('m', plan.hop), reached(bound, 'm')])),
|
|
70
|
+
grouped(all([sql.scoped('m', plan.hop), reached(bound, 'm')])),
|
|
203
71
|
], ' ')));
|
|
204
72
|
}
|
|
205
73
|
if (plan.backlinks !== null) {
|
|
206
74
|
const field = plan.backlinks.field;
|
|
207
75
|
parts.push(exists(joined([
|
|
208
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`),
|
|
209
|
-
...(field === null ? [] : [grouped(
|
|
210
|
-
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')])),
|
|
211
79
|
], ' ')));
|
|
212
80
|
}
|
|
213
81
|
return parts;
|
|
@@ -215,11 +83,9 @@ function related(bound, plan) {
|
|
|
215
83
|
function filtered(bound, plan) {
|
|
216
84
|
const keys = bound.rooms.map(({ key }) => key);
|
|
217
85
|
return all([
|
|
218
|
-
fragment(`d.key IN ${
|
|
219
|
-
...(plan.paths === null ? [] : [fragment(`d.path IN ${list(plan.paths).sql}`, plan.paths)]),
|
|
220
|
-
...(plan.folder === null ? [] : [fragment('d.folder = ?', [plan.folder])]),
|
|
86
|
+
fragment(`d.key IN ${listed(keys).sql} AND ${VISIBLE('d')}`, keys),
|
|
221
87
|
reached(bound, 'd'),
|
|
222
|
-
|
|
88
|
+
sql.tests('d', plan),
|
|
223
89
|
...related(bound, plan),
|
|
224
90
|
]);
|
|
225
91
|
}
|
|
@@ -250,108 +116,65 @@ const GROUP_TIES = {
|
|
|
250
116
|
locale: undefined,
|
|
251
117
|
localeOptions: undefined,
|
|
252
118
|
};
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return { placement: 1, klass: 3, scalar: JSON.stringify(value) };
|
|
264
|
-
}
|
|
265
|
-
function placement(term) {
|
|
266
|
-
const nulls = term.ordering.nulls === 'first' ? 0 : 2;
|
|
267
|
-
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]);
|
|
268
|
-
}
|
|
269
|
-
function klass(term) {
|
|
270
|
-
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);
|
|
271
|
-
}
|
|
272
|
-
function classed(term, which) {
|
|
273
|
-
const kinds = which === 1 ? "('boolean', 'number')" : which === 2 ? "('string')" : "('array')";
|
|
274
|
-
return fragment(`(CASE WHEN ${term.kind.sql} IN ${kinds} THEN ${term.value.sql} END)`, [
|
|
275
|
-
...term.kind.bind,
|
|
276
|
-
...term.value.bind,
|
|
277
|
-
]);
|
|
278
|
-
}
|
|
279
|
-
function collated(bound, term, which) {
|
|
280
|
-
if (which === 1)
|
|
281
|
-
return '';
|
|
282
|
-
const name = bound.collate(term.ordering, which === 3, term.total === true);
|
|
283
|
-
return name === null ? '' : ` COLLATE ${name}`;
|
|
284
|
-
}
|
|
285
|
-
function sorted(bound, term) {
|
|
286
|
-
const direction = term.ordering.direction === 'desc' ? 'DESC' : 'ASC';
|
|
287
|
-
return joined([
|
|
288
|
-
placement(term),
|
|
289
|
-
fragment(`${klass(term).sql} ${direction}`, klass(term).bind),
|
|
290
|
-
...[1, 2, 3].map((which) => {
|
|
291
|
-
const value = classed(term, which);
|
|
292
|
-
return fragment(`${value.sql}${collated(bound, term, which)} ${direction}`, value.bind);
|
|
293
|
-
}),
|
|
294
|
-
], ', ');
|
|
295
|
-
}
|
|
296
|
-
function beyond(bound, term, value) {
|
|
297
|
-
const held = classOf(value, term.ordering);
|
|
298
|
-
const place = placement(term);
|
|
299
|
-
if (held.placement !== 1) {
|
|
300
|
-
return {
|
|
301
|
-
beyond: fragment(`${place.sql} > ${held.placement}`, place.bind),
|
|
302
|
-
equal: fragment(`${place.sql} = ${held.placement}`, place.bind),
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
const after = term.ordering.direction === 'desc' ? '<' : '>';
|
|
306
|
-
const kind = klass(term);
|
|
307
|
-
const scalar = classed(term, held.klass);
|
|
308
|
-
const collation = collated(bound, term, held.klass);
|
|
309
|
-
return {
|
|
310
|
-
beyond: any([
|
|
311
|
-
fragment(`${place.sql} > 1`, place.bind),
|
|
312
|
-
all([
|
|
313
|
-
fragment(`${place.sql} = 1`, place.bind),
|
|
314
|
-
any([
|
|
315
|
-
fragment(`${kind.sql} ${after} ${held.klass}`, kind.bind),
|
|
316
|
-
all([
|
|
317
|
-
fragment(`${kind.sql} = ${held.klass}`, kind.bind),
|
|
318
|
-
fragment(`${scalar.sql} ${after} ?${collation}`, [...scalar.bind, held.scalar]),
|
|
319
|
-
]),
|
|
320
|
-
]),
|
|
321
|
-
]),
|
|
322
|
-
]),
|
|
323
|
-
equal: all([
|
|
324
|
-
fragment(`${place.sql} = 1 AND ${kind.sql} = ${held.klass}`, [...place.bind, ...kind.bind]),
|
|
325
|
-
fragment(`${scalar.sql} = ?${collation}`, [...scalar.bind, held.scalar]),
|
|
326
|
-
]),
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
function following(parts, tie) {
|
|
330
|
-
let condition = tie;
|
|
331
|
-
for (const part of [...parts].reverse()) {
|
|
332
|
-
condition = any([part.beyond, all([part.equal, condition])]);
|
|
333
|
-
}
|
|
334
|
-
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]);
|
|
335
129
|
}
|
|
336
|
-
function keyTerm(
|
|
130
|
+
function keyTerm(key, ordering, index, prefix) {
|
|
337
131
|
if (key.of === 'field') {
|
|
338
132
|
const alias = `o${index}`;
|
|
339
133
|
return {
|
|
340
|
-
term: {
|
|
134
|
+
term: {
|
|
135
|
+
ordering,
|
|
136
|
+
kind: fragment(`${alias}.kind`),
|
|
137
|
+
value: fragment(`${alias}.value`),
|
|
138
|
+
instant: fragment(`${alias}.instant`),
|
|
139
|
+
},
|
|
341
140
|
join: joined([
|
|
342
141
|
fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
|
|
343
|
-
grouped(
|
|
142
|
+
grouped(sql.placed(alias, key.field, 'exact')),
|
|
344
143
|
fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
|
|
345
144
|
], ''),
|
|
346
145
|
};
|
|
347
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
|
+
}
|
|
348
169
|
const expression = key.of === 'room'
|
|
349
|
-
? located(
|
|
170
|
+
? located('d')
|
|
350
171
|
: key.of === 'path'
|
|
351
172
|
? fragment('d.path')
|
|
352
173
|
: key.of === 'type'
|
|
353
174
|
? fragment('d.type')
|
|
354
|
-
:
|
|
175
|
+
: key.of === 'subfolder'
|
|
176
|
+
? subfolderOf('d.folder', prefix)
|
|
177
|
+
: fragment('d.folder');
|
|
355
178
|
return {
|
|
356
179
|
term: {
|
|
357
180
|
ordering,
|
|
@@ -372,12 +195,20 @@ function decoded(kind, value) {
|
|
|
372
195
|
return JSON.parse(value);
|
|
373
196
|
return null;
|
|
374
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
|
+
}
|
|
375
204
|
function decodedKey(key, row, index) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
return
|
|
379
|
-
|
|
380
|
-
|
|
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');
|
|
381
212
|
}
|
|
382
213
|
const registered = new WeakMap();
|
|
383
214
|
function hashed(text) {
|
|
@@ -428,9 +259,6 @@ function collating(connection) {
|
|
|
428
259
|
return name;
|
|
429
260
|
};
|
|
430
261
|
}
|
|
431
|
-
function bodied(shape) {
|
|
432
|
-
return shape.sections !== null || shape.preamble || shape.markdown;
|
|
433
|
-
}
|
|
434
262
|
function masked(pointers) {
|
|
435
263
|
const root = { whole: false, members: new Map() };
|
|
436
264
|
for (const pointer of pointers) {
|
|
@@ -499,8 +327,10 @@ function documentOf(shape, room, row, linked) {
|
|
|
499
327
|
binaryFile: binaryRecord(path, binary, revision ?? undefined),
|
|
500
328
|
}),
|
|
501
329
|
...(shape.markdown && content !== null ? { markdown: content } : {}),
|
|
330
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
502
331
|
...(shape.fileId ? { fileId } : {}),
|
|
503
332
|
...(shape.revision ? { revision } : {}),
|
|
333
|
+
...heldFacts(shape, row),
|
|
504
334
|
};
|
|
505
335
|
}
|
|
506
336
|
const read = bodied(shape) ? parse(row.content, path) : null;
|
|
@@ -523,13 +353,19 @@ function documentOf(shape, room, row, linked) {
|
|
|
523
353
|
...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
|
|
524
354
|
...(shape.markdown ? { markdown: row.content } : {}),
|
|
525
355
|
...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
|
|
526
|
-
...(shape.change
|
|
527
|
-
? {
|
|
528
|
-
change: row.change === null ? null : JSON.parse(row.change),
|
|
529
|
-
}
|
|
530
|
-
: {}),
|
|
356
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
531
357
|
...(shape.fileId ? { fileId } : {}),
|
|
532
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 } : {}),
|
|
533
369
|
};
|
|
534
370
|
}
|
|
535
371
|
function backlinksOf(connection, bound, rows) {
|
|
@@ -555,10 +391,10 @@ function backlinksOf(connection, bound, rows) {
|
|
|
555
391
|
return linked;
|
|
556
392
|
}
|
|
557
393
|
function documents(connection, bound, plan, shape, budget) {
|
|
558
|
-
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));
|
|
559
395
|
const filter = filtered(bound, plan);
|
|
560
396
|
const cursor = plan.after?.values ?? null;
|
|
561
|
-
const room = located(
|
|
397
|
+
const room = located('d');
|
|
562
398
|
const tie = cursor === null
|
|
563
399
|
? null
|
|
564
400
|
: (() => {
|
|
@@ -574,19 +410,20 @@ function documents(connection, bound, plan, shape, budget) {
|
|
|
574
410
|
})();
|
|
575
411
|
const after = cursor === null || tie === null
|
|
576
412
|
? null
|
|
577
|
-
: 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);
|
|
578
414
|
const selected = joined([
|
|
579
|
-
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'),
|
|
580
416
|
...terms.map(({ term }, index) => joined([
|
|
581
417
|
fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
582
418
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
419
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
583
420
|
], '')),
|
|
584
421
|
fragment(' FROM replica_entries d'),
|
|
585
422
|
...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
|
|
586
423
|
fragment(' WHERE '),
|
|
587
424
|
after === null ? filter : all([filter, after]),
|
|
588
425
|
fragment(' ORDER BY '),
|
|
589
|
-
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')], ', '),
|
|
590
427
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
591
428
|
], '');
|
|
592
429
|
const rows = connection.rows(selected.sql, selected.bind);
|
|
@@ -625,7 +462,7 @@ function withContent(connection, rows, budget) {
|
|
|
625
462
|
return rows;
|
|
626
463
|
const ids = rows.map((row) => row.id);
|
|
627
464
|
const sizes = new Map(connection
|
|
628
|
-
.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)
|
|
629
466
|
.map((row) => [row.id, Number(row.size ?? 0)]));
|
|
630
467
|
let cut = rows.length;
|
|
631
468
|
let spent = 0;
|
|
@@ -640,18 +477,22 @@ function withContent(connection, rows, budget) {
|
|
|
640
477
|
const kept = rows.slice(0, cut);
|
|
641
478
|
const read = kept.map((row) => row.id);
|
|
642
479
|
const held = new Map(connection
|
|
643
|
-
.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)
|
|
644
481
|
.map((row) => [row.id, row.content]));
|
|
645
482
|
return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
|
|
646
483
|
}
|
|
647
|
-
function storedFolders(bound, plan) {
|
|
648
|
-
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)
|
|
649
490
|
return null;
|
|
650
491
|
const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
|
|
651
492
|
if (whole.length === 0)
|
|
652
493
|
return null;
|
|
653
494
|
const keys = whole.map(({ key }) => key);
|
|
654
|
-
const prefix = plan.root.pathPrefix;
|
|
495
|
+
const prefix = plan.root.pathPrefix ?? '';
|
|
655
496
|
const outside = (bound.reach?.areas ?? []).flatMap((area) => {
|
|
656
497
|
const key = bound.keys.get(area.room);
|
|
657
498
|
if (key === undefined)
|
|
@@ -659,63 +500,146 @@ function storedFolders(bound, plan) {
|
|
|
659
500
|
const fence = fenced('f', key, area, true);
|
|
660
501
|
return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
|
|
661
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
|
+
];
|
|
662
512
|
return all([
|
|
663
|
-
fragment(`f.key IN ${
|
|
513
|
+
fragment(`f.key IN ${listed(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
|
|
664
514
|
...outside,
|
|
665
|
-
...
|
|
666
|
-
|
|
667
|
-
: [
|
|
668
|
-
fragment('(f.path = ? OR (f.path >= ? AND f.path < ?))', [
|
|
669
|
-
prefix,
|
|
670
|
-
`${prefix}/`,
|
|
671
|
-
`${prefix}0`,
|
|
672
|
-
]),
|
|
673
|
-
]),
|
|
515
|
+
...under,
|
|
516
|
+
...(plan.outside ?? []).map((folder) => fragment(`NOT ${within('f', folder).sql}`, within('f', folder).bind)),
|
|
674
517
|
...(plan.folder === null ? [] : [fragment('f.path = ?', [plan.folder])]),
|
|
675
518
|
]);
|
|
676
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
|
+
}
|
|
677
535
|
function groups(connection, bound, plan, shape) {
|
|
536
|
+
const prefix = plan.root.pathPrefix;
|
|
678
537
|
const keyed = shape.groupBy.map((key, index) => {
|
|
679
|
-
const { term, join } = keyTerm(
|
|
538
|
+
const { term, join } = keyTerm(key, GROUP_ORDER, index, prefix);
|
|
680
539
|
const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
|
|
681
540
|
return { term: { ...term, kind }, join };
|
|
682
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);
|
|
683
548
|
const filter = filtered(bound, plan);
|
|
684
549
|
const inner = joined([
|
|
685
550
|
fragment('SELECT '),
|
|
686
551
|
joined(keyed.map(({ term }, index) => joined([
|
|
687
552
|
fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
688
553
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
554
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
689
555
|
], '')), ', '),
|
|
690
|
-
fragment(', 1 AS n
|
|
691
|
-
...
|
|
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]),
|
|
692
566
|
fragment(' WHERE '),
|
|
693
567
|
filter,
|
|
694
568
|
], '');
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
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
|
+
];
|
|
698
578
|
const source = folders === null
|
|
699
579
|
? inner
|
|
700
580
|
: joined([
|
|
701
581
|
inner,
|
|
702
|
-
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),
|
|
703
583
|
folders,
|
|
704
584
|
], '');
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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,
|
|
713
608
|
kind: fragment(`k${index}`),
|
|
714
609
|
value: fragment(`v${index}`),
|
|
715
610
|
});
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
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 }));
|
|
719
643
|
const ties = keyed.flatMap((_, index) => [
|
|
720
644
|
{ ...outer(index, GROUP_TIES), total: true },
|
|
721
645
|
{ ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
|
|
@@ -725,34 +649,44 @@ function groups(connection, bound, plan, shape) {
|
|
|
725
649
|
? null
|
|
726
650
|
: cursor[shape.orderBy.length].flatMap((value) => [
|
|
727
651
|
value,
|
|
728
|
-
|
|
652
|
+
heldKind(value ?? null),
|
|
729
653
|
]);
|
|
730
654
|
const after = cursor === null || tieValues === null
|
|
731
655
|
? null
|
|
732
|
-
: following(ordered.map((term, index) => beyond(bound, term, cursor[index])), following(ties
|
|
733
|
-
|
|
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}`)], '');
|
|
734
660
|
const windowed = joined([
|
|
735
661
|
fragment('SELECT * FROM ('),
|
|
736
662
|
grouping,
|
|
737
663
|
fragment(')'),
|
|
738
664
|
...(after === null ? [] : [fragment(' WHERE '), after]),
|
|
739
665
|
fragment(' ORDER BY '),
|
|
740
|
-
joined([...ordered, ...ties].map((term) => sorted(bound, term)), ', '),
|
|
666
|
+
joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
|
|
741
667
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
742
668
|
], '');
|
|
743
669
|
const rows = connection.rows(windowed.sql, windowed.bind);
|
|
744
670
|
const kept = plan.limit === null ? rows : rows.slice(0, plan.limit);
|
|
745
671
|
const keyOf = (row) => keyed.map((_, index) => decoded(row[`k${index}`], row[`v${index}`]));
|
|
746
|
-
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
|
+
}));
|
|
747
684
|
const last = kept.at(-1);
|
|
748
685
|
const next = plan.limit === null || rows.length <= plan.limit
|
|
749
686
|
? undefined
|
|
750
687
|
: last === undefined
|
|
751
688
|
? (plan.after?.text ?? FROM_START)
|
|
752
|
-
: cursorOf([
|
|
753
|
-
...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : decoded(last[`k${by}`], last[`v${by}`])),
|
|
754
|
-
keyOf(last),
|
|
755
|
-
]);
|
|
689
|
+
: cursorOf([...shape.orderBy.map(({ by }) => valueOf(last, by)), keyOf(last)]);
|
|
756
690
|
const total = plan.total
|
|
757
691
|
? Number(connection.rows(joined([fragment('SELECT count(*) AS held FROM ('), grouping, fragment(')')], '').sql, grouping.bind)[0].held)
|
|
758
692
|
: undefined;
|
|
@@ -770,9 +704,6 @@ function boundOf(connection, plan, rooms) {
|
|
|
770
704
|
collate: collating(connection),
|
|
771
705
|
};
|
|
772
706
|
}
|
|
773
|
-
export function selected(connection, plan, rooms) {
|
|
774
|
-
return filtered(boundOf(connection, plan, rooms), plan);
|
|
775
|
-
}
|
|
776
707
|
export function answerQuery(connection, plan, rooms, budget = null) {
|
|
777
708
|
const bound = boundOf(connection, plan, rooms);
|
|
778
709
|
if (rooms.length === 0) {
|