@agentshouse/mdruntime 0.1.0-c0ffc146f48dd → 0.1.0-c112fd663ab59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/acceptance.d.ts +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 +45 -25
- package/dist/adapter.d.ts.map +1 -1
- package/dist/attached-model.d.ts +3 -10
- package/dist/attached-model.d.ts.map +1 -1
- package/dist/attached-model.js +21 -56
- package/dist/bootstrap.d.ts +8 -4
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +210 -78
- 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 +22 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +7 -2
- package/dist/collection.d.ts +2 -1
- package/dist/collection.d.ts.map +1 -1
- package/dist/compile.d.ts +6 -6
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +240 -315
- package/dist/connection.d.ts +5 -142
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +103 -5071
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +7 -0
- package/dist/conversation-live.d.ts +136 -0
- package/dist/conversation-live.d.ts.map +1 -0
- package/dist/conversation-live.js +248 -0
- package/dist/conversation-row.d.ts +33 -0
- package/dist/conversation-row.d.ts.map +1 -0
- package/dist/conversation-row.js +53 -0
- package/dist/conversation.d.ts +34 -38
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +861 -850
- 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 -0
- package/dist/durability.d.ts.map +1 -1
- package/dist/entry.d.ts +2 -2
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +17 -7
- package/dist/expectation.d.ts +4 -2
- package/dist/expectation.d.ts.map +1 -1
- package/dist/expectation.js +33 -11
- package/dist/failure.d.ts +1 -0
- package/dist/failure.d.ts.map +1 -1
- package/dist/failure.js +3 -0
- package/dist/figures.d.ts +49 -0
- package/dist/figures.d.ts.map +1 -0
- package/dist/figures.js +197 -0
- package/dist/holdings.d.ts +29 -0
- package/dist/holdings.d.ts.map +1 -0
- package/dist/holdings.js +95 -0
- package/dist/host.d.ts +4 -5
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +23 -22
- 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 +25 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- 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 +33 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +54 -0
- package/dist/lifecycle.d.ts +7 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +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 +1 -1
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js +3 -3
- package/dist/memory.d.ts +5 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +50 -10
- package/dist/messages.d.ts +27 -3
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +103 -31
- package/dist/model.d.ts +2 -9
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +0 -9
- 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 +2 -2
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +14 -14
- package/dist/opened.js +3 -3
- package/dist/opening.d.ts +4 -0
- package/dist/opening.d.ts.map +1 -0
- package/dist/opening.js +60 -0
- package/dist/outcome.d.ts +4 -1
- package/dist/outcome.d.ts.map +1 -1
- package/dist/outcome.js +3 -0
- package/dist/overlay.d.ts +1 -0
- package/dist/overlay.d.ts.map +1 -1
- package/dist/overlay.js +11 -0
- package/dist/parts.d.ts +60 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +126 -0
- package/dist/paths.d.ts +1 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +22 -0
- package/dist/preparation.d.ts +20 -0
- package/dist/preparation.d.ts.map +1 -0
- package/dist/preparation.js +233 -0
- package/dist/queries.d.ts +16 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/queries.js +272 -0
- package/dist/query.d.ts +13 -209
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -505
- package/dist/questions.d.ts +34 -0
- package/dist/questions.d.ts.map +1 -0
- package/dist/questions.js +122 -0
- package/dist/reach.d.ts +14 -12
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +167 -114
- package/dist/react.d.ts +13 -2
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +10 -1
- package/dist/read.d.ts +7 -0
- package/dist/read.d.ts.map +1 -0
- package/dist/read.js +17 -0
- package/dist/readiness.d.ts +0 -3
- package/dist/readiness.d.ts.map +1 -1
- package/dist/reads.d.ts +37 -0
- package/dist/reads.d.ts.map +1 -0
- package/dist/reads.js +174 -0
- package/dist/recovery.d.ts.map +1 -1
- package/dist/recovery.js +9 -1
- package/dist/refresh.d.ts +1 -1
- package/dist/refresh.d.ts.map +1 -1
- package/dist/refresh.js +7 -0
- 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 +9 -8
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +53 -17
- package/dist/room-draft.d.ts +3 -4
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +7 -7
- package/dist/room-drafts.d.ts +24 -0
- package/dist/room-drafts.d.ts.map +1 -0
- package/dist/room-drafts.js +217 -0
- package/dist/rows.d.ts +14 -21
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +48 -184
- package/dist/runtime.d.ts +43 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1 -0
- package/dist/save.d.ts +5 -1
- package/dist/save.d.ts.map +1 -1
- package/dist/save.js +34 -21
- 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 -6
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +13 -95
- 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 +0 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +0 -6
- package/dist/store/fences.d.ts +16 -0
- package/dist/store/fences.d.ts.map +1 -0
- package/dist/store/fences.js +26 -0
- package/dist/store/installs.d.ts +30 -0
- package/dist/store/installs.d.ts.map +1 -0
- package/dist/store/installs.js +34 -0
- package/dist/store/readiness.d.ts +10 -0
- package/dist/store/readiness.d.ts.map +1 -0
- package/dist/store/readiness.js +52 -0
- package/dist/store/records.d.ts +212 -0
- package/dist/store/records.d.ts.map +1 -0
- package/dist/store/records.js +431 -0
- package/dist/store/room-held.d.ts +8 -0
- package/dist/store/room-held.d.ts.map +1 -0
- package/dist/store/room-held.js +17 -0
- package/dist/store/space.d.ts +11 -0
- package/dist/store/space.d.ts.map +1 -0
- package/dist/store/space.js +41 -0
- package/dist/store/watchers.d.ts +17 -0
- package/dist/store/watchers.d.ts.map +1 -0
- package/dist/store/watchers.js +56 -0
- package/dist/store/writes.d.ts +24 -0
- package/dist/store/writes.d.ts.map +1 -0
- package/dist/store/writes.js +125 -0
- package/dist/store.d.ts +107 -145
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +143 -366
- package/dist/stored.d.ts +59 -33
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +278 -189
- 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 +120 -29
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +216 -54
- package/dist/tallies.d.ts +18 -0
- package/dist/tallies.d.ts.map +1 -0
- package/dist/tallies.js +129 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +7 -4
- 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 +16 -1
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +6 -0
- package/package.json +2 -2
- package/dist/fence.d.ts +0 -6
- package/dist/fence.d.ts.map +0 -1
- package/dist/fence.js +0 -10
- package/dist/fulltext.d.ts +0 -27
- package/dist/fulltext.d.ts.map +0 -1
- package/dist/fulltext.js +0 -370
- package/dist/words.d.ts +0 -19
- package/dist/words.d.ts.map +0 -1
- package/dist/words.js +0 -156
package/dist/compile.js
CHANGED
|
@@ -1,127 +1,13 @@
|
|
|
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
|
-
sql: parts.map((part) => part.sql).join(separator),
|
|
12
|
-
bind: parts.flatMap((part) => part.bind),
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
function balanced(parts, separator) {
|
|
16
|
-
if (parts.length === 1)
|
|
17
|
-
return grouped(parts[0]);
|
|
18
|
-
const half = Math.ceil(parts.length / 2);
|
|
19
|
-
return grouped(joined([balanced(parts.slice(0, half), separator), balanced(parts.slice(half), separator)], separator));
|
|
20
|
-
}
|
|
21
|
-
function all(parts) {
|
|
22
|
-
return parts.length === 0 ? fragment('1') : balanced(parts, ' AND ');
|
|
23
|
-
}
|
|
24
|
-
function any(parts) {
|
|
25
|
-
return parts.length === 0 ? fragment('0') : balanced(parts, ' OR ');
|
|
26
|
-
}
|
|
27
|
-
function grouped(part) {
|
|
28
|
-
return { sql: `(${part.sql})`, bind: part.bind };
|
|
29
|
-
}
|
|
30
|
-
function list(values) {
|
|
31
|
-
return fragment(`(${values.map(() => '?').join(', ')})`, values);
|
|
32
|
-
}
|
|
33
|
-
function sqlValue(value) {
|
|
34
|
-
if (value === null)
|
|
35
|
-
return { kind: 'null', value: null };
|
|
36
|
-
if (typeof value === 'boolean')
|
|
37
|
-
return { kind: 'boolean', value: value ? 1 : 0 };
|
|
38
|
-
if (typeof value === 'number')
|
|
39
|
-
return { kind: 'number', value };
|
|
40
|
-
return { kind: 'string', value };
|
|
41
|
-
}
|
|
42
|
-
function same(alias, value) {
|
|
43
|
-
const held = sqlValue(value);
|
|
44
|
-
return held.kind === 'null'
|
|
45
|
-
? fragment(`${alias}.kind = 'null'`)
|
|
46
|
-
: fragment(`${alias}.kind = ? AND ${alias}.value = ?`, [held.kind, held.value]);
|
|
47
|
-
}
|
|
48
|
-
const BOUNDS = { gt: '>', gte: '>=', lt: '<', lte: '<=' };
|
|
49
|
-
function pointed(alias, field, reach) {
|
|
50
|
-
const { keys, places, glob } = placedOf(field, reach);
|
|
51
|
-
return fragment(`${alias}.keys = ? AND ${alias}.places ${glob ? 'GLOB' : '='} ?`, [keys, places]);
|
|
52
|
-
}
|
|
53
|
-
function heldValues(doc, field, alias, where) {
|
|
54
|
-
return joined([
|
|
55
|
-
fragment(`SELECT 1 FROM replica_values ${alias} WHERE ${alias}.doc = ${doc}.id AND `),
|
|
56
|
-
grouped(pointed(alias, field, 'value')),
|
|
57
|
-
fragment(' AND '),
|
|
58
|
-
grouped(where),
|
|
59
|
-
], '');
|
|
60
|
-
}
|
|
61
|
-
function exists(part) {
|
|
62
|
-
return { sql: `EXISTS (${part.sql})`, bind: part.bind };
|
|
63
|
-
}
|
|
64
|
-
function itemOf(held, where) {
|
|
65
|
-
return exists(joined([
|
|
66
|
-
fragment(`SELECT 1 FROM replica_values i WHERE i.doc = ${held}.doc AND i.keys = ${held}.keys AND i.places = ${held}.places AND i.item >= 0 AND `),
|
|
67
|
-
grouped(where),
|
|
68
|
-
], ''));
|
|
69
|
-
}
|
|
70
|
-
function matching(held, values) {
|
|
71
|
-
return any(values.map((value) => any([
|
|
72
|
-
same(held, value),
|
|
73
|
-
all([fragment(`${held}.kind = 'array'`), itemOf(held, same('i', value))]),
|
|
74
|
-
])));
|
|
75
|
-
}
|
|
76
|
-
function tested(doc, field, test) {
|
|
77
|
-
const reached = exists(heldValues(doc, field, 'r', fragment('r.item = -1')));
|
|
78
|
-
if (test.op === 'exists')
|
|
79
|
-
return test.exists ? reached : fragment(`NOT ${reached.sql}`, reached.bind);
|
|
80
|
-
if (test.op === 'eq' || test.op === 'in') {
|
|
81
|
-
const values = test.op === 'eq' ? [test.value] : test.values;
|
|
82
|
-
return exists(heldValues(doc, field, 'v', any(values.map((value) => same('v', value)))));
|
|
83
|
-
}
|
|
84
|
-
if (test.op === 'ne' || test.op === 'nin') {
|
|
85
|
-
const values = test.op === 'ne' ? [test.value] : test.values;
|
|
86
|
-
const unmatched = matching('h', values);
|
|
87
|
-
return any([
|
|
88
|
-
fragment(`NOT ${reached.sql}`, reached.bind),
|
|
89
|
-
exists(heldValues(doc, field, 'h', all([fragment('h.item = -1'), fragment(`NOT (${unmatched.sql})`, unmatched.bind)]))),
|
|
90
|
-
]);
|
|
91
|
-
}
|
|
92
|
-
if (test.op === 'range') {
|
|
93
|
-
return exists(heldValues(doc, field, 'v', all([
|
|
94
|
-
fragment('v.item = -1'),
|
|
95
|
-
...test.bounds.map(([operator, bound]) => {
|
|
96
|
-
const held = sqlValue(bound);
|
|
97
|
-
return fragment(`v.kind = ? AND v.value ${BOUNDS[operator]} ?`, [
|
|
98
|
-
held.kind,
|
|
99
|
-
held.value,
|
|
100
|
-
]);
|
|
101
|
-
}),
|
|
102
|
-
])));
|
|
103
|
-
}
|
|
104
|
-
if (test.op === 'all') {
|
|
105
|
-
return exists(heldValues(doc, field, 'h', all([
|
|
106
|
-
fragment("h.item = -1 AND h.kind = 'array'"),
|
|
107
|
-
...test.values.map((value) => itemOf('h', same('i', value))),
|
|
108
|
-
])));
|
|
109
|
-
}
|
|
110
|
-
return exists(heldValues(doc, field, 'v', fragment("v.item = -1 AND v.kind = 'array' AND v.size = ?", [test.size])));
|
|
111
|
-
}
|
|
112
|
-
function conditioned(doc, condition) {
|
|
113
|
-
if ('every' in condition)
|
|
114
|
-
return all(condition.every.map((one) => conditioned(doc, one)));
|
|
115
|
-
if ('some' in condition)
|
|
116
|
-
return any(condition.some.map((one) => conditioned(doc, one)));
|
|
117
|
-
return tested(doc, condition.field, condition.test);
|
|
118
|
-
}
|
|
119
|
-
function located(bound, alias) {
|
|
120
|
-
if (bound.rooms.length === 0)
|
|
121
|
-
return fragment("''");
|
|
122
|
-
return fragment(`CASE ${alias}.key ${bound.rooms.map(() => 'WHEN ? THEN ?').join(' ')} END`, bound.rooms.flatMap(({ key, locator }) => [key, locator]));
|
|
123
|
-
}
|
|
124
|
-
function within(alias, path) {
|
|
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) {
|
|
125
11
|
return fragment(`(${alias}.path = ? OR (${alias}.path >= ? AND ${alias}.path < ?))`, [
|
|
126
12
|
path,
|
|
127
13
|
`${path}/`,
|
|
@@ -164,7 +50,7 @@ function reached(bound, alias) {
|
|
|
164
50
|
}));
|
|
165
51
|
return all([
|
|
166
52
|
any([
|
|
167
|
-
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${
|
|
53
|
+
...(rooms.length === 0 ? [] : [fragment(`${alias}.key IN ${listed(rooms).sql}`, rooms)]),
|
|
168
54
|
...(documents.length === 0
|
|
169
55
|
? []
|
|
170
56
|
: [
|
|
@@ -174,40 +60,22 @@ function reached(bound, alias) {
|
|
|
174
60
|
...(reach.areas.length === 0 ? [] : [fragment(`NOT ${grouped(outside).sql}`, outside.bind)]),
|
|
175
61
|
]);
|
|
176
62
|
}
|
|
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
63
|
function related(bound, plan) {
|
|
196
64
|
const parts = [];
|
|
197
65
|
if (plan.hop !== null) {
|
|
198
66
|
parts.push(exists(joined([
|
|
199
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`),
|
|
200
|
-
grouped(
|
|
68
|
+
grouped(sql.placed('l', plan.hop.field, 'link')),
|
|
201
69
|
fragment('AND'),
|
|
202
|
-
grouped(all([scoped('m', plan.hop), reached(bound, 'm')])),
|
|
70
|
+
grouped(all([sql.scoped('m', plan.hop), reached(bound, 'm')])),
|
|
203
71
|
], ' ')));
|
|
204
72
|
}
|
|
205
73
|
if (plan.backlinks !== null) {
|
|
206
74
|
const field = plan.backlinks.field;
|
|
207
75
|
parts.push(exists(joined([
|
|
208
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`),
|
|
209
|
-
...(field === null ? [] : [grouped(
|
|
210
|
-
grouped(all([scoped('s', plan.backlinks), reached(bound, 's')])),
|
|
77
|
+
...(field === null ? [] : [grouped(sql.placed('l', field, 'link')), fragment('AND')]),
|
|
78
|
+
grouped(all([sql.scoped('s', plan.backlinks), reached(bound, 's')])),
|
|
211
79
|
], ' ')));
|
|
212
80
|
}
|
|
213
81
|
return parts;
|
|
@@ -215,11 +83,9 @@ function related(bound, plan) {
|
|
|
215
83
|
function filtered(bound, plan) {
|
|
216
84
|
const keys = bound.rooms.map(({ key }) => key);
|
|
217
85
|
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])]),
|
|
86
|
+
fragment(`d.key IN ${listed(keys).sql} AND ${VISIBLE('d')}`, keys),
|
|
221
87
|
reached(bound, 'd'),
|
|
222
|
-
|
|
88
|
+
sql.tests('d', plan),
|
|
223
89
|
...related(bound, plan),
|
|
224
90
|
]);
|
|
225
91
|
}
|
|
@@ -250,108 +116,65 @@ const GROUP_TIES = {
|
|
|
250
116
|
locale: undefined,
|
|
251
117
|
localeOptions: undefined,
|
|
252
118
|
};
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return { placement: 1, klass: 3, scalar: JSON.stringify(value) };
|
|
264
|
-
}
|
|
265
|
-
function placement(term) {
|
|
266
|
-
const nulls = term.ordering.nulls === 'first' ? 0 : 2;
|
|
267
|
-
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]);
|
|
268
|
-
}
|
|
269
|
-
function klass(term) {
|
|
270
|
-
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);
|
|
271
|
-
}
|
|
272
|
-
function classed(term, which) {
|
|
273
|
-
const kinds = which === 1 ? "('boolean', 'number')" : which === 2 ? "('string')" : "('array')";
|
|
274
|
-
return fragment(`(CASE WHEN ${term.kind.sql} IN ${kinds} THEN ${term.value.sql} END)`, [
|
|
275
|
-
...term.kind.bind,
|
|
276
|
-
...term.value.bind,
|
|
277
|
-
]);
|
|
278
|
-
}
|
|
279
|
-
function collated(bound, term, which) {
|
|
280
|
-
if (which === 1)
|
|
281
|
-
return '';
|
|
282
|
-
const name = bound.collate(term.ordering, which === 3, term.total === true);
|
|
283
|
-
return name === null ? '' : ` COLLATE ${name}`;
|
|
284
|
-
}
|
|
285
|
-
function sorted(bound, term) {
|
|
286
|
-
const direction = term.ordering.direction === 'desc' ? 'DESC' : 'ASC';
|
|
287
|
-
return joined([
|
|
288
|
-
placement(term),
|
|
289
|
-
fragment(`${klass(term).sql} ${direction}`, klass(term).bind),
|
|
290
|
-
...[1, 2, 3].map((which) => {
|
|
291
|
-
const value = classed(term, which);
|
|
292
|
-
return fragment(`${value.sql}${collated(bound, term, which)} ${direction}`, value.bind);
|
|
293
|
-
}),
|
|
294
|
-
], ', ');
|
|
295
|
-
}
|
|
296
|
-
function beyond(bound, term, value) {
|
|
297
|
-
const held = classOf(value, term.ordering);
|
|
298
|
-
const place = placement(term);
|
|
299
|
-
if (held.placement !== 1) {
|
|
300
|
-
return {
|
|
301
|
-
beyond: fragment(`${place.sql} > ${held.placement}`, place.bind),
|
|
302
|
-
equal: fragment(`${place.sql} = ${held.placement}`, place.bind),
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
const after = term.ordering.direction === 'desc' ? '<' : '>';
|
|
306
|
-
const kind = klass(term);
|
|
307
|
-
const scalar = classed(term, held.klass);
|
|
308
|
-
const collation = collated(bound, term, held.klass);
|
|
309
|
-
return {
|
|
310
|
-
beyond: any([
|
|
311
|
-
fragment(`${place.sql} > 1`, place.bind),
|
|
312
|
-
all([
|
|
313
|
-
fragment(`${place.sql} = 1`, place.bind),
|
|
314
|
-
any([
|
|
315
|
-
fragment(`${kind.sql} ${after} ${held.klass}`, kind.bind),
|
|
316
|
-
all([
|
|
317
|
-
fragment(`${kind.sql} = ${held.klass}`, kind.bind),
|
|
318
|
-
fragment(`${scalar.sql} ${after} ?${collation}`, [...scalar.bind, held.scalar]),
|
|
319
|
-
]),
|
|
320
|
-
]),
|
|
321
|
-
]),
|
|
322
|
-
]),
|
|
323
|
-
equal: all([
|
|
324
|
-
fragment(`${place.sql} = 1 AND ${kind.sql} = ${held.klass}`, [...place.bind, ...kind.bind]),
|
|
325
|
-
fragment(`${scalar.sql} = ?${collation}`, [...scalar.bind, held.scalar]),
|
|
326
|
-
]),
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
function following(parts, tie) {
|
|
330
|
-
let condition = tie;
|
|
331
|
-
for (const part of [...parts].reverse()) {
|
|
332
|
-
condition = any([part.beyond, all([part.equal, condition])]);
|
|
333
|
-
}
|
|
334
|
-
return condition;
|
|
119
|
+
const GREATEST = { ...GROUP_ORDER, direction: 'desc', nulls: 'last' };
|
|
120
|
+
const TIMES = {
|
|
121
|
+
created: { value: 'd.created', instant: 'd.created_instant' },
|
|
122
|
+
updated: { value: "(d.change ->> '$.time')", instant: 'd.changed_instant' },
|
|
123
|
+
};
|
|
124
|
+
function subfolderOf(column, prefix) {
|
|
125
|
+
const root = prefix ?? '';
|
|
126
|
+
const base = root === '' ? '' : `${root}/`;
|
|
127
|
+
const rest = `substr(${column}, length(?) + 1)`;
|
|
128
|
+
return fragment(`(CASE WHEN ${column} = ? THEN NULL WHEN instr(${rest}, '/') = 0 THEN ${column} ELSE ? || substr(${rest}, 1, instr(${rest}, '/') - 1) END)`, [root, base, base, base, base]);
|
|
335
129
|
}
|
|
336
|
-
function keyTerm(
|
|
130
|
+
function keyTerm(key, ordering, index, prefix) {
|
|
337
131
|
if (key.of === 'field') {
|
|
338
132
|
const alias = `o${index}`;
|
|
339
133
|
return {
|
|
340
|
-
term: {
|
|
134
|
+
term: {
|
|
135
|
+
ordering,
|
|
136
|
+
kind: fragment(`${alias}.kind`),
|
|
137
|
+
value: fragment(`${alias}.value`),
|
|
138
|
+
instant: fragment(`${alias}.instant`),
|
|
139
|
+
},
|
|
341
140
|
join: joined([
|
|
342
141
|
fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
|
|
343
|
-
grouped(
|
|
142
|
+
grouped(sql.placed(alias, key.field, 'exact')),
|
|
344
143
|
fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
|
|
345
144
|
], ''),
|
|
346
145
|
};
|
|
347
146
|
}
|
|
147
|
+
if (key.of === 'size') {
|
|
148
|
+
return {
|
|
149
|
+
term: {
|
|
150
|
+
ordering,
|
|
151
|
+
kind: fragment("(CASE WHEN d.bytes IS NULL THEN 'null' ELSE 'number' END)"),
|
|
152
|
+
value: fragment('d.bytes'),
|
|
153
|
+
},
|
|
154
|
+
join: null,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (key.of === 'created' || key.of === 'updated') {
|
|
158
|
+
const { value, instant } = TIMES[key.of];
|
|
159
|
+
return {
|
|
160
|
+
term: {
|
|
161
|
+
ordering,
|
|
162
|
+
kind: fragment(`(CASE WHEN ${value} IS NULL THEN 'null' ELSE 'string' END)`),
|
|
163
|
+
value: fragment(value),
|
|
164
|
+
instant: fragment(instant),
|
|
165
|
+
},
|
|
166
|
+
join: null,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
348
169
|
const expression = key.of === 'room'
|
|
349
|
-
? located(
|
|
170
|
+
? located('d')
|
|
350
171
|
: key.of === 'path'
|
|
351
172
|
? fragment('d.path')
|
|
352
173
|
: key.of === 'type'
|
|
353
174
|
? fragment('d.type')
|
|
354
|
-
:
|
|
175
|
+
: key.of === 'subfolder'
|
|
176
|
+
? subfolderOf('d.folder', prefix)
|
|
177
|
+
: fragment('d.folder');
|
|
355
178
|
return {
|
|
356
179
|
term: {
|
|
357
180
|
ordering,
|
|
@@ -372,12 +195,20 @@ function decoded(kind, value) {
|
|
|
372
195
|
return JSON.parse(value);
|
|
373
196
|
return null;
|
|
374
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
|
+
}
|
|
375
204
|
function decodedKey(key, row, index) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
return
|
|
379
|
-
|
|
380
|
-
|
|
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');
|
|
381
212
|
}
|
|
382
213
|
const registered = new WeakMap();
|
|
383
214
|
function hashed(text) {
|
|
@@ -428,9 +259,6 @@ function collating(connection) {
|
|
|
428
259
|
return name;
|
|
429
260
|
};
|
|
430
261
|
}
|
|
431
|
-
function bodied(shape) {
|
|
432
|
-
return shape.sections !== null || shape.preamble || shape.markdown;
|
|
433
|
-
}
|
|
434
262
|
function masked(pointers) {
|
|
435
263
|
const root = { whole: false, members: new Map() };
|
|
436
264
|
for (const pointer of pointers) {
|
|
@@ -485,8 +313,8 @@ function sectionsOf(sections, names) {
|
|
|
485
313
|
function documentOf(shape, room, row, linked) {
|
|
486
314
|
const path = row.path;
|
|
487
315
|
const type = row.type;
|
|
488
|
-
const fileId = row.file_id
|
|
489
|
-
const revision = row.revision
|
|
316
|
+
const fileId = row.file_id;
|
|
317
|
+
const revision = row.revision;
|
|
490
318
|
if (type === null) {
|
|
491
319
|
const binary = row.binary === null ? null : JSON.parse(row.binary);
|
|
492
320
|
const content = row.content ?? null;
|
|
@@ -499,11 +327,13 @@ function documentOf(shape, room, row, linked) {
|
|
|
499
327
|
binaryFile: binaryRecord(path, binary, revision ?? undefined),
|
|
500
328
|
}),
|
|
501
329
|
...(shape.markdown && content !== null ? { markdown: content } : {}),
|
|
330
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
502
331
|
...(shape.fileId ? { fileId } : {}),
|
|
503
332
|
...(shape.revision ? { revision } : {}),
|
|
333
|
+
...heldFacts(shape, row),
|
|
504
334
|
};
|
|
505
335
|
}
|
|
506
|
-
const read =
|
|
336
|
+
const read = shape.sections !== null || shape.preamble ? parse(row.content, path) : null;
|
|
507
337
|
const body = read?.ok === true ? read.document : null;
|
|
508
338
|
const fields = JSON.parse(row.fields ?? '{}');
|
|
509
339
|
return {
|
|
@@ -523,13 +353,19 @@ function documentOf(shape, room, row, linked) {
|
|
|
523
353
|
...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
|
|
524
354
|
...(shape.markdown ? { markdown: row.content } : {}),
|
|
525
355
|
...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
|
|
526
|
-
...(shape.change
|
|
527
|
-
? {
|
|
528
|
-
change: row.change === null ? null : JSON.parse(row.change),
|
|
529
|
-
}
|
|
530
|
-
: {}),
|
|
356
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
531
357
|
...(shape.fileId ? { fileId } : {}),
|
|
532
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 } : {}),
|
|
533
369
|
};
|
|
534
370
|
}
|
|
535
371
|
function backlinksOf(connection, bound, rows) {
|
|
@@ -541,8 +377,8 @@ function backlinksOf(connection, bound, rows) {
|
|
|
541
377
|
held.push(row.path);
|
|
542
378
|
byKey.set(key, held);
|
|
543
379
|
}
|
|
380
|
+
const reach = reached(bound, 's');
|
|
544
381
|
for (const [key, targets] of byKey) {
|
|
545
|
-
const reach = reached(bound, 's');
|
|
546
382
|
const room = bound.rooms.find((one) => one.key === key).locator;
|
|
547
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]);
|
|
548
384
|
for (const row of found) {
|
|
@@ -555,38 +391,36 @@ function backlinksOf(connection, bound, rows) {
|
|
|
555
391
|
return linked;
|
|
556
392
|
}
|
|
557
393
|
function documents(connection, bound, plan, shape, budget) {
|
|
558
|
-
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));
|
|
559
395
|
const filter = filtered(bound, plan);
|
|
560
396
|
const cursor = plan.after?.values ?? null;
|
|
561
|
-
const room = located(
|
|
562
|
-
const
|
|
397
|
+
const room = located('d');
|
|
398
|
+
const after = cursor === null
|
|
563
399
|
? null
|
|
564
400
|
: (() => {
|
|
565
401
|
const key = cursor[shape.orderBy.length];
|
|
566
|
-
return any([
|
|
402
|
+
return sql.following(terms.map(({ term }, index) => sql.beyond(bound.collate, term, cursor[index])), any([
|
|
567
403
|
fragment(`${room.sql} > ?`, [...room.bind, roomOfKey(key)]),
|
|
568
404
|
fragment(`${room.sql} = ? AND d.path > ?`, [
|
|
569
405
|
...room.bind,
|
|
570
406
|
roomOfKey(key),
|
|
571
407
|
pathOfKey(key),
|
|
572
408
|
]),
|
|
573
|
-
]);
|
|
409
|
+
]));
|
|
574
410
|
})();
|
|
575
|
-
const after = cursor === null || tie === null
|
|
576
|
-
? null
|
|
577
|
-
: following(terms.map(({ term }, index) => beyond(bound, term, cursor[index])), tie);
|
|
578
411
|
const selected = joined([
|
|
579
|
-
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'),
|
|
580
413
|
...terms.map(({ term }, index) => joined([
|
|
581
414
|
fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
582
415
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
416
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
583
417
|
], '')),
|
|
584
418
|
fragment(' FROM replica_entries d'),
|
|
585
419
|
...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
|
|
586
420
|
fragment(' WHERE '),
|
|
587
421
|
after === null ? filter : all([filter, after]),
|
|
588
422
|
fragment(' ORDER BY '),
|
|
589
|
-
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')], ', '),
|
|
590
424
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
591
425
|
], '');
|
|
592
426
|
const rows = connection.rows(selected.sql, selected.bind);
|
|
@@ -625,7 +459,7 @@ function withContent(connection, rows, budget) {
|
|
|
625
459
|
return rows;
|
|
626
460
|
const ids = rows.map((row) => row.id);
|
|
627
461
|
const sizes = new Map(connection
|
|
628
|
-
.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)
|
|
629
463
|
.map((row) => [row.id, Number(row.size ?? 0)]));
|
|
630
464
|
let cut = rows.length;
|
|
631
465
|
let spent = 0;
|
|
@@ -640,18 +474,22 @@ function withContent(connection, rows, budget) {
|
|
|
640
474
|
const kept = rows.slice(0, cut);
|
|
641
475
|
const read = kept.map((row) => row.id);
|
|
642
476
|
const held = new Map(connection
|
|
643
|
-
.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)
|
|
644
478
|
.map((row) => [row.id, row.content]));
|
|
645
479
|
return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
|
|
646
480
|
}
|
|
647
|
-
function storedFolders(bound, plan) {
|
|
648
|
-
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)
|
|
649
487
|
return null;
|
|
650
488
|
const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
|
|
651
489
|
if (whole.length === 0)
|
|
652
490
|
return null;
|
|
653
491
|
const keys = whole.map(({ key }) => key);
|
|
654
|
-
const prefix = plan.root.pathPrefix;
|
|
492
|
+
const prefix = plan.root.pathPrefix ?? '';
|
|
655
493
|
const outside = (bound.reach?.areas ?? []).flatMap((area) => {
|
|
656
494
|
const key = bound.keys.get(area.room);
|
|
657
495
|
if (key === undefined)
|
|
@@ -659,100 +497,190 @@ function storedFolders(bound, plan) {
|
|
|
659
497
|
const fence = fenced('f', key, area, true);
|
|
660
498
|
return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
|
|
661
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
|
+
];
|
|
662
509
|
return all([
|
|
663
|
-
fragment(`f.key IN ${
|
|
510
|
+
fragment(`f.key IN ${listed(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
|
|
664
511
|
...outside,
|
|
665
|
-
...
|
|
666
|
-
|
|
667
|
-
: [
|
|
668
|
-
fragment('(f.path = ? OR (f.path >= ? AND f.path < ?))', [
|
|
669
|
-
prefix,
|
|
670
|
-
`${prefix}/`,
|
|
671
|
-
`${prefix}0`,
|
|
672
|
-
]),
|
|
673
|
-
]),
|
|
512
|
+
...under,
|
|
513
|
+
...(plan.outside ?? []).map((folder) => fragment(`NOT ${within('f', folder).sql}`, within('f', folder).bind)),
|
|
674
514
|
...(plan.folder === null ? [] : [fragment('f.path = ?', [plan.folder])]),
|
|
675
515
|
]);
|
|
676
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
|
+
}
|
|
677
532
|
function groups(connection, bound, plan, shape) {
|
|
533
|
+
const prefix = plan.root.pathPrefix;
|
|
678
534
|
const keyed = shape.groupBy.map((key, index) => {
|
|
679
|
-
const { term, join } = keyTerm(
|
|
535
|
+
const { term, join } = keyTerm(key, GROUP_ORDER, index, prefix);
|
|
680
536
|
const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
|
|
681
537
|
return { term: { ...term, kind }, join };
|
|
682
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);
|
|
683
545
|
const filter = filtered(bound, plan);
|
|
684
546
|
const inner = joined([
|
|
685
547
|
fragment('SELECT '),
|
|
686
548
|
joined(keyed.map(({ term }, index) => joined([
|
|
687
549
|
fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
688
550
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
551
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
689
552
|
], '')), ', '),
|
|
690
|
-
fragment(', 1 AS n
|
|
691
|
-
...
|
|
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]),
|
|
692
563
|
fragment(' WHERE '),
|
|
693
564
|
filter,
|
|
694
565
|
], '');
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
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
|
+
];
|
|
698
575
|
const source = folders === null
|
|
699
576
|
? inner
|
|
700
577
|
: joined([
|
|
701
578
|
inner,
|
|
702
|
-
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),
|
|
703
580
|
folders,
|
|
704
581
|
], '');
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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,
|
|
713
605
|
kind: fragment(`k${index}`),
|
|
714
606
|
value: fragment(`v${index}`),
|
|
715
607
|
});
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
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 }));
|
|
719
640
|
const ties = keyed.flatMap((_, index) => [
|
|
720
641
|
{ ...outer(index, GROUP_TIES), total: true },
|
|
721
642
|
{ ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
|
|
722
643
|
]);
|
|
723
644
|
const cursor = plan.after?.values ?? null;
|
|
724
|
-
const
|
|
725
|
-
? null
|
|
726
|
-
: cursor[shape.orderBy.length].flatMap((value) => [
|
|
727
|
-
value,
|
|
728
|
-
kindOf(value ?? null),
|
|
729
|
-
]);
|
|
730
|
-
const after = cursor === null || tieValues === null
|
|
645
|
+
const after = cursor === null
|
|
731
646
|
? null
|
|
732
|
-
:
|
|
733
|
-
|
|
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}`)], '');
|
|
734
654
|
const windowed = joined([
|
|
735
655
|
fragment('SELECT * FROM ('),
|
|
736
656
|
grouping,
|
|
737
657
|
fragment(')'),
|
|
738
658
|
...(after === null ? [] : [fragment(' WHERE '), after]),
|
|
739
659
|
fragment(' ORDER BY '),
|
|
740
|
-
joined([...ordered, ...ties].map((term) => sorted(bound, term)), ', '),
|
|
660
|
+
joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
|
|
741
661
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
742
662
|
], '');
|
|
743
663
|
const rows = connection.rows(windowed.sql, windowed.bind);
|
|
744
664
|
const kept = plan.limit === null ? rows : rows.slice(0, plan.limit);
|
|
745
665
|
const keyOf = (row) => keyed.map((_, index) => decoded(row[`k${index}`], row[`v${index}`]));
|
|
746
|
-
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
|
+
}));
|
|
747
678
|
const last = kept.at(-1);
|
|
748
679
|
const next = plan.limit === null || rows.length <= plan.limit
|
|
749
680
|
? undefined
|
|
750
681
|
: last === undefined
|
|
751
682
|
? (plan.after?.text ?? FROM_START)
|
|
752
|
-
: cursorOf([
|
|
753
|
-
...shape.orderBy.map(({ by }) => by === 'count' ? Number(last.n) : decoded(last[`k${by}`], last[`v${by}`])),
|
|
754
|
-
keyOf(last),
|
|
755
|
-
]);
|
|
683
|
+
: cursorOf([...shape.orderBy.map(({ by }) => valueOf(last, by)), keyOf(last)]);
|
|
756
684
|
const total = plan.total
|
|
757
685
|
? Number(connection.rows(joined([fragment('SELECT count(*) AS held FROM ('), grouping, fragment(')')], '').sql, grouping.bind)[0].held)
|
|
758
686
|
: undefined;
|
|
@@ -770,9 +698,6 @@ function boundOf(connection, plan, rooms) {
|
|
|
770
698
|
collate: collating(connection),
|
|
771
699
|
};
|
|
772
700
|
}
|
|
773
|
-
export function selected(connection, plan, rooms) {
|
|
774
|
-
return filtered(boundOf(connection, plan, rooms), plan);
|
|
775
|
-
}
|
|
776
701
|
export function answerQuery(connection, plan, rooms, budget = null) {
|
|
777
702
|
const bound = boundOf(connection, plan, rooms);
|
|
778
703
|
if (rooms.length === 0) {
|