@agentshouse/mdruntime 0.1.0-cba6f2439bb25 → 0.1.0-cbbae1fff01c6
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 +10 -10
- package/dist/acceptance.d.ts +3 -0
- package/dist/acceptance.d.ts.map +1 -0
- package/dist/acceptance.js +308 -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 +95 -103
- 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 +12 -11
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +213 -89
- 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-set.d.ts +4 -0
- package/dist/change-set.d.ts.map +1 -1
- package/dist/change-set.js +6 -0
- package/dist/change.d.ts +1 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +4 -2
- 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 +24 -7
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +9 -5
- package/dist/collection.d.ts +23 -7
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +13 -19
- package/dist/compile.d.ts +6 -6
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +272 -332
- package/dist/connection.d.ts +5 -143
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +105 -5253
- 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 +135 -0
- package/dist/conversation-live.d.ts.map +1 -0
- package/dist/conversation-live.js +247 -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 +41 -47
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +874 -911
- package/dist/declaration.d.ts +10 -0
- package/dist/declaration.d.ts.map +1 -0
- package/dist/declaration.js +24 -0
- package/dist/discovery.d.ts +10 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +148 -0
- package/dist/durability.d.ts +15 -1
- 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/expectation.d.ts +4 -2
- package/dist/expectation.d.ts.map +1 -1
- package/dist/expectation.js +33 -11
- package/dist/failure.d.ts +1 -0
- package/dist/failure.d.ts.map +1 -1
- package/dist/failure.js +3 -0
- package/dist/figures.d.ts +49 -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 +20 -8
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +44 -34
- 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 +28 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/install.d.ts +54 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +538 -0
- package/dist/job.d.ts +5 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +7 -0
- package/dist/lifecycle.d.ts +7 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +272 -0
- package/dist/listing.d.ts +48 -0
- package/dist/listing.d.ts.map +1 -0
- package/dist/listing.js +139 -0
- package/dist/live.d.ts +5 -4
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +64 -23
- package/dist/locks.d.ts +7 -0
- package/dist/locks.d.ts.map +1 -0
- package/dist/locks.js +33 -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 +50 -0
- package/dist/media.d.ts +2 -2
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js +22 -12
- package/dist/memory.d.ts +14 -5
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +177 -85
- package/dist/messages.d.ts +24 -3
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +89 -31
- package/dist/model.d.ts +2 -11
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +0 -23
- package/dist/models.d.ts +10 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +156 -0
- package/dist/observation.d.ts +2 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +14 -14
- package/dist/opened.d.ts +1 -1
- package/dist/opened.d.ts.map +1 -1
- package/dist/opened.js +5 -5
- package/dist/opening.d.ts +4 -0
- package/dist/opening.d.ts.map +1 -0
- package/dist/opening.js +59 -0
- package/dist/outcome.d.ts +4 -4
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +3 -0
- package/dist/overlay.d.ts +3 -1
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +43 -21
- package/dist/parts.d.ts +11 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +21 -0
- package/dist/paths.d.ts +1 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +22 -0
- package/dist/preparation.d.ts +20 -0
- package/dist/preparation.d.ts.map +1 -0
- package/dist/preparation.js +233 -0
- package/dist/presence.d.ts +52 -0
- package/dist/presence.d.ts.map +1 -0
- package/dist/presence.js +220 -0
- package/dist/queries.d.ts +16 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/queries.js +270 -0
- package/dist/query.d.ts +14 -210
- 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/queue.d.ts +40 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +455 -0
- package/dist/reach.d.ts +15 -13
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +187 -140
- package/dist/react.d.ts +14 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +10 -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 +2 -16
- package/dist/readiness.d.ts.map +1 -1
- package/dist/readiness.js +4 -10
- package/dist/reads.d.ts +36 -10
- package/dist/reads.d.ts.map +1 -1
- package/dist/reads.js +160 -77
- 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 +35 -7
- package/dist/refusal.d.ts +5 -0
- package/dist/refusal.d.ts.map +1 -0
- package/dist/refusal.js +57 -0
- package/dist/replica.d.ts +11 -17
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +64 -34
- package/dist/room-draft.d.ts +7 -8
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +100 -103
- package/dist/room-drafts.d.ts +24 -0
- package/dist/room-drafts.d.ts.map +1 -0
- package/dist/room-drafts.js +217 -0
- package/dist/rows.d.ts +14 -21
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +49 -186
- package/dist/runtime.d.ts +34 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1 -0
- package/dist/save.d.ts +41 -86
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +239 -116
- package/dist/saves.d.ts +32 -0
- package/dist/saves.d.ts.map +1 -0
- package/dist/saves.js +413 -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 -1
- package/dist/shared.d.ts +1 -7
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +13 -105
- package/dist/shown.d.ts +32 -0
- package/dist/shown.d.ts.map +1 -0
- package/dist/shown.js +290 -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/session.d.ts +38 -0
- package/dist/socket/session.d.ts.map +1 -0
- package/dist/socket/session.js +23 -0
- package/dist/socket.d.ts +67 -0
- package/dist/socket.d.ts.map +1 -0
- package/dist/socket.js +170 -0
- package/dist/source.d.ts +61 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +82 -0
- package/dist/status.d.ts +0 -6
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +0 -6
- 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 +210 -0
- package/dist/store/records.d.ts.map +1 -0
- package/dist/store/records.js +428 -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 +41 -0
- package/dist/store/watchers.d.ts +17 -0
- package/dist/store/watchers.d.ts.map +1 -0
- package/dist/store/watchers.js +56 -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 +110 -157
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +162 -394
- package/dist/stored.d.ts +59 -38
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +287 -206
- 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 +19 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +521 -0
- package/dist/tables.d.ts +116 -56
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +214 -98
- 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 +21 -9
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +10 -4
- package/package.json +3 -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,125 +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
|
-
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])));
|
|
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
|
+
]);
|
|
111
16
|
}
|
|
112
|
-
function
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return
|
|
117
|
-
|
|
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
|
+
});
|
|
118
23
|
}
|
|
119
|
-
function
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
+
]);
|
|
123
34
|
}
|
|
124
35
|
function reached(bound, alias) {
|
|
125
36
|
const reach = bound.reach;
|
|
@@ -133,42 +44,20 @@ function reached(bound, alias) {
|
|
|
133
44
|
const key = bound.keys.get(roomOfKey(held));
|
|
134
45
|
return key === undefined ? [] : [[key, pathOfKey(held)]];
|
|
135
46
|
});
|
|
136
|
-
const
|
|
47
|
+
const outside = any(reach.areas.flatMap((area) => {
|
|
137
48
|
const key = bound.keys.get(area.room);
|
|
138
|
-
return key === undefined ? [] : [
|
|
139
|
-
});
|
|
140
|
-
const beneath = (area) => fragment(`${alias}.key = ? AND (${alias}.path = ? OR (${alias}.path >= ? AND ${alias}.path < ?))`, [area.key, area.path, `${area.path}/`, `${area.path}0`]);
|
|
141
|
-
const typed = (types) => fragment(`${alias}.type IN ${list(types).sql}`, types);
|
|
142
|
-
const outside = any(areas.map((area) => area.types.length === 0
|
|
143
|
-
? beneath(area)
|
|
144
|
-
: all([beneath(area), fragment(`NOT ${grouped(typed(area.types)).sql}`, area.types)])));
|
|
49
|
+
return key === undefined ? [] : [fenced(alias, key, area, false)];
|
|
50
|
+
}));
|
|
145
51
|
return all([
|
|
146
52
|
any([
|
|
147
|
-
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${
|
|
53
|
+
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${listed(rooms).sql}`, rooms)]),
|
|
148
54
|
...(documents.length === 0
|
|
149
55
|
? []
|
|
150
56
|
: [
|
|
151
57
|
fragment(`EXISTS (SELECT 1 FROM json_each(?) j WHERE j.value ->> 0 = ${alias}.key AND j.value ->> 1 = ${alias}.path)`, [JSON.stringify(documents)]),
|
|
152
58
|
]),
|
|
153
|
-
...areas.flatMap((area) => area.types.length === 0 ? [] : [all([beneath(area), typed(area.types)])]),
|
|
154
59
|
]),
|
|
155
|
-
...(areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
|
|
156
|
-
]);
|
|
157
|
-
}
|
|
158
|
-
function scoped(alias, scope) {
|
|
159
|
-
return all([
|
|
160
|
-
...(scope.types === null
|
|
161
|
-
? []
|
|
162
|
-
: [fragment(`${alias}.type IN ${list(scope.types).sql}`, scope.types)]),
|
|
163
|
-
...(scope.pathPrefix === null || scope.pathPrefix === ''
|
|
164
|
-
? []
|
|
165
|
-
: [
|
|
166
|
-
fragment(`${alias}.path >= ? AND ${alias}.path < ?`, [
|
|
167
|
-
`${scope.pathPrefix}/`,
|
|
168
|
-
`${scope.pathPrefix}0`,
|
|
169
|
-
]),
|
|
170
|
-
]),
|
|
171
|
-
...(scope.where === null ? [] : [conditioned(alias, scope.where)]),
|
|
60
|
+
...(reach.areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
|
|
172
61
|
]);
|
|
173
62
|
}
|
|
174
63
|
function related(bound, plan) {
|
|
@@ -176,17 +65,17 @@ function related(bound, plan) {
|
|
|
176
65
|
if (plan.hop !== null) {
|
|
177
66
|
parts.push(exists(joined([
|
|
178
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`),
|
|
179
|
-
grouped(
|
|
68
|
+
grouped(sql.placed('l', plan.hop.field, 'link')),
|
|
180
69
|
fragment('AND'),
|
|
181
|
-
grouped(all([scoped('m', plan.hop), reached(bound, 'm')])),
|
|
70
|
+
grouped(all([sql.scoped('m', plan.hop), reached(bound, 'm')])),
|
|
182
71
|
], ' ')));
|
|
183
72
|
}
|
|
184
73
|
if (plan.backlinks !== null) {
|
|
185
74
|
const field = plan.backlinks.field;
|
|
186
75
|
parts.push(exists(joined([
|
|
187
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`),
|
|
188
|
-
...(field === null ? [] : [grouped(
|
|
189
|
-
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')])),
|
|
190
79
|
], ' ')));
|
|
191
80
|
}
|
|
192
81
|
return parts;
|
|
@@ -194,11 +83,9 @@ function related(bound, plan) {
|
|
|
194
83
|
function filtered(bound, plan) {
|
|
195
84
|
const keys = bound.rooms.map(({ key }) => key);
|
|
196
85
|
return all([
|
|
197
|
-
fragment(`d.key IN ${
|
|
198
|
-
...(plan.paths === null ? [] : [fragment(`d.path IN ${list(plan.paths).sql}`, plan.paths)]),
|
|
199
|
-
...(plan.folder === null ? [] : [fragment('d.folder = ?', [plan.folder])]),
|
|
86
|
+
fragment(`d.key IN ${listed(keys).sql} AND ${VISIBLE('d')}`, keys),
|
|
200
87
|
reached(bound, 'd'),
|
|
201
|
-
|
|
88
|
+
sql.tests('d', plan),
|
|
202
89
|
...related(bound, plan),
|
|
203
90
|
]);
|
|
204
91
|
}
|
|
@@ -229,108 +116,65 @@ const GROUP_TIES = {
|
|
|
229
116
|
locale: undefined,
|
|
230
117
|
localeOptions: undefined,
|
|
231
118
|
};
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return { placement: 1, klass: 3, scalar: JSON.stringify(value) };
|
|
243
|
-
}
|
|
244
|
-
function placement(term) {
|
|
245
|
-
const nulls = term.ordering.nulls === 'first' ? 0 : 2;
|
|
246
|
-
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]);
|
|
247
|
-
}
|
|
248
|
-
function klass(term) {
|
|
249
|
-
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);
|
|
250
|
-
}
|
|
251
|
-
function classed(term, which) {
|
|
252
|
-
const kinds = which === 1 ? "('boolean', 'number')" : which === 2 ? "('string')" : "('array')";
|
|
253
|
-
return fragment(`(CASE WHEN ${term.kind.sql} IN ${kinds} THEN ${term.value.sql} END)`, [
|
|
254
|
-
...term.kind.bind,
|
|
255
|
-
...term.value.bind,
|
|
256
|
-
]);
|
|
257
|
-
}
|
|
258
|
-
function collated(bound, term, which) {
|
|
259
|
-
if (which === 1)
|
|
260
|
-
return '';
|
|
261
|
-
const name = bound.collate(term.ordering, which === 3, term.total === true);
|
|
262
|
-
return name === null ? '' : ` COLLATE ${name}`;
|
|
263
|
-
}
|
|
264
|
-
function sorted(bound, term) {
|
|
265
|
-
const direction = term.ordering.direction === 'desc' ? 'DESC' : 'ASC';
|
|
266
|
-
return joined([
|
|
267
|
-
placement(term),
|
|
268
|
-
fragment(`${klass(term).sql} ${direction}`, klass(term).bind),
|
|
269
|
-
...[1, 2, 3].map((which) => {
|
|
270
|
-
const value = classed(term, which);
|
|
271
|
-
return fragment(`${value.sql}${collated(bound, term, which)} ${direction}`, value.bind);
|
|
272
|
-
}),
|
|
273
|
-
], ', ');
|
|
274
|
-
}
|
|
275
|
-
function beyond(bound, term, value) {
|
|
276
|
-
const held = classOf(value, term.ordering);
|
|
277
|
-
const place = placement(term);
|
|
278
|
-
if (held.placement !== 1) {
|
|
279
|
-
return {
|
|
280
|
-
beyond: fragment(`${place.sql} > ${held.placement}`, place.bind),
|
|
281
|
-
equal: fragment(`${place.sql} = ${held.placement}`, place.bind),
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
const after = term.ordering.direction === 'desc' ? '<' : '>';
|
|
285
|
-
const kind = klass(term);
|
|
286
|
-
const scalar = classed(term, held.klass);
|
|
287
|
-
const collation = collated(bound, term, held.klass);
|
|
288
|
-
return {
|
|
289
|
-
beyond: any([
|
|
290
|
-
fragment(`${place.sql} > 1`, place.bind),
|
|
291
|
-
all([
|
|
292
|
-
fragment(`${place.sql} = 1`, place.bind),
|
|
293
|
-
any([
|
|
294
|
-
fragment(`${kind.sql} ${after} ${held.klass}`, kind.bind),
|
|
295
|
-
all([
|
|
296
|
-
fragment(`${kind.sql} = ${held.klass}`, kind.bind),
|
|
297
|
-
fragment(`${scalar.sql} ${after} ?${collation}`, [...scalar.bind, held.scalar]),
|
|
298
|
-
]),
|
|
299
|
-
]),
|
|
300
|
-
]),
|
|
301
|
-
]),
|
|
302
|
-
equal: all([
|
|
303
|
-
fragment(`${place.sql} = 1 AND ${kind.sql} = ${held.klass}`, [...place.bind, ...kind.bind]),
|
|
304
|
-
fragment(`${scalar.sql} = ?${collation}`, [...scalar.bind, held.scalar]),
|
|
305
|
-
]),
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
function following(parts, tie) {
|
|
309
|
-
let condition = tie;
|
|
310
|
-
for (const part of [...parts].reverse()) {
|
|
311
|
-
condition = any([part.beyond, all([part.equal, condition])]);
|
|
312
|
-
}
|
|
313
|
-
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]);
|
|
314
129
|
}
|
|
315
|
-
function keyTerm(
|
|
130
|
+
function keyTerm(key, ordering, index, prefix) {
|
|
316
131
|
if (key.of === 'field') {
|
|
317
132
|
const alias = `o${index}`;
|
|
318
133
|
return {
|
|
319
|
-
term: {
|
|
134
|
+
term: {
|
|
135
|
+
ordering,
|
|
136
|
+
kind: fragment(`${alias}.kind`),
|
|
137
|
+
value: fragment(`${alias}.value`),
|
|
138
|
+
instant: fragment(`${alias}.instant`),
|
|
139
|
+
},
|
|
320
140
|
join: joined([
|
|
321
141
|
fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
|
|
322
|
-
grouped(
|
|
142
|
+
grouped(sql.placed(alias, key.field, 'exact')),
|
|
323
143
|
fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
|
|
324
144
|
], ''),
|
|
325
145
|
};
|
|
326
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
|
+
}
|
|
327
169
|
const expression = key.of === 'room'
|
|
328
|
-
? located(
|
|
170
|
+
? located('d')
|
|
329
171
|
: key.of === 'path'
|
|
330
172
|
? fragment('d.path')
|
|
331
173
|
: key.of === 'type'
|
|
332
174
|
? fragment('d.type')
|
|
333
|
-
:
|
|
175
|
+
: key.of === 'subfolder'
|
|
176
|
+
? subfolderOf('d.folder', prefix)
|
|
177
|
+
: fragment('d.folder');
|
|
334
178
|
return {
|
|
335
179
|
term: {
|
|
336
180
|
ordering,
|
|
@@ -351,12 +195,20 @@ function decoded(kind, value) {
|
|
|
351
195
|
return JSON.parse(value);
|
|
352
196
|
return null;
|
|
353
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
|
+
}
|
|
354
204
|
function decodedKey(key, row, index) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
return
|
|
358
|
-
|
|
359
|
-
|
|
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');
|
|
360
212
|
}
|
|
361
213
|
const registered = new WeakMap();
|
|
362
214
|
function hashed(text) {
|
|
@@ -407,9 +259,6 @@ function collating(connection) {
|
|
|
407
259
|
return name;
|
|
408
260
|
};
|
|
409
261
|
}
|
|
410
|
-
function bodied(shape) {
|
|
411
|
-
return shape.sections !== null || shape.preamble || shape.markdown;
|
|
412
|
-
}
|
|
413
262
|
function masked(pointers) {
|
|
414
263
|
const root = { whole: false, members: new Map() };
|
|
415
264
|
for (const pointer of pointers) {
|
|
@@ -464,8 +313,8 @@ function sectionsOf(sections, names) {
|
|
|
464
313
|
function documentOf(shape, room, row, linked) {
|
|
465
314
|
const path = row.path;
|
|
466
315
|
const type = row.type;
|
|
467
|
-
const fileId = row.file_id
|
|
468
|
-
const revision = row.revision
|
|
316
|
+
const fileId = row.file_id;
|
|
317
|
+
const revision = row.revision;
|
|
469
318
|
if (type === null) {
|
|
470
319
|
const binary = row.binary === null ? null : JSON.parse(row.binary);
|
|
471
320
|
const content = row.content ?? null;
|
|
@@ -478,11 +327,13 @@ function documentOf(shape, room, row, linked) {
|
|
|
478
327
|
binaryFile: binaryRecord(path, binary, revision ?? undefined),
|
|
479
328
|
}),
|
|
480
329
|
...(shape.markdown && content !== null ? { markdown: content } : {}),
|
|
330
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
481
331
|
...(shape.fileId ? { fileId } : {}),
|
|
482
332
|
...(shape.revision ? { revision } : {}),
|
|
333
|
+
...heldFacts(shape, row),
|
|
483
334
|
};
|
|
484
335
|
}
|
|
485
|
-
const read =
|
|
336
|
+
const read = shape.sections !== null || shape.preamble ? parse(row.content, path) : null;
|
|
486
337
|
const body = read?.ok === true ? read.document : null;
|
|
487
338
|
const fields = JSON.parse(row.fields ?? '{}');
|
|
488
339
|
return {
|
|
@@ -500,15 +351,21 @@ function documentOf(shape, room, row, linked) {
|
|
|
500
351
|
? {}
|
|
501
352
|
: { sections: sectionsOf(body.sections, shape.sections) }),
|
|
502
353
|
...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
|
|
503
|
-
...(shape.markdown
|
|
354
|
+
...(shape.markdown ? { markdown: row.content } : {}),
|
|
504
355
|
...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
|
|
505
|
-
...(shape.change
|
|
506
|
-
? {
|
|
507
|
-
change: row.change === null ? null : JSON.parse(row.change),
|
|
508
|
-
}
|
|
509
|
-
: {}),
|
|
356
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
510
357
|
...(shape.fileId ? { fileId } : {}),
|
|
511
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 } : {}),
|
|
512
369
|
};
|
|
513
370
|
}
|
|
514
371
|
function backlinksOf(connection, bound, rows) {
|
|
@@ -520,8 +377,8 @@ function backlinksOf(connection, bound, rows) {
|
|
|
520
377
|
held.push(row.path);
|
|
521
378
|
byKey.set(key, held);
|
|
522
379
|
}
|
|
380
|
+
const reach = reached(bound, 's');
|
|
523
381
|
for (const [key, targets] of byKey) {
|
|
524
|
-
const reach = reached(bound, 's');
|
|
525
382
|
const room = bound.rooms.find((one) => one.key === key).locator;
|
|
526
383
|
const found = connection.rows(`SELECT target, path, type, field FROM (SELECT l.target, s.path, s.type, l.field, ROW_NUMBER() OVER (PARTITION BY l.target ORDER BY s.path, l.field) AS rank FROM (SELECT DISTINCT doc, target, field FROM replica_links WHERE key = ? AND kind = 'field' AND target IN (SELECT value FROM json_each(?))) l JOIN replica_entries s ON s.id = l.doc AND ${VISIBLE('s')} WHERE ${reach.sql}) WHERE rank <= ? ORDER BY target, path, field`, [key, JSON.stringify(targets), ...reach.bind, MAXIMUM_WINDOW]);
|
|
527
384
|
for (const row of found) {
|
|
@@ -534,38 +391,36 @@ function backlinksOf(connection, bound, rows) {
|
|
|
534
391
|
return linked;
|
|
535
392
|
}
|
|
536
393
|
function documents(connection, bound, plan, shape, budget) {
|
|
537
|
-
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));
|
|
538
395
|
const filter = filtered(bound, plan);
|
|
539
396
|
const cursor = plan.after?.values ?? null;
|
|
540
|
-
const room = located(
|
|
541
|
-
const
|
|
397
|
+
const room = located('d');
|
|
398
|
+
const after = cursor === null
|
|
542
399
|
? null
|
|
543
400
|
: (() => {
|
|
544
401
|
const key = cursor[shape.orderBy.length];
|
|
545
|
-
return any([
|
|
402
|
+
return sql.following(terms.map(({ term }, index) => sql.beyond(bound.collate, term, cursor[index])), any([
|
|
546
403
|
fragment(`${room.sql} > ?`, [...room.bind, roomOfKey(key)]),
|
|
547
404
|
fragment(`${room.sql} = ? AND d.path > ?`, [
|
|
548
405
|
...room.bind,
|
|
549
406
|
roomOfKey(key),
|
|
550
407
|
pathOfKey(key),
|
|
551
408
|
]),
|
|
552
|
-
]);
|
|
409
|
+
]));
|
|
553
410
|
})();
|
|
554
|
-
const after = cursor === null || tie === null
|
|
555
|
-
? null
|
|
556
|
-
: following(terms.map(({ term }, index) => beyond(bound, term, cursor[index])), tie);
|
|
557
411
|
const selected = joined([
|
|
558
|
-
fragment('SELECT d.id, d.key, d.path, d.type, d.binary, d.revision, d.fields, d.change, d.file_id'),
|
|
412
|
+
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'),
|
|
559
413
|
...terms.map(({ term }, index) => joined([
|
|
560
414
|
fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
561
415
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
416
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
562
417
|
], '')),
|
|
563
418
|
fragment(' FROM replica_entries d'),
|
|
564
419
|
...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
|
|
565
420
|
fragment(' WHERE '),
|
|
566
421
|
after === null ? filter : all([filter, after]),
|
|
567
422
|
fragment(' ORDER BY '),
|
|
568
|
-
joined([...terms.map(({ term }) => sorted(bound, term)), room, fragment('d.path')], ', '),
|
|
423
|
+
joined([...terms.map(({ term }) => sql.sorted(bound.collate, term)), room, fragment('d.path')], ', '),
|
|
569
424
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
570
425
|
], '');
|
|
571
426
|
const rows = connection.rows(selected.sql, selected.bind);
|
|
@@ -604,7 +459,7 @@ function withContent(connection, rows, budget) {
|
|
|
604
459
|
return rows;
|
|
605
460
|
const ids = rows.map((row) => row.id);
|
|
606
461
|
const sizes = new Map(connection
|
|
607
|
-
.rows(`SELECT id, octet_length(content) AS size FROM replica_entries WHERE id IN ${
|
|
462
|
+
.rows(`SELECT id, octet_length(content) AS size FROM replica_entries WHERE id IN ${listed(ids).sql}`, ids)
|
|
608
463
|
.map((row) => [row.id, Number(row.size ?? 0)]));
|
|
609
464
|
let cut = rows.length;
|
|
610
465
|
let spent = 0;
|
|
@@ -619,125 +474,213 @@ function withContent(connection, rows, budget) {
|
|
|
619
474
|
const kept = rows.slice(0, cut);
|
|
620
475
|
const read = kept.map((row) => row.id);
|
|
621
476
|
const held = new Map(connection
|
|
622
|
-
.rows(`SELECT id, content FROM replica_entries WHERE id IN ${
|
|
477
|
+
.rows(`SELECT id, content FROM replica_entries WHERE id IN ${listed(read).sql}`, read)
|
|
623
478
|
.map((row) => [row.id, row.content]));
|
|
624
479
|
return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
|
|
625
480
|
}
|
|
626
|
-
function storedFolders(bound, plan) {
|
|
627
|
-
if (plan.paths !== null || plan.
|
|
481
|
+
function storedFolders(bound, plan, subfolder) {
|
|
482
|
+
if (plan.paths !== null || plan.fileIds !== null)
|
|
483
|
+
return null;
|
|
484
|
+
if (plan.hop !== null || plan.backlinks !== null)
|
|
485
|
+
return null;
|
|
486
|
+
if (subfolder && plan.folder !== null)
|
|
628
487
|
return null;
|
|
629
488
|
const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
|
|
630
489
|
if (whole.length === 0)
|
|
631
490
|
return null;
|
|
632
491
|
const keys = whole.map(({ key }) => key);
|
|
633
|
-
const prefix = plan.root.pathPrefix;
|
|
634
|
-
const
|
|
492
|
+
const prefix = plan.root.pathPrefix ?? '';
|
|
493
|
+
const outside = (bound.reach?.areas ?? []).flatMap((area) => {
|
|
635
494
|
const key = bound.keys.get(area.room);
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
key,
|
|
641
|
-
area.path,
|
|
642
|
-
`${area.path}/`,
|
|
643
|
-
`${area.path}0`,
|
|
644
|
-
]),
|
|
645
|
-
];
|
|
495
|
+
if (key === undefined)
|
|
496
|
+
return [];
|
|
497
|
+
const fence = fenced('f', key, area, true);
|
|
498
|
+
return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
|
|
646
499
|
});
|
|
500
|
+
const under = prefix === ''
|
|
501
|
+
? subfolder
|
|
502
|
+
? [fragment("f.path <> ''")]
|
|
503
|
+
: []
|
|
504
|
+
: [
|
|
505
|
+
subfolder
|
|
506
|
+
? fragment('(f.path >= ? AND f.path < ?)', [`${prefix}/`, `${prefix}0`])
|
|
507
|
+
: within('f', prefix),
|
|
508
|
+
];
|
|
647
509
|
return all([
|
|
648
|
-
fragment(`f.key IN ${
|
|
649
|
-
...
|
|
650
|
-
...
|
|
651
|
-
|
|
652
|
-
: [
|
|
653
|
-
fragment('(f.path = ? OR (f.path >= ? AND f.path < ?))', [
|
|
654
|
-
prefix,
|
|
655
|
-
`${prefix}/`,
|
|
656
|
-
`${prefix}0`,
|
|
657
|
-
]),
|
|
658
|
-
]),
|
|
510
|
+
fragment(`f.key IN ${listed(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
|
|
511
|
+
...outside,
|
|
512
|
+
...under,
|
|
513
|
+
...(plan.outside ?? []).map((folder) => fragment(`NOT ${within('f', folder).sql}`, within('f', folder).bind)),
|
|
659
514
|
...(plan.folder === null ? [] : [fragment('f.path = ?', [plan.folder])]),
|
|
660
515
|
]);
|
|
661
516
|
}
|
|
517
|
+
const SIZES = [
|
|
518
|
+
'CASE WHEN d.binary IS NULL THEN 1 ELSE 0 END AS dn',
|
|
519
|
+
'CASE WHEN d.binary IS NULL THEN d.bytes ELSE 0 END AS ds',
|
|
520
|
+
'CASE WHEN d.binary IS NULL THEN 0 ELSE 1 END AS fn',
|
|
521
|
+
'CASE WHEN d.binary IS NULL THEN 0 ELSE d.bytes END AS fs',
|
|
522
|
+
];
|
|
523
|
+
const LATEST = "d.changed_instant || ' ' || (d.change ->> '$.time') AS u";
|
|
524
|
+
function greatestTerm(ordering) {
|
|
525
|
+
return { ordering, kind: fragment('gk'), value: fragment('gv'), instant: fragment('gi') };
|
|
526
|
+
}
|
|
527
|
+
function latestOf(held) {
|
|
528
|
+
if (typeof held !== 'string')
|
|
529
|
+
return null;
|
|
530
|
+
return held.slice(held.indexOf(' ') + 1);
|
|
531
|
+
}
|
|
662
532
|
function groups(connection, bound, plan, shape) {
|
|
533
|
+
const prefix = plan.root.pathPrefix;
|
|
663
534
|
const keyed = shape.groupBy.map((key, index) => {
|
|
664
|
-
const { term, join } = keyTerm(
|
|
535
|
+
const { term, join } = keyTerm(key, GROUP_ORDER, index, prefix);
|
|
665
536
|
const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
|
|
666
537
|
return { term: { ...term, kind }, join };
|
|
667
538
|
});
|
|
539
|
+
const ordering = shape.orderBy.map(({ by }) => by);
|
|
540
|
+
const sized = shape.size || ordering.includes('size');
|
|
541
|
+
const dated = shape.updated || ordering.includes('updated');
|
|
542
|
+
const greatest = shape.greatest === null
|
|
543
|
+
? null
|
|
544
|
+
: keyTerm({ of: 'field', field: shape.greatest }, GREATEST, keyed.length, prefix);
|
|
668
545
|
const filter = filtered(bound, plan);
|
|
669
546
|
const inner = joined([
|
|
670
547
|
fragment('SELECT '),
|
|
671
548
|
joined(keyed.map(({ term }, index) => joined([
|
|
672
549
|
fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
673
550
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
551
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
674
552
|
], '')), ', '),
|
|
675
|
-
fragment(', 1 AS n
|
|
676
|
-
...
|
|
553
|
+
fragment(['', '1 AS n', ...(sized ? SIZES : []), ...(dated ? [LATEST] : [])].join(', ')),
|
|
554
|
+
...(greatest === null
|
|
555
|
+
? []
|
|
556
|
+
: [
|
|
557
|
+
fragment(`, ${greatest.term.kind.sql} AS gk`, greatest.term.kind.bind),
|
|
558
|
+
fragment(`, ${greatest.term.value.sql} AS gv`, greatest.term.value.bind),
|
|
559
|
+
fragment(`, ${instantOf(greatest.term).sql} AS gi`, instantOf(greatest.term).bind),
|
|
560
|
+
]),
|
|
561
|
+
fragment(' FROM replica_entries d'),
|
|
562
|
+
...[...keyed, ...(greatest === null ? [] : [greatest])].flatMap(({ join }) => join === null ? [] : [fragment(' '), join]),
|
|
677
563
|
fragment(' WHERE '),
|
|
678
564
|
filter,
|
|
679
565
|
], '');
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
566
|
+
const only = shape.groupBy.length === 1 ? shape.groupBy[0].of : null;
|
|
567
|
+
const subfolder = only === 'subfolder';
|
|
568
|
+
const folders = only === 'folder' || subfolder ? storedFolders(bound, plan, subfolder) : null;
|
|
569
|
+
const folderKey = subfolder ? subfolderOf('f.path', prefix) : fragment('f.path');
|
|
570
|
+
const zeros = [
|
|
571
|
+
...(sized ? ['0', '0', '0', '0'] : []),
|
|
572
|
+
...(dated ? ['NULL'] : []),
|
|
573
|
+
...(greatest === null ? [] : ['NULL', 'NULL', 'NULL']),
|
|
574
|
+
];
|
|
683
575
|
const source = folders === null
|
|
684
576
|
? inner
|
|
685
577
|
: joined([
|
|
686
578
|
inner,
|
|
687
|
-
fragment(
|
|
579
|
+
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),
|
|
688
580
|
folders,
|
|
689
581
|
], '');
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
582
|
+
const held = keyed
|
|
583
|
+
.map((_, index) => `k${index}, i${index}, CASE WHEN i${index} IS NULL THEN v${index} END`)
|
|
584
|
+
.join(', ');
|
|
585
|
+
const ranked = greatest === null
|
|
586
|
+
? source
|
|
587
|
+
: joined([
|
|
588
|
+
fragment(`SELECT *, ROW_NUMBER() OVER (PARTITION BY ${held} ORDER BY `),
|
|
589
|
+
sql.sorted(bound.collate, greatestTerm(GREATEST)),
|
|
590
|
+
fragment(', gv) AS gr FROM ('),
|
|
591
|
+
source,
|
|
592
|
+
fragment(')'),
|
|
593
|
+
], '');
|
|
594
|
+
const columns = [
|
|
595
|
+
...keyed.map((_, index) => `k${index}, min(v${index}) AS v${index}, i${index}`),
|
|
596
|
+
'sum(n) AS n',
|
|
597
|
+
...(sized ? ['sum(dn) AS dn', 'sum(ds) AS ds', 'sum(fn) AS fn', 'sum(fs) AS fs'] : []),
|
|
598
|
+
...(dated ? ['max(u) AS u'] : []),
|
|
599
|
+
...(greatest === null
|
|
600
|
+
? []
|
|
601
|
+
: ['gk', 'gv', 'gi'].map((column) => `min(CASE WHEN gr = 1 THEN ${column} END) AS ${column}`)),
|
|
602
|
+
].join(', ');
|
|
603
|
+
const outer = (index, order) => ({
|
|
604
|
+
ordering: order,
|
|
698
605
|
kind: fragment(`k${index}`),
|
|
699
606
|
value: fragment(`v${index}`),
|
|
700
607
|
});
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
608
|
+
const termOf = (by, order) => {
|
|
609
|
+
if (by === 'count')
|
|
610
|
+
return { ordering: order, kind: fragment("'number'"), value: fragment('n') };
|
|
611
|
+
if (by === 'size') {
|
|
612
|
+
return { ordering: order, kind: fragment("'number'"), value: fragment('(ds + fs)') };
|
|
613
|
+
}
|
|
614
|
+
if (by === 'updated') {
|
|
615
|
+
return {
|
|
616
|
+
ordering: order,
|
|
617
|
+
kind: fragment("(CASE WHEN u IS NULL THEN 'null' ELSE 'string' END)"),
|
|
618
|
+
value: fragment("substr(u, instr(u, ' ') + 1)"),
|
|
619
|
+
instant: fragment("substr(u, 1, instr(u, ' ') - 1)"),
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
if (by === 'greatest')
|
|
623
|
+
return greatestTerm(order);
|
|
624
|
+
return { ...outer(by, order), instant: fragment(`i${by}`) };
|
|
625
|
+
};
|
|
626
|
+
const valueOf = (row, by) => {
|
|
627
|
+
if (by === 'count')
|
|
628
|
+
return Number(row.n);
|
|
629
|
+
if (by === 'size')
|
|
630
|
+
return Number(row.ds) + Number(row.fs);
|
|
631
|
+
if (by === 'updated') {
|
|
632
|
+
const text = latestOf(row.u);
|
|
633
|
+
return text === null ? null : sql.sortValue('string', text, row.u.split(' ')[0]);
|
|
634
|
+
}
|
|
635
|
+
if (by === 'greatest')
|
|
636
|
+
return sortedOf(row.gk, row.gv, row.gi);
|
|
637
|
+
return sortedValue(row, by);
|
|
638
|
+
};
|
|
639
|
+
const ordered = shape.orderBy.map(({ by, direction, nulls }) => termOf(by, { ...GROUP_ORDER, direction, nulls: nulls ?? GROUP_ORDER.nulls }));
|
|
704
640
|
const ties = keyed.flatMap((_, index) => [
|
|
705
641
|
{ ...outer(index, GROUP_TIES), total: true },
|
|
706
642
|
{ ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
|
|
707
643
|
]);
|
|
708
644
|
const cursor = plan.after?.values ?? null;
|
|
709
|
-
const
|
|
710
|
-
? null
|
|
711
|
-
: cursor[shape.orderBy.length].flatMap((value) => [
|
|
712
|
-
value,
|
|
713
|
-
kindOf(value ?? null),
|
|
714
|
-
]);
|
|
715
|
-
const after = cursor === null || tieValues === null
|
|
645
|
+
const after = cursor === null
|
|
716
646
|
? null
|
|
717
|
-
:
|
|
718
|
-
|
|
647
|
+
: (() => {
|
|
648
|
+
const tieValues = cursor[shape.orderBy.length].flatMap((value) => [value, heldKind(value ?? null)]);
|
|
649
|
+
return sql.following(ordered.map((term, index) => sql.beyond(bound.collate, term, cursor[index])), sql.following(ties
|
|
650
|
+
.slice(0, -1)
|
|
651
|
+
.map((term, index) => sql.beyond(bound.collate, term, tieValues[index])), sql.beyond(bound.collate, ties.at(-1), tieValues.at(-1)).beyond));
|
|
652
|
+
})();
|
|
653
|
+
const grouping = joined([fragment(`SELECT ${columns} FROM (`), ranked, fragment(`) GROUP BY ${held}`)], '');
|
|
719
654
|
const windowed = joined([
|
|
720
655
|
fragment('SELECT * FROM ('),
|
|
721
656
|
grouping,
|
|
722
657
|
fragment(')'),
|
|
723
658
|
...(after === null ? [] : [fragment(' WHERE '), after]),
|
|
724
659
|
fragment(' ORDER BY '),
|
|
725
|
-
joined([...ordered, ...ties].map((term) => sorted(bound, term)), ', '),
|
|
660
|
+
joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
|
|
726
661
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
727
662
|
], '');
|
|
728
663
|
const rows = connection.rows(windowed.sql, windowed.bind);
|
|
729
664
|
const kept = plan.limit === null ? rows : rows.slice(0, plan.limit);
|
|
730
665
|
const keyOf = (row) => keyed.map((_, index) => decoded(row[`k${index}`], row[`v${index}`]));
|
|
731
|
-
const found = kept.map((row) => ({
|
|
666
|
+
const found = kept.map((row) => ({
|
|
667
|
+
key: keyOf(row),
|
|
668
|
+
count: Number(row.n),
|
|
669
|
+
...(shape.size
|
|
670
|
+
? {
|
|
671
|
+
documents: { count: Number(row.dn), size: Number(row.ds) },
|
|
672
|
+
files: { count: Number(row.fn), size: Number(row.fs) },
|
|
673
|
+
}
|
|
674
|
+
: {}),
|
|
675
|
+
...(shape.updated ? { updated: latestOf(row.u) } : {}),
|
|
676
|
+
...(shape.greatest === null ? {} : { greatest: decoded(row.gk, row.gv) }),
|
|
677
|
+
}));
|
|
732
678
|
const last = kept.at(-1);
|
|
733
679
|
const next = plan.limit === null || rows.length <= plan.limit
|
|
734
680
|
? undefined
|
|
735
681
|
: last === undefined
|
|
736
682
|
? (plan.after?.text ?? FROM_START)
|
|
737
|
-
: cursorOf([
|
|
738
|
-
...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : decoded(last[`k${by}`], last[`v${by}`])),
|
|
739
|
-
keyOf(last),
|
|
740
|
-
]);
|
|
683
|
+
: cursorOf([...shape.orderBy.map(({ by }) => valueOf(last, by)), keyOf(last)]);
|
|
741
684
|
const total = plan.total
|
|
742
685
|
? Number(connection.rows(joined([fragment('SELECT count(*) AS held FROM ('), grouping, fragment(')')], '').sql, grouping.bind)[0].held)
|
|
743
686
|
: undefined;
|
|
@@ -755,9 +698,6 @@ function boundOf(connection, plan, rooms) {
|
|
|
755
698
|
collate: collating(connection),
|
|
756
699
|
};
|
|
757
700
|
}
|
|
758
|
-
export function selected(connection, plan, rooms) {
|
|
759
|
-
return filtered(boundOf(connection, plan, rooms), plan);
|
|
760
|
-
}
|
|
761
701
|
export function answerQuery(connection, plan, rooms, budget = null) {
|
|
762
702
|
const bound = boundOf(connection, plan, rooms);
|
|
763
703
|
if (rooms.length === 0) {
|