@sixb/pg 0.1.14 → 0.1.16
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/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +743 -313
- package/dist/index.js.map +15 -15
- package/dist/latest-run-query.d.ts +2 -0
- package/dist/latest-run-query.d.ts.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
- package/dist/ontology-storage/shared.d.ts +12 -9
- package/dist/ontology-storage/shared.d.ts.map +1 -1
- package/dist/ontology-storage/source-cleanup.d.ts +7 -1
- package/dist/ontology-storage/source-cleanup.d.ts.map +1 -1
- package/dist/ontology-storage/source-roots.d.ts +8 -0
- package/dist/ontology-storage/source-roots.d.ts.map +1 -1
- package/dist/ontology-storage/sources.d.ts +8 -3
- package/dist/ontology-storage/sources.d.ts.map +1 -1
- package/dist/pg-client.d.ts +2 -0
- package/dist/pg-client.d.ts.map +1 -1
- package/dist/pg-projection-run-storage.d.ts +3 -1
- package/dist/pg-projection-run-storage.d.ts.map +1 -1
- package/dist/storage-errors.d.ts +13 -3
- package/dist/storage-errors.d.ts.map +1 -1
- package/dist/transactions.d.ts +23 -1
- package/dist/transactions.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +8 -1
- package/src/latest-run-query.ts +10 -2
- package/src/migrations/051-projection-run-supersession.sql +43 -0
- package/src/migrations/052-compact-source-storage.sql +118 -0
- package/src/migrations.ts +26 -23
- package/src/objects/query-compiler.ts +1 -1
- package/src/ontology-storage/materialization-state.ts +20 -46
- package/src/ontology-storage/shared.ts +18 -12
- package/src/ontology-storage/source-cleanup.ts +66 -22
- package/src/ontology-storage/source-roots.ts +164 -102
- package/src/ontology-storage/sources.ts +150 -120
- package/src/pg-client.ts +132 -2
- package/src/pg-projection-run-storage.ts +56 -2
- package/src/storage-errors.ts +101 -15
- package/src/transactions.ts +113 -20
|
@@ -4,12 +4,91 @@ import {
|
|
|
4
4
|
MaterializationValidationError,
|
|
5
5
|
projectionEntityKey,
|
|
6
6
|
} from "@sixb/core/internal/materialization"
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
sourceEntityColumns,
|
|
9
|
+
sourceStageRoots,
|
|
10
|
+
} from "@sixb/core/internal/ontology-storage-provider"
|
|
8
11
|
import type { SourceActivationWrite, StageSourceRowsInput } from "@sixb/core/storage"
|
|
9
12
|
import type { SQLClient } from "../pg-client"
|
|
10
13
|
import type { PgOntologySourceAssertionRow } from "./shared"
|
|
11
14
|
import { jsonParameter, type PgOntologySourceRow } from "./shared"
|
|
12
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Source storage in three levels: a version (`ontology_sources`, one per run), its roots (one per
|
|
18
|
+
* logical dataset row, keyed by the canonical entity key) and their rows (what each root asserts).
|
|
19
|
+
*
|
|
20
|
+
* A root is live while its version is published (active or superseded), it is not retired and it
|
|
21
|
+
* is not a deletion. Activation retires the live roots its candidate replaces and flips the
|
|
22
|
+
* version; the candidate's own roots are never rewritten. One live root per source and key holds
|
|
23
|
+
* because activation, under the source fence, retires every root the candidate replaces.
|
|
24
|
+
*/
|
|
25
|
+
const PUBLISHED = "('active', 'superseded')"
|
|
26
|
+
|
|
27
|
+
/** The columns `sourceAssertion` reads, over `versions`, `roots` and `rows`. */
|
|
28
|
+
export function sourceAssertionColumns(sql: SQLClient) {
|
|
29
|
+
return sql.unsafe(`versions.source_id, versions.materialization_id, roots.root_key,
|
|
30
|
+
roots.staging_ordinal, rows.entity_kind, rows.object_type_id, rows.primary_id,
|
|
31
|
+
rows.source_type_id, rows.source_primary_id, rows.link_id, rows.target_type_id,
|
|
32
|
+
rows.target_primary_id, rows.payload`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Keeps `rows` to those of live roots in `projectId`, joined as `roots` and `versions`. */
|
|
36
|
+
export function liveSourceRowsJoin(sql: SQLClient, projectId: string) {
|
|
37
|
+
return sql`
|
|
38
|
+
JOIN ontology_source_roots AS roots ON roots.id = rows.root_id
|
|
39
|
+
AND roots.project_id = ${projectId} AND roots.retired_at IS NULL AND NOT roots.deleted
|
|
40
|
+
JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
41
|
+
AND versions.status IN ${sql.unsafe(PUBLISHED)}
|
|
42
|
+
`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function candidateVersion(
|
|
46
|
+
sql: SQLClient,
|
|
47
|
+
input: {
|
|
48
|
+
readonly projectId: string
|
|
49
|
+
readonly sourceId: string
|
|
50
|
+
readonly materializationId: string
|
|
51
|
+
}
|
|
52
|
+
) {
|
|
53
|
+
return sql`(SELECT version_id FROM ontology_sources WHERE project_id = ${input.projectId}
|
|
54
|
+
AND source_id = ${input.sourceId} AND materialization_id = ${input.materializationId})`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The typed identity of `rows` equals the one requested. */
|
|
58
|
+
const ENTITY_MATCH = `rows.entity_kind = requested.entity_kind
|
|
59
|
+
AND rows.object_type_id IS NOT DISTINCT FROM requested.object_type_id
|
|
60
|
+
AND rows.primary_id IS NOT DISTINCT FROM requested.primary_id
|
|
61
|
+
AND rows.source_type_id IS NOT DISTINCT FROM requested.source_type_id
|
|
62
|
+
AND rows.source_primary_id IS NOT DISTINCT FROM requested.source_primary_id
|
|
63
|
+
AND rows.link_id IS NOT DISTINCT FROM requested.link_id
|
|
64
|
+
AND rows.target_type_id IS NOT DISTINCT FROM requested.target_type_id
|
|
65
|
+
AND rows.target_primary_id IS NOT DISTINCT FROM requested.target_primary_id`
|
|
66
|
+
|
|
67
|
+
const REQUESTED_ENTITY = `entity_kind TEXT, object_type_id TEXT, primary_id TEXT,
|
|
68
|
+
source_type_id TEXT, source_primary_id TEXT, link_id TEXT, target_type_id TEXT,
|
|
69
|
+
target_primary_id TEXT`
|
|
70
|
+
|
|
71
|
+
/** An entity's own key, and for a link the key of the object root that may assert it. */
|
|
72
|
+
function requestedEntity(ref: ProjectionEntityRef) {
|
|
73
|
+
const columns = sourceEntityColumns(ref)
|
|
74
|
+
const key = projectionEntityKey(ref)
|
|
75
|
+
return {
|
|
76
|
+
entity_kind: ref.kind,
|
|
77
|
+
object_type_id: columns.objectTypeId,
|
|
78
|
+
primary_id: columns.primaryId,
|
|
79
|
+
source_type_id: columns.sourceTypeId,
|
|
80
|
+
source_primary_id: columns.sourcePrimaryId,
|
|
81
|
+
link_id: columns.linkId,
|
|
82
|
+
target_type_id: columns.targetTypeId,
|
|
83
|
+
target_primary_id: columns.targetPrimaryId,
|
|
84
|
+
root_keys:
|
|
85
|
+
ref.kind === "object"
|
|
86
|
+
? [key]
|
|
87
|
+
: [key, projectionEntityKey({ kind: "object", ref: ref.ref.source })],
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The candidate's rows, plus the rows of the live roots it may replace. */
|
|
13
92
|
export function replacementSourceRows(
|
|
14
93
|
sql: SQLClient,
|
|
15
94
|
input: {
|
|
@@ -19,27 +98,32 @@ export function replacementSourceRows(
|
|
|
19
98
|
readonly incremental: boolean
|
|
20
99
|
}
|
|
21
100
|
) {
|
|
101
|
+
const live = sql`roots.retired_at IS NULL AND NOT roots.deleted
|
|
102
|
+
AND versions.source_id = ${input.sourceId} AND versions.status IN ${sql.unsafe(PUBLISHED)}`
|
|
22
103
|
const roots = input.incremental
|
|
23
104
|
? sql`
|
|
24
|
-
SELECT
|
|
105
|
+
SELECT live.id FROM ontology_source_roots AS changed
|
|
25
106
|
CROSS JOIN LATERAL (
|
|
26
|
-
SELECT roots
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
107
|
+
SELECT roots.id FROM ontology_source_roots AS roots
|
|
108
|
+
JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
109
|
+
WHERE roots.project_id = ${input.projectId} AND roots.root_key = changed.root_key AND ${live}
|
|
110
|
+
OFFSET 0
|
|
111
|
+
) AS live
|
|
112
|
+
WHERE changed.version_id = ${candidateVersion(sql, input)}
|
|
113
|
+
`
|
|
114
|
+
: sql`
|
|
115
|
+
SELECT roots.id FROM ontology_sources AS versions
|
|
116
|
+
JOIN ontology_source_roots AS roots ON roots.version_id = versions.version_id
|
|
117
|
+
WHERE versions.project_id = ${input.projectId} AND ${live}
|
|
32
118
|
`
|
|
33
|
-
: sql`SELECT * FROM ontology_source_roots WHERE project_id = ${input.projectId} AND source_id = ${input.sourceId} AND active`
|
|
34
119
|
return sql`
|
|
35
|
-
SELECT
|
|
36
|
-
|
|
120
|
+
SELECT rows.* FROM ontology_source_roots AS roots
|
|
121
|
+
JOIN ontology_source_rows AS rows ON rows.root_id = roots.id
|
|
122
|
+
WHERE roots.version_id = ${candidateVersion(sql, input)}
|
|
37
123
|
UNION ALL
|
|
38
124
|
SELECT rows.* FROM (${roots}) AS roots
|
|
39
125
|
CROSS JOIN LATERAL (
|
|
40
|
-
SELECT rows.* FROM ontology_source_rows AS rows WHERE rows.
|
|
41
|
-
AND rows.source_id = roots.source_id AND rows.materialization_id = roots.materialization_id
|
|
42
|
-
AND rows.root_sort_key = roots.root_sort_key OFFSET 0
|
|
126
|
+
SELECT rows.* FROM ontology_source_rows AS rows WHERE rows.root_id = roots.id OFFSET 0
|
|
43
127
|
) AS rows
|
|
44
128
|
`
|
|
45
129
|
}
|
|
@@ -57,38 +141,31 @@ export async function stageSourceRoots(
|
|
|
57
141
|
const values = jsonParameter(
|
|
58
142
|
sql,
|
|
59
143
|
roots.map((root) => ({
|
|
60
|
-
|
|
61
|
-
root_kind: root.root.kind,
|
|
62
|
-
root_key: JSON.parse(root.rootKey),
|
|
63
|
-
root: root.root,
|
|
144
|
+
root_key: root.rootKey,
|
|
64
145
|
staging_ordinal: root.stagingOrdinal,
|
|
65
146
|
deleted: root.deleted,
|
|
66
147
|
}))
|
|
67
148
|
)
|
|
68
|
-
const inserted = await sql<{
|
|
69
|
-
INSERT INTO ontology_source_roots (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
FROM jsonb_to_recordset(${values}) AS input(
|
|
74
|
-
root_sort_key TEXT, root_kind TEXT, root_key JSONB, root JSONB, staging_ordinal BIGINT, deleted BOOLEAN)
|
|
75
|
-
ON CONFLICT DO NOTHING RETURNING root_sort_key
|
|
149
|
+
const inserted = await sql<{ root_key: string }[]>`
|
|
150
|
+
INSERT INTO ontology_source_roots (version_id, project_id, root_key, staging_ordinal, deleted)
|
|
151
|
+
SELECT ${manifest.version_id}, ${input.projectId}, root_key, staging_ordinal, deleted
|
|
152
|
+
FROM jsonb_to_recordset(${values}) AS input(root_key TEXT, staging_ordinal BIGINT, deleted BOOLEAN)
|
|
153
|
+
ON CONFLICT DO NOTHING RETURNING root_key
|
|
76
154
|
`
|
|
77
155
|
if (inserted.length === roots.length) return
|
|
78
|
-
const insertedKeys = new Set(inserted.map((row) => row.
|
|
79
|
-
const skipped = roots.filter((root) => !insertedKeys.has(root.
|
|
156
|
+
const insertedKeys = new Set(inserted.map((row) => row.root_key))
|
|
157
|
+
const skipped = roots.filter((root) => !insertedKeys.has(root.rootKey))
|
|
80
158
|
const stored = await sql<
|
|
81
|
-
{
|
|
159
|
+
{ root_key: string; staging_ordinal: string | number; deleted: boolean }[]
|
|
82
160
|
>`
|
|
83
|
-
SELECT
|
|
84
|
-
WHERE
|
|
85
|
-
AND
|
|
86
|
-
AND root_sort_key = ANY(${sql.array(skipped.map((root) => root.sortKey))}::text[])
|
|
161
|
+
SELECT root_key, staging_ordinal, deleted FROM ontology_source_roots
|
|
162
|
+
WHERE version_id = ${manifest.version_id}
|
|
163
|
+
AND root_key = ANY(${sql.array(skipped.map((root) => root.rootKey))}::text[])
|
|
87
164
|
`
|
|
88
|
-
const existing = new Map(stored.map((root) => [root.
|
|
165
|
+
const existing = new Map(stored.map((root) => [root.root_key, root]))
|
|
89
166
|
if (
|
|
90
167
|
skipped.some((root) => {
|
|
91
|
-
const previous = existing.get(root.
|
|
168
|
+
const previous = existing.get(root.rootKey)
|
|
92
169
|
return (
|
|
93
170
|
!previous ||
|
|
94
171
|
Number(previous.staging_ordinal) !== root.stagingOrdinal ||
|
|
@@ -101,6 +178,7 @@ export async function stageSourceRoots(
|
|
|
101
178
|
)
|
|
102
179
|
}
|
|
103
180
|
|
|
181
|
+
/** Retires what the candidate replaces; its own roots go live when its version turns active. */
|
|
104
182
|
export async function activateSourceRoots(
|
|
105
183
|
sql: SQLClient,
|
|
106
184
|
projectId: string,
|
|
@@ -117,24 +195,27 @@ export async function activateSourceRoots(
|
|
|
117
195
|
"Source delta base changed before activation."
|
|
118
196
|
)
|
|
119
197
|
if (candidate.base_materialization_id !== null && Number(candidate.root_count) === 0) return
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
198
|
+
const live = sql`roots.retired_at IS NULL AND NOT roots.deleted
|
|
199
|
+
AND versions.version_id = roots.version_id AND versions.project_id = ${projectId}
|
|
200
|
+
AND versions.source_id = ${activation.source.projectionId}
|
|
201
|
+
AND versions.status IN ${sql.unsafe(PUBLISHED)}`
|
|
202
|
+
if (candidate.base_materialization_id === null) {
|
|
203
|
+
await sql`
|
|
204
|
+
UPDATE ontology_source_roots AS roots SET retired_at = ${activation.updatedAt}
|
|
205
|
+
FROM ontology_sources AS versions WHERE ${live}
|
|
206
|
+
`
|
|
207
|
+
} else {
|
|
208
|
+
await sql`
|
|
209
|
+
UPDATE ontology_source_roots AS roots SET retired_at = ${activation.updatedAt}
|
|
210
|
+
FROM ontology_source_roots AS changed, ontology_sources AS versions
|
|
211
|
+
WHERE changed.version_id = ${candidate.version_id} AND roots.project_id = ${projectId}
|
|
212
|
+
AND roots.root_key = changed.root_key AND ${live}
|
|
213
|
+
`
|
|
214
|
+
}
|
|
215
|
+
// Deletions are never live; retiring them lets cleanup remove them with their version.
|
|
133
216
|
await sql`
|
|
134
|
-
UPDATE ontology_source_roots SET
|
|
135
|
-
|
|
136
|
-
WHERE project_id = ${projectId} AND source_id = ${activation.source.projectionId}
|
|
137
|
-
AND materialization_id = ${activation.materializationId}
|
|
217
|
+
UPDATE ontology_source_roots SET retired_at = ${activation.updatedAt}
|
|
218
|
+
WHERE version_id = ${candidate.version_id} AND deleted
|
|
138
219
|
`
|
|
139
220
|
}
|
|
140
221
|
|
|
@@ -144,13 +225,11 @@ export async function assertSourceRootCoverage(
|
|
|
144
225
|
): Promise<void> {
|
|
145
226
|
const [invalid] = await sql`
|
|
146
227
|
SELECT 1 FROM ontology_source_roots AS roots
|
|
147
|
-
WHERE
|
|
148
|
-
AND materialization_id = ${manifest.materialization_id}
|
|
228
|
+
WHERE roots.version_id = ${manifest.version_id}
|
|
149
229
|
AND roots.deleted = EXISTS (
|
|
150
|
-
SELECT 1 FROM ontology_source_rows AS rows
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
) LIMIT 1
|
|
230
|
+
SELECT 1 FROM ontology_source_rows AS rows WHERE rows.root_id = roots.id
|
|
231
|
+
)
|
|
232
|
+
LIMIT 1
|
|
154
233
|
`
|
|
155
234
|
if (invalid)
|
|
156
235
|
throw new MaterializationValidationError(
|
|
@@ -158,35 +237,29 @@ export async function assertSourceRootCoverage(
|
|
|
158
237
|
)
|
|
159
238
|
}
|
|
160
239
|
|
|
240
|
+
/** The live assertions of `refs`, whichever source asserts them. */
|
|
161
241
|
export function activeSourceRows(
|
|
162
242
|
sql: SQLClient,
|
|
163
243
|
projectId: string,
|
|
164
244
|
refs: readonly ProjectionEntityRef[]
|
|
165
245
|
) {
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
entity.kind === "object"
|
|
170
|
-
? [entitySortKey]
|
|
171
|
-
: [
|
|
172
|
-
entitySortKey,
|
|
173
|
-
utf8SortKey(projectionEntityKey({ kind: "object", ref: entity.ref.source })),
|
|
174
|
-
]
|
|
175
|
-
return roots.map((rootSortKey) => ({
|
|
176
|
-
root_sort_key: rootSortKey,
|
|
177
|
-
entity_sort_key: entitySortKey,
|
|
178
|
-
}))
|
|
246
|
+
const requested = refs.flatMap((ref) => {
|
|
247
|
+
const { root_keys, ...entity } = requestedEntity(ref)
|
|
248
|
+
return root_keys.map((root_key) => ({ ...entity, root_key }))
|
|
179
249
|
})
|
|
180
250
|
return sql<PgOntologySourceAssertionRow[]>`
|
|
181
|
-
SELECT
|
|
251
|
+
SELECT ${sourceAssertionColumns(sql)}
|
|
252
|
+
FROM jsonb_to_recordset(${jsonParameter(sql, requested)})
|
|
253
|
+
AS requested(root_key TEXT, ${sql.unsafe(REQUESTED_ENTITY)})
|
|
182
254
|
JOIN ontology_source_roots AS roots ON roots.project_id = ${projectId}
|
|
183
|
-
AND roots.
|
|
184
|
-
JOIN
|
|
185
|
-
AND
|
|
186
|
-
|
|
255
|
+
AND roots.root_key = requested.root_key AND roots.retired_at IS NULL AND NOT roots.deleted
|
|
256
|
+
JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
257
|
+
AND versions.status IN ${sql.unsafe(PUBLISHED)}
|
|
258
|
+
JOIN ontology_source_rows AS rows ON rows.root_id = roots.id AND ${sql.unsafe(ENTITY_MATCH)}
|
|
187
259
|
`
|
|
188
260
|
}
|
|
189
261
|
|
|
262
|
+
/** The candidate's assertions of `refs`, and optionally the live ones they would replace. */
|
|
190
263
|
export function replacementAssertionRows(
|
|
191
264
|
sql: SQLClient,
|
|
192
265
|
input: {
|
|
@@ -198,52 +271,41 @@ export function replacementAssertionRows(
|
|
|
198
271
|
refs: readonly ProjectionEntityRef[]
|
|
199
272
|
}
|
|
200
273
|
) {
|
|
201
|
-
const
|
|
202
|
-
entity_kind: ref.kind,
|
|
203
|
-
entity_key: JSON.parse(projectionEntityKey(ref)),
|
|
204
|
-
root_keys:
|
|
205
|
-
ref.kind === "object"
|
|
206
|
-
? [utf8SortKey(projectionEntityKey(ref))]
|
|
207
|
-
: [
|
|
208
|
-
utf8SortKey(projectionEntityKey(ref)),
|
|
209
|
-
utf8SortKey(projectionEntityKey({ kind: "object", ref: ref.ref.source })),
|
|
210
|
-
],
|
|
211
|
-
}))
|
|
274
|
+
const version = candidateVersion(sql, input)
|
|
212
275
|
const previous = !input.includePrevious
|
|
213
276
|
? sql``
|
|
214
277
|
: sql`UNION ALL
|
|
215
|
-
SELECT
|
|
278
|
+
SELECT ${sourceAssertionColumns(sql)} FROM (
|
|
216
279
|
SELECT roots.* FROM ontology_source_roots AS roots
|
|
217
|
-
|
|
218
|
-
|
|
280
|
+
JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
281
|
+
WHERE roots.project_id = ${input.projectId} AND roots.root_key = ANY(requested.root_keys)
|
|
282
|
+
AND roots.retired_at IS NULL AND NOT roots.deleted
|
|
283
|
+
AND versions.source_id = ${input.sourceId} AND versions.status IN ${sql.unsafe(PUBLISHED)}
|
|
219
284
|
${
|
|
220
285
|
!input.incremental
|
|
221
286
|
? sql``
|
|
222
287
|
: sql`AND EXISTS (SELECT 1 FROM ontology_source_roots AS changed
|
|
223
|
-
WHERE changed.
|
|
224
|
-
AND changed.materialization_id = ${input.materializationId} AND changed.root_sort_key = roots.root_sort_key)`
|
|
288
|
+
WHERE changed.version_id = ${version} AND changed.root_key = roots.root_key)`
|
|
225
289
|
}
|
|
226
|
-
|
|
227
290
|
OFFSET 0
|
|
228
291
|
) AS roots
|
|
292
|
+
JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
229
293
|
CROSS JOIN LATERAL (
|
|
230
294
|
SELECT rows.* FROM ontology_source_rows AS rows
|
|
231
|
-
WHERE rows.
|
|
232
|
-
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key
|
|
233
|
-
AND rows.entity_kind = requested.entity_kind AND rows.entity_key = requested.entity_key
|
|
295
|
+
WHERE rows.root_id = roots.id AND ${sql.unsafe(ENTITY_MATCH)}
|
|
234
296
|
OFFSET 0
|
|
235
297
|
) AS rows
|
|
236
298
|
`
|
|
237
299
|
// Keep each JSON request as an indexed identity lookup even with fresh/stale statistics.
|
|
238
300
|
// Flattening this LATERAL caused a cold 10k-root run to spend seconds on each state page.
|
|
239
301
|
return sql<PgOntologySourceAssertionRow[]>`
|
|
240
|
-
SELECT selected.* FROM jsonb_to_recordset(${jsonParameter(sql,
|
|
241
|
-
AS requested(
|
|
302
|
+
SELECT selected.* FROM jsonb_to_recordset(${jsonParameter(sql, input.refs.map(requestedEntity))})
|
|
303
|
+
AS requested(${sql.unsafe(REQUESTED_ENTITY)}, root_keys TEXT[])
|
|
242
304
|
CROSS JOIN LATERAL (
|
|
243
|
-
SELECT
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
305
|
+
SELECT ${sourceAssertionColumns(sql)} FROM ontology_source_roots AS roots
|
|
306
|
+
JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
307
|
+
JOIN ontology_source_rows AS rows ON rows.root_id = roots.id AND ${sql.unsafe(ENTITY_MATCH)}
|
|
308
|
+
WHERE roots.version_id = ${version} AND roots.root_key = ANY(requested.root_keys)
|
|
247
309
|
${previous}
|
|
248
310
|
OFFSET 0
|
|
249
311
|
) AS selected
|