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