@agentshouse/mdruntime 0.1.0-cfcf93be6310b → 0.1.0-cff1b70c6ad46
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 +9 -9
- 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 +24 -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 +207 -76
- 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 +17 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +4 -1
- package/dist/compile.d.ts +7 -7
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +237 -315
- package/dist/connection.d.ts +5 -140
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +101 -5280
- 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 +29 -35
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +840 -841
- 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 +16 -7
- package/dist/expectation.d.ts +4 -2
- package/dist/expectation.d.ts.map +1 -1
- package/dist/expectation.js +33 -11
- 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 +1 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +12 -5
- 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 +20 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/install.d.ts +56 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +554 -0
- package/dist/job.d.ts +20 -1
- package/dist/job.d.ts.map +1 -1
- package/dist/job.js +26 -0
- package/dist/lifecycle.d.ts +7 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +268 -0
- package/dist/listing.d.ts +53 -0
- package/dist/listing.d.ts.map +1 -0
- package/dist/listing.js +176 -0
- package/dist/live.d.ts +4 -4
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +52 -16
- package/dist/locks.d.ts +9 -0
- package/dist/locks.d.ts.map +1 -0
- package/dist/locks.js +61 -0
- package/dist/log.d.ts +1 -0
- package/dist/log.d.ts.map +1 -1
- package/dist/lost.d.ts +5 -0
- package/dist/lost.d.ts.map +1 -0
- package/dist/lost.js +61 -0
- package/dist/media.d.ts +1 -1
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js +3 -3
- package/dist/memory.d.ts +3 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +43 -4
- package/dist/messages.d.ts +25 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +91 -18
- 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 +195 -0
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +12 -17
- package/dist/opened.js +3 -3
- package/dist/opening.d.ts +4 -0
- package/dist/opening.d.ts.map +1 -0
- package/dist/opening.js +57 -0
- package/dist/outcome.d.ts +4 -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 +226 -0
- package/dist/queries.d.ts +16 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/queries.js +272 -0
- package/dist/query.d.ts +13 -213
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +3 -524
- 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 +8 -6
- package/dist/reach.d.ts.map +1 -1
- package/dist/reach.js +159 -100
- 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 -1
- 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 +171 -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 +8 -20
- 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 +7 -7
- package/dist/replica.d.ts.map +1 -1
- package/dist/replica.js +51 -19
- package/dist/room-draft.d.ts +2 -4
- package/dist/room-draft.d.ts.map +1 -1
- package/dist/room-draft.js +3 -3
- 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 -22
- package/dist/rows.d.ts.map +1 -1
- package/dist/rows.js +48 -189
- 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 +21 -20
- 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.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 +31 -0
- package/dist/shown.d.ts.map +1 -0
- package/dist/shown.js +292 -0
- package/dist/socket/admission.d.ts +7 -0
- package/dist/socket/admission.d.ts.map +1 -0
- package/dist/socket/admission.js +39 -0
- package/dist/socket/answers.d.ts +6 -0
- package/dist/socket/answers.d.ts.map +1 -0
- package/dist/socket/answers.js +20 -0
- package/dist/socket/lanes.d.ts +20 -0
- package/dist/socket/lanes.d.ts.map +1 -0
- package/dist/socket/lanes.js +77 -0
- package/dist/socket/reads.d.ts +10 -0
- package/dist/socket/reads.d.ts.map +1 -0
- package/dist/socket/reads.js +31 -0
- package/dist/socket/rooms.d.ts +44 -0
- package/dist/socket/rooms.d.ts.map +1 -0
- package/dist/socket/rooms.js +90 -0
- package/dist/socket/session.d.ts +38 -0
- package/dist/socket/session.d.ts.map +1 -0
- package/dist/socket/session.js +23 -0
- package/dist/socket.d.ts +70 -0
- package/dist/socket.d.ts.map +1 -0
- package/dist/socket.js +175 -0
- package/dist/source.d.ts +61 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +82 -0
- package/dist/status.d.ts +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 -153
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +143 -396
- package/dist/stored.d.ts +59 -65
- package/dist/stored.d.ts.map +1 -1
- package/dist/stored.js +258 -158
- package/dist/surface.d.ts +88 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +223 -0
- package/dist/sync.d.ts +19 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +532 -0
- package/dist/tables.d.ts +114 -51
- package/dist/tables.d.ts.map +1 -1
- package/dist/tables.js +214 -59
- 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 +4 -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/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,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 {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const VISIBLE = (alias) => `${alias}.visible = 1 AND ${alias}.present = 1 AND ${alias}.queried = 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 { 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`;
|
|
121
7
|
function located(alias) {
|
|
122
8
|
return fragment(`(SELECT k.locator FROM replica_keys k WHERE k.key = ${alias}.key)`);
|
|
123
9
|
}
|
|
124
|
-
function within(alias, path) {
|
|
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,15 +83,10 @@ 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
|
-
...(plan.text === null
|
|
225
|
-
? []
|
|
226
|
-
: [fragment(`${HOLDS_PHRASE}(d.content, ?)`, [plan.text.toLowerCase()])]),
|
|
227
90
|
]);
|
|
228
91
|
}
|
|
229
92
|
function orderingOf(options) {
|
|
@@ -253,108 +116,65 @@ const GROUP_TIES = {
|
|
|
253
116
|
locale: undefined,
|
|
254
117
|
localeOptions: undefined,
|
|
255
118
|
};
|
|
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;
|
|
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]);
|
|
338
129
|
}
|
|
339
|
-
function keyTerm(key, ordering, index) {
|
|
130
|
+
function keyTerm(key, ordering, index, prefix) {
|
|
340
131
|
if (key.of === 'field') {
|
|
341
132
|
const alias = `o${index}`;
|
|
342
133
|
return {
|
|
343
|
-
term: {
|
|
134
|
+
term: {
|
|
135
|
+
ordering,
|
|
136
|
+
kind: fragment(`${alias}.kind`),
|
|
137
|
+
value: fragment(`${alias}.value`),
|
|
138
|
+
instant: fragment(`${alias}.instant`),
|
|
139
|
+
},
|
|
344
140
|
join: joined([
|
|
345
141
|
fragment(`LEFT JOIN replica_values ${alias} ON ${alias}.doc = d.id AND `),
|
|
346
|
-
grouped(
|
|
142
|
+
grouped(sql.placed(alias, key.field, 'exact')),
|
|
347
143
|
fragment(` AND ${alias}.item = -1 AND ${alias}.kind NOT IN ('object', 'undefined')`),
|
|
348
144
|
], ''),
|
|
349
145
|
};
|
|
350
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
|
+
}
|
|
351
169
|
const expression = key.of === 'room'
|
|
352
170
|
? located('d')
|
|
353
171
|
: key.of === 'path'
|
|
354
172
|
? fragment('d.path')
|
|
355
173
|
: key.of === 'type'
|
|
356
174
|
? fragment('d.type')
|
|
357
|
-
:
|
|
175
|
+
: key.of === 'subfolder'
|
|
176
|
+
? subfolderOf('d.folder', prefix)
|
|
177
|
+
: fragment('d.folder');
|
|
358
178
|
return {
|
|
359
179
|
term: {
|
|
360
180
|
ordering,
|
|
@@ -375,12 +195,20 @@ function decoded(kind, value) {
|
|
|
375
195
|
return JSON.parse(value);
|
|
376
196
|
return null;
|
|
377
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
|
+
}
|
|
378
204
|
function decodedKey(key, row, index) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
return
|
|
382
|
-
|
|
383
|
-
|
|
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');
|
|
384
212
|
}
|
|
385
213
|
const registered = new WeakMap();
|
|
386
214
|
function hashed(text) {
|
|
@@ -485,12 +313,11 @@ 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
|
|
490
|
-
const content = row.content ?? null;
|
|
491
|
-
const unheld = Number(row.unheld) === 1 ? { textHeld: false } : {};
|
|
316
|
+
const fileId = row.file_id;
|
|
317
|
+
const revision = row.revision;
|
|
492
318
|
if (type === null) {
|
|
493
319
|
const binary = row.binary === null ? null : JSON.parse(row.binary);
|
|
320
|
+
const content = row.content ?? null;
|
|
494
321
|
return {
|
|
495
322
|
room,
|
|
496
323
|
path,
|
|
@@ -500,12 +327,13 @@ function documentOf(shape, room, row, linked) {
|
|
|
500
327
|
binaryFile: binaryRecord(path, binary, revision ?? undefined),
|
|
501
328
|
}),
|
|
502
329
|
...(shape.markdown && content !== null ? { markdown: content } : {}),
|
|
503
|
-
...
|
|
330
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
504
331
|
...(shape.fileId ? { fileId } : {}),
|
|
505
332
|
...(shape.revision ? { revision } : {}),
|
|
333
|
+
...heldFacts(shape, row),
|
|
506
334
|
};
|
|
507
335
|
}
|
|
508
|
-
const read =
|
|
336
|
+
const read = shape.sections !== null || shape.preamble ? parse(row.content, path) : null;
|
|
509
337
|
const body = read?.ok === true ? read.document : null;
|
|
510
338
|
const fields = JSON.parse(row.fields ?? '{}');
|
|
511
339
|
return {
|
|
@@ -523,16 +351,21 @@ function documentOf(shape, room, row, linked) {
|
|
|
523
351
|
? {}
|
|
524
352
|
: { sections: sectionsOf(body.sections, shape.sections) }),
|
|
525
353
|
...(shape.preamble && body !== null ? { preamble: body.preamble } : {}),
|
|
526
|
-
...(shape.markdown
|
|
527
|
-
...unheld,
|
|
354
|
+
...(shape.markdown ? { markdown: row.content } : {}),
|
|
528
355
|
...(shape.backlinks ? { backlinks: linked.get(documentKey(room, path)) ?? [] } : {}),
|
|
529
|
-
...(shape.change
|
|
530
|
-
? {
|
|
531
|
-
change: row.change === null ? null : JSON.parse(row.change),
|
|
532
|
-
}
|
|
533
|
-
: {}),
|
|
356
|
+
...(shape.change ? { change: changeOf(row) } : {}),
|
|
534
357
|
...(shape.fileId ? { fileId } : {}),
|
|
535
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 } : {}),
|
|
536
369
|
};
|
|
537
370
|
}
|
|
538
371
|
function backlinksOf(connection, bound, rows) {
|
|
@@ -544,8 +377,8 @@ function backlinksOf(connection, bound, rows) {
|
|
|
544
377
|
held.push(row.path);
|
|
545
378
|
byKey.set(key, held);
|
|
546
379
|
}
|
|
380
|
+
const reach = reached(bound, 's');
|
|
547
381
|
for (const [key, targets] of byKey) {
|
|
548
|
-
const reach = reached(bound, 's');
|
|
549
382
|
const room = bound.rooms.find((one) => one.key === key).locator;
|
|
550
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]);
|
|
551
384
|
for (const row of found) {
|
|
@@ -558,38 +391,36 @@ function backlinksOf(connection, bound, rows) {
|
|
|
558
391
|
return linked;
|
|
559
392
|
}
|
|
560
393
|
function documents(connection, bound, plan, shape, budget) {
|
|
561
|
-
const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(key, orderingOf(options), index));
|
|
394
|
+
const terms = shape.orderBy.map(({ key, options }, index) => keyTerm(key, orderingOf(options), index, plan.root.pathPrefix));
|
|
562
395
|
const filter = filtered(bound, plan);
|
|
563
396
|
const cursor = plan.after?.values ?? null;
|
|
564
397
|
const room = located('d');
|
|
565
|
-
const
|
|
398
|
+
const after = cursor === null
|
|
566
399
|
? null
|
|
567
400
|
: (() => {
|
|
568
401
|
const key = cursor[shape.orderBy.length];
|
|
569
|
-
return any([
|
|
402
|
+
return sql.following(terms.map(({ term }, index) => sql.beyond(bound.collate, term, cursor[index])), any([
|
|
570
403
|
fragment(`${room.sql} > ?`, [...room.bind, roomOfKey(key)]),
|
|
571
404
|
fragment(`${room.sql} = ? AND d.path > ?`, [
|
|
572
405
|
...room.bind,
|
|
573
406
|
roomOfKey(key),
|
|
574
407
|
pathOfKey(key),
|
|
575
408
|
]),
|
|
576
|
-
]);
|
|
409
|
+
]));
|
|
577
410
|
})();
|
|
578
|
-
const after = cursor === null || tie === null
|
|
579
|
-
? null
|
|
580
|
-
: following(terms.map(({ term }, index) => beyond(bound, term, cursor[index])), tie);
|
|
581
411
|
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.
|
|
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'),
|
|
583
413
|
...terms.map(({ term }, index) => joined([
|
|
584
414
|
fragment(`, ${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
585
415
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
416
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
586
417
|
], '')),
|
|
587
418
|
fragment(' FROM replica_entries d'),
|
|
588
419
|
...terms.flatMap(({ join }) => (join === null ? [] : [fragment(' '), join])),
|
|
589
420
|
fragment(' WHERE '),
|
|
590
421
|
after === null ? filter : all([filter, after]),
|
|
591
422
|
fragment(' ORDER BY '),
|
|
592
|
-
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')], ', '),
|
|
593
424
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
594
425
|
], '');
|
|
595
426
|
const rows = connection.rows(selected.sql, selected.bind);
|
|
@@ -628,7 +459,7 @@ function withContent(connection, rows, budget) {
|
|
|
628
459
|
return rows;
|
|
629
460
|
const ids = rows.map((row) => row.id);
|
|
630
461
|
const sizes = new Map(connection
|
|
631
|
-
.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)
|
|
632
463
|
.map((row) => [row.id, Number(row.size ?? 0)]));
|
|
633
464
|
let cut = rows.length;
|
|
634
465
|
let spent = 0;
|
|
@@ -643,18 +474,22 @@ function withContent(connection, rows, budget) {
|
|
|
643
474
|
const kept = rows.slice(0, cut);
|
|
644
475
|
const read = kept.map((row) => row.id);
|
|
645
476
|
const held = new Map(connection
|
|
646
|
-
.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)
|
|
647
478
|
.map((row) => [row.id, row.content]));
|
|
648
479
|
return kept.map((row) => ({ ...row, content: held.get(row.id) ?? null }));
|
|
649
480
|
}
|
|
650
|
-
function storedFolders(bound, plan) {
|
|
651
|
-
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)
|
|
652
487
|
return null;
|
|
653
488
|
const whole = bound.rooms.filter(({ locator }) => bound.reach === null || bound.reach.rooms.includes(locator));
|
|
654
489
|
if (whole.length === 0)
|
|
655
490
|
return null;
|
|
656
491
|
const keys = whole.map(({ key }) => key);
|
|
657
|
-
const prefix = plan.root.pathPrefix;
|
|
492
|
+
const prefix = plan.root.pathPrefix ?? '';
|
|
658
493
|
const outside = (bound.reach?.areas ?? []).flatMap((area) => {
|
|
659
494
|
const key = bound.keys.get(area.room);
|
|
660
495
|
if (key === undefined)
|
|
@@ -662,100 +497,190 @@ function storedFolders(bound, plan) {
|
|
|
662
497
|
const fence = fenced('f', key, area, true);
|
|
663
498
|
return [fragment(`NOT ${grouped(fence).sql}`, fence.bind)];
|
|
664
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
|
+
];
|
|
665
509
|
return all([
|
|
666
|
-
fragment(`f.key IN ${
|
|
510
|
+
fragment(`f.key IN ${listed(keys).sql} AND f.layer = 0 AND f.present = 1`, keys),
|
|
667
511
|
...outside,
|
|
668
|
-
...
|
|
669
|
-
|
|
670
|
-
: [
|
|
671
|
-
fragment('(f.path = ? OR (f.path >= ? AND f.path < ?))', [
|
|
672
|
-
prefix,
|
|
673
|
-
`${prefix}/`,
|
|
674
|
-
`${prefix}0`,
|
|
675
|
-
]),
|
|
676
|
-
]),
|
|
512
|
+
...under,
|
|
513
|
+
...(plan.outside ?? []).map((folder) => fragment(`NOT ${within('f', folder).sql}`, within('f', folder).bind)),
|
|
677
514
|
...(plan.folder === null ? [] : [fragment('f.path = ?', [plan.folder])]),
|
|
678
515
|
]);
|
|
679
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
|
+
}
|
|
680
532
|
function groups(connection, bound, plan, shape) {
|
|
533
|
+
const prefix = plan.root.pathPrefix;
|
|
681
534
|
const keyed = shape.groupBy.map((key, index) => {
|
|
682
|
-
const { term, join } = keyTerm(key, GROUP_ORDER, index);
|
|
535
|
+
const { term, join } = keyTerm(key, GROUP_ORDER, index, prefix);
|
|
683
536
|
const kind = fragment(`COALESCE(${term.kind.sql}, 'null')`, term.kind.bind);
|
|
684
537
|
return { term: { ...term, kind }, join };
|
|
685
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);
|
|
686
545
|
const filter = filtered(bound, plan);
|
|
687
546
|
const inner = joined([
|
|
688
547
|
fragment('SELECT '),
|
|
689
548
|
joined(keyed.map(({ term }, index) => joined([
|
|
690
549
|
fragment(`${term.kind.sql} AS k${index}`, term.kind.bind),
|
|
691
550
|
fragment(`, ${term.value.sql} AS v${index}`, term.value.bind),
|
|
551
|
+
fragment(`, ${instantOf(term).sql} AS i${index}`, instantOf(term).bind),
|
|
692
552
|
], '')), ', '),
|
|
693
|
-
fragment(', 1 AS n
|
|
694
|
-
...
|
|
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]),
|
|
695
563
|
fragment(' WHERE '),
|
|
696
564
|
filter,
|
|
697
565
|
], '');
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
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
|
+
];
|
|
701
575
|
const source = folders === null
|
|
702
576
|
? inner
|
|
703
577
|
: joined([
|
|
704
578
|
inner,
|
|
705
|
-
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),
|
|
706
580
|
folders,
|
|
707
581
|
], '');
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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,
|
|
716
605
|
kind: fragment(`k${index}`),
|
|
717
606
|
value: fragment(`v${index}`),
|
|
718
607
|
});
|
|
719
|
-
const
|
|
720
|
-
|
|
721
|
-
|
|
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 }));
|
|
722
640
|
const ties = keyed.flatMap((_, index) => [
|
|
723
641
|
{ ...outer(index, GROUP_TIES), total: true },
|
|
724
642
|
{ ordering: GROUP_TIES, kind: fragment("'string'"), value: fragment(`k${index}`) },
|
|
725
643
|
]);
|
|
726
644
|
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
|
|
645
|
+
const after = cursor === null
|
|
734
646
|
? null
|
|
735
|
-
:
|
|
736
|
-
|
|
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}`)], '');
|
|
737
654
|
const windowed = joined([
|
|
738
655
|
fragment('SELECT * FROM ('),
|
|
739
656
|
grouping,
|
|
740
657
|
fragment(')'),
|
|
741
658
|
...(after === null ? [] : [fragment(' WHERE '), after]),
|
|
742
659
|
fragment(' ORDER BY '),
|
|
743
|
-
joined([...ordered, ...ties].map((term) => sorted(bound, term)), ', '),
|
|
660
|
+
joined([...ordered, ...ties].map((term) => sql.sorted(bound.collate, term)), ', '),
|
|
744
661
|
...(plan.limit === null ? [] : [fragment(' LIMIT ?', [plan.limit + 1])]),
|
|
745
662
|
], '');
|
|
746
663
|
const rows = connection.rows(windowed.sql, windowed.bind);
|
|
747
664
|
const kept = plan.limit === null ? rows : rows.slice(0, plan.limit);
|
|
748
665
|
const keyOf = (row) => keyed.map((_, index) => decoded(row[`k${index}`], row[`v${index}`]));
|
|
749
|
-
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
|
+
}));
|
|
750
678
|
const last = kept.at(-1);
|
|
751
679
|
const next = plan.limit === null || rows.length <= plan.limit
|
|
752
680
|
? undefined
|
|
753
681
|
: last === undefined
|
|
754
682
|
? (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
|
-
]);
|
|
683
|
+
: cursorOf([...shape.orderBy.map(({ by }) => valueOf(last, by)), keyOf(last)]);
|
|
759
684
|
const total = plan.total
|
|
760
685
|
? Number(connection.rows(joined([fragment('SELECT count(*) AS held FROM ('), grouping, fragment(')')], '').sql, grouping.bind)[0].held)
|
|
761
686
|
: undefined;
|
|
@@ -773,9 +698,6 @@ function boundOf(connection, plan, rooms) {
|
|
|
773
698
|
collate: collating(connection),
|
|
774
699
|
};
|
|
775
700
|
}
|
|
776
|
-
export function selected(connection, plan, rooms) {
|
|
777
|
-
return filtered(boundOf(connection, plan, rooms), plan);
|
|
778
|
-
}
|
|
779
701
|
export function answerQuery(connection, plan, rooms, budget = null) {
|
|
780
702
|
const bound = boundOf(connection, plan, rooms);
|
|
781
703
|
if (rooms.length === 0) {
|