@agentshouse/mdruntime 0.1.0-c3c7796382e28 → 0.1.0-c3faae279d4d2
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 +385 -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 +90 -70
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +3 -10
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +29 -47
- package/dist/bootstrap.d.ts +14 -17
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +259 -152
- 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/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 +21 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +4 -2
- package/dist/collection.d.ts +2 -4
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +1 -17
- package/dist/compile.d.ts +6 -6
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +272 -326
- package/dist/connection.d.ts +5 -143
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +102 -5122
- 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 +41 -43
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +870 -779
- 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 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +3 -2
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +18 -8
- package/dist/figures.d.ts +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 +4 -7
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +29 -31
- 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 +26 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/install.d.ts +56 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +554 -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 +176 -0
- package/dist/live.d.ts +5 -4
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +62 -23
- 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/media.d.ts +2 -2
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js +22 -12
- package/dist/memory.d.ts +12 -5
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +175 -83
- package/dist/messages.d.ts +27 -3
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +103 -31
- package/dist/model.d.ts +2 -10
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +0 -18
- package/dist/models.d.ts +14 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +195 -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.js +3 -3
- 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 -4
- 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 +60 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +126 -0
- package/dist/paths.d.ts +2 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +26 -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 +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/reach.d.ts +15 -12
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +185 -139
- 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 +3 -18
- 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 +157 -86
- package/dist/recovery.d.ts +1 -0
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +35 -1
- package/dist/refresh.d.ts +7 -5
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +33 -5
- package/dist/refusal.d.ts +5 -0
- package/dist/refusal.d.ts.map +1 -0
- package/dist/refusal.js +55 -0
- package/dist/replica.d.ts +10 -10
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +53 -21
- package/dist/room-draft.d.ts +21 -7
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +101 -54
- 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 +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 +7 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +31 -24
- package/dist/saves.d.ts +70 -0
- package/dist/saves.d.ts.map +1 -0
- package/dist/saves.js +1005 -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 +31 -0
- package/dist/shown.d.ts.map +1 -0
- package/dist/shown.js +292 -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 +44 -0
- package/dist/socket/rooms.d.ts.map +1 -0
- package/dist/socket/rooms.js +90 -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 +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 +82 -0
- package/dist/status.d.ts +4 -5
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +3 -7
- package/dist/store/fences.d.ts +16 -0
- package/dist/store/fences.d.ts.map +1 -0
- package/dist/store/fences.js +26 -0
- package/dist/store/installs.d.ts +30 -0
- package/dist/store/installs.d.ts.map +1 -0
- package/dist/store/installs.js +34 -0
- package/dist/store/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 +431 -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 +115 -171
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +143 -416
- package/dist/stored.d.ts +61 -57
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +311 -233
- 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 +532 -0
- package/dist/tables.d.ts +126 -64
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +221 -105
- 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 +17 -5
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +7 -1
- package/package.json +2 -2
- package/dist/fence.d.ts +0 -6
- package/dist/fence.d.ts.map +0 -1
- package/dist/fence.js +0 -10
- package/dist/fulltext.d.ts +0 -27
- package/dist/fulltext.d.ts.map +0 -1
- package/dist/fulltext.js +0 -368
- package/dist/words.d.ts +0 -19
- package/dist/words.d.ts.map +0 -1
- package/dist/words.js +0 -150
package/dist/compile.js
CHANGED
|
@@ -1,119 +1,36 @@
|
|
|
1
|
-
import { ARRAY_INDEX, parse, pointerSegments } from '@agentshouse/mdmodel';
|
|
1
|
+
import { ARRAY_INDEX, bodied, byCodePoint, compared, cursorOf, heldKind, parse, pointerSegments, sql, } from '@agentshouse/mdmodel';
|
|
2
2
|
import { binaryRecord } from './binary.js';
|
|
3
|
-
import { documentKey,
|
|
4
|
-
import {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return parts.length === 0 ? fragment('1') : joined(parts.map(grouped), ' AND ');
|
|
17
|
-
}
|
|
18
|
-
function any(parts) {
|
|
19
|
-
return parts.length === 0 ? fragment('0') : joined(parts.map(grouped), ' OR ');
|
|
20
|
-
}
|
|
21
|
-
function grouped(part) {
|
|
22
|
-
return { sql: `(${part.sql})`, bind: part.bind };
|
|
23
|
-
}
|
|
24
|
-
function list(values) {
|
|
25
|
-
return fragment(`(${values.map(() => '?').join(', ')})`, values);
|
|
26
|
-
}
|
|
27
|
-
function sqlValue(value) {
|
|
28
|
-
if (value === null)
|
|
29
|
-
return { kind: 'null', value: null };
|
|
30
|
-
if (typeof value === 'boolean')
|
|
31
|
-
return { kind: 'boolean', value: value ? 1 : 0 };
|
|
32
|
-
if (typeof value === 'number')
|
|
33
|
-
return { kind: 'number', value };
|
|
34
|
-
return { kind: 'string', value };
|
|
35
|
-
}
|
|
36
|
-
function same(alias, value) {
|
|
37
|
-
const held = sqlValue(value);
|
|
38
|
-
return held.kind === 'null'
|
|
39
|
-
? fragment(`${alias}.kind = 'null'`)
|
|
40
|
-
: fragment(`${alias}.kind = ? AND ${alias}.value = ?`, [held.kind, held.value]);
|
|
41
|
-
}
|
|
42
|
-
const BOUNDS = { gt: '>', gte: '>=', lt: '<', lte: '<=' };
|
|
43
|
-
function pointed(alias, field, reach) {
|
|
44
|
-
const { keys, places, glob } = placedOf(field, reach);
|
|
45
|
-
return fragment(`${alias}.keys = ? AND ${alias}.places ${glob ? 'GLOB' : '='} ?`, [keys, places]);
|
|
46
|
-
}
|
|
47
|
-
function heldValues(doc, field, alias, where) {
|
|
48
|
-
return joined([
|
|
49
|
-
fragment(`SELECT 1 FROM replica_values ${alias} WHERE ${alias}.doc = ${doc}.id AND `),
|
|
50
|
-
grouped(pointed(alias, field, 'value')),
|
|
51
|
-
fragment(' AND '),
|
|
52
|
-
grouped(where),
|
|
53
|
-
], '');
|
|
54
|
-
}
|
|
55
|
-
function exists(part) {
|
|
56
|
-
return { sql: `EXISTS (${part.sql})`, bind: part.bind };
|
|
57
|
-
}
|
|
58
|
-
function itemOf(held, where) {
|
|
59
|
-
return exists(joined([
|
|
60
|
-
fragment(`SELECT 1 FROM replica_values i WHERE i.doc = ${held}.doc AND i.keys = ${held}.keys AND i.places = ${held}.places AND i.item >= 0 AND `),
|
|
61
|
-
grouped(where),
|
|
62
|
-
], ''));
|
|
63
|
-
}
|
|
64
|
-
function matching(held, values) {
|
|
65
|
-
return any(values.map((value) => any([
|
|
66
|
-
same(held, value),
|
|
67
|
-
all([fragment(`${held}.kind = 'array'`), itemOf(held, same('i', value))]),
|
|
68
|
-
])));
|
|
69
|
-
}
|
|
70
|
-
function tested(doc, field, test) {
|
|
71
|
-
const reached = exists(heldValues(doc, field, 'r', fragment('r.item = -1')));
|
|
72
|
-
if (test.op === 'exists')
|
|
73
|
-
return test.exists ? reached : fragment(`NOT ${reached.sql}`, reached.bind);
|
|
74
|
-
if (test.op === 'eq' || test.op === 'in') {
|
|
75
|
-
const values = test.op === 'eq' ? [test.value] : test.values;
|
|
76
|
-
return exists(heldValues(doc, field, 'v', any(values.map((value) => same('v', value)))));
|
|
77
|
-
}
|
|
78
|
-
if (test.op === 'ne' || test.op === 'nin') {
|
|
79
|
-
const values = test.op === 'ne' ? [test.value] : test.values;
|
|
80
|
-
const unmatched = matching('h', values);
|
|
81
|
-
return any([
|
|
82
|
-
fragment(`NOT ${reached.sql}`, reached.bind),
|
|
83
|
-
exists(heldValues(doc, field, 'h', all([fragment('h.item = -1'), fragment(`NOT (${unmatched.sql})`, unmatched.bind)]))),
|
|
84
|
-
]);
|
|
85
|
-
}
|
|
86
|
-
if (test.op === 'range') {
|
|
87
|
-
return exists(heldValues(doc, field, 'v', all([
|
|
88
|
-
fragment('v.item = -1'),
|
|
89
|
-
...test.bounds.map(([operator, bound]) => {
|
|
90
|
-
const held = sqlValue(bound);
|
|
91
|
-
return fragment(`v.kind = ? AND v.value ${BOUNDS[operator]} ?`, [
|
|
92
|
-
held.kind,
|
|
93
|
-
held.value,
|
|
94
|
-
]);
|
|
95
|
-
}),
|
|
96
|
-
])));
|
|
97
|
-
}
|
|
98
|
-
if (test.op === 'all') {
|
|
99
|
-
return exists(heldValues(doc, field, 'h', all([
|
|
100
|
-
fragment("h.item = -1 AND h.kind = 'array'"),
|
|
101
|
-
...test.values.map((value) => itemOf('h', same('i', value))),
|
|
102
|
-
])));
|
|
103
|
-
}
|
|
104
|
-
return exists(heldValues(doc, field, 'v', fragment("v.item = -1 AND v.kind = 'array' AND v.size = ?", [test.size])));
|
|
3
|
+
import { documentKey, pathOfKey, roomOfKey } from './rows.js';
|
|
4
|
+
import { FROM_START, MAXIMUM_WINDOW } from './windows.js';
|
|
5
|
+
const { all, any, exists, fragment, grouped, joined, listed } = sql;
|
|
6
|
+
export const VISIBLE = (alias) => `${alias}.visible = 1 AND ${alias}.present = 1 AND ${alias}.queried = 1`;
|
|
7
|
+
function located(alias) {
|
|
8
|
+
return fragment(`(SELECT k.locator FROM replica_keys k WHERE k.key = ${alias}.key)`);
|
|
9
|
+
}
|
|
10
|
+
export function within(alias, path) {
|
|
11
|
+
return fragment(`(${alias}.path = ? OR (${alias}.path >= ? AND ${alias}.path < ?))`, [
|
|
12
|
+
path,
|
|
13
|
+
`${path}/`,
|
|
14
|
+
`${path}0`,
|
|
15
|
+
]);
|
|
105
16
|
}
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return
|
|
111
|
-
|
|
17
|
+
function ancestors(area) {
|
|
18
|
+
const depth = area.path.split('/').length;
|
|
19
|
+
return area.open.flatMap((open) => {
|
|
20
|
+
const segments = open.split('/');
|
|
21
|
+
return segments.slice(depth - 1, -1).map((_, at) => segments.slice(0, depth + at).join('/'));
|
|
22
|
+
});
|
|
112
23
|
}
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
24
|
+
function fenced(alias, key, area, folders) {
|
|
25
|
+
const opened = any([
|
|
26
|
+
...area.open.map((open) => within(alias, open)),
|
|
27
|
+
...(folders ? ancestors(area).map((path) => fragment(`${alias}.path = ?`, [path])) : []),
|
|
28
|
+
]);
|
|
29
|
+
return all([
|
|
30
|
+
fragment(`${alias}.key = ?`, [key]),
|
|
31
|
+
within(alias, area.path),
|
|
32
|
+
fragment(`NOT ${grouped(opened).sql}`, opened.bind),
|
|
33
|
+
]);
|
|
117
34
|
}
|
|
118
35
|
function reached(bound, alias) {
|
|
119
36
|
const reach = bound.reach;
|
|
@@ -127,42 +44,20 @@ function reached(bound, alias) {
|
|
|
127
44
|
const key = bound.keys.get(roomOfKey(held));
|
|
128
45
|
return key === undefined ? [] : [[key, pathOfKey(held)]];
|
|
129
46
|
});
|
|
130
|
-
const
|
|
47
|
+
const outside = any(reach.areas.flatMap((area) => {
|
|
131
48
|
const key = bound.keys.get(area.room);
|
|
132
|
-
return key === undefined ? [] : [
|
|
133
|
-
});
|
|
134
|
-
const beneath = (area) => fragment(`${alias}.key = ? AND (${alias}.path = ? OR (${alias}.path >= ? AND ${alias}.path < ?))`, [area.key, area.path, `${area.path}/`, `${area.path}0`]);
|
|
135
|
-
const typed = (types) => fragment(`${alias}.type IN ${list(types).sql}`, types);
|
|
136
|
-
const outside = any(areas.map((area) => area.types.length === 0
|
|
137
|
-
? beneath(area)
|
|
138
|
-
: all([beneath(area), fragment(`NOT ${grouped(typed(area.types)).sql}`, area.types)])));
|
|
49
|
+
return key === undefined ? [] : [fenced(alias, key, area, false)];
|
|
50
|
+
}));
|
|
139
51
|
return all([
|
|
140
52
|
any([
|
|
141
|
-
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${
|
|
53
|
+
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${listed(rooms).sql}`, rooms)]),
|
|
142
54
|
...(documents.length === 0
|
|
143
55
|
? []
|
|
144
56
|
: [
|
|
145
57
|
fragment(`EXISTS (SELECT 1 FROM json_each(?) j WHERE j.value ->> 0 = ${alias}.key AND j.value ->> 1 = ${alias}.path)`, [JSON.stringify(documents)]),
|
|
146
58
|
]),
|
|
147
|
-
...areas.flatMap((area) => area.types.length === 0 ? [] : [all([beneath(area), typed(area.types)])]),
|
|
148
59
|
]),
|
|
149
|
-
...(areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
|
|
150
|
-
]);
|
|
151
|
-
}
|
|
152
|
-
function scoped(alias, scope) {
|
|
153
|
-
return all([
|
|
154
|
-
...(scope.types === null
|
|
155
|
-
? []
|
|
156
|
-
: [fragment(`${alias}.type IN ${list(scope.types).sql}`, scope.types)]),
|
|
157
|
-
...(scope.pathPrefix === null || scope.pathPrefix === ''
|
|
158
|
-
? []
|
|
159
|
-
: [
|
|
160
|
-
fragment(`${alias}.path >= ? AND ${alias}.path < ?`, [
|
|
161
|
-
`${scope.pathPrefix}/`,
|
|
162
|
-
`${scope.pathPrefix}0`,
|
|
163
|
-
]),
|
|
164
|
-
]),
|
|
165
|
-
...(scope.where === null ? [] : [conditioned(alias, scope.where)]),
|
|
60
|
+
...(reach.areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
|
|
166
61
|
]);
|
|
167
62
|
}
|
|
168
63
|
function related(bound, plan) {
|
|
@@ -170,17 +65,17 @@ function related(bound, plan) {
|
|
|
170
65
|
if (plan.hop !== null) {
|
|
171
66
|
parts.push(exists(joined([
|
|
172
67
|
fragment(`SELECT 1 FROM replica_links l JOIN replica_entries m ON m.key = d.key AND m.path = l.target AND ${VISIBLE('m')} WHERE l.doc = d.id AND l.kind = 'field' AND`),
|
|
173
|
-
grouped(
|
|
68
|
+
grouped(sql.placed('l', plan.hop.field, 'link')),
|
|
174
69
|
fragment('AND'),
|
|
175
|
-
grouped(all([scoped('m', plan.hop), reached(bound, 'm')])),
|
|
70
|
+
grouped(all([sql.scoped('m', plan.hop), reached(bound, 'm')])),
|
|
176
71
|
], ' ')));
|
|
177
72
|
}
|
|
178
73
|
if (plan.backlinks !== null) {
|
|
179
74
|
const field = plan.backlinks.field;
|
|
180
75
|
parts.push(exists(joined([
|
|
181
76
|
fragment(`SELECT 1 FROM replica_links l JOIN replica_entries s ON s.id = l.doc AND ${VISIBLE('s')} WHERE l.key = d.key AND l.target = d.path AND l.kind = 'field' AND`),
|
|
182
|
-
...(field === null ? [] : [grouped(
|
|
183
|
-
grouped(all([scoped('s', plan.backlinks), reached(bound, 's')])),
|
|
77
|
+
...(field === null ? [] : [grouped(sql.placed('l', field, 'link')), fragment('AND')]),
|
|
78
|
+
grouped(all([sql.scoped('s', plan.backlinks), reached(bound, 's')])),
|
|
184
79
|
], ' ')));
|
|
185
80
|
}
|
|
186
81
|
return parts;
|
|
@@ -188,11 +83,9 @@ function related(bound, plan) {
|
|
|
188
83
|
function filtered(bound, plan) {
|
|
189
84
|
const keys = bound.rooms.map(({ key }) => key);
|
|
190
85
|
return all([
|
|
191
|
-
fragment(`d.key IN ${
|
|
192
|
-
...(plan.paths === null ? [] : [fragment(`d.path IN ${list(plan.paths).sql}`, plan.paths)]),
|
|
193
|
-
...(plan.folder === null ? [] : [fragment('d.folder = ?', [plan.folder])]),
|
|
86
|
+
fragment(`d.key IN ${listed(keys).sql} AND ${VISIBLE('d')}`, keys),
|
|
194
87
|
reached(bound, 'd'),
|
|
195
|
-
|
|
88
|
+
sql.tests('d', plan),
|
|
196
89
|
...related(bound, plan),
|
|
197
90
|
]);
|
|
198
91
|
}
|
|
@@ -223,108 +116,65 @@ const GROUP_TIES = {
|
|
|
223
116
|
locale: undefined,
|
|
224
117
|
localeOptions: undefined,
|
|
225
118
|
};
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return { placement: 1, klass: 3, scalar: JSON.stringify(value) };
|
|
237
|
-
}
|
|
238
|
-
function placement(term) {
|
|
239
|
-
const nulls = term.ordering.nulls === 'first' ? 0 : 2;
|
|
240
|
-
return fragment(`CASE WHEN ${term.kind.sql} IS NULL OR ${term.kind.sql} = 'null' THEN ${nulls} ELSE 1 END`, [...term.kind.bind, ...term.kind.bind]);
|
|
241
|
-
}
|
|
242
|
-
function klass(term) {
|
|
243
|
-
return fragment(`CASE ${term.kind.sql} WHEN 'boolean' THEN 1 WHEN 'number' THEN 1 WHEN 'string' THEN 2 WHEN 'array' THEN 3 ELSE 4 END`, term.kind.bind);
|
|
244
|
-
}
|
|
245
|
-
function classed(term, which) {
|
|
246
|
-
const kinds = which === 1 ? "('boolean', 'number')" : which === 2 ? "('string')" : "('array')";
|
|
247
|
-
return fragment(`(CASE WHEN ${term.kind.sql} IN ${kinds} THEN ${term.value.sql} END)`, [
|
|
248
|
-
...term.kind.bind,
|
|
249
|
-
...term.value.bind,
|
|
250
|
-
]);
|
|
251
|
-
}
|
|
252
|
-
function collated(bound, term, which) {
|
|
253
|
-
if (which === 1)
|
|
254
|
-
return '';
|
|
255
|
-
const name = bound.collate(term.ordering, which === 3, term.total === true);
|
|
256
|
-
return name === null ? '' : ` COLLATE ${name}`;
|
|
257
|
-
}
|
|
258
|
-
function sorted(bound, term) {
|
|
259
|
-
const direction = term.ordering.direction === 'desc' ? 'DESC' : 'ASC';
|
|
260
|
-
return joined([
|
|
261
|
-
placement(term),
|
|
262
|
-
fragment(`${klass(term).sql} ${direction}`, klass(term).bind),
|
|
263
|
-
...[1, 2, 3].map((which) => {
|
|
264
|
-
const value = classed(term, which);
|
|
265
|
-
return fragment(`${value.sql}${collated(bound, term, which)} ${direction}`, value.bind);
|
|
266
|
-
}),
|
|
267
|
-
], ', ');
|
|
268
|
-
}
|
|
269
|
-
function beyond(bound, term, value) {
|
|
270
|
-
const held = classOf(value, term.ordering);
|
|
271
|
-
const place = placement(term);
|
|
272
|
-
if (held.placement !== 1) {
|
|
273
|
-
return {
|
|
274
|
-
beyond: fragment(`${place.sql} > ${held.placement}`, place.bind),
|
|
275
|
-
equal: fragment(`${place.sql} = ${held.placement}`, place.bind),
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
const after = term.ordering.direction === 'desc' ? '<' : '>';
|
|
279
|
-
const kind = klass(term);
|
|
280
|
-
const scalar = classed(term, held.klass);
|
|
281
|
-
const collation = collated(bound, term, held.klass);
|
|
282
|
-
return {
|
|
283
|
-
beyond: any([
|
|
284
|
-
fragment(`${place.sql} > 1`, place.bind),
|
|
285
|
-
all([
|
|
286
|
-
fragment(`${place.sql} = 1`, place.bind),
|
|
287
|
-
any([
|
|
288
|
-
fragment(`${kind.sql} ${after} ${held.klass}`, kind.bind),
|
|
289
|
-
all([
|
|
290
|
-
fragment(`${kind.sql} = ${held.klass}`, kind.bind),
|
|
291
|
-
fragment(`${scalar.sql} ${after} ?${collation}`, [...scalar.bind, held.scalar]),
|
|
292
|
-
]),
|
|
293
|
-
]),
|
|
294
|
-
]),
|
|
295
|
-
]),
|
|
296
|
-
equal: all([
|
|
297
|
-
fragment(`${place.sql} = 1 AND ${kind.sql} = ${held.klass}`, [...place.bind, ...kind.bind]),
|
|
298
|
-
fragment(`${scalar.sql} = ?${collation}`, [...scalar.bind, held.scalar]),
|
|
299
|
-
]),
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
function following(parts, tie) {
|
|
303
|
-
let condition = tie;
|
|
304
|
-
for (const part of [...parts].reverse()) {
|
|
305
|
-
condition = any([part.beyond, all([part.equal, condition])]);
|
|
306
|
-
}
|
|
307
|
-
return condition;
|
|
119
|
+
const GREATEST = { ...GROUP_ORDER, direction: 'desc', nulls: 'last' };
|
|
120
|
+
const TIMES = {
|
|
121
|
+
created: { value: 'd.created', instant: 'd.created_instant' },
|
|
122
|
+
updated: { value: "(d.change ->> '$.time')", instant: 'd.changed_instant' },
|
|
123
|
+
};
|
|
124
|
+
function subfolderOf(column, prefix) {
|
|
125
|
+
const root = prefix ?? '';
|
|
126
|
+
const base = root === '' ? '' : `${root}/`;
|
|
127
|
+
const rest = `substr(${column}, length(?) + 1)`;
|
|
128
|
+
return fragment(`(CASE WHEN ${column} = ? THEN NULL WHEN instr(${rest}, '/') = 0 THEN ${column} ELSE ? || substr(${rest}, 1, instr(${rest}, '/') - 1) END)`, [root, base, base, base, base]);
|
|
308
129
|
}
|
|
309
|
-
function keyTerm(
|
|
130
|
+
function keyTerm(key, ordering, index, prefix) {
|
|
310
131
|
if (key.of === 'field') {
|
|
311
132
|
const alias = `o${index}`;
|
|
312
133
|
return {
|
|
313
|
-
term: {
|
|
134
|
+
term: {
|
|
135
|
+
ordering,
|
|
136
|
+
kind: fragment(`${alias}.kind`),
|
|
137
|
+
value: fragment(`${alias}.value`),
|
|
138
|
+
instant: fragment(`${alias}.instant`),
|
|
139
|
+
},
|
|
314
140
|
join: joined([
|
|
315
141
|
fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
|
|
316
|
-
grouped(
|
|
142
|
+
grouped(sql.placed(alias, key.field, 'exact')),
|
|
317
143
|
fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
|
|
318
144
|
], ''),
|
|
319
145
|
};
|
|
320
146
|
}
|
|
147
|
+
if (key.of === 'size') {
|
|
148
|
+
return {
|
|
149
|
+
term: {
|
|
150
|
+
ordering,
|
|
151
|
+
kind: fragment("(CASE WHEN d.bytes IS NULL THEN 'null' ELSE 'number' END)"),
|
|
152
|
+
value: fragment('d.bytes'),
|
|
153
|
+
},
|
|
154
|
+
join: null,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (key.of === 'created' || key.of === 'updated') {
|
|
158
|
+
const { value, instant } = TIMES[key.of];
|
|
159
|
+
return {
|
|
160
|
+
term: {
|
|
161
|
+
ordering,
|
|
162
|
+
kind: fragment(`(CASE WHEN ${value} IS NULL THEN 'null' ELSE 'string' END)`),
|
|
163
|
+
value: fragment(value),
|
|
164
|
+
instant: fragment(instant),
|
|
165
|
+
},
|
|
166
|
+
join: null,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
321
169
|
const expression = key.of === 'room'
|
|
322
|
-
? located(
|
|
170
|
+
? located('d')
|
|
323
171
|
: key.of === 'path'
|
|
324
172
|
? fragment('d.path')
|
|
325
173
|
: key.of === 'type'
|
|
326
174
|
? fragment('d.type')
|
|
327
|
-
:
|
|
175
|
+
: key.of === 'subfolder'
|
|
176
|
+
? subfolderOf('d.folder', prefix)
|
|
177
|
+
: fragment('d.folder');
|
|
328
178
|
return {
|
|
329
179
|
term: {
|
|
330
180
|
ordering,
|
|
@@ -345,12 +195,20 @@ function decoded(kind, value) {
|
|
|
345
195
|
return JSON.parse(value);
|
|
346
196
|
return null;
|
|
347
197
|
}
|
|
198
|
+
function sortedOf(kind, value, instant) {
|
|
199
|
+
return sql.sortValue(kind, decoded(kind, value), instant);
|
|
200
|
+
}
|
|
201
|
+
function sortedValue(row, index) {
|
|
202
|
+
return sortedOf(row[`k${index}`], row[`v${index}`], row[`i${index}`]);
|
|
203
|
+
}
|
|
348
204
|
function decodedKey(key, row, index) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
return
|
|
352
|
-
|
|
353
|
-
|
|
205
|
+
const kind = row[`k${index}`];
|
|
206
|
+
if (key.of === 'field' && (kind === null || kind === undefined))
|
|
207
|
+
return undefined;
|
|
208
|
+
return sortedValue(row, index);
|
|
209
|
+
}
|
|
210
|
+
function instantOf(term) {
|
|
211
|
+
return term.instant ?? fragment('NULL');
|
|
354
212
|
}
|
|
355
213
|
const registered = new WeakMap();
|
|
356
214
|
function hashed(text) {
|
|
@@ -401,9 +259,6 @@ function collating(connection) {
|
|
|
401
259
|
return name;
|
|
402
260
|
};
|
|
403
261
|
}
|
|
404
|
-
function bodied(shape) {
|
|
405
|
-
return shape.sections !== null || shape.preamble || shape.markdown;
|
|
406
|
-
}
|
|
407
262
|
function masked(pointers) {
|
|
408
263
|
const root = { whole: false, members: new Map() };
|
|
409
264
|
for (const pointer of pointers) {
|
|
@@ -458,8 +313,8 @@ function sectionsOf(sections, names) {
|
|
|
458
313
|
function documentOf(shape, room, row, linked) {
|
|
459
314
|
const path = row.path;
|
|
460
315
|
const type = row.type;
|
|
461
|
-
const fileId = row.file_id
|
|
462
|
-
const revision = row.revision
|
|
316
|
+
const fileId = row.file_id;
|
|
317
|
+
const revision = row.revision;
|
|
463
318
|
if (type === null) {
|
|
464
319
|
const binary = row.binary === null ? null : JSON.parse(row.binary);
|
|
465
320
|
const content = row.content ?? null;
|
|
@@ -472,11 +327,13 @@ function documentOf(shape, room, row, linked) {
|
|
|
472
327
|
binaryFile: binaryRecord(path, binary, revision ?? undefined),
|
|
473
328
|
}),
|
|
474
329
|
...(shape.markdown && content !== null ? { markdown: content } : {}),
|
|
330
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
475
331
|
...(shape.fileId ? { fileId } : {}),
|
|
476
332
|
...(shape.revision ? { revision } : {}),
|
|
333
|
+
...heldFacts(shape, row),
|
|
477
334
|
};
|
|
478
335
|
}
|
|
479
|
-
const read =
|
|
336
|
+
const read = shape.sections !== null || shape.preamble ? parse(row.content, path) : null;
|
|
480
337
|
const body = read?.ok === true ? read.document : null;
|
|
481
338
|
const fields = JSON.parse(row.fields ?? '{}');
|
|
482
339
|
return {
|
|
@@ -494,15 +351,21 @@ function documentOf(shape, room, row, linked) {
|
|
|
494
351
|
? {}
|
|
495
352
|
: { sections: sectionsOf(body.sections, shape.sections) }),
|
|
496
353
|
...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
|
|
497
|
-
...(shape.markdown
|
|
354
|
+
...(shape.markdown ? { markdown: row.content } : {}),
|
|
498
355
|
...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
|
|
499
|
-
...(shape.change
|
|
500
|
-
? {
|
|
501
|
-
change: row.change === null ? null : JSON.parse(row.change),
|
|
502
|
-
}
|
|
503
|
-
: {}),
|
|
356
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
504
357
|
...(shape.fileId ? { fileId } : {}),
|
|
505
358
|
...(shape.revision ? { revision } : {}),
|
|
359
|
+
...heldFacts(shape, row),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function changeOf(row) {
|
|
363
|
+
return row.change === null ? null : JSON.parse(row.change);
|
|
364
|
+
}
|
|
365
|
+
function heldFacts(shape, row) {
|
|
366
|
+
return {
|
|
367
|
+
...(shape.size ? { size: row.bytes === null ? null : Number(row.bytes) } : {}),
|
|
368
|
+
...(shape.created ? { created: row.created } : {}),
|
|
506
369
|
};
|
|
507
370
|
}
|
|
508
371
|
function backlinksOf(connection, bound, rows) {
|
|
@@ -514,8 +377,8 @@ function backlinksOf(connection, bound, rows) {
|
|
|
514
377
|
held.push(row.path);
|
|
515
378
|
byKey.set(key, held);
|
|
516
379
|
}
|
|
380
|
+
const reach = reached(bound, 's');
|
|
517
381
|
for (const [key, targets] of byKey) {
|
|
518
|
-
const reach = reached(bound, 's');
|
|
519
382
|
const room = bound.rooms.find((one) => one.key === key).locator;
|
|
520
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]);
|
|
521
384
|
for (const row of found) {
|
|
@@ -528,38 +391,36 @@ function backlinksOf(connection, bound, rows) {
|
|
|
528
391
|
return linked;
|
|
529
392
|
}
|
|
530
393
|
function documents(connection, bound, plan, shape, budget) {
|
|
531
|
-
const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(
|
|
394
|
+
const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(key, orderingOf(options), index, plan.root.pathPrefix));
|
|
532
395
|
const filter = filtered(bound, plan);
|
|
533
396
|
const cursor = plan.after?.values ?? null;
|
|
534
|
-
const room = located(
|
|
535
|
-
const
|
|
397
|
+
const room = located('d');
|
|
398
|
+
const after = cursor === null
|
|
536
399
|
? null
|
|
537
400
|
: (() => {
|
|
538
401
|
const key = cursor[shape.orderBy.length];
|
|
539
|
-
return any([
|
|
402
|
+
return sql.following(terms.map(({ term }, index) => sql.beyond(bound.collate, term, cursor[index])), any([
|
|
540
403
|
fragment(`${room.sql} > ?`, [...room.bind, roomOfKey(key)]),
|
|
541
404
|
fragment(`${room.sql} = ? AND d.path > ?`, [
|
|
542
405
|
...room.bind,
|
|
543
406
|
roomOfKey(key),
|
|
544
407
|
pathOfKey(key),
|
|
545
408
|
]),
|
|
546
|
-
]);
|
|
409
|
+
]));
|
|
547
410
|
})();
|
|
548
|
-
const after = cursor === null || tie === null
|
|
549
|
-
? null
|
|
550
|
-
: following(terms.map(({ term }, index) => beyond(bound, term, cursor[index])), tie);
|
|
551
411
|
const selected = joined([
|
|
552
|
-
fragment('SELECT d.id, d.key, d.path, d.type, d.binary, d.revision, d.fields, d.change, d.file_id'),
|
|
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'),
|
|
553
413
|
...terms.map(({ term }, index) => joined([
|
|
554
414
|
fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
555
415
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
416
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
556
417
|
], '')),
|
|
557
418
|
fragment(' FROM replica_entries d'),
|
|
558
419
|
...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
|
|
559
420
|
fragment(' WHERE '),
|
|
560
421
|
after === null ? filter : all([filter, after]),
|
|
561
422
|
fragment(' ORDER BY '),
|
|
562
|
-
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')], ', '),
|
|
563
424
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
564
425
|
], '');
|
|
565
426
|
const rows = connection.rows(selected.sql, selected.bind);
|
|
@@ -598,7 +459,7 @@ function withContent(connection, rows, budget) {
|
|
|
598
459
|
return rows;
|
|
599
460
|
const ids = rows.map((row) => row.id);
|
|
600
461
|
const sizes = new Map(connection
|
|
601
|
-
.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)
|
|
602
463
|
.map((row) => [row.id, Number(row.size ?? 0)]));
|
|
603
464
|
let cut = rows.length;
|
|
604
465
|
let spent = 0;
|
|
@@ -613,125 +474,213 @@ function withContent(connection, rows, budget) {
|
|
|
613
474
|
const kept = rows.slice(0, cut);
|
|
614
475
|
const read = kept.map((row) => row.id);
|
|
615
476
|
const held = new Map(connection
|
|
616
|
-
.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)
|
|
617
478
|
.map((row) => [row.id, row.content]));
|
|
618
479
|
return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
|
|
619
480
|
}
|
|
620
|
-
function storedFolders(bound, plan) {
|
|
621
|
-
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)
|
|
622
487
|
return null;
|
|
623
488
|
const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
|
|
624
489
|
if (whole.length === 0)
|
|
625
490
|
return null;
|
|
626
491
|
const keys = whole.map(({ key }) => key);
|
|
627
|
-
const prefix = plan.root.pathPrefix;
|
|
628
|
-
const
|
|
492
|
+
const prefix = plan.root.pathPrefix ?? '';
|
|
493
|
+
const outside = (bound.reach?.areas ?? []).flatMap((area) => {
|
|
629
494
|
const key = bound.keys.get(area.room);
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
key,
|
|
635
|
-
area.path,
|
|
636
|
-
`${area.path}/`,
|
|
637
|
-
`${area.path}0`,
|
|
638
|
-
]),
|
|
639
|
-
];
|
|
495
|
+
if (key === undefined)
|
|
496
|
+
return [];
|
|
497
|
+
const fence = fenced('f', key, area, true);
|
|
498
|
+
return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
|
|
640
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
|
+
];
|
|
641
509
|
return all([
|
|
642
|
-
fragment(`f.key IN ${
|
|
643
|
-
...
|
|
644
|
-
...
|
|
645
|
-
|
|
646
|
-
: [
|
|
647
|
-
fragment('(f.path = ? OR (f.path >= ? AND f.path < ?))', [
|
|
648
|
-
prefix,
|
|
649
|
-
`${prefix}/`,
|
|
650
|
-
`${prefix}0`,
|
|
651
|
-
]),
|
|
652
|
-
]),
|
|
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)),
|
|
653
514
|
...(plan.folder === null ? [] : [fragment('f.path = ?', [plan.folder])]),
|
|
654
515
|
]);
|
|
655
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
|
+
}
|
|
656
532
|
function groups(connection, bound, plan, shape) {
|
|
533
|
+
const prefix = plan.root.pathPrefix;
|
|
657
534
|
const keyed = shape.groupBy.map((key, index) => {
|
|
658
|
-
const { term, join } = keyTerm(
|
|
535
|
+
const { term, join } = keyTerm(key, GROUP_ORDER, index, prefix);
|
|
659
536
|
const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
|
|
660
537
|
return { term: { ...term, kind }, join };
|
|
661
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);
|
|
662
545
|
const filter = filtered(bound, plan);
|
|
663
546
|
const inner = joined([
|
|
664
547
|
fragment('SELECT '),
|
|
665
548
|
joined(keyed.map(({ term }, index) => joined([
|
|
666
549
|
fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
667
550
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
551
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
668
552
|
], '')), ', '),
|
|
669
|
-
fragment(', 1 AS n
|
|
670
|
-
...
|
|
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]),
|
|
671
563
|
fragment(' WHERE '),
|
|
672
564
|
filter,
|
|
673
565
|
], '');
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
|
|
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
|
+
];
|
|
677
575
|
const source = folders === null
|
|
678
576
|
? inner
|
|
679
577
|
: joined([
|
|
680
578
|
inner,
|
|
681
|
-
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),
|
|
682
580
|
folders,
|
|
683
581
|
], '');
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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,
|
|
692
605
|
kind: fragment(`k${index}`),
|
|
693
606
|
value: fragment(`v${index}`),
|
|
694
607
|
});
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
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 }));
|
|
698
640
|
const ties = keyed.flatMap((_, index) => [
|
|
699
641
|
{ ...outer(index, GROUP_TIES), total: true },
|
|
700
642
|
{ ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
|
|
701
643
|
]);
|
|
702
644
|
const cursor = plan.after?.values ?? null;
|
|
703
|
-
const
|
|
704
|
-
? null
|
|
705
|
-
: cursor[shape.orderBy.length].flatMap((value) => [
|
|
706
|
-
value,
|
|
707
|
-
kindOf(value ?? null),
|
|
708
|
-
]);
|
|
709
|
-
const after = cursor === null || tieValues === null
|
|
645
|
+
const after = cursor === null
|
|
710
646
|
? null
|
|
711
|
-
:
|
|
712
|
-
|
|
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}`)], '');
|
|
713
654
|
const windowed = joined([
|
|
714
655
|
fragment('SELECT * FROM ('),
|
|
715
656
|
grouping,
|
|
716
657
|
fragment(')'),
|
|
717
658
|
...(after === null ? [] : [fragment(' WHERE '), after]),
|
|
718
659
|
fragment(' ORDER BY '),
|
|
719
|
-
joined([...ordered, ...ties].map((term) => sorted(bound, term)), ', '),
|
|
660
|
+
joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
|
|
720
661
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
721
662
|
], '');
|
|
722
663
|
const rows = connection.rows(windowed.sql, windowed.bind);
|
|
723
664
|
const kept = plan.limit === null ? rows : rows.slice(0, plan.limit);
|
|
724
665
|
const keyOf = (row) => keyed.map((_, index) => decoded(row[`k${index}`], row[`v${index}`]));
|
|
725
|
-
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
|
+
}));
|
|
726
678
|
const last = kept.at(-1);
|
|
727
679
|
const next = plan.limit === null || rows.length <= plan.limit
|
|
728
680
|
? undefined
|
|
729
681
|
: last === undefined
|
|
730
682
|
? (plan.after?.text ?? FROM_START)
|
|
731
|
-
: cursorOf([
|
|
732
|
-
...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : decoded(last[`k${by}`], last[`v${by}`])),
|
|
733
|
-
keyOf(last),
|
|
734
|
-
]);
|
|
683
|
+
: cursorOf([...shape.orderBy.map(({ by }) => valueOf(last, by)), keyOf(last)]);
|
|
735
684
|
const total = plan.total
|
|
736
685
|
? Number(connection.rows(joined([fragment('SELECT count(*) AS held FROM ('), grouping, fragment(')')], '').sql, grouping.bind)[0].held)
|
|
737
686
|
: undefined;
|
|
@@ -749,9 +698,6 @@ function boundOf(connection, plan, rooms) {
|
|
|
749
698
|
collate: collating(connection),
|
|
750
699
|
};
|
|
751
700
|
}
|
|
752
|
-
export function selected(connection, plan, rooms) {
|
|
753
|
-
return filtered(boundOf(connection, plan, rooms), plan);
|
|
754
|
-
}
|
|
755
701
|
export function answerQuery(connection, plan, rooms, budget = null) {
|
|
756
702
|
const bound = boundOf(connection, plan, rooms);
|
|
757
703
|
if (rooms.length === 0) {
|