@agentshouse/mdruntime 0.1.0-cee9c8aa4cd86 → 0.1.0-cef76ea952695
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 +19 -17
- 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 +111 -109
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +5 -11
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +36 -47
- package/dist/binary.d.ts +0 -5
- package/dist/binary.d.ts.map +1 -1
- package/dist/binary.js +1 -1
- package/dist/bootstrap.d.ts +22 -10
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +303 -123
- 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 +30 -14
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +61 -10
- package/dist/collection.d.ts +7 -4
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +24 -19
- package/dist/compile.d.ts +6 -6
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +290 -297
- package/dist/connection.d.ts +6 -142
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +105 -4827
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/{chat/context.js → context.js} +1 -4
- package/dist/conversation-live.d.ts +135 -0
- package/dist/conversation-live.d.ts.map +1 -0
- package/dist/conversation-live.js +247 -0
- package/dist/conversation-row.d.ts +33 -0
- package/dist/conversation-row.d.ts.map +1 -0
- package/dist/conversation-row.js +53 -0
- package/dist/conversation.d.ts +42 -46
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +901 -815
- 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 -4
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +5 -0
- package/dist/entry.d.ts +3 -2
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +18 -8
- package/dist/expectation.d.ts +4 -2
- package/dist/expectation.d.ts.map +1 -1
- package/dist/expectation.js +33 -11
- package/dist/failure.d.ts +1 -0
- package/dist/failure.d.ts.map +1 -1
- package/dist/failure.js +3 -0
- package/dist/figures.d.ts +49 -0
- package/dist/figures.d.ts.map +1 -0
- package/dist/figures.js +197 -0
- package/dist/file.d.ts +2 -11
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +6 -4
- 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 -6
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +59 -38
- 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 +30 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -3
- package/dist/install.d.ts +56 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +556 -0
- package/dist/job.d.ts +5 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +7 -0
- package/dist/lifecycle.d.ts +7 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +270 -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 +63 -21
- package/dist/locks.d.ts +9 -0
- package/dist/locks.d.ts.map +1 -0
- package/dist/locks.js +62 -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 +8 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +218 -0
- package/dist/memory.d.ts +13 -6
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +219 -102
- package/dist/messages.d.ts +24 -3
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +89 -31
- package/dist/model.d.ts +8 -10
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +6 -18
- package/dist/models.d.ts +14 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +197 -0
- package/dist/observation.d.ts +3 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +15 -15
- 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 +60 -0
- package/dist/outcome.d.ts +17 -7
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +19 -5
- package/dist/overlay.d.ts +1 -0
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +12 -1
- package/dist/parts.d.ts +11 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +21 -0
- package/dist/paths.d.ts +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 +233 -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 +19 -206
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -494
- package/dist/{chat/questions.d.ts → questions.d.ts} +5 -2
- package/dist/questions.d.ts.map +1 -0
- package/dist/{chat/questions.js → questions.js} +13 -2
- package/dist/reach.d.ts +20 -13
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +204 -135
- package/dist/react.d.ts +14 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +10 -1
- package/dist/{chat/read.d.ts → read.d.ts} +2 -0
- package/dist/read.d.ts.map +1 -0
- package/dist/{chat/read.js → read.js} +4 -0
- package/dist/readiness.d.ts +12 -16
- package/dist/readiness.d.ts.map +1 -1
- package/dist/readiness.js +2 -8
- package/dist/reads.d.ts +36 -10
- package/dist/reads.d.ts.map +1 -1
- package/dist/reads.js +159 -83
- 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 +10 -4
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +92 -30
- package/dist/refusal.d.ts +5 -0
- package/dist/refusal.d.ts.map +1 -0
- package/dist/refusal.js +57 -0
- package/dist/replica.d.ts +11 -10
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +56 -23
- package/dist/room-draft.d.ts +22 -7
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +134 -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 +18 -16
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +45 -153
- 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 +46 -27
- 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 +290 -0
- package/dist/socket/admission.d.ts +7 -0
- package/dist/socket/admission.d.ts.map +1 -0
- package/dist/socket/admission.js +39 -0
- package/dist/socket/answers.d.ts +6 -0
- package/dist/socket/answers.d.ts.map +1 -0
- package/dist/socket/answers.js +20 -0
- package/dist/socket/lanes.d.ts +20 -0
- package/dist/socket/lanes.d.ts.map +1 -0
- package/dist/socket/lanes.js +77 -0
- package/dist/socket/reads.d.ts +10 -0
- package/dist/socket/reads.d.ts.map +1 -0
- package/dist/socket/reads.js +31 -0
- package/dist/socket/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 +210 -0
- package/dist/store/records.d.ts.map +1 -0
- package/dist/store/records.js +428 -0
- package/dist/store/room-held.d.ts +8 -0
- package/dist/store/room-held.d.ts.map +1 -0
- package/dist/store/room-held.js +17 -0
- package/dist/store/space.d.ts +11 -0
- package/dist/store/space.d.ts.map +1 -0
- package/dist/store/space.js +41 -0
- package/dist/store/watchers.d.ts +17 -0
- package/dist/store/watchers.d.ts.map +1 -0
- package/dist/store/watchers.js +56 -0
- package/dist/store/writes.d.ts +24 -0
- package/dist/store/writes.d.ts.map +1 -0
- package/dist/store/writes.js +125 -0
- package/dist/store.d.ts +105 -149
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +143 -390
- package/dist/stored.d.ts +67 -49
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +358 -253
- package/dist/surface.d.ts +88 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +224 -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 +140 -60
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +268 -101
- 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 +22 -10
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +9 -3
- package/package.json +12 -18
- package/AGENTS.md +0 -320
- package/dist/chat/acts.d.ts +0 -33
- package/dist/chat/acts.d.ts.map +0 -1
- package/dist/chat/acts.js +0 -36
- package/dist/chat/context.d.ts +0 -3
- package/dist/chat/context.d.ts.map +0 -1
- package/dist/chat/earlier.d.ts +0 -10
- package/dist/chat/earlier.d.ts.map +0 -1
- package/dist/chat/earlier.js +0 -54
- package/dist/chat/house.d.ts +0 -25
- package/dist/chat/house.d.ts.map +0 -1
- package/dist/chat/house.js +0 -38
- package/dist/chat/input.d.ts +0 -11
- package/dist/chat/input.d.ts.map +0 -1
- package/dist/chat/input.js +0 -92
- package/dist/chat/kit.d.ts +0 -61
- package/dist/chat/kit.d.ts.map +0 -1
- package/dist/chat/kit.js +0 -123
- package/dist/chat/lasting.d.ts +0 -6
- package/dist/chat/lasting.d.ts.map +0 -1
- package/dist/chat/lasting.js +0 -19
- package/dist/chat/list.d.ts +0 -28
- package/dist/chat/list.d.ts.map +0 -1
- package/dist/chat/list.js +0 -103
- package/dist/chat/markdown.d.ts +0 -21
- package/dist/chat/markdown.d.ts.map +0 -1
- package/dist/chat/markdown.js +0 -246
- package/dist/chat/message.d.ts +0 -7
- package/dist/chat/message.d.ts.map +0 -1
- package/dist/chat/message.js +0 -254
- package/dist/chat/messages.d.ts +0 -35
- package/dist/chat/messages.d.ts.map +0 -1
- package/dist/chat/messages.js +0 -210
- package/dist/chat/name.d.ts +0 -7
- package/dist/chat/name.d.ts.map +0 -1
- package/dist/chat/name.js +0 -37
- package/dist/chat/opened.d.ts +0 -12
- package/dist/chat/opened.d.ts.map +0 -1
- package/dist/chat/opened.js +0 -19
- package/dist/chat/overlay.d.ts +0 -32
- package/dist/chat/overlay.d.ts.map +0 -1
- package/dist/chat/overlay.js +0 -103
- package/dist/chat/parts.d.ts +0 -58
- package/dist/chat/parts.d.ts.map +0 -1
- package/dist/chat/parts.js +0 -120
- package/dist/chat/pieces.d.ts +0 -62
- package/dist/chat/pieces.d.ts.map +0 -1
- package/dist/chat/pieces.js +0 -1110
- package/dist/chat/process.d.ts +0 -20
- package/dist/chat/process.d.ts.map +0 -1
- package/dist/chat/process.js +0 -32
- package/dist/chat/provider.d.ts +0 -70
- package/dist/chat/provider.d.ts.map +0 -1
- package/dist/chat/provider.js +0 -635
- package/dist/chat/question.d.ts +0 -9
- package/dist/chat/question.d.ts.map +0 -1
- package/dist/chat/question.js +0 -69
- package/dist/chat/questions.d.ts.map +0 -1
- package/dist/chat/read.d.ts.map +0 -1
- package/dist/chat/ready.d.ts +0 -37
- package/dist/chat/ready.d.ts.map +0 -1
- package/dist/chat/ready.js +0 -93
- package/dist/chat/room-file.d.ts +0 -8
- package/dist/chat/room-file.d.ts.map +0 -1
- package/dist/chat/room-file.js +0 -72
- package/dist/chat/steady.d.ts +0 -3
- package/dist/chat/steady.d.ts.map +0 -1
- package/dist/chat/steady.js +0 -26
- package/dist/chat/style.d.ts +0 -4
- package/dist/chat/style.d.ts.map +0 -1
- package/dist/chat/style.js +0 -307
- package/dist/chat.d.ts +0 -15
- package/dist/chat.d.ts.map +0 -1
- package/dist/chat.js +0 -8
- package/dist/fence.d.ts +0 -6
- package/dist/fence.d.ts.map +0 -1
- package/dist/fence.js +0 -10
- package/dist/fulltext.d.ts +0 -27
- package/dist/fulltext.d.ts.map +0 -1
- package/dist/fulltext.js +0 -368
- package/dist/words.d.ts +0 -19
- package/dist/words.d.ts.map +0 -1
- package/dist/words.js +0 -150
package/dist/compile.js
CHANGED
|
@@ -1,107 +1,36 @@
|
|
|
1
|
-
import { ARRAY_INDEX, parse, pointerSegments } from '@agentshouse/mdmodel';
|
|
1
|
+
import { ARRAY_INDEX, bodied, byCodePoint, compared, cursorOf, heldKind, parse, pointerSegments, sql, } from '@agentshouse/mdmodel';
|
|
2
2
|
import { binaryRecord } from './binary.js';
|
|
3
|
-
import { documentKey,
|
|
4
|
-
import {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return parts.length === 0 ? fragment('1') : joined(parts.map(grouped), ' AND ');
|
|
17
|
-
}
|
|
18
|
-
function any(parts) {
|
|
19
|
-
return parts.length === 0 ? fragment('0') : joined(parts.map(grouped), ' OR ');
|
|
20
|
-
}
|
|
21
|
-
function grouped(part) {
|
|
22
|
-
return { sql: `(${part.sql})`, bind: part.bind };
|
|
23
|
-
}
|
|
24
|
-
function list(values) {
|
|
25
|
-
return fragment(`(${values.map(() => '?').join(', ')})`, values);
|
|
26
|
-
}
|
|
27
|
-
function sqlValue(value) {
|
|
28
|
-
if (value === null)
|
|
29
|
-
return { kind: 'null', value: null };
|
|
30
|
-
if (typeof value === 'boolean')
|
|
31
|
-
return { kind: 'boolean', value: value ? 1 : 0 };
|
|
32
|
-
if (typeof value === 'number')
|
|
33
|
-
return { kind: 'number', value };
|
|
34
|
-
return { kind: 'string', value };
|
|
35
|
-
}
|
|
36
|
-
function same(alias, value) {
|
|
37
|
-
const held = sqlValue(value);
|
|
38
|
-
return held.kind === 'null'
|
|
39
|
-
? fragment(`${alias}.kind = 'null'`)
|
|
40
|
-
: fragment(`${alias}.kind = ? AND ${alias}.value = ?`, [held.kind, held.value]);
|
|
41
|
-
}
|
|
42
|
-
const BOUNDS = { gt: '>', gte: '>=', lt: '<', lte: '<=' };
|
|
43
|
-
function heldValues(doc, field, alias, where) {
|
|
44
|
-
return fragment(`SELECT 1 FROM replica_values ${alias} WHERE ${alias}.doc = ${doc}.id AND ${alias}.form = ? AND (${where.sql})`, [field, ...where.bind]);
|
|
45
|
-
}
|
|
46
|
-
function exists(part) {
|
|
47
|
-
return { sql: `EXISTS (${part.sql})`, bind: part.bind };
|
|
48
|
-
}
|
|
49
|
-
function matching(held, doc, field, values) {
|
|
50
|
-
return any(values.map((value) => any([
|
|
51
|
-
same(held, value),
|
|
52
|
-
all([
|
|
53
|
-
fragment(`${held}.kind = 'array'`),
|
|
54
|
-
exists(heldValues(doc, field, 'i', all([fragment(`i.held = ${held}.held AND i.item >= 0`), same('i', value)]))),
|
|
55
|
-
]),
|
|
56
|
-
])));
|
|
57
|
-
}
|
|
58
|
-
function tested(doc, field, test) {
|
|
59
|
-
const reached = exists(heldValues(doc, field, 'r', fragment('r.item = -1')));
|
|
60
|
-
if (test.op === 'exists')
|
|
61
|
-
return test.exists ? reached : fragment(`NOT ${reached.sql}`, reached.bind);
|
|
62
|
-
if (test.op === 'eq' || test.op === 'in') {
|
|
63
|
-
const values = test.op === 'eq' ? [test.value] : test.values;
|
|
64
|
-
return exists(heldValues(doc, field, 'v', any(values.map((value) => same('v', value)))));
|
|
65
|
-
}
|
|
66
|
-
if (test.op === 'ne' || test.op === 'nin') {
|
|
67
|
-
const values = test.op === 'ne' ? [test.value] : test.values;
|
|
68
|
-
const unmatched = matching('h', doc, field, values);
|
|
69
|
-
return any([
|
|
70
|
-
fragment(`NOT ${reached.sql}`, reached.bind),
|
|
71
|
-
exists(heldValues(doc, field, 'h', all([fragment('h.item = -1'), fragment(`NOT (${unmatched.sql})`, unmatched.bind)]))),
|
|
72
|
-
]);
|
|
73
|
-
}
|
|
74
|
-
if (test.op === 'range') {
|
|
75
|
-
return exists(heldValues(doc, field, 'v', all([
|
|
76
|
-
fragment('v.item = -1'),
|
|
77
|
-
...test.bounds.map(([operator, bound]) => {
|
|
78
|
-
const held = sqlValue(bound);
|
|
79
|
-
return fragment(`v.kind = ? AND v.value ${BOUNDS[operator]} ?`, [
|
|
80
|
-
held.kind,
|
|
81
|
-
held.value,
|
|
82
|
-
]);
|
|
83
|
-
}),
|
|
84
|
-
])));
|
|
85
|
-
}
|
|
86
|
-
if (test.op === 'all') {
|
|
87
|
-
return exists(heldValues(doc, field, 'h', all([
|
|
88
|
-
fragment("h.item = -1 AND h.kind = 'array'"),
|
|
89
|
-
...test.values.map((value) => exists(heldValues(doc, field, 'i', all([fragment('i.held = h.held AND i.item >= 0'), same('i', value)])))),
|
|
90
|
-
])));
|
|
91
|
-
}
|
|
92
|
-
return exists(heldValues(doc, field, 'v', fragment("v.item = -1 AND v.kind = 'array' AND v.size = ?", [test.size])));
|
|
3
|
+
import { documentKey, pathOfKey, roomOfKey } from './rows.js';
|
|
4
|
+
import { FROM_START, MAXIMUM_WINDOW } from './windows.js';
|
|
5
|
+
const { all, any, exists, fragment, grouped, joined, listed } = sql;
|
|
6
|
+
export const VISIBLE = (alias) => `${alias}.visible = 1 AND ${alias}.present = 1 AND ${alias}.queried = 1`;
|
|
7
|
+
function located(alias) {
|
|
8
|
+
return fragment(`(SELECT k.locator FROM replica_keys k WHERE k.key = ${alias}.key)`);
|
|
9
|
+
}
|
|
10
|
+
export function within(alias, path) {
|
|
11
|
+
return fragment(`(${alias}.path = ? OR (${alias}.path >= ? AND ${alias}.path < ?))`, [
|
|
12
|
+
path,
|
|
13
|
+
`${path}/`,
|
|
14
|
+
`${path}0`,
|
|
15
|
+
]);
|
|
93
16
|
}
|
|
94
|
-
function
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return
|
|
99
|
-
|
|
17
|
+
function ancestors(area) {
|
|
18
|
+
const depth = area.path.split('/').length;
|
|
19
|
+
return area.open.flatMap((open) => {
|
|
20
|
+
const segments = open.split('/');
|
|
21
|
+
return segments.slice(depth - 1, -1).map((_, at) => segments.slice(0, depth + at).join('/'));
|
|
22
|
+
});
|
|
100
23
|
}
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
24
|
+
function fenced(alias, key, area, folders) {
|
|
25
|
+
const opened = any([
|
|
26
|
+
...area.open.map((open) => within(alias, open)),
|
|
27
|
+
...(folders ? ancestors(area).map((path) => fragment(`${alias}.path = ?`, [path])) : []),
|
|
28
|
+
]);
|
|
29
|
+
return all([
|
|
30
|
+
fragment(`${alias}.key = ?`, [key]),
|
|
31
|
+
within(alias, area.path),
|
|
32
|
+
fragment(`NOT ${grouped(opened).sql}`, opened.bind),
|
|
33
|
+
]);
|
|
105
34
|
}
|
|
106
35
|
function reached(bound, alias) {
|
|
107
36
|
const reach = bound.reach;
|
|
@@ -115,44 +44,38 @@ function reached(bound, alias) {
|
|
|
115
44
|
const key = bound.keys.get(roomOfKey(held));
|
|
116
45
|
return key === undefined ? [] : [[key, pathOfKey(held)]];
|
|
117
46
|
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
: [
|
|
123
|
-
fragment(`EXISTS (SELECT 1 FROM json_each(?) j WHERE j.value ->> 0 = ${alias}.key AND j.value ->> 1 = ${alias}.path)`, [JSON.stringify(documents)]),
|
|
124
|
-
]),
|
|
125
|
-
]);
|
|
126
|
-
}
|
|
127
|
-
function scoped(alias, scope) {
|
|
47
|
+
const outside = any(reach.areas.flatMap((area) => {
|
|
48
|
+
const key = bound.keys.get(area.room);
|
|
49
|
+
return key === undefined ? [] : [fenced(alias, key, area, false)];
|
|
50
|
+
}));
|
|
128
51
|
return all([
|
|
129
|
-
|
|
130
|
-
? []
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
fragment(`${alias}.path >= ? AND ${alias}.path < ?`, [
|
|
136
|
-
`${scope.pathPrefix}/`,
|
|
137
|
-
`${scope.pathPrefix}0`,
|
|
52
|
+
any([
|
|
53
|
+
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${listed(rooms).sql}`, rooms)]),
|
|
54
|
+
...(documents.length === 0
|
|
55
|
+
? []
|
|
56
|
+
: [
|
|
57
|
+
fragment(`EXISTS (SELECT 1 FROM json_each(?) j WHERE j.value ->> 0 = ${alias}.key AND j.value ->> 1 = ${alias}.path)`, [JSON.stringify(documents)]),
|
|
138
58
|
]),
|
|
139
|
-
|
|
140
|
-
...(
|
|
59
|
+
]),
|
|
60
|
+
...(reach.areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
|
|
141
61
|
]);
|
|
142
62
|
}
|
|
143
63
|
function related(bound, plan) {
|
|
144
64
|
const parts = [];
|
|
145
65
|
if (plan.hop !== null) {
|
|
146
66
|
parts.push(exists(joined([
|
|
147
|
-
fragment(`SELECT 1 FROM replica_links l JOIN replica_entries m ON m.key = d.key AND m.path = l.target AND ${VISIBLE('m')} WHERE l.doc = d.id AND l.kind = 'field' AND
|
|
148
|
-
grouped(
|
|
67
|
+
fragment(`SELECT 1 FROM replica_links l JOIN replica_entries m ON m.key = d.key AND m.path = l.target AND ${VISIBLE('m')} WHERE l.doc = d.id AND l.kind = 'field' AND`),
|
|
68
|
+
grouped(sql.placed('l', plan.hop.field, 'link')),
|
|
69
|
+
fragment('AND'),
|
|
70
|
+
grouped(all([sql.scoped('m', plan.hop), reached(bound, 'm')])),
|
|
149
71
|
], ' ')));
|
|
150
72
|
}
|
|
151
73
|
if (plan.backlinks !== null) {
|
|
152
74
|
const field = plan.backlinks.field;
|
|
153
75
|
parts.push(exists(joined([
|
|
154
|
-
fragment(`SELECT 1 FROM replica_links l JOIN replica_entries s ON s.id = l.doc AND ${VISIBLE('s')} WHERE l.key = d.key AND l.target = d.path AND l.kind = 'field'
|
|
155
|
-
grouped(
|
|
76
|
+
fragment(`SELECT 1 FROM replica_links l JOIN replica_entries s ON s.id = l.doc AND ${VISIBLE('s')} WHERE l.key = d.key AND l.target = d.path AND l.kind = 'field' AND`),
|
|
77
|
+
...(field === null ? [] : [grouped(sql.placed('l', field, 'link')), fragment('AND')]),
|
|
78
|
+
grouped(all([sql.scoped('s', plan.backlinks), reached(bound, 's')])),
|
|
156
79
|
], ' ')));
|
|
157
80
|
}
|
|
158
81
|
return parts;
|
|
@@ -160,11 +83,9 @@ function related(bound, plan) {
|
|
|
160
83
|
function filtered(bound, plan) {
|
|
161
84
|
const keys = bound.rooms.map(({ key }) => key);
|
|
162
85
|
return all([
|
|
163
|
-
fragment(`d.key IN ${
|
|
164
|
-
...(plan.paths === null ? [] : [fragment(`d.path IN ${list(plan.paths).sql}`, plan.paths)]),
|
|
165
|
-
...(plan.folder === null ? [] : [fragment('d.folder = ?', [plan.folder])]),
|
|
86
|
+
fragment(`d.key IN ${listed(keys).sql} AND ${VISIBLE('d')}`, keys),
|
|
166
87
|
reached(bound, 'd'),
|
|
167
|
-
|
|
88
|
+
sql.tests('d', plan),
|
|
168
89
|
...related(bound, plan),
|
|
169
90
|
]);
|
|
170
91
|
}
|
|
@@ -195,104 +116,65 @@ const GROUP_TIES = {
|
|
|
195
116
|
locale: undefined,
|
|
196
117
|
localeOptions: undefined,
|
|
197
118
|
};
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
return { placement: 1, klass: 3, scalar: JSON.stringify(value) };
|
|
209
|
-
}
|
|
210
|
-
function placement(term) {
|
|
211
|
-
const nulls = term.ordering.nulls === 'first' ? 0 : 2;
|
|
212
|
-
return fragment(`CASE WHEN ${term.kind.sql} IS NULL OR ${term.kind.sql} = 'null' THEN ${nulls} ELSE 1 END`, [...term.kind.bind, ...term.kind.bind]);
|
|
213
|
-
}
|
|
214
|
-
function klass(term) {
|
|
215
|
-
return fragment(`CASE ${term.kind.sql} WHEN 'boolean' THEN 1 WHEN 'number' THEN 1 WHEN 'string' THEN 2 WHEN 'array' THEN 3 ELSE 4 END`, term.kind.bind);
|
|
216
|
-
}
|
|
217
|
-
function classed(term, which) {
|
|
218
|
-
const kinds = which === 1 ? "('boolean', 'number')" : which === 2 ? "('string')" : "('array')";
|
|
219
|
-
return fragment(`(CASE WHEN ${term.kind.sql} IN ${kinds} THEN ${term.value.sql} END)`, [
|
|
220
|
-
...term.kind.bind,
|
|
221
|
-
...term.value.bind,
|
|
222
|
-
]);
|
|
119
|
+
const GREATEST = { ...GROUP_ORDER, direction: 'desc', nulls: 'last' };
|
|
120
|
+
const TIMES = {
|
|
121
|
+
created: { value: 'd.created', instant: 'd.created_instant' },
|
|
122
|
+
updated: { value: "(d.change ->> '$.time')", instant: 'd.changed_instant' },
|
|
123
|
+
};
|
|
124
|
+
function subfolderOf(column, prefix) {
|
|
125
|
+
const root = prefix ?? '';
|
|
126
|
+
const base = root === '' ? '' : `${root}/`;
|
|
127
|
+
const rest = `substr(${column}, length(?) + 1)`;
|
|
128
|
+
return fragment(`(CASE WHEN ${column} = ? THEN NULL WHEN instr(${rest}, '/') = 0 THEN ${column} ELSE ? || substr(${rest}, 1, instr(${rest}, '/') - 1) END)`, [root, base, base, base, base]);
|
|
223
129
|
}
|
|
224
|
-
function
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
const name = bound.collate(term.ordering, which === 3, term.total === true);
|
|
228
|
-
return name === null ? '' : ` COLLATE ${name}`;
|
|
229
|
-
}
|
|
230
|
-
function sorted(bound, term) {
|
|
231
|
-
const direction = term.ordering.direction === 'desc' ? 'DESC' : 'ASC';
|
|
232
|
-
return joined([
|
|
233
|
-
placement(term),
|
|
234
|
-
fragment(`${klass(term).sql} ${direction}`, klass(term).bind),
|
|
235
|
-
...[1, 2, 3].map((which) => {
|
|
236
|
-
const value = classed(term, which);
|
|
237
|
-
return fragment(`${value.sql}${collated(bound, term, which)} ${direction}`, value.bind);
|
|
238
|
-
}),
|
|
239
|
-
], ', ');
|
|
240
|
-
}
|
|
241
|
-
function beyond(bound, term, value) {
|
|
242
|
-
const held = classOf(value, term.ordering);
|
|
243
|
-
const place = placement(term);
|
|
244
|
-
if (held.placement !== 1) {
|
|
130
|
+
function keyTerm(key, ordering, index, prefix) {
|
|
131
|
+
if (key.of === 'field') {
|
|
132
|
+
const alias = `o${index}`;
|
|
245
133
|
return {
|
|
246
|
-
|
|
247
|
-
|
|
134
|
+
term: {
|
|
135
|
+
ordering,
|
|
136
|
+
kind: fragment(`${alias}.kind`),
|
|
137
|
+
value: fragment(`${alias}.value`),
|
|
138
|
+
instant: fragment(`${alias}.instant`),
|
|
139
|
+
},
|
|
140
|
+
join: joined([
|
|
141
|
+
fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
|
|
142
|
+
grouped(sql.placed(alias, key.field, 'exact')),
|
|
143
|
+
fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
|
|
144
|
+
], ''),
|
|
248
145
|
};
|
|
249
146
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
any([
|
|
260
|
-
fragment(`${kind.sql} ${after} ${held.klass}`, kind.bind),
|
|
261
|
-
all([
|
|
262
|
-
fragment(`${kind.sql} = ${held.klass}`, kind.bind),
|
|
263
|
-
fragment(`${scalar.sql} ${after} ?${collation}`, [...scalar.bind, held.scalar]),
|
|
264
|
-
]),
|
|
265
|
-
]),
|
|
266
|
-
]),
|
|
267
|
-
]),
|
|
268
|
-
equal: all([
|
|
269
|
-
fragment(`${place.sql} = 1 AND ${kind.sql} = ${held.klass}`, [...place.bind, ...kind.bind]),
|
|
270
|
-
fragment(`${scalar.sql} = ?${collation}`, [...scalar.bind, held.scalar]),
|
|
271
|
-
]),
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
function following(parts, tie) {
|
|
275
|
-
let condition = tie;
|
|
276
|
-
for (const part of [...parts].reverse()) {
|
|
277
|
-
condition = any([part.beyond, all([part.equal, condition])]);
|
|
147
|
+
if (key.of === 'size') {
|
|
148
|
+
return {
|
|
149
|
+
term: {
|
|
150
|
+
ordering,
|
|
151
|
+
kind: fragment("(CASE WHEN d.bytes IS NULL THEN 'null' ELSE 'number' END)"),
|
|
152
|
+
value: fragment('d.bytes'),
|
|
153
|
+
},
|
|
154
|
+
join: null,
|
|
155
|
+
};
|
|
278
156
|
}
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
function keyTerm(bound, key, ordering, index) {
|
|
282
|
-
if (key.of === 'field') {
|
|
283
|
-
const alias = `o${index}`;
|
|
157
|
+
if (key.of === 'created' || key.of === 'updated') {
|
|
158
|
+
const { value, instant } = TIMES[key.of];
|
|
284
159
|
return {
|
|
285
|
-
term: {
|
|
286
|
-
|
|
160
|
+
term: {
|
|
161
|
+
ordering,
|
|
162
|
+
kind: fragment(`(CASE WHEN ${value} IS NULL THEN 'null' ELSE 'string' END)`),
|
|
163
|
+
value: fragment(value),
|
|
164
|
+
instant: fragment(instant),
|
|
165
|
+
},
|
|
166
|
+
join: null,
|
|
287
167
|
};
|
|
288
168
|
}
|
|
289
169
|
const expression = key.of === 'room'
|
|
290
|
-
? located(
|
|
170
|
+
? located('d')
|
|
291
171
|
: key.of === 'path'
|
|
292
172
|
? fragment('d.path')
|
|
293
173
|
: key.of === 'type'
|
|
294
174
|
? fragment('d.type')
|
|
295
|
-
:
|
|
175
|
+
: key.of === 'subfolder'
|
|
176
|
+
? subfolderOf('d.folder', prefix)
|
|
177
|
+
: fragment('d.folder');
|
|
296
178
|
return {
|
|
297
179
|
term: {
|
|
298
180
|
ordering,
|
|
@@ -313,12 +195,20 @@ function decoded(kind, value) {
|
|
|
313
195
|
return JSON.parse(value);
|
|
314
196
|
return null;
|
|
315
197
|
}
|
|
198
|
+
function sortedOf(kind, value, instant) {
|
|
199
|
+
return sql.sortValue(kind, decoded(kind, value), instant);
|
|
200
|
+
}
|
|
201
|
+
function sortedValue(row, index) {
|
|
202
|
+
return sortedOf(row[`k${index}`], row[`v${index}`], row[`i${index}`]);
|
|
203
|
+
}
|
|
316
204
|
function decodedKey(key, row, index) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
return
|
|
320
|
-
|
|
321
|
-
|
|
205
|
+
const kind = row[`k${index}`];
|
|
206
|
+
if (key.of === 'field' && (kind === null || kind === undefined))
|
|
207
|
+
return undefined;
|
|
208
|
+
return sortedValue(row, index);
|
|
209
|
+
}
|
|
210
|
+
function instantOf(term) {
|
|
211
|
+
return term.instant ?? fragment('NULL');
|
|
322
212
|
}
|
|
323
213
|
const registered = new WeakMap();
|
|
324
214
|
function hashed(text) {
|
|
@@ -369,9 +259,6 @@ function collating(connection) {
|
|
|
369
259
|
return name;
|
|
370
260
|
};
|
|
371
261
|
}
|
|
372
|
-
function bodied(shape) {
|
|
373
|
-
return shape.sections !== null || shape.preamble || shape.markdown;
|
|
374
|
-
}
|
|
375
262
|
function masked(pointers) {
|
|
376
263
|
const root = { whole: false, members: new Map() };
|
|
377
264
|
for (const pointer of pointers) {
|
|
@@ -426,7 +313,8 @@ function sectionsOf(sections, names) {
|
|
|
426
313
|
function documentOf(shape, room, row, linked) {
|
|
427
314
|
const path = row.path;
|
|
428
315
|
const type = row.type;
|
|
429
|
-
const fileId = row.file_id
|
|
316
|
+
const fileId = row.file_id;
|
|
317
|
+
const revision = row.revision;
|
|
430
318
|
if (type === null) {
|
|
431
319
|
const binary = row.binary === null ? null : JSON.parse(row.binary);
|
|
432
320
|
const content = row.content ?? null;
|
|
@@ -436,15 +324,16 @@ function documentOf(shape, room, row, linked) {
|
|
|
436
324
|
...(binary === null
|
|
437
325
|
? {}
|
|
438
326
|
: {
|
|
439
|
-
binaryFile: binaryRecord(path, binary,
|
|
327
|
+
binaryFile: binaryRecord(path, binary, revision ?? undefined),
|
|
440
328
|
}),
|
|
441
|
-
...(shape.markdown && content !== null
|
|
442
|
-
|
|
443
|
-
: {}),
|
|
329
|
+
...(shape.markdown && content !== null ? { markdown: content } : {}),
|
|
330
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
444
331
|
...(shape.fileId ? { fileId } : {}),
|
|
332
|
+
...(shape.revision ? { revision } : {}),
|
|
333
|
+
...heldFacts(shape, row),
|
|
445
334
|
};
|
|
446
335
|
}
|
|
447
|
-
const read =
|
|
336
|
+
const read = shape.sections !== null || shape.preamble ? parse(row.content, path) : null;
|
|
448
337
|
const body = read?.ok === true ? read.document : null;
|
|
449
338
|
const fields = JSON.parse(row.fields ?? '{}');
|
|
450
339
|
return {
|
|
@@ -462,14 +351,21 @@ function documentOf(shape, room, row, linked) {
|
|
|
462
351
|
? {}
|
|
463
352
|
: { sections: sectionsOf(body.sections, shape.sections) }),
|
|
464
353
|
...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
|
|
465
|
-
...(shape.markdown
|
|
354
|
+
...(shape.markdown ? { markdown: row.content } : {}),
|
|
466
355
|
...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
|
|
467
|
-
...(shape.change
|
|
468
|
-
? {
|
|
469
|
-
change: row.change === null ? null : JSON.parse(row.change),
|
|
470
|
-
}
|
|
471
|
-
: {}),
|
|
356
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
472
357
|
...(shape.fileId ? { fileId } : {}),
|
|
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 } : {}),
|
|
473
369
|
};
|
|
474
370
|
}
|
|
475
371
|
function backlinksOf(connection, bound, rows) {
|
|
@@ -481,8 +377,8 @@ function backlinksOf(connection, bound, rows) {
|
|
|
481
377
|
held.push(row.path);
|
|
482
378
|
byKey.set(key, held);
|
|
483
379
|
}
|
|
380
|
+
const reach = reached(bound, 's');
|
|
484
381
|
for (const [key, targets] of byKey) {
|
|
485
|
-
const reach = reached(bound, 's');
|
|
486
382
|
const room = bound.rooms.find((one) => one.key === key).locator;
|
|
487
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]);
|
|
488
384
|
for (const row of found) {
|
|
@@ -495,38 +391,36 @@ function backlinksOf(connection, bound, rows) {
|
|
|
495
391
|
return linked;
|
|
496
392
|
}
|
|
497
393
|
function documents(connection, bound, plan, shape, budget) {
|
|
498
|
-
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));
|
|
499
395
|
const filter = filtered(bound, plan);
|
|
500
396
|
const cursor = plan.after?.values ?? null;
|
|
501
|
-
const room = located(
|
|
502
|
-
const
|
|
397
|
+
const room = located('d');
|
|
398
|
+
const after = cursor === null
|
|
503
399
|
? null
|
|
504
400
|
: (() => {
|
|
505
401
|
const key = cursor[shape.orderBy.length];
|
|
506
|
-
return any([
|
|
402
|
+
return sql.following(terms.map(({ term }, index) => sql.beyond(bound.collate, term, cursor[index])), any([
|
|
507
403
|
fragment(`${room.sql} > ?`, [...room.bind, roomOfKey(key)]),
|
|
508
404
|
fragment(`${room.sql} = ? AND d.path > ?`, [
|
|
509
405
|
...room.bind,
|
|
510
406
|
roomOfKey(key),
|
|
511
407
|
pathOfKey(key),
|
|
512
408
|
]),
|
|
513
|
-
]);
|
|
409
|
+
]));
|
|
514
410
|
})();
|
|
515
|
-
const after = cursor === null || tie === null
|
|
516
|
-
? null
|
|
517
|
-
: following(terms.map(({ term }, index) => beyond(bound, term, cursor[index])), tie);
|
|
518
411
|
const selected = joined([
|
|
519
|
-
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'),
|
|
520
413
|
...terms.map(({ term }, index) => joined([
|
|
521
414
|
fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
522
415
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
416
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
523
417
|
], '')),
|
|
524
418
|
fragment(' FROM replica_entries d'),
|
|
525
419
|
...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
|
|
526
420
|
fragment(' WHERE '),
|
|
527
421
|
after === null ? filter : all([filter, after]),
|
|
528
422
|
fragment(' ORDER BY '),
|
|
529
|
-
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')], ', '),
|
|
530
424
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
531
425
|
], '');
|
|
532
426
|
const rows = connection.rows(selected.sql, selected.bind);
|
|
@@ -565,7 +459,7 @@ function withContent(connection, rows, budget) {
|
|
|
565
459
|
return rows;
|
|
566
460
|
const ids = rows.map((row) => row.id);
|
|
567
461
|
const sizes = new Map(connection
|
|
568
|
-
.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)
|
|
569
463
|
.map((row) => [row.id, Number(row.size ?? 0)]));
|
|
570
464
|
let cut = rows.length;
|
|
571
465
|
let spent = 0;
|
|
@@ -580,111 +474,213 @@ function withContent(connection, rows, budget) {
|
|
|
580
474
|
const kept = rows.slice(0, cut);
|
|
581
475
|
const read = kept.map((row) => row.id);
|
|
582
476
|
const held = new Map(connection
|
|
583
|
-
.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)
|
|
584
478
|
.map((row) => [row.id, row.content]));
|
|
585
479
|
return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
|
|
586
480
|
}
|
|
587
|
-
function storedFolders(bound, plan) {
|
|
588
|
-
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)
|
|
589
487
|
return null;
|
|
590
488
|
const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
|
|
591
489
|
if (whole.length === 0)
|
|
592
490
|
return null;
|
|
593
491
|
const keys = whole.map(({ key }) => key);
|
|
594
|
-
const prefix = plan.root.pathPrefix;
|
|
492
|
+
const prefix = plan.root.pathPrefix ?? '';
|
|
493
|
+
const outside = (bound.reach?.areas ?? []).flatMap((area) => {
|
|
494
|
+
const key = bound.keys.get(area.room);
|
|
495
|
+
if (key === undefined)
|
|
496
|
+
return [];
|
|
497
|
+
const fence = fenced('f', key, area, true);
|
|
498
|
+
return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
|
|
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
|
+
];
|
|
595
509
|
return all([
|
|
596
|
-
fragment(`f.key IN ${
|
|
597
|
-
...
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
fragment('(f.path = ? OR (f.path >= ? AND f.path < ?))', [
|
|
601
|
-
prefix,
|
|
602
|
-
`${prefix}/`,
|
|
603
|
-
`${prefix}0`,
|
|
604
|
-
]),
|
|
605
|
-
]),
|
|
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)),
|
|
606
514
|
...(plan.folder === null ? [] : [fragment('f.path = ?', [plan.folder])]),
|
|
607
515
|
]);
|
|
608
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
|
+
}
|
|
609
532
|
function groups(connection, bound, plan, shape) {
|
|
533
|
+
const prefix = plan.root.pathPrefix;
|
|
610
534
|
const keyed = shape.groupBy.map((key, index) => {
|
|
611
|
-
const { term, join } = keyTerm(
|
|
535
|
+
const { term, join } = keyTerm(key, GROUP_ORDER, index, prefix);
|
|
612
536
|
const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
|
|
613
537
|
return { term: { ...term, kind }, join };
|
|
614
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);
|
|
615
545
|
const filter = filtered(bound, plan);
|
|
616
546
|
const inner = joined([
|
|
617
547
|
fragment('SELECT '),
|
|
618
548
|
joined(keyed.map(({ term }, index) => joined([
|
|
619
549
|
fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
620
550
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
551
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
621
552
|
], '')), ', '),
|
|
622
|
-
fragment(', 1 AS n
|
|
623
|
-
...
|
|
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]),
|
|
624
563
|
fragment(' WHERE '),
|
|
625
564
|
filter,
|
|
626
565
|
], '');
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
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
|
+
];
|
|
630
575
|
const source = folders === null
|
|
631
576
|
? inner
|
|
632
577
|
: joined([
|
|
633
578
|
inner,
|
|
634
|
-
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),
|
|
635
580
|
folders,
|
|
636
581
|
], '');
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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,
|
|
645
605
|
kind: fragment(`k${index}`),
|
|
646
606
|
value: fragment(`v${index}`),
|
|
647
607
|
});
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
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 }));
|
|
651
640
|
const ties = keyed.flatMap((_, index) => [
|
|
652
641
|
{ ...outer(index, GROUP_TIES), total: true },
|
|
653
642
|
{ ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
|
|
654
643
|
]);
|
|
655
644
|
const cursor = plan.after?.values ?? null;
|
|
656
|
-
const
|
|
645
|
+
const after = cursor === null
|
|
657
646
|
? null
|
|
658
|
-
:
|
|
659
|
-
value,
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
const grouping = joined([fragment(`SELECT ${columns}, sum(n) AS n FROM (`), source, fragment(`) GROUP BY ${columns}`)], '');
|
|
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}`)], '');
|
|
666
654
|
const windowed = joined([
|
|
667
655
|
fragment('SELECT * FROM ('),
|
|
668
656
|
grouping,
|
|
669
657
|
fragment(')'),
|
|
670
658
|
...(after === null ? [] : [fragment(' WHERE '), after]),
|
|
671
659
|
fragment(' ORDER BY '),
|
|
672
|
-
joined([...ordered, ...ties].map((term) => sorted(bound, term)), ', '),
|
|
660
|
+
joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
|
|
673
661
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
674
662
|
], '');
|
|
675
663
|
const rows = connection.rows(windowed.sql, windowed.bind);
|
|
676
664
|
const kept = plan.limit === null ? rows : rows.slice(0, plan.limit);
|
|
677
665
|
const keyOf = (row) => keyed.map((_, index) => decoded(row[`k${index}`], row[`v${index}`]));
|
|
678
|
-
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
|
+
}));
|
|
679
678
|
const last = kept.at(-1);
|
|
680
679
|
const next = plan.limit === null || rows.length <= plan.limit
|
|
681
680
|
? undefined
|
|
682
681
|
: last === undefined
|
|
683
682
|
? (plan.after?.text ?? FROM_START)
|
|
684
|
-
: cursorOf([
|
|
685
|
-
...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : decoded(last[`k${by}`], last[`v${by}`])),
|
|
686
|
-
keyOf(last),
|
|
687
|
-
]);
|
|
683
|
+
: cursorOf([...shape.orderBy.map(({ by }) => valueOf(last, by)), keyOf(last)]);
|
|
688
684
|
const total = plan.total
|
|
689
685
|
? Number(connection.rows(joined([fragment('SELECT count(*) AS held FROM ('), grouping, fragment(')')], '').sql, grouping.bind)[0].held)
|
|
690
686
|
: undefined;
|
|
@@ -702,9 +698,6 @@ function boundOf(connection, plan, rooms) {
|
|
|
702
698
|
collate: collating(connection),
|
|
703
699
|
};
|
|
704
700
|
}
|
|
705
|
-
export function selected(connection, plan, rooms) {
|
|
706
|
-
return filtered(boundOf(connection, plan, rooms), plan);
|
|
707
|
-
}
|
|
708
701
|
export function answerQuery(connection, plan, rooms, budget = null) {
|
|
709
702
|
const bound = boundOf(connection, plan, rooms);
|
|
710
703
|
if (rooms.length === 0) {
|