@sixb/pg 0.1.20 → 0.1.22
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 +1 -0
- package/dist/agents/agent-storage.d.ts +8 -2
- package/dist/agents/agent-storage.d.ts.map +1 -1
- package/dist/agents/approvals.d.ts +48 -0
- package/dist/agents/approvals.d.ts.map +1 -0
- package/dist/agents/checkpoints.d.ts +1 -0
- package/dist/agents/checkpoints.d.ts.map +1 -1
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/memory.d.ts +19 -0
- package/dist/agents/memory.d.ts.map +1 -0
- package/dist/agents/messages.d.ts.map +1 -1
- package/dist/agents/rows.d.ts +15 -1
- package/dist/agents/rows.d.ts.map +1 -1
- package/dist/agents/runs.d.ts +3 -1
- package/dist/agents/runs.d.ts.map +1 -1
- package/dist/agents/settings.d.ts +29 -0
- package/dist/agents/settings.d.ts.map +1 -0
- package/dist/agents/threads.d.ts +10 -1
- package/dist/agents/threads.d.ts.map +1 -1
- package/dist/ai-cost-groups.d.ts.map +1 -1
- package/dist/auth-storage/auth-storage.d.ts +3 -2
- package/dist/auth-storage/auth-storage.d.ts.map +1 -1
- package/dist/auth-storage/sessions.d.ts +2 -1
- package/dist/auth-storage/sessions.d.ts.map +1 -1
- package/dist/auth-storage/users.d.ts +8 -4
- package/dist/auth-storage/users.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6001 -2923
- package/dist/index.js.map +45 -37
- package/dist/migrations.d.ts.map +1 -1
- package/dist/objects/conditional-reader.d.ts +17 -0
- package/dist/objects/conditional-reader.d.ts.map +1 -0
- package/dist/objects/query-compiler.d.ts +27 -1
- package/dist/objects/query-compiler.d.ts.map +1 -1
- package/dist/objects/query-indexes.d.ts.map +1 -1
- package/dist/objects/read-scope.d.ts +10 -1
- package/dist/objects/read-scope.d.ts.map +1 -1
- package/dist/objects/reader.d.ts +2 -0
- package/dist/objects/reader.d.ts.map +1 -1
- package/dist/objects/scoped-reader.d.ts +12 -0
- package/dist/objects/scoped-reader.d.ts.map +1 -0
- package/dist/objects/selected-reader.d.ts +1 -0
- package/dist/objects/selected-reader.d.ts.map +1 -1
- package/dist/objects/storage.d.ts +7 -1
- package/dist/objects/storage.d.ts.map +1 -1
- package/dist/ontology-storage/index.d.ts +2 -0
- package/dist/ontology-storage/index.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-session.d.ts +6 -0
- package/dist/ontology-storage/materialization-session.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-writer.d.ts +15 -0
- package/dist/ontology-storage/materialization-writer.d.ts.map +1 -1
- package/dist/ontology-storage/materializations.d.ts.map +1 -1
- package/dist/ontology-storage/replacement-plans.d.ts.map +1 -1
- package/dist/ontology-storage/sources.d.ts +2 -1
- package/dist/ontology-storage/sources.d.ts.map +1 -1
- package/dist/ontology-storage/unique-values.d.ts +60 -0
- package/dist/ontology-storage/unique-values.d.ts.map +1 -0
- package/dist/pg-action-run-storage.d.ts +4 -0
- package/dist/pg-action-run-storage.d.ts.map +1 -1
- package/dist/pg-activity-storage.d.ts +22 -0
- package/dist/pg-activity-storage.d.ts.map +1 -0
- package/dist/pg-ai-cost-storage.d.ts.map +1 -1
- package/dist/pg-ai-usage-storage.d.ts +8 -1
- package/dist/pg-ai-usage-storage.d.ts.map +1 -1
- package/dist/pg-artifact-storage.d.ts +26 -0
- package/dist/pg-artifact-storage.d.ts.map +1 -0
- package/dist/pg-execution-storage.d.ts +3 -1
- package/dist/pg-execution-storage.d.ts.map +1 -1
- package/dist/pg-workflow-run-storage.d.ts +4 -0
- package/dist/pg-workflow-run-storage.d.ts.map +1 -1
- package/dist/share-grant-storage.d.ts +2 -1
- package/dist/share-grant-storage.d.ts.map +1 -1
- package/dist/storage-errors.d.ts +2 -0
- package/dist/storage-errors.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/agents/agent-storage.ts +11 -2
- package/src/agents/approvals.ts +400 -0
- package/src/agents/checkpoints.ts +8 -4
- package/src/agents/index.ts +1 -0
- package/src/agents/memory.ts +175 -0
- package/src/agents/messages.ts +14 -7
- package/src/agents/rows.ts +46 -1
- package/src/agents/runs.ts +154 -22
- package/src/agents/settings.ts +105 -0
- package/src/agents/threads.ts +379 -3
- package/src/ai-cost-groups.ts +11 -1
- package/src/auth-storage/auth-storage.ts +45 -13
- package/src/auth-storage/sessions.ts +20 -0
- package/src/auth-storage/users.ts +105 -43
- package/src/index.ts +10 -0
- package/src/migrations/063-artifacts.sql +67 -0
- package/src/migrations/064-drop-output-diagnostics.sql +14 -0
- package/src/migrations/065-plan-work-payload-statistics.sql +5 -0
- package/src/migrations/066-activity.sql +61 -0
- package/src/migrations/067-activity-timeline.sql +15 -0
- package/src/migrations/068-activity-interactions.sql +20 -0
- package/src/migrations/069-artifact-kinds.sql +4 -0
- package/src/migrations/070-artifact-state.sql +16 -0
- package/src/migrations/071-agent-thread-participants.sql +16 -0
- package/src/migrations/072-forked-context-checkpoints.sql +23 -0
- package/src/migrations/073-unique-values.sql +25 -0
- package/src/migrations/074-generated-media-usage.sql +21 -0
- package/src/migrations/075-model-call-files.sql +11 -0
- package/src/migrations/076-agent-memory.sql +33 -0
- package/src/migrations/077-artifact-threads.sql +21 -0
- package/src/migrations/078-agent-approvals.sql +58 -0
- package/src/migrations/079-agent-approval-resume.sql +20 -0
- package/src/migrations/080-subagent-threads.sql +145 -0
- package/src/migrations/081-subagent-questions.sql +6 -0
- package/src/migrations.ts +52 -0
- package/src/objects/conditional-reader.ts +44 -0
- package/src/objects/prepare-queries.ts +1 -1
- package/src/objects/query-compiler.ts +96 -33
- package/src/objects/query-indexes.ts +29 -10
- package/src/objects/read-scope.ts +140 -25
- package/src/objects/reader.ts +50 -6
- package/src/objects/scoped-reader.ts +101 -0
- package/src/objects/selected-reader.ts +16 -76
- package/src/objects/storage.ts +24 -5
- package/src/objects/text-count-index.ts +1 -0
- package/src/ontology-storage/index.ts +3 -0
- package/src/ontology-storage/materialization-session.ts +8 -0
- package/src/ontology-storage/materialization-writer.ts +76 -1
- package/src/ontology-storage/materializations.ts +20 -1
- package/src/ontology-storage/replacement-plans.ts +25 -7
- package/src/ontology-storage/sources.ts +37 -0
- package/src/ontology-storage/unique-values.ts +274 -0
- package/src/pg-action-run-storage.ts +23 -0
- package/src/pg-activity-storage.ts +486 -0
- package/src/pg-ai-cost-storage.ts +62 -12
- package/src/pg-ai-usage-storage.ts +90 -1
- package/src/pg-artifact-storage.ts +521 -0
- package/src/pg-execution-storage.ts +83 -0
- package/src/pg-workflow-run-storage.ts +23 -0
- package/src/share-grant-storage.ts +21 -0
- package/src/storage-errors.ts +7 -0
- package/src/transactions.ts +1 -1
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
CompiledConditionalObjectReadScope,
|
|
3
|
+
CompiledSelectedObjectReadScope,
|
|
4
|
+
} from "@sixb/core/storage"
|
|
2
5
|
import {
|
|
3
6
|
type CompiledPgObjectQuery,
|
|
4
7
|
compilePgObjectStatement,
|
|
8
|
+
compilePredicate,
|
|
5
9
|
type PgObjectQuerySource,
|
|
6
10
|
} from "./query-compiler"
|
|
7
11
|
|
|
@@ -30,16 +34,78 @@ export function compilePgSelectedObjectReadSource(
|
|
|
30
34
|
objects: scope.objects,
|
|
31
35
|
steps: scope.steps,
|
|
32
36
|
})
|
|
33
|
-
const
|
|
37
|
+
const source: PgSelectedObjectReadSource = {
|
|
38
|
+
...scopedQuerySource(compileSelectedReadScopeCte(scopeJson, projectId)),
|
|
39
|
+
objectPropertyPermissionsTable: "_sixb_scope_object_permissions",
|
|
40
|
+
traversalProbe: { sql: "", args: [] },
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const traversalProbe = compilePgObjectStatement(
|
|
44
|
+
`
|
|
45
|
+
SELECT COUNT(*)::bigint AS total
|
|
46
|
+
FROM (
|
|
47
|
+
SELECT 1 AS traversal_fact
|
|
48
|
+
FROM _sixb_scope_root_facts
|
|
49
|
+
|
|
50
|
+
UNION ALL
|
|
51
|
+
|
|
52
|
+
SELECT 1 AS traversal_fact
|
|
53
|
+
FROM _sixb_scope_reached_links
|
|
54
|
+
|
|
55
|
+
LIMIT ?
|
|
56
|
+
) AS bounded_traversal_facts
|
|
57
|
+
`,
|
|
58
|
+
[BigInt(maxTraversalFacts) + 1n],
|
|
59
|
+
source
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return Object.freeze({ ...source, traversalProbe })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Prepare a conditional scope as PostgreSQL relations that filter stored rows as they are read.
|
|
67
|
+
*
|
|
68
|
+
* Both relations are NOT MATERIALIZED, so PostgreSQL inlines them into each statement: its
|
|
69
|
+
* filters, index scans and LIMIT reach the stored tables, and the conditions become one more
|
|
70
|
+
* filter. An object of a conditioned type is visible while its condition matches; a link while
|
|
71
|
+
* each of its conditioned endpoints is a visible object.
|
|
72
|
+
*/
|
|
73
|
+
export function compilePgConditionalObjectReadSource(
|
|
74
|
+
projectId: string,
|
|
75
|
+
scope: CompiledConditionalObjectReadScope
|
|
76
|
+
): PgObjectQuerySource {
|
|
77
|
+
const filters = new Map(
|
|
78
|
+
scope.conditions.map((condition) => [
|
|
79
|
+
condition.objectTypeId,
|
|
80
|
+
compilePredicate(condition.predicate),
|
|
81
|
+
])
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return Object.freeze({
|
|
85
|
+
...scopedQuerySource(compileConditionalReadScopeCte(projectId, filters)),
|
|
86
|
+
// One type's visible objects are the stored ones its own condition matches.
|
|
87
|
+
objectsOfType: (objectTypeId: string) => {
|
|
88
|
+
const filter = filters.get(objectTypeId)
|
|
89
|
+
return filter ? { table: "objects", filter } : { table: "objects" }
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface CompiledReadScopeCte {
|
|
95
|
+
readonly sql: string
|
|
96
|
+
readonly args: readonly unknown[]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Prefix every statement of a query with the scope's relations, ahead of its own arguments. */
|
|
100
|
+
function scopedQuerySource(compiled: CompiledReadScopeCte): PgObjectQuerySource {
|
|
34
101
|
const wrapStatement = (sql: string, args: readonly unknown[] = []) => ({
|
|
35
102
|
sql: `${compiled.sql}\n${sql}`,
|
|
36
103
|
args: [...compiled.args, ...args],
|
|
37
104
|
})
|
|
38
105
|
|
|
39
|
-
|
|
106
|
+
return {
|
|
40
107
|
objectsTable: "_sixb_scope_objects",
|
|
41
108
|
linksTable: "_sixb_scope_links",
|
|
42
|
-
objectPropertyPermissionsTable: "_sixb_scope_object_permissions",
|
|
43
109
|
wrapStatement,
|
|
44
110
|
wrapQuery: (query: CompiledPgObjectQuery): CompiledPgObjectQuery => ({
|
|
45
111
|
...query,
|
|
@@ -58,34 +124,83 @@ export function compilePgSelectedObjectReadSource(
|
|
|
58
124
|
}
|
|
59
125
|
: {}),
|
|
60
126
|
}),
|
|
61
|
-
traversalProbe: { sql: "", args: [] },
|
|
62
127
|
}
|
|
128
|
+
}
|
|
63
129
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
FROM _sixb_scope_root_facts
|
|
130
|
+
/** Conditions keyed by their concrete type, each compiled over that type's `properties`. */
|
|
131
|
+
type ConditionFilters = ReadonlyMap<
|
|
132
|
+
string,
|
|
133
|
+
{ readonly sql: string; readonly args: readonly unknown[] }
|
|
134
|
+
>
|
|
70
135
|
|
|
71
|
-
|
|
136
|
+
function compileConditionalReadScopeCte(
|
|
137
|
+
projectId: string,
|
|
138
|
+
filters: ConditionFilters
|
|
139
|
+
): CompiledReadScopeCte {
|
|
140
|
+
if (filters.size === 0) {
|
|
141
|
+
return {
|
|
142
|
+
sql: `
|
|
143
|
+
WITH
|
|
144
|
+
_sixb_scope_objects AS NOT MATERIALIZED (
|
|
145
|
+
SELECT * FROM objects WHERE project_id = ?
|
|
146
|
+
),
|
|
147
|
+
_sixb_scope_links AS NOT MATERIALIZED (
|
|
148
|
+
SELECT * FROM links WHERE project_id = ?
|
|
149
|
+
)
|
|
150
|
+
`,
|
|
151
|
+
args: [projectId, projectId],
|
|
152
|
+
}
|
|
153
|
+
}
|
|
72
154
|
|
|
73
|
-
|
|
74
|
-
|
|
155
|
+
const conditionedTypeArgs = [...filters.keys()]
|
|
156
|
+
const conditionedTypes = conditionedTypeArgs.map(() => "?::text").join(", ")
|
|
75
157
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
158
|
+
const matches: string[] = []
|
|
159
|
+
const matchArgs: unknown[] = []
|
|
160
|
+
for (const [objectTypeId, filter] of filters) {
|
|
161
|
+
matches.push(`(object_type_id = ?::text AND ${filter.sql})`)
|
|
162
|
+
matchArgs.push(objectTypeId, ...filter.args)
|
|
163
|
+
}
|
|
82
164
|
|
|
83
|
-
|
|
84
|
-
|
|
165
|
+
const visibleEndpoint = (end: "source" | "target") => `
|
|
166
|
+
(
|
|
167
|
+
stored.${end}_type_id NOT IN (${conditionedTypes})
|
|
168
|
+
OR EXISTS (
|
|
169
|
+
SELECT 1
|
|
170
|
+
FROM _sixb_scope_objects AS endpoint
|
|
171
|
+
WHERE endpoint.project_id = stored.project_id
|
|
172
|
+
AND endpoint.object_type_id = stored.${end}_type_id
|
|
173
|
+
AND endpoint.primary_id = stored.${end}_id
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
`
|
|
85
177
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
178
|
+
return {
|
|
179
|
+
sql: `
|
|
180
|
+
WITH
|
|
181
|
+
_sixb_scope_objects AS NOT MATERIALIZED (
|
|
182
|
+
SELECT *
|
|
183
|
+
FROM objects
|
|
184
|
+
WHERE project_id = ?
|
|
185
|
+
AND (object_type_id NOT IN (${conditionedTypes}) OR ${matches.join(" OR ")})
|
|
186
|
+
),
|
|
187
|
+
_sixb_scope_links AS NOT MATERIALIZED (
|
|
188
|
+
SELECT stored.*
|
|
189
|
+
FROM links AS stored
|
|
190
|
+
WHERE stored.project_id = ?
|
|
191
|
+
AND ${visibleEndpoint("source")}
|
|
192
|
+
AND ${visibleEndpoint("target")}
|
|
193
|
+
)
|
|
194
|
+
`,
|
|
195
|
+
args: [
|
|
196
|
+
projectId,
|
|
197
|
+
...conditionedTypeArgs,
|
|
198
|
+
...matchArgs,
|
|
199
|
+
projectId,
|
|
200
|
+
...conditionedTypeArgs,
|
|
201
|
+
...conditionedTypeArgs,
|
|
202
|
+
],
|
|
203
|
+
}
|
|
89
204
|
}
|
|
90
205
|
|
|
91
206
|
function compileSelectedReadScopeCte(scopeJson: string, projectId: string): CompiledReadScopeCte {
|
package/src/objects/reader.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
compilePgObjectStatement,
|
|
31
31
|
DEFAULT_OBJECT_QUERY_SOURCE,
|
|
32
32
|
type PgObjectQuerySource,
|
|
33
|
+
pgObjectsOfType,
|
|
33
34
|
} from "./query-compiler"
|
|
34
35
|
import { findPgSortIndexBound } from "./query-indexes"
|
|
35
36
|
import type { PgSelectedObjectReadSource } from "./read-scope"
|
|
@@ -258,13 +259,14 @@ export class PgObjectReader {
|
|
|
258
259
|
}): Promise<ObjectRow | null> {
|
|
259
260
|
const sql = this.sql
|
|
260
261
|
const source = this.source
|
|
262
|
+
const objects = pgObjectsOfType(source, params.objectTypeId)
|
|
261
263
|
const statement = compilePgObjectStatement(
|
|
262
264
|
`
|
|
263
265
|
SELECT *
|
|
264
|
-
FROM ${
|
|
265
|
-
WHERE project_id = ? AND object_type_id = ? AND primary_id = ?
|
|
266
|
+
FROM ${objects.table}
|
|
267
|
+
WHERE project_id = ? AND object_type_id = ? AND primary_id = ? ${objects.filter}
|
|
266
268
|
`,
|
|
267
|
-
[params.projectId, params.objectTypeId, params.primaryId],
|
|
269
|
+
[params.projectId, params.objectTypeId, params.primaryId, ...objects.args],
|
|
268
270
|
source
|
|
269
271
|
)
|
|
270
272
|
const [row] = await sql.unsafe<ObjectDatabaseRow[]>(
|
|
@@ -275,6 +277,44 @@ export class PgObjectReader {
|
|
|
275
277
|
return row ? rowToObject(row) : null
|
|
276
278
|
}
|
|
277
279
|
|
|
280
|
+
/** On a selected source, the holder must be in scope and selected for the property. */
|
|
281
|
+
async getByUniqueValue(
|
|
282
|
+
params: Parameters<ObjectReadStorage["getByUniqueValue"]>[0]
|
|
283
|
+
): Promise<ObjectRow | null> {
|
|
284
|
+
const source = this.source
|
|
285
|
+
const selected = "objectPropertyPermissionsTable" in source
|
|
286
|
+
const permitted = selected
|
|
287
|
+
? `AND EXISTS (
|
|
288
|
+
SELECT 1 FROM ${source.objectPropertyPermissionsTable} AS permitted
|
|
289
|
+
WHERE permitted.project_id = objects.project_id
|
|
290
|
+
AND permitted.object_type_id = objects.object_type_id
|
|
291
|
+
AND permitted.primary_id = objects.primary_id
|
|
292
|
+
AND permitted.property_id = held.property_id
|
|
293
|
+
)`
|
|
294
|
+
: ""
|
|
295
|
+
const statement = compilePgObjectStatement(
|
|
296
|
+
`
|
|
297
|
+
SELECT objects.*
|
|
298
|
+
FROM object_unique_values AS held
|
|
299
|
+
JOIN ${source.objectsTable} AS objects
|
|
300
|
+
ON objects.project_id = held.project_id
|
|
301
|
+
AND objects.object_type_id = held.object_type_id
|
|
302
|
+
AND objects.primary_id = held.primary_id
|
|
303
|
+
WHERE held.project_id = ? AND held.object_type_id = ? AND held.property_id = ?
|
|
304
|
+
AND held.value_hash = sha256(convert_to(?, 'UTF8')) AND held.value_key = ?
|
|
305
|
+
${permitted}
|
|
306
|
+
`,
|
|
307
|
+
[params.projectId, params.objectTypeId, params.propertyId, params.valueKey, params.valueKey],
|
|
308
|
+
source
|
|
309
|
+
)
|
|
310
|
+
const [row] = await this.sql.unsafe<ObjectDatabaseRow[]>(
|
|
311
|
+
statement.sql,
|
|
312
|
+
statement.args as SqlParameter[]
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
return row ? rowToObject(row) : null
|
|
316
|
+
}
|
|
317
|
+
|
|
278
318
|
async selectsObjectProperties(
|
|
279
319
|
params: Parameters<ObjectReadStorage["selectsObjectProperties"]>[0]
|
|
280
320
|
): Promise<readonly boolean[]> {
|
|
@@ -592,11 +632,15 @@ export class PgObjectReader {
|
|
|
592
632
|
const order = params.order ?? "desc"
|
|
593
633
|
const orderColumn =
|
|
594
634
|
orderBy === "primaryId" ? "primary_id" : orderBy === "createdAt" ? "created_at" : "updated_at"
|
|
635
|
+
const typed =
|
|
636
|
+
typeof params.objectTypeId === "string"
|
|
637
|
+
? pgObjectsOfType(source, params.objectTypeId)
|
|
638
|
+
: { table: source.objectsTable, filter: "", args: [] }
|
|
595
639
|
const filters = ["project_id = ?"]
|
|
596
640
|
const args: unknown[] = [params.projectId]
|
|
597
641
|
if (typeof params.objectTypeId === "string") {
|
|
598
|
-
filters.push(
|
|
599
|
-
args.push(params.objectTypeId)
|
|
642
|
+
filters.push(`object_type_id = ? ${typed.filter}`)
|
|
643
|
+
args.push(params.objectTypeId, ...typed.args)
|
|
600
644
|
} else if (params.objectTypeId !== undefined) {
|
|
601
645
|
filters.push("object_type_id IN (SELECT jsonb_array_elements_text(?::text::jsonb))")
|
|
602
646
|
args.push(JSON.stringify(params.objectTypeId))
|
|
@@ -626,7 +670,7 @@ export class PgObjectReader {
|
|
|
626
670
|
args.push(params.createdBefore)
|
|
627
671
|
}
|
|
628
672
|
|
|
629
|
-
const objectsTable =
|
|
673
|
+
const objectsTable = typed.table
|
|
630
674
|
const whereSql = filters.join(" AND ")
|
|
631
675
|
const countStatement = compilePgObjectStatement(
|
|
632
676
|
`SELECT COUNT(*)::int AS total FROM ${objectsTable} WHERE ${whereSql}`,
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { ObjectQuery } from "@sixb/core"
|
|
2
|
+
import type {
|
|
3
|
+
ObjectQueryCapabilities,
|
|
4
|
+
ObjectReadExecutionLimits,
|
|
5
|
+
ObjectReadStorage,
|
|
6
|
+
} from "@sixb/core/storage"
|
|
7
|
+
import {
|
|
8
|
+
assertObjectReaderProject,
|
|
9
|
+
assertObjectReadFacetCount,
|
|
10
|
+
assertObjectReadOutputWithinLimit,
|
|
11
|
+
} from "@sixb/core/storage"
|
|
12
|
+
import { assertLinkQueryLimit, type PgObjectReader } from "./reader"
|
|
13
|
+
|
|
14
|
+
/** Run one terminal on a reader over the scope's source; a query terminal passes its query. */
|
|
15
|
+
export type PgScopedRead = <T>(
|
|
16
|
+
run: (reader: PgObjectReader) => Promise<T>,
|
|
17
|
+
query?: ObjectQuery
|
|
18
|
+
) => Promise<T>
|
|
19
|
+
|
|
20
|
+
/** Answer every terminal through one scoped source; the scope decides how each read runs. */
|
|
21
|
+
export function createPgScopedReader(
|
|
22
|
+
params: {
|
|
23
|
+
projectId: string
|
|
24
|
+
read: PgScopedRead
|
|
25
|
+
limits?: ObjectReadExecutionLimits
|
|
26
|
+
},
|
|
27
|
+
queryCapabilities: () => ObjectQueryCapabilities
|
|
28
|
+
): ObjectReadStorage {
|
|
29
|
+
const { projectId, limits } = params
|
|
30
|
+
const assertProject = (actualProjectId: string): void =>
|
|
31
|
+
assertObjectReaderProject(projectId, actualProjectId)
|
|
32
|
+
const read = async <T>(
|
|
33
|
+
run: (reader: PgObjectReader) => Promise<T>,
|
|
34
|
+
query?: ObjectQuery
|
|
35
|
+
): Promise<T> => {
|
|
36
|
+
const value = await params.read(run, query)
|
|
37
|
+
if (limits) assertObjectReadOutputWithinLimit(value, limits)
|
|
38
|
+
return value
|
|
39
|
+
}
|
|
40
|
+
const readMap = async <TKey, TValue>(
|
|
41
|
+
run: (reader: PgObjectReader) => Promise<Map<TKey, TValue>>
|
|
42
|
+
): Promise<Map<TKey, TValue>> => {
|
|
43
|
+
const value = await params.read(run)
|
|
44
|
+
if (limits) assertObjectReadOutputWithinLimit([...value.entries()], limits)
|
|
45
|
+
return value
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return Object.freeze({
|
|
49
|
+
queryCapabilities,
|
|
50
|
+
queryObjects: async (input) => {
|
|
51
|
+
assertProject(input.projectId)
|
|
52
|
+
return read((reader) => reader.queryObjects(input), input.query)
|
|
53
|
+
},
|
|
54
|
+
countObjects: async (input) => {
|
|
55
|
+
assertProject(input.projectId)
|
|
56
|
+
return read((reader) => reader.countObjects(input), input.query)
|
|
57
|
+
},
|
|
58
|
+
existsObjects: async (input) => {
|
|
59
|
+
assertProject(input.projectId)
|
|
60
|
+
return read((reader) => reader.existsObjects(input), input.query)
|
|
61
|
+
},
|
|
62
|
+
facetObjects: async (input) => {
|
|
63
|
+
assertProject(input.projectId)
|
|
64
|
+
assertObjectReadFacetCount(input.facets.length)
|
|
65
|
+
return read((reader) => reader.facetObjects(input), input.query)
|
|
66
|
+
},
|
|
67
|
+
getByPrimaryId: async (input) => {
|
|
68
|
+
assertProject(input.projectId)
|
|
69
|
+
return read((reader) => reader.getByPrimaryId(input))
|
|
70
|
+
},
|
|
71
|
+
getByUniqueValue: async (input) => {
|
|
72
|
+
assertProject(input.projectId)
|
|
73
|
+
return read((reader) => reader.getByUniqueValue(input))
|
|
74
|
+
},
|
|
75
|
+
selectsObjectProperties: async (input) => {
|
|
76
|
+
assertProject(input.projectId)
|
|
77
|
+
return read((reader) => reader.selectsObjectProperties(input))
|
|
78
|
+
},
|
|
79
|
+
listLinks: async (input) => {
|
|
80
|
+
assertProject(input.projectId)
|
|
81
|
+
return read((reader) => reader.listLinks(input))
|
|
82
|
+
},
|
|
83
|
+
getByPrimaryIdBatch: async (input) => {
|
|
84
|
+
assertProject(input.projectId)
|
|
85
|
+
return readMap((reader) => reader.getByPrimaryIdBatch(input))
|
|
86
|
+
},
|
|
87
|
+
listLinksBatch: async (input) => {
|
|
88
|
+
assertProject(input.projectId)
|
|
89
|
+
return readMap((reader) => reader.listLinksBatch(input))
|
|
90
|
+
},
|
|
91
|
+
queryLinks: async (input) => {
|
|
92
|
+
assertProject(input.projectId)
|
|
93
|
+
assertLinkQueryLimit(input.limit)
|
|
94
|
+
return read((reader) => reader.queryLinks(input))
|
|
95
|
+
},
|
|
96
|
+
list: async (input) => {
|
|
97
|
+
assertProject(input.projectId)
|
|
98
|
+
return read((reader) => reader.list(input))
|
|
99
|
+
},
|
|
100
|
+
} satisfies ObjectReadStorage)
|
|
101
|
+
}
|
|
@@ -4,19 +4,14 @@ import type {
|
|
|
4
4
|
ObjectReadExecutionLimits,
|
|
5
5
|
ObjectReadStorage,
|
|
6
6
|
} from "@sixb/core/storage"
|
|
7
|
-
import {
|
|
8
|
-
assertObjectReaderProject,
|
|
9
|
-
assertObjectReadFacetCount,
|
|
10
|
-
assertObjectReadOutputWithinLimit,
|
|
11
|
-
ObjectReadLimitExceededError,
|
|
12
|
-
snapshotObjectReadExecutionLimits,
|
|
13
|
-
} from "@sixb/core/storage"
|
|
7
|
+
import { ObjectReadLimitExceededError, snapshotObjectReadExecutionLimits } from "@sixb/core/storage"
|
|
14
8
|
import type { SQLClient, SqlParameter } from "../pg-client"
|
|
15
9
|
import { type PgStoreClient, runPgRepeatableReadTransaction } from "../transactions"
|
|
16
10
|
import { compilePgSelectedObjectReadSource, type PgSelectedObjectReadSource } from "./read-scope"
|
|
11
|
+
import { PgObjectReader } from "./reader"
|
|
12
|
+
import { createPgScopedReader } from "./scoped-reader"
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
/** Keep the traversal probe and every terminal statement on one repeatable-read snapshot. */
|
|
20
15
|
export function createPgSelectedReader(
|
|
21
16
|
sql: PgStoreClient,
|
|
22
17
|
params: {
|
|
@@ -33,74 +28,19 @@ export function createPgSelectedReader(
|
|
|
33
28
|
params.scope,
|
|
34
29
|
limits.maxTraversalFacts
|
|
35
30
|
)
|
|
36
|
-
const assertProject = (actualProjectId: string): void =>
|
|
37
|
-
assertObjectReaderProject(projectId, actualProjectId)
|
|
38
|
-
const read = <T>(run: (reader: PgObjectReader) => Promise<T>): Promise<T> =>
|
|
39
|
-
runPgRepeatableReadTransaction(sql, async (tx) => {
|
|
40
|
-
await assertTraversalBudget(tx, source, limits.maxTraversalFacts)
|
|
41
|
-
const value = await run(new PgObjectReader(tx, source))
|
|
42
|
-
assertObjectReadOutputWithinLimit(value, limits)
|
|
43
|
-
return value
|
|
44
|
-
})
|
|
45
|
-
const readMap = <TKey, TValue>(
|
|
46
|
-
run: (reader: PgObjectReader) => Promise<Map<TKey, TValue>>
|
|
47
|
-
): Promise<Map<TKey, TValue>> =>
|
|
48
|
-
runPgRepeatableReadTransaction(sql, async (tx) => {
|
|
49
|
-
await assertTraversalBudget(tx, source, limits.maxTraversalFacts)
|
|
50
|
-
const value = await run(new PgObjectReader(tx, source))
|
|
51
|
-
assertObjectReadOutputWithinLimit([...value.entries()], limits)
|
|
52
|
-
return value
|
|
53
|
-
})
|
|
54
31
|
|
|
55
|
-
return
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return read((reader) => reader.existsObjects(input))
|
|
68
|
-
},
|
|
69
|
-
facetObjects: async (input) => {
|
|
70
|
-
assertProject(input.projectId)
|
|
71
|
-
assertObjectReadFacetCount(input.facets.length)
|
|
72
|
-
return read((reader) => reader.facetObjects(input))
|
|
73
|
-
},
|
|
74
|
-
getByPrimaryId: async (input) => {
|
|
75
|
-
assertProject(input.projectId)
|
|
76
|
-
return read((reader) => reader.getByPrimaryId(input))
|
|
77
|
-
},
|
|
78
|
-
selectsObjectProperties: async (input) => {
|
|
79
|
-
assertProject(input.projectId)
|
|
80
|
-
return read((reader) => reader.selectsObjectProperties(input))
|
|
81
|
-
},
|
|
82
|
-
listLinks: async (input) => {
|
|
83
|
-
assertProject(input.projectId)
|
|
84
|
-
return read((reader) => reader.listLinks(input))
|
|
85
|
-
},
|
|
86
|
-
getByPrimaryIdBatch: async (input) => {
|
|
87
|
-
assertProject(input.projectId)
|
|
88
|
-
return readMap((reader) => reader.getByPrimaryIdBatch(input))
|
|
89
|
-
},
|
|
90
|
-
listLinksBatch: async (input) => {
|
|
91
|
-
assertProject(input.projectId)
|
|
92
|
-
return readMap((reader) => reader.listLinksBatch(input))
|
|
93
|
-
},
|
|
94
|
-
queryLinks: async (input) => {
|
|
95
|
-
assertProject(input.projectId)
|
|
96
|
-
assertLinkQueryLimit(input.limit)
|
|
97
|
-
return read((reader) => reader.queryLinks(input))
|
|
98
|
-
},
|
|
99
|
-
list: async (input) => {
|
|
100
|
-
assertProject(input.projectId)
|
|
101
|
-
return read((reader) => reader.list(input))
|
|
102
|
-
},
|
|
103
|
-
} satisfies ObjectReadStorage)
|
|
32
|
+
return createPgScopedReader(
|
|
33
|
+
{
|
|
34
|
+
projectId,
|
|
35
|
+
read: (run) =>
|
|
36
|
+
runPgRepeatableReadTransaction(sql, async (tx) => {
|
|
37
|
+
await assertTraversalBudget(tx, source, limits.maxTraversalFacts)
|
|
38
|
+
return run(new PgObjectReader(tx, source))
|
|
39
|
+
}),
|
|
40
|
+
limits,
|
|
41
|
+
},
|
|
42
|
+
queryCapabilities
|
|
43
|
+
)
|
|
104
44
|
}
|
|
105
45
|
|
|
106
46
|
async function assertTraversalBudget(
|
package/src/objects/storage.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { hasVectorProfile } from "@sixb/core/internal/query"
|
|
2
2
|
import type {
|
|
3
|
+
CompiledConditionalObjectReadScope,
|
|
3
4
|
CompiledSelectedObjectReadScope,
|
|
4
5
|
CountObjectsInput,
|
|
5
6
|
CountObjectsResult,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
runPgRepeatableReadTransaction,
|
|
29
30
|
} from "../transactions"
|
|
30
31
|
import { PgAggregateAdmission } from "./aggregate-admission"
|
|
32
|
+
import { createPgConditionalReader } from "./conditional-reader"
|
|
31
33
|
import { DEFAULT_OBJECT_QUERY_SOURCE } from "./query-compiler"
|
|
32
34
|
import { assertLinkQueryLimit, PgObjectReader } from "./reader"
|
|
33
35
|
import {
|
|
@@ -71,6 +73,7 @@ const PG_OBJECT_QUERY_CAPABILITIES: ObjectQueryCapabilities = {
|
|
|
71
73
|
in: true,
|
|
72
74
|
exists: true,
|
|
73
75
|
contains: true,
|
|
76
|
+
constant: true,
|
|
74
77
|
},
|
|
75
78
|
sortKinds: {
|
|
76
79
|
property: true,
|
|
@@ -110,17 +113,17 @@ const PG_OBJECT_QUERY_CAPABILITIES: ObjectQueryCapabilities = {
|
|
|
110
113
|
|
|
111
114
|
/** PostgreSQL object provider; interactive reads share one source-aware reader. */
|
|
112
115
|
export class PgObjectStorage implements ObjectStorage {
|
|
116
|
+
private readonly aggregateAdmission?: PgAggregateAdmission
|
|
113
117
|
private readonly reader: PgObjectReader
|
|
114
118
|
|
|
115
119
|
constructor(
|
|
116
120
|
private readonly sql: PgStoreClient,
|
|
117
121
|
maxConcurrentAggregates = 1
|
|
118
122
|
) {
|
|
119
|
-
this.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
)
|
|
123
|
+
this.aggregateAdmission = canStartPgTransaction(sql)
|
|
124
|
+
? new PgAggregateAdmission(maxConcurrentAggregates)
|
|
125
|
+
: undefined
|
|
126
|
+
this.reader = new PgObjectReader(sql, DEFAULT_OBJECT_QUERY_SOURCE, this.aggregateAdmission)
|
|
124
127
|
}
|
|
125
128
|
|
|
126
129
|
queryCapabilities(): ObjectQueryCapabilities {
|
|
@@ -135,6 +138,16 @@ export class PgObjectStorage implements ObjectStorage {
|
|
|
135
138
|
return createPgSelectedReader(this.sql, params, () => this.queryCapabilities())
|
|
136
139
|
}
|
|
137
140
|
|
|
141
|
+
createConditionalReadScope(params: {
|
|
142
|
+
projectId: string
|
|
143
|
+
scope: CompiledConditionalObjectReadScope
|
|
144
|
+
}): ObjectReadStorage {
|
|
145
|
+
return createPgConditionalReader(this.sql, params, {
|
|
146
|
+
aggregateAdmission: this.aggregateAdmission,
|
|
147
|
+
queryCapabilities: () => this.queryCapabilities(),
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
138
151
|
async queryObjects(params: QueryObjectsInput): Promise<QueryObjectsResult> {
|
|
139
152
|
if (hasVectorProfile(params.query))
|
|
140
153
|
return runPgRepeatableReadTransaction(this.sql, (tx) =>
|
|
@@ -175,6 +188,12 @@ export class PgObjectStorage implements ObjectStorage {
|
|
|
175
188
|
return this.reader.getByPrimaryId(params)
|
|
176
189
|
}
|
|
177
190
|
|
|
191
|
+
async getByUniqueValue(
|
|
192
|
+
params: Parameters<ObjectReadStorage["getByUniqueValue"]>[0]
|
|
193
|
+
): Promise<ObjectRow | null> {
|
|
194
|
+
return this.reader.getByUniqueValue(params)
|
|
195
|
+
}
|
|
196
|
+
|
|
178
197
|
async selectsObjectProperties(
|
|
179
198
|
params: Parameters<ObjectReadStorage["selectsObjectProperties"]>[0]
|
|
180
199
|
): Promise<readonly boolean[]> {
|
|
@@ -401,6 +401,7 @@ function stringPredicate(
|
|
|
401
401
|
? `(${items.join(predicate.op === "and" ? " AND " : " OR ")})`
|
|
402
402
|
: undefined
|
|
403
403
|
}
|
|
404
|
+
if (predicate.op === "constant") return predicate.value ? "true" : "false"
|
|
404
405
|
if ((predicate.op !== "eq" && predicate.op !== "in") || predicate.scalarKind === "decimal")
|
|
405
406
|
return undefined
|
|
406
407
|
const column = columns.get(predicate.propertyId)
|
|
@@ -7,6 +7,7 @@ import { PgOntologyOutboxStorage } from "./outbox"
|
|
|
7
7
|
import { PgOntologyReplacementPlanStorage } from "./replacement-plans"
|
|
8
8
|
import type { PgRootOperation } from "./shared"
|
|
9
9
|
import { PgOntologySourceStorage } from "./sources"
|
|
10
|
+
import { PgOntologyUniqueValueStorage } from "./unique-values"
|
|
10
11
|
import { PgVectorIndexingStorage } from "./vector-indexing"
|
|
11
12
|
import { PgOntologyVectorStorage } from "./vectors"
|
|
12
13
|
|
|
@@ -18,6 +19,7 @@ export class PgOntologyStorage implements OntologyStorage {
|
|
|
18
19
|
readonly outbox: PgOntologyOutboxStorage
|
|
19
20
|
readonly vectorIndexing: PgVectorIndexingStorage
|
|
20
21
|
readonly vectors: PgOntologyVectorStorage
|
|
22
|
+
readonly uniqueValues: PgOntologyUniqueValueStorage
|
|
21
23
|
|
|
22
24
|
constructor(input: {
|
|
23
25
|
readonly sql: SQLClient
|
|
@@ -32,6 +34,7 @@ export class PgOntologyStorage implements OntologyStorage {
|
|
|
32
34
|
)
|
|
33
35
|
this.replacementPlans = new PgOntologyReplacementPlanStorage(input.sql)
|
|
34
36
|
this.outbox = new PgOntologyOutboxStorage(input.runRootOperation)
|
|
37
|
+
this.uniqueValues = new PgOntologyUniqueValueStorage(input.runRootOperation)
|
|
35
38
|
this.vectorIndexing = new PgVectorIndexingStorage(
|
|
36
39
|
input.sql,
|
|
37
40
|
input.runRootOperation,
|
|
@@ -35,6 +35,9 @@ export class PgMaterializationSessionState extends ProviderMaterializationSessio
|
|
|
35
35
|
stagedWorkCount = 0
|
|
36
36
|
changedObjects = 0
|
|
37
37
|
changedLinks = 0
|
|
38
|
+
changedUniqueValues = 0
|
|
39
|
+
/** What unique value items this session staged, known without reading its work back. */
|
|
40
|
+
readonly uniqueWork = { releases: false, claims: false }
|
|
38
41
|
workAnalyzed = false
|
|
39
42
|
/** Work staged on the session lives in a temp table; a plan-bound session reads its plan's. */
|
|
40
43
|
readonly workTable: string
|
|
@@ -156,6 +159,11 @@ export class PgMaterializationSessions {
|
|
|
156
159
|
throw error
|
|
157
160
|
}
|
|
158
161
|
session.stagedWorkCount += records.length
|
|
162
|
+
for (const { record } of records) {
|
|
163
|
+
if (record.kind !== "plan") continue
|
|
164
|
+
if (record.item.kind === "unique-value-release") session.uniqueWork.releases = true
|
|
165
|
+
if (record.item.kind === "unique-value-claim") session.uniqueWork.claims = true
|
|
166
|
+
}
|
|
159
167
|
}
|
|
160
168
|
|
|
161
169
|
/**
|