@agentshouse/mdruntime 0.1.0-cb1f56e16291b → 0.1.0-cb3bb3b233fa1
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 +113 -81
- 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/authored.d.ts +88 -0
- package/dist/authored.d.ts.map +1 -0
- package/dist/authored.js +345 -0
- 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 +5 -0
- package/dist/change.d.ts.map +1 -1
- package/dist/change.js +7 -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 +30 -22
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +39 -20
- package/dist/collection.d.ts +26 -4
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +19 -2
- package/dist/column.d.ts +5 -0
- package/dist/column.d.ts.map +1 -0
- package/dist/column.js +9 -0
- package/dist/compile.d.ts +9 -7
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +252 -318
- package/dist/connection.d.ts +5 -141
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +111 -5282
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +1 -1
- 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 +40 -0
- package/dist/conversation-row.d.ts.map +1 -0
- package/dist/conversation-row.js +66 -0
- package/dist/conversation.d.ts +62 -43
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +987 -902
- 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 -9
- 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 +1 -1
- package/dist/file.d.ts.map +1 -1
- package/dist/file.js +3 -2
- package/dist/holdings.d.ts +29 -0
- package/dist/holdings.d.ts.map +1 -0
- package/dist/holdings.js +95 -0
- package/dist/host.d.ts +20 -4
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +72 -28
- 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 +32 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -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 +282 -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 +8 -6
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +110 -31
- package/dist/loaded.d.ts +5 -0
- package/dist/loaded.d.ts.map +1 -0
- package/dist/loaded.js +40 -0
- 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 +6 -1
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js +68 -17
- package/dist/memory.d.ts +11 -3
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +132 -33
- package/dist/messages.d.ts +33 -3
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +98 -27
- 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 +27 -12
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +184 -85
- 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 +17 -2
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +35 -2
- package/dist/overlay.d.ts +7 -2
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +48 -24
- 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 +231 -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 +13 -213
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -524
- package/dist/questions.d.ts +30 -0
- package/dist/questions.d.ts.map +1 -0
- package/dist/questions.js +116 -0
- package/dist/queue.d.ts +40 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +480 -0
- package/dist/reach.d.ts +10 -10
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +235 -120
- 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 -4
- 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 +1 -1
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +10 -22
- 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 +66 -35
- 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 +32 -9
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +273 -66
- package/dist/room-drafts.d.ts +24 -0
- package/dist/room-drafts.d.ts.map +1 -0
- package/dist/room-drafts.js +220 -0
- package/dist/room-list.d.ts +10 -0
- package/dist/room-list.d.ts.map +1 -0
- package/dist/room-list.js +45 -0
- package/dist/rows.d.ts +16 -23
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +49 -191
- 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 +332 -228
- package/dist/saves.d.ts +34 -0
- package/dist/saves.d.ts.map +1 -0
- package/dist/saves.js +437 -0
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +1 -1
- 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 +59 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +93 -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 +223 -0
- package/dist/store/records.d.ts.map +1 -0
- package/dist/store/records.js +465 -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 +115 -152
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +171 -395
- package/dist/stored.d.ts +72 -67
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +317 -166
- package/dist/surface.d.ts +90 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +225 -0
- package/dist/sync.d.ts +19 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +536 -0
- package/dist/tables.d.ts +112 -89
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +197 -283
- 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 +68 -7
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +23 -4
- 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/job.d.ts +0 -14
- package/dist/job.d.ts.map +0 -1
- package/dist/job.js +0 -28
- package/dist/log.d.ts +0 -5
- package/dist/log.d.ts.map +0 -1
- package/dist/log.js +0 -14
- package/dist/phrase.d.ts +0 -3
- package/dist/phrase.d.ts.map +0 -1
- package/dist/phrase.js +0 -8
package/dist/compile.js
CHANGED
|
@@ -1,127 +1,14 @@
|
|
|
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 { documentKey,
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
const VISIBLE = (alias) => `${alias}.visible = 1 AND ${alias}.present = 1
|
|
8
|
-
function fragment(sql, bind = []) {
|
|
9
|
-
return { sql, bind };
|
|
10
|
-
}
|
|
11
|
-
function joined(parts, separator) {
|
|
12
|
-
return {
|
|
13
|
-
sql: parts.map((part) => part.sql).join(separator),
|
|
14
|
-
bind: parts.flatMap((part) => part.bind),
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
function balanced(parts, separator) {
|
|
18
|
-
if (parts.length === 1)
|
|
19
|
-
return grouped(parts[0]);
|
|
20
|
-
const half = Math.ceil(parts.length / 2);
|
|
21
|
-
return grouped(joined([balanced(parts.slice(0, half), separator), balanced(parts.slice(half), separator)], separator));
|
|
22
|
-
}
|
|
23
|
-
function all(parts) {
|
|
24
|
-
return parts.length === 0 ? fragment('1') : balanced(parts, ' AND ');
|
|
25
|
-
}
|
|
26
|
-
function any(parts) {
|
|
27
|
-
return parts.length === 0 ? fragment('0') : balanced(parts, ' OR ');
|
|
28
|
-
}
|
|
29
|
-
function grouped(part) {
|
|
30
|
-
return { sql: `(${part.sql})`, bind: part.bind };
|
|
31
|
-
}
|
|
32
|
-
function list(values) {
|
|
33
|
-
return fragment(`(${values.map(() => '?').join(', ')})`, values);
|
|
34
|
-
}
|
|
35
|
-
function sqlValue(value) {
|
|
36
|
-
if (value === null)
|
|
37
|
-
return { kind: 'null', value: null };
|
|
38
|
-
if (typeof value === 'boolean')
|
|
39
|
-
return { kind: 'boolean', value: value ? 1 : 0 };
|
|
40
|
-
if (typeof value === 'number')
|
|
41
|
-
return { kind: 'number', value };
|
|
42
|
-
return { kind: 'string', value };
|
|
43
|
-
}
|
|
44
|
-
function same(alias, value) {
|
|
45
|
-
const held = sqlValue(value);
|
|
46
|
-
return held.kind === 'null'
|
|
47
|
-
? fragment(`${alias}.kind = 'null'`)
|
|
48
|
-
: fragment(`${alias}.kind = ? AND ${alias}.value = ?`, [held.kind, held.value]);
|
|
49
|
-
}
|
|
50
|
-
const BOUNDS = { gt: '>', gte: '>=', lt: '<', lte: '<=' };
|
|
51
|
-
function pointed(alias, field, reach) {
|
|
52
|
-
const { keys, places, glob } = placedOf(field, reach);
|
|
53
|
-
return fragment(`${alias}.keys = ? AND ${alias}.places ${glob ? 'GLOB' : '='} ?`, [keys, places]);
|
|
54
|
-
}
|
|
55
|
-
function heldValues(doc, field, alias, where) {
|
|
56
|
-
return joined([
|
|
57
|
-
fragment(`SELECT 1 FROM replica_values ${alias} WHERE ${alias}.doc = ${doc}.id AND `),
|
|
58
|
-
grouped(pointed(alias, field, 'value')),
|
|
59
|
-
fragment(' AND '),
|
|
60
|
-
grouped(where),
|
|
61
|
-
], '');
|
|
62
|
-
}
|
|
63
|
-
function exists(part) {
|
|
64
|
-
return { sql: `EXISTS (${part.sql})`, bind: part.bind };
|
|
65
|
-
}
|
|
66
|
-
function itemOf(held, where) {
|
|
67
|
-
return exists(joined([
|
|
68
|
-
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 `),
|
|
69
|
-
grouped(where),
|
|
70
|
-
], ''));
|
|
71
|
-
}
|
|
72
|
-
function matching(held, values) {
|
|
73
|
-
return any(values.map((value) => any([
|
|
74
|
-
same(held, value),
|
|
75
|
-
all([fragment(`${held}.kind = 'array'`), itemOf(held, same('i', value))]),
|
|
76
|
-
])));
|
|
77
|
-
}
|
|
78
|
-
function tested(doc, field, test) {
|
|
79
|
-
const reached = exists(heldValues(doc, field, 'r', fragment('r.item = -1')));
|
|
80
|
-
if (test.op === 'exists')
|
|
81
|
-
return test.exists ? reached : fragment(`NOT ${reached.sql}`, reached.bind);
|
|
82
|
-
if (test.op === 'eq' || test.op === 'in') {
|
|
83
|
-
const values = test.op === 'eq' ? [test.value] : test.values;
|
|
84
|
-
return exists(heldValues(doc, field, 'v', any(values.map((value) => same('v', value)))));
|
|
85
|
-
}
|
|
86
|
-
if (test.op === 'ne' || test.op === 'nin') {
|
|
87
|
-
const values = test.op === 'ne' ? [test.value] : test.values;
|
|
88
|
-
const unmatched = matching('h', values);
|
|
89
|
-
return any([
|
|
90
|
-
fragment(`NOT ${reached.sql}`, reached.bind),
|
|
91
|
-
exists(heldValues(doc, field, 'h', all([fragment('h.item = -1'), fragment(`NOT (${unmatched.sql})`, unmatched.bind)]))),
|
|
92
|
-
]);
|
|
93
|
-
}
|
|
94
|
-
if (test.op === 'range') {
|
|
95
|
-
return exists(heldValues(doc, field, 'v', all([
|
|
96
|
-
fragment('v.item = -1'),
|
|
97
|
-
...test.bounds.map(([operator, bound]) => {
|
|
98
|
-
const held = sqlValue(bound);
|
|
99
|
-
return fragment(`v.kind = ? AND v.value ${BOUNDS[operator]} ?`, [
|
|
100
|
-
held.kind,
|
|
101
|
-
held.value,
|
|
102
|
-
]);
|
|
103
|
-
}),
|
|
104
|
-
])));
|
|
105
|
-
}
|
|
106
|
-
if (test.op === 'all') {
|
|
107
|
-
return exists(heldValues(doc, field, 'h', all([
|
|
108
|
-
fragment("h.item = -1 AND h.kind = 'array'"),
|
|
109
|
-
...test.values.map((value) => itemOf('h', same('i', value))),
|
|
110
|
-
])));
|
|
111
|
-
}
|
|
112
|
-
return exists(heldValues(doc, field, 'v', fragment("v.item = -1 AND v.kind = 'array' AND v.size = ?", [test.size])));
|
|
113
|
-
}
|
|
114
|
-
function conditioned(doc, condition) {
|
|
115
|
-
if ('every' in condition)
|
|
116
|
-
return all(condition.every.map((one) => conditioned(doc, one)));
|
|
117
|
-
if ('some' in condition)
|
|
118
|
-
return any(condition.some.map((one) => conditioned(doc, one)));
|
|
119
|
-
return tested(doc, condition.field, condition.test);
|
|
120
|
-
}
|
|
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`;
|
|
121
8
|
function located(alias) {
|
|
122
9
|
return fragment(`(SELECT k.locator FROM replica_keys k WHERE k.key = ${alias}.key)`);
|
|
123
10
|
}
|
|
124
|
-
function within(alias, path) {
|
|
11
|
+
export function within(alias, path) {
|
|
125
12
|
return fragment(`(${alias}.path = ? OR (${alias}.path >= ? AND ${alias}.path < ?))`, [
|
|
126
13
|
path,
|
|
127
14
|
`${path}/`,
|
|
@@ -164,7 +51,7 @@ function reached(bound, alias) {
|
|
|
164
51
|
}));
|
|
165
52
|
return all([
|
|
166
53
|
any([
|
|
167
|
-
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${
|
|
54
|
+
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${listed(rooms).sql}`, rooms)]),
|
|
168
55
|
...(documents.length === 0
|
|
169
56
|
? []
|
|
170
57
|
: [
|
|
@@ -174,40 +61,22 @@ function reached(bound, alias) {
|
|
|
174
61
|
...(reach.areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
|
|
175
62
|
]);
|
|
176
63
|
}
|
|
177
|
-
function unindexed(column) {
|
|
178
|
-
return `+${column}`;
|
|
179
|
-
}
|
|
180
|
-
function scoped(alias, scope, byPaths = false) {
|
|
181
|
-
const type = byPaths ? unindexed(`${alias}.type`) : `${alias}.type`;
|
|
182
|
-
return all([
|
|
183
|
-
...(scope.types === null ? [] : [fragment(`${type} IN ${list(scope.types).sql}`, scope.types)]),
|
|
184
|
-
...(scope.pathPrefix === null || scope.pathPrefix === ''
|
|
185
|
-
? []
|
|
186
|
-
: [
|
|
187
|
-
fragment(`${alias}.path >= ? AND ${alias}.path < ?`, [
|
|
188
|
-
`${scope.pathPrefix}/`,
|
|
189
|
-
`${scope.pathPrefix}0`,
|
|
190
|
-
]),
|
|
191
|
-
]),
|
|
192
|
-
...(scope.where === null ? [] : [conditioned(alias, scope.where)]),
|
|
193
|
-
]);
|
|
194
|
-
}
|
|
195
64
|
function related(bound, plan) {
|
|
196
65
|
const parts = [];
|
|
197
66
|
if (plan.hop !== null) {
|
|
198
67
|
parts.push(exists(joined([
|
|
199
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`),
|
|
200
|
-
grouped(
|
|
69
|
+
grouped(sql.placed('l', plan.hop.field, 'link')),
|
|
201
70
|
fragment('AND'),
|
|
202
|
-
grouped(all([scoped('m', plan.hop), reached(bound, 'm')])),
|
|
71
|
+
grouped(all([sql.scoped('m', plan.hop), reached(bound, 'm')])),
|
|
203
72
|
], ' ')));
|
|
204
73
|
}
|
|
205
74
|
if (plan.backlinks !== null) {
|
|
206
75
|
const field = plan.backlinks.field;
|
|
207
76
|
parts.push(exists(joined([
|
|
208
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`),
|
|
209
|
-
...(field === null ? [] : [grouped(
|
|
210
|
-
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')])),
|
|
211
80
|
], ' ')));
|
|
212
81
|
}
|
|
213
82
|
return parts;
|
|
@@ -215,15 +84,10 @@ function related(bound, plan) {
|
|
|
215
84
|
function filtered(bound, plan) {
|
|
216
85
|
const keys = bound.rooms.map(({ key }) => key);
|
|
217
86
|
return all([
|
|
218
|
-
fragment(`d.key IN ${
|
|
219
|
-
...(plan.paths === null ? [] : [fragment(`d.path IN ${list(plan.paths).sql}`, plan.paths)]),
|
|
220
|
-
...(plan.folder === null ? [] : [fragment('d.folder = ?', [plan.folder])]),
|
|
87
|
+
fragment(`d.key IN ${listed(keys).sql} AND ${VISIBLE('d')}`, keys),
|
|
221
88
|
reached(bound, 'd'),
|
|
222
|
-
|
|
89
|
+
sql.tests('d', plan),
|
|
223
90
|
...related(bound, plan),
|
|
224
|
-
...(plan.text === null
|
|
225
|
-
? []
|
|
226
|
-
: [fragment(`${HOLDS_PHRASE}(d.content, ?)`, [plan.text.toLowerCase()])]),
|
|
227
91
|
]);
|
|
228
92
|
}
|
|
229
93
|
function orderingOf(options) {
|
|
@@ -253,108 +117,65 @@ const GROUP_TIES = {
|
|
|
253
117
|
locale: undefined,
|
|
254
118
|
localeOptions: undefined,
|
|
255
119
|
};
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return { placement: 1, klass: 3, scalar: JSON.stringify(value) };
|
|
267
|
-
}
|
|
268
|
-
function placement(term) {
|
|
269
|
-
const nulls = term.ordering.nulls === 'first' ? 0 : 2;
|
|
270
|
-
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]);
|
|
271
|
-
}
|
|
272
|
-
function klass(term) {
|
|
273
|
-
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);
|
|
274
|
-
}
|
|
275
|
-
function classed(term, which) {
|
|
276
|
-
const kinds = which === 1 ? "('boolean', 'number')" : which === 2 ? "('string')" : "('array')";
|
|
277
|
-
return fragment(`(CASE WHEN ${term.kind.sql} IN ${kinds} THEN ${term.value.sql} END)`, [
|
|
278
|
-
...term.kind.bind,
|
|
279
|
-
...term.value.bind,
|
|
280
|
-
]);
|
|
281
|
-
}
|
|
282
|
-
function collated(bound, term, which) {
|
|
283
|
-
if (which === 1)
|
|
284
|
-
return '';
|
|
285
|
-
const name = bound.collate(term.ordering, which === 3, term.total === true);
|
|
286
|
-
return name === null ? '' : ` COLLATE ${name}`;
|
|
287
|
-
}
|
|
288
|
-
function sorted(bound, term) {
|
|
289
|
-
const direction = term.ordering.direction === 'desc' ? 'DESC' : 'ASC';
|
|
290
|
-
return joined([
|
|
291
|
-
placement(term),
|
|
292
|
-
fragment(`${klass(term).sql} ${direction}`, klass(term).bind),
|
|
293
|
-
...[1, 2, 3].map((which) => {
|
|
294
|
-
const value = classed(term, which);
|
|
295
|
-
return fragment(`${value.sql}${collated(bound, term, which)} ${direction}`, value.bind);
|
|
296
|
-
}),
|
|
297
|
-
], ', ');
|
|
298
|
-
}
|
|
299
|
-
function beyond(bound, term, value) {
|
|
300
|
-
const held = classOf(value, term.ordering);
|
|
301
|
-
const place = placement(term);
|
|
302
|
-
if (held.placement !== 1) {
|
|
303
|
-
return {
|
|
304
|
-
beyond: fragment(`${place.sql} > ${held.placement}`, place.bind),
|
|
305
|
-
equal: fragment(`${place.sql} = ${held.placement}`, place.bind),
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
const after = term.ordering.direction === 'desc' ? '<' : '>';
|
|
309
|
-
const kind = klass(term);
|
|
310
|
-
const scalar = classed(term, held.klass);
|
|
311
|
-
const collation = collated(bound, term, held.klass);
|
|
312
|
-
return {
|
|
313
|
-
beyond: any([
|
|
314
|
-
fragment(`${place.sql} > 1`, place.bind),
|
|
315
|
-
all([
|
|
316
|
-
fragment(`${place.sql} = 1`, place.bind),
|
|
317
|
-
any([
|
|
318
|
-
fragment(`${kind.sql} ${after} ${held.klass}`, kind.bind),
|
|
319
|
-
all([
|
|
320
|
-
fragment(`${kind.sql} = ${held.klass}`, kind.bind),
|
|
321
|
-
fragment(`${scalar.sql} ${after} ?${collation}`, [...scalar.bind, held.scalar]),
|
|
322
|
-
]),
|
|
323
|
-
]),
|
|
324
|
-
]),
|
|
325
|
-
]),
|
|
326
|
-
equal: all([
|
|
327
|
-
fragment(`${place.sql} = 1 AND ${kind.sql} = ${held.klass}`, [...place.bind, ...kind.bind]),
|
|
328
|
-
fragment(`${scalar.sql} = ?${collation}`, [...scalar.bind, held.scalar]),
|
|
329
|
-
]),
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
function following(parts, tie) {
|
|
333
|
-
let condition = tie;
|
|
334
|
-
for (const part of [...parts].reverse()) {
|
|
335
|
-
condition = any([part.beyond, all([part.equal, condition])]);
|
|
336
|
-
}
|
|
337
|
-
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]);
|
|
338
130
|
}
|
|
339
|
-
function keyTerm(key, ordering, index) {
|
|
131
|
+
function keyTerm(key, ordering, index, prefix) {
|
|
340
132
|
if (key.of === 'field') {
|
|
341
133
|
const alias = `o${index}`;
|
|
342
134
|
return {
|
|
343
|
-
term: {
|
|
135
|
+
term: {
|
|
136
|
+
ordering,
|
|
137
|
+
kind: fragment(`${alias}.kind`),
|
|
138
|
+
value: fragment(`${alias}.value`),
|
|
139
|
+
instant: fragment(`${alias}.instant`),
|
|
140
|
+
},
|
|
344
141
|
join: joined([
|
|
345
142
|
fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
|
|
346
|
-
grouped(
|
|
143
|
+
grouped(sql.placed(alias, key.field, 'exact')),
|
|
347
144
|
fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
|
|
348
145
|
], ''),
|
|
349
146
|
};
|
|
350
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
|
+
}
|
|
351
170
|
const expression = key.of === 'room'
|
|
352
171
|
? located('d')
|
|
353
172
|
: key.of === 'path'
|
|
354
173
|
? fragment('d.path')
|
|
355
174
|
: key.of === 'type'
|
|
356
175
|
? fragment('d.type')
|
|
357
|
-
:
|
|
176
|
+
: key.of === 'subfolder'
|
|
177
|
+
? subfolderOf('d.folder', prefix)
|
|
178
|
+
: fragment('d.folder');
|
|
358
179
|
return {
|
|
359
180
|
term: {
|
|
360
181
|
ordering,
|
|
@@ -375,12 +196,20 @@ function decoded(kind, value) {
|
|
|
375
196
|
return JSON.parse(value);
|
|
376
197
|
return null;
|
|
377
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
|
+
}
|
|
378
205
|
function decodedKey(key, row, index) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
return
|
|
382
|
-
|
|
383
|
-
|
|
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');
|
|
384
213
|
}
|
|
385
214
|
const registered = new WeakMap();
|
|
386
215
|
function hashed(text) {
|
|
@@ -482,15 +311,23 @@ function sectionsOf(sections, names) {
|
|
|
482
311
|
return sections;
|
|
483
312
|
return Object.fromEntries(names.flatMap((name) => (Object.hasOwn(sections, name) ? [[name, sections[name]]] : [])));
|
|
484
313
|
}
|
|
485
|
-
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;
|
|
486
324
|
const path = row.path;
|
|
487
325
|
const type = row.type;
|
|
488
|
-
const fileId = row.file_id
|
|
489
|
-
const revision = row.revision
|
|
490
|
-
const content = row.content ?? null;
|
|
491
|
-
const unheld = Number(row.unheld) === 1 ? { textHeld: false } : {};
|
|
326
|
+
const fileId = row.file_id;
|
|
327
|
+
const revision = row.revision;
|
|
492
328
|
if (type === null) {
|
|
493
329
|
const binary = row.binary === null ? null : JSON.parse(row.binary);
|
|
330
|
+
const content = row.content ?? null;
|
|
494
331
|
return {
|
|
495
332
|
room,
|
|
496
333
|
path,
|
|
@@ -500,13 +337,15 @@ function documentOf(shape, room, row, linked) {
|
|
|
500
337
|
binaryFile: binaryRecord(path, binary, revision ?? undefined),
|
|
501
338
|
}),
|
|
502
339
|
...(shape.markdown && content !== null ? { markdown: content } : {}),
|
|
503
|
-
...
|
|
340
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
504
341
|
...(shape.fileId ? { fileId } : {}),
|
|
505
342
|
...(shape.revision ? { revision } : {}),
|
|
343
|
+
...heldFacts(shape, row),
|
|
506
344
|
};
|
|
507
345
|
}
|
|
508
|
-
const
|
|
509
|
-
|
|
346
|
+
const body = shape.sections !== null || shape.preamble
|
|
347
|
+
? freeParts(queried, path, type, row.content)
|
|
348
|
+
: null;
|
|
510
349
|
const fields = JSON.parse(row.fields ?? '{}');
|
|
511
350
|
return {
|
|
512
351
|
room,
|
|
@@ -523,16 +362,21 @@ function documentOf(shape, room, row, linked) {
|
|
|
523
362
|
? {}
|
|
524
363
|
: { sections: sectionsOf(body.sections, shape.sections) }),
|
|
525
364
|
...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
|
|
526
|
-
...(shape.markdown
|
|
527
|
-
...unheld,
|
|
365
|
+
...(shape.markdown ? { markdown: row.content } : {}),
|
|
528
366
|
...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
|
|
529
|
-
...(shape.change
|
|
530
|
-
? {
|
|
531
|
-
change: row.change === null ? null : JSON.parse(row.change),
|
|
532
|
-
}
|
|
533
|
-
: {}),
|
|
367
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
534
368
|
...(shape.fileId ? { fileId } : {}),
|
|
535
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 } : {}),
|
|
536
380
|
};
|
|
537
381
|
}
|
|
538
382
|
function backlinksOf(connection, bound, rows) {
|
|
@@ -544,8 +388,8 @@ function backlinksOf(connection, bound, rows) {
|
|
|
544
388
|
held.push(row.path);
|
|
545
389
|
byKey.set(key, held);
|
|
546
390
|
}
|
|
391
|
+
const reach = reached(bound, 's');
|
|
547
392
|
for (const [key, targets] of byKey) {
|
|
548
|
-
const reach = reached(bound, 's');
|
|
549
393
|
const room = bound.rooms.find((one) => one.key === key).locator;
|
|
550
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]);
|
|
551
395
|
for (const row of found) {
|
|
@@ -558,38 +402,36 @@ function backlinksOf(connection, bound, rows) {
|
|
|
558
402
|
return linked;
|
|
559
403
|
}
|
|
560
404
|
function documents(connection, bound, plan, shape, budget) {
|
|
561
|
-
const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(key, orderingOf(options), index));
|
|
405
|
+
const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(key, orderingOf(options), index, plan.root.pathPrefix));
|
|
562
406
|
const filter = filtered(bound, plan);
|
|
563
407
|
const cursor = plan.after?.values ?? null;
|
|
564
408
|
const room = located('d');
|
|
565
|
-
const
|
|
409
|
+
const after = cursor === null
|
|
566
410
|
? null
|
|
567
411
|
: (() => {
|
|
568
412
|
const key = cursor[shape.orderBy.length];
|
|
569
|
-
return any([
|
|
413
|
+
return sql.following(terms.map(({ term }, index) => sql.beyond(bound.collate, term, cursor[index])), any([
|
|
570
414
|
fragment(`${room.sql} > ?`, [...room.bind, roomOfKey(key)]),
|
|
571
415
|
fragment(`${room.sql} = ? AND d.path > ?`, [
|
|
572
416
|
...room.bind,
|
|
573
417
|
roomOfKey(key),
|
|
574
418
|
pathOfKey(key),
|
|
575
419
|
]),
|
|
576
|
-
]);
|
|
420
|
+
]));
|
|
577
421
|
})();
|
|
578
|
-
const after = cursor === null || tie === null
|
|
579
|
-
? null
|
|
580
|
-
: following(terms.map(({ term }, index) => beyond(bound, term, cursor[index])), tie);
|
|
581
422
|
const selected = joined([
|
|
582
|
-
fragment('SELECT d.id, d.key, d.path, d.type, d.binary, d.revision, d.fields, d.change, d.file_id, d.
|
|
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'),
|
|
583
424
|
...terms.map(({ term }, index) => joined([
|
|
584
425
|
fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
585
426
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
427
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
586
428
|
], '')),
|
|
587
429
|
fragment(' FROM replica_entries d'),
|
|
588
430
|
...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
|
|
589
431
|
fragment(' WHERE '),
|
|
590
432
|
after === null ? filter : all([filter, after]),
|
|
591
433
|
fragment(' ORDER BY '),
|
|
592
|
-
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')], ', '),
|
|
593
435
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
594
436
|
], '');
|
|
595
437
|
const rows = connection.rows(selected.sql, selected.bind);
|
|
@@ -597,8 +439,9 @@ function documents(connection, bound, plan, shape, budget) {
|
|
|
597
439
|
const kept = bodied(shape) ? withContent(connection, windowed, budget) : windowed;
|
|
598
440
|
const cut = kept.length;
|
|
599
441
|
const roomOf = new Map(bound.rooms.map(({ key, locator }) => [key, locator]));
|
|
442
|
+
const queried = new Map(bound.rooms.map((one) => [one.key, one]));
|
|
600
443
|
const linked = shape.backlinks ? backlinksOf(connection, bound, kept) : new Map();
|
|
601
|
-
const found = kept.map((row) => documentOf(shape,
|
|
444
|
+
const found = kept.map((row) => documentOf(shape, queried.get(Number(row.key)), row, linked));
|
|
602
445
|
const last = kept.at(-1);
|
|
603
446
|
const cursorAt = (row) => cursorOf([
|
|
604
447
|
...shape.orderBy.map(({ key }, index) => decodedKey(key, row, index)),
|
|
@@ -628,7 +471,7 @@ function withContent(connection, rows, budget) {
|
|
|
628
471
|
return rows;
|
|
629
472
|
const ids = rows.map((row) => row.id);
|
|
630
473
|
const sizes = new Map(connection
|
|
631
|
-
.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)
|
|
632
475
|
.map((row) => [row.id, Number(row.size ?? 0)]));
|
|
633
476
|
let cut = rows.length;
|
|
634
477
|
let spent = 0;
|
|
@@ -643,18 +486,22 @@ function withContent(connection, rows, budget) {
|
|
|
643
486
|
const kept = rows.slice(0, cut);
|
|
644
487
|
const read = kept.map((row) => row.id);
|
|
645
488
|
const held = new Map(connection
|
|
646
|
-
.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)
|
|
647
490
|
.map((row) => [row.id, row.content]));
|
|
648
491
|
return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
|
|
649
492
|
}
|
|
650
|
-
function storedFolders(bound, plan) {
|
|
651
|
-
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)
|
|
652
499
|
return null;
|
|
653
500
|
const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
|
|
654
501
|
if (whole.length === 0)
|
|
655
502
|
return null;
|
|
656
503
|
const keys = whole.map(({ key }) => key);
|
|
657
|
-
const prefix = plan.root.pathPrefix;
|
|
504
|
+
const prefix = plan.root.pathPrefix ?? '';
|
|
658
505
|
const outside = (bound.reach?.areas ?? []).flatMap((area) => {
|
|
659
506
|
const key = bound.keys.get(area.room);
|
|
660
507
|
if (key === undefined)
|
|
@@ -662,100 +509,190 @@ function storedFolders(bound, plan) {
|
|
|
662
509
|
const fence = fenced('f', key, area, true);
|
|
663
510
|
return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
|
|
664
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
|
+
];
|
|
665
521
|
return all([
|
|
666
|
-
fragment(`f.key IN ${
|
|
522
|
+
fragment(`f.key IN ${listed(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
|
|
667
523
|
...outside,
|
|
668
|
-
...
|
|
669
|
-
|
|
670
|
-
: [
|
|
671
|
-
fragment('(f.path = ? OR (f.path >= ? AND f.path < ?))', [
|
|
672
|
-
prefix,
|
|
673
|
-
`${prefix}/`,
|
|
674
|
-
`${prefix}0`,
|
|
675
|
-
]),
|
|
676
|
-
]),
|
|
524
|
+
...under,
|
|
525
|
+
...(plan.outside ?? []).map((folder) => fragment(`NOT ${within('f', folder).sql}`, within('f', folder).bind)),
|
|
677
526
|
...(plan.folder === null ? [] : [fragment('f.path = ?', [plan.folder])]),
|
|
678
527
|
]);
|
|
679
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
|
+
}
|
|
680
544
|
function groups(connection, bound, plan, shape) {
|
|
545
|
+
const prefix = plan.root.pathPrefix;
|
|
681
546
|
const keyed = shape.groupBy.map((key, index) => {
|
|
682
|
-
const { term, join } = keyTerm(key, GROUP_ORDER, index);
|
|
547
|
+
const { term, join } = keyTerm(key, GROUP_ORDER, index, prefix);
|
|
683
548
|
const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
|
|
684
549
|
return { term: { ...term, kind }, join };
|
|
685
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);
|
|
686
557
|
const filter = filtered(bound, plan);
|
|
687
558
|
const inner = joined([
|
|
688
559
|
fragment('SELECT '),
|
|
689
560
|
joined(keyed.map(({ term }, index) => joined([
|
|
690
561
|
fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
691
562
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
563
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
692
564
|
], '')), ', '),
|
|
693
|
-
fragment(', 1 AS n
|
|
694
|
-
...
|
|
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]),
|
|
695
575
|
fragment(' WHERE '),
|
|
696
576
|
filter,
|
|
697
577
|
], '');
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
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
|
+
];
|
|
701
587
|
const source = folders === null
|
|
702
588
|
? inner
|
|
703
589
|
: joined([
|
|
704
590
|
inner,
|
|
705
|
-
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),
|
|
706
592
|
folders,
|
|
707
593
|
], '');
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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,
|
|
716
617
|
kind: fragment(`k${index}`),
|
|
717
618
|
value: fragment(`v${index}`),
|
|
718
619
|
});
|
|
719
|
-
const
|
|
720
|
-
|
|
721
|
-
|
|
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 }));
|
|
722
652
|
const ties = keyed.flatMap((_, index) => [
|
|
723
653
|
{ ...outer(index, GROUP_TIES), total: true },
|
|
724
654
|
{ ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
|
|
725
655
|
]);
|
|
726
656
|
const cursor = plan.after?.values ?? null;
|
|
727
|
-
const
|
|
728
|
-
? null
|
|
729
|
-
: cursor[shape.orderBy.length].flatMap((value) => [
|
|
730
|
-
value,
|
|
731
|
-
kindOf(value ?? null),
|
|
732
|
-
]);
|
|
733
|
-
const after = cursor === null || tieValues === null
|
|
657
|
+
const after = cursor === null
|
|
734
658
|
? null
|
|
735
|
-
:
|
|
736
|
-
|
|
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}`)], '');
|
|
737
666
|
const windowed = joined([
|
|
738
667
|
fragment('SELECT * FROM ('),
|
|
739
668
|
grouping,
|
|
740
669
|
fragment(')'),
|
|
741
670
|
...(after === null ? [] : [fragment(' WHERE '), after]),
|
|
742
671
|
fragment(' ORDER BY '),
|
|
743
|
-
joined([...ordered, ...ties].map((term) => sorted(bound, term)), ', '),
|
|
672
|
+
joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
|
|
744
673
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
745
674
|
], '');
|
|
746
675
|
const rows = connection.rows(windowed.sql, windowed.bind);
|
|
747
676
|
const kept = plan.limit === null ? rows : rows.slice(0, plan.limit);
|
|
748
677
|
const keyOf = (row) => keyed.map((_, index) => decoded(row[`k${index}`], row[`v${index}`]));
|
|
749
|
-
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
|
+
}));
|
|
750
690
|
const last = kept.at(-1);
|
|
751
691
|
const next = plan.limit === null || rows.length <= plan.limit
|
|
752
692
|
? undefined
|
|
753
693
|
: last === undefined
|
|
754
694
|
? (plan.after?.text ?? FROM_START)
|
|
755
|
-
: cursorOf([
|
|
756
|
-
...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : decoded(last[`k${by}`], last[`v${by}`])),
|
|
757
|
-
keyOf(last),
|
|
758
|
-
]);
|
|
695
|
+
: cursorOf([...shape.orderBy.map(({ by }) => valueOf(last, by)), keyOf(last)]);
|
|
759
696
|
const total = plan.total
|
|
760
697
|
? Number(connection.rows(joined([fragment('SELECT count(*) AS held FROM ('), grouping, fragment(')')], '').sql, grouping.bind)[0].held)
|
|
761
698
|
: undefined;
|
|
@@ -773,9 +710,6 @@ function boundOf(connection, plan, rooms) {
|
|
|
773
710
|
collate: collating(connection),
|
|
774
711
|
};
|
|
775
712
|
}
|
|
776
|
-
export function selected(connection, plan, rooms) {
|
|
777
|
-
return filtered(boundOf(connection, plan, rooms), plan);
|
|
778
|
-
}
|
|
779
713
|
export function answerQuery(connection, plan, rooms, budget = null) {
|
|
780
714
|
const bound = boundOf(connection, plan, rooms);
|
|
781
715
|
if (rooms.length === 0) {
|