@sixb/pg 0.1.16 → 0.1.17
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 +125 -0
- package/dist/index.d.ts +15 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3201 -1473
- package/dist/index.js.map +32 -25
- package/dist/migrations.d.ts.map +1 -1
- package/dist/objects/aggregate-admission.d.ts +10 -0
- package/dist/objects/aggregate-admission.d.ts.map +1 -0
- package/dist/objects/preparation-lock.d.ts +6 -0
- package/dist/objects/preparation-lock.d.ts.map +1 -0
- package/dist/objects/prepare-queries.d.ts +5 -0
- package/dist/objects/prepare-queries.d.ts.map +1 -0
- package/dist/objects/query-compiler.d.ts +19 -0
- package/dist/objects/query-compiler.d.ts.map +1 -1
- package/dist/objects/query-indexes.d.ts +43 -0
- package/dist/objects/query-indexes.d.ts.map +1 -0
- package/dist/objects/reader.d.ts +7 -1
- package/dist/objects/reader.d.ts.map +1 -1
- package/dist/objects/storage.d.ts +1 -1
- package/dist/objects/storage.d.ts.map +1 -1
- package/dist/objects/text-count-index.d.ts +24 -0
- package/dist/objects/text-count-index.d.ts.map +1 -0
- 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 +18 -38
- package/dist/ontology-storage/materialization-session.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-state.d.ts +3 -26
- package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-writer.d.ts +51 -9
- package/dist/ontology-storage/materialization-writer.d.ts.map +1 -1
- package/dist/ontology-storage/materializations.d.ts +5 -7
- package/dist/ontology-storage/materializations.d.ts.map +1 -1
- package/dist/ontology-storage/outbox.d.ts.map +1 -1
- package/dist/ontology-storage/replacement-plans.d.ts +36 -0
- package/dist/ontology-storage/replacement-plans.d.ts.map +1 -0
- package/dist/ontology-storage/shared.d.ts +11 -5
- package/dist/ontology-storage/shared.d.ts.map +1 -1
- package/dist/ontology-storage/source-cleanup.d.ts.map +1 -1
- package/dist/ontology-storage/source-roots.d.ts +14 -1
- package/dist/ontology-storage/source-roots.d.ts.map +1 -1
- package/dist/ontology-storage/sources.d.ts +1 -2
- package/dist/ontology-storage/sources.d.ts.map +1 -1
- package/dist/pg-ai-cost-storage.d.ts.map +1 -1
- package/dist/pg-ai-limit-storage.d.ts.map +1 -1
- package/dist/pg-ai-usage-storage.d.ts +2 -0
- package/dist/pg-ai-usage-storage.d.ts.map +1 -1
- package/dist/pg-execution-storage.d.ts +28 -0
- package/dist/pg-execution-storage.d.ts.map +1 -1
- package/dist/pg-file-download-grant-storage.d.ts +11 -0
- package/dist/pg-file-download-grant-storage.d.ts.map +1 -0
- package/dist/pg-projection-run-storage.d.ts.map +1 -1
- package/dist/pg-rules-storage.d.ts.map +1 -1
- package/dist/transactions.d.ts +6 -2
- package/dist/transactions.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +53 -3
- package/src/migrations/053-replacement-plans.sql +72 -0
- package/src/migrations/054-slim-ontology-outbox.sql +108 -0
- package/src/migrations/055-object-query-preparation.sql +11 -0
- package/src/migrations/056-commit-touches.sql +41 -0
- package/src/migrations/057-audio-transcription.sql +10 -0
- package/src/migrations/058-reranking-model-kind.sql +11 -0
- package/src/migrations/059-file-download-grants.sql +16 -0
- package/src/migrations/060-drained-source-versions.sql +11 -0
- package/src/migrations.ts +20 -0
- package/src/objects/aggregate-admission.ts +21 -0
- package/src/objects/preparation-lock.ts +15 -0
- package/src/objects/prepare-queries.ts +181 -0
- package/src/objects/query-compiler.ts +404 -114
- package/src/objects/query-indexes.ts +348 -0
- package/src/objects/reader.ts +140 -51
- package/src/objects/storage.ts +15 -3
- package/src/objects/text-count-index.ts +455 -0
- package/src/ontology-storage/index.ts +3 -0
- package/src/ontology-storage/materialization-session.ts +116 -275
- package/src/ontology-storage/materialization-state.ts +8 -259
- package/src/ontology-storage/materialization-writer.ts +618 -508
- package/src/ontology-storage/materializations.ts +106 -289
- package/src/ontology-storage/outbox.ts +46 -15
- package/src/ontology-storage/replacement-plans.ts +1110 -0
- package/src/ontology-storage/shared.ts +27 -15
- package/src/ontology-storage/source-cleanup.ts +22 -10
- package/src/ontology-storage/source-roots.ts +35 -8
- package/src/ontology-storage/sources.ts +0 -30
- package/src/pg-ai-cost-storage.ts +12 -0
- package/src/pg-ai-limit-storage.ts +13 -2
- package/src/pg-ai-usage-storage.ts +8 -0
- package/src/pg-execution-storage.ts +8 -3
- package/src/pg-file-download-grant-storage.ts +76 -0
- package/src/pg-projection-run-storage.ts +6 -1
- package/src/pg-rules-storage.ts +7 -3
- package/src/transactions.ts +10 -3
|
@@ -22,6 +22,7 @@ export interface PgObjectQueryPageRow {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export interface CompiledPgObjectQuery {
|
|
25
|
+
traversalProbe?: { sql: string; args: unknown[]; limit: number }
|
|
25
26
|
vectorProbe?: { sql: string; args: unknown[]; limit: number }
|
|
26
27
|
sql: string
|
|
27
28
|
args: unknown[]
|
|
@@ -52,6 +53,11 @@ interface CompiledHasMoreProbe {
|
|
|
52
53
|
|
|
53
54
|
interface CompileContext {
|
|
54
55
|
probeLimit: boolean
|
|
56
|
+
startPredicate?: string
|
|
57
|
+
isolateSortOverflow?: boolean
|
|
58
|
+
/** A consuming operator supplies ordering; bounded inputs still keep their own order. */
|
|
59
|
+
unordered?: boolean
|
|
60
|
+
correlatedTraversal?: boolean
|
|
55
61
|
source: PgObjectQuerySource
|
|
56
62
|
}
|
|
57
63
|
|
|
@@ -105,7 +111,12 @@ const DEFAULT_EXPANSION_FANOUT = 1_000
|
|
|
105
111
|
export function compilePgObjectQuery(
|
|
106
112
|
projectId: string,
|
|
107
113
|
query: ObjectQuery,
|
|
108
|
-
options: {
|
|
114
|
+
options: {
|
|
115
|
+
includeTotal?: boolean
|
|
116
|
+
source?: PgObjectQuerySource
|
|
117
|
+
correlatedTraversal?: boolean
|
|
118
|
+
sortIndexBound?: string
|
|
119
|
+
} = {}
|
|
109
120
|
): CompiledPgObjectQuery {
|
|
110
121
|
if (hasVectorProfile(query) && !isVectorProfileQuery(query)) {
|
|
111
122
|
throw new ObjectQueryExecutionError(
|
|
@@ -114,14 +125,43 @@ export function compilePgObjectQuery(
|
|
|
114
125
|
)
|
|
115
126
|
}
|
|
116
127
|
const source = options.source ?? DEFAULT_OBJECT_QUERY_SOURCE
|
|
117
|
-
|
|
128
|
+
let compiled = source.wrapQuery(
|
|
118
129
|
compileObjectQueryInternal(projectId, query, {
|
|
119
130
|
probeLimit: options.includeTotal === false,
|
|
131
|
+
correlatedTraversal: options.correlatedTraversal,
|
|
120
132
|
source,
|
|
121
133
|
})
|
|
122
134
|
)
|
|
135
|
+
if (options.sortIndexBound && source === DEFAULT_OBJECT_QUERY_SOURCE) {
|
|
136
|
+
let bounded = query
|
|
137
|
+
while (bounded.kind === "expand") bounded = bounded.input
|
|
138
|
+
if (bounded.kind === "page" || bounded.kind === "limit") {
|
|
139
|
+
const branches = [options.sortIndexBound, `NOT (${options.sortIndexBound})`].map(
|
|
140
|
+
(startPredicate, branch) =>
|
|
141
|
+
compileObjectQueryInternal(projectId, query, {
|
|
142
|
+
probeLimit: options.includeTotal === false,
|
|
143
|
+
source,
|
|
144
|
+
startPredicate,
|
|
145
|
+
isolateSortOverflow: branch === 1,
|
|
146
|
+
correlatedTraversal: options.correlatedTraversal,
|
|
147
|
+
})
|
|
148
|
+
)
|
|
149
|
+
const limit =
|
|
150
|
+
bounded.kind === "page"
|
|
151
|
+
? bounded.pageSize + 1
|
|
152
|
+
: bounded.limit + (options.includeTotal === false ? 1 : 0)
|
|
153
|
+
compiled = {
|
|
154
|
+
...compiled,
|
|
155
|
+
sql: `SELECT * FROM ((${branches[0]!.sql}) UNION ALL (${branches[1]!.sql})) AS input ORDER BY ${compiled.order.sql} LIMIT ?`,
|
|
156
|
+
args: [...branches[0]!.args, ...branches[1]!.args, ...compiled.order.args, limit],
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const traversal = !options.correlatedTraversal && boundedTraversal(query)
|
|
161
|
+
const traversalProbe = traversal ? compileTraversalProbe(projectId, traversal, source) : undefined
|
|
123
162
|
return {
|
|
124
163
|
...compiled,
|
|
164
|
+
...(traversalProbe ? { traversalProbe } : {}),
|
|
125
165
|
sql: numberPlaceholders(compiled.sql),
|
|
126
166
|
totalSql: numberPlaceholders(compiled.totalSql),
|
|
127
167
|
vectorProbe: compiled.vectorProbe
|
|
@@ -238,12 +278,72 @@ export function compilePgObjectFacetQuery(
|
|
|
238
278
|
)
|
|
239
279
|
}
|
|
240
280
|
|
|
281
|
+
/** A single aggregate computes every facet and its exact total under one SQL snapshot. */
|
|
282
|
+
export function compilePgObjectFacetsQuery(
|
|
283
|
+
projectId: string,
|
|
284
|
+
query: ObjectQuery,
|
|
285
|
+
facets: readonly { propertyId: string; limit: number }[],
|
|
286
|
+
options: { source?: PgObjectQuerySource } = {}
|
|
287
|
+
): CompiledPgFacetQuery {
|
|
288
|
+
const querySource = options.source ?? DEFAULT_OBJECT_QUERY_SOURCE
|
|
289
|
+
const source = compileAggregateSource(projectId, query, querySource)
|
|
290
|
+
return compilePgFacetSummary(
|
|
291
|
+
numberCompiledQuery(querySource.wrapStatement(source.sql, source.args)),
|
|
292
|
+
facets
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Input is a numbered statement returning properties; compact read paths can supply it too. */
|
|
297
|
+
export function compilePgFacetSummary(
|
|
298
|
+
source: CompiledPgFacetQuery,
|
|
299
|
+
facets: readonly { propertyId: string; limit: number }[],
|
|
300
|
+
nativeValues = false
|
|
301
|
+
): CompiledPgFacetQuery {
|
|
302
|
+
const args = [...source.args]
|
|
303
|
+
const bind = (value: unknown) => {
|
|
304
|
+
args.push(value)
|
|
305
|
+
return `$${args.length}`
|
|
306
|
+
}
|
|
307
|
+
const fields = facets.map((facet, i) => {
|
|
308
|
+
if (nativeValues) return `f${i}_type, f${i}_value`
|
|
309
|
+
const property = bind(facet.propertyId)
|
|
310
|
+
// Group JSONB before rendering: text would split equal numeric values such as 1.0 / 1.00.
|
|
311
|
+
return `jsonb_typeof(input.properties -> ${property}::text) AS f${i}_type, input.properties -> ${property}::text AS f${i}_value`
|
|
312
|
+
})
|
|
313
|
+
if (!fields.length)
|
|
314
|
+
return {
|
|
315
|
+
sql: `SELECT -1 AS facet_index, NULL::text AS value_type, NULL::text AS value_text, count(*) AS count FROM (${source.sql}) AS input`,
|
|
316
|
+
args,
|
|
317
|
+
}
|
|
318
|
+
const columns = facets.map((_, i) => `f${i}_type, f${i}_value`)
|
|
319
|
+
const groups = facets.map((_, i) => `WHEN GROUPING(f${i}_type) = 0 THEN ${i}`).join(" ")
|
|
320
|
+
const buckets = facets.map(
|
|
321
|
+
(facet, i) =>
|
|
322
|
+
`(SELECT ${i} AS facet_index, f${i}_type AS value_type, ${nativeValues ? `f${i}_value` : `(f${i}_value #>> '{}')`} AS value_text, count FROM _sixb_facet_groups WHERE facet_index = ${i} AND f${i}_type IS NOT NULL ORDER BY count DESC, value_text ASC LIMIT ${bind(facet.limit)})`
|
|
323
|
+
)
|
|
324
|
+
return {
|
|
325
|
+
sql: `WITH _sixb_facet_groups AS MATERIALIZED (
|
|
326
|
+
SELECT CASE ${groups} ELSE -1 END AS facet_index, ${columns.join(", ")}, count(*) AS count
|
|
327
|
+
FROM (SELECT ${fields.join(", ")} FROM (${source.sql}) AS input) AS _sixb_values
|
|
328
|
+
GROUP BY GROUPING SETS ((), ${columns.map((column) => `(${column})`).join(", ")})
|
|
329
|
+
)
|
|
330
|
+
SELECT -1 AS facet_index, NULL::text AS value_type, NULL::text AS value_text, count FROM _sixb_facet_groups WHERE facet_index = -1
|
|
331
|
+
UNION ALL ${buckets.join(" UNION ALL ")}`,
|
|
332
|
+
args,
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
241
336
|
function compileObjectQueryInternal(
|
|
242
337
|
projectId: string,
|
|
243
338
|
query: ObjectQuery,
|
|
244
339
|
ctx: CompileContext
|
|
245
340
|
): CompiledPgObjectQuery {
|
|
246
341
|
switch (query.kind) {
|
|
342
|
+
case "rerank":
|
|
343
|
+
throw new ObjectQueryExecutionError(
|
|
344
|
+
"reranking_requires_runtime",
|
|
345
|
+
"Reranking must execute in the model runtime, not storage."
|
|
346
|
+
)
|
|
247
347
|
case "start":
|
|
248
348
|
return compileStart(projectId, query, ctx)
|
|
249
349
|
case "refs":
|
|
@@ -366,12 +466,14 @@ function compileStart(
|
|
|
366
466
|
SELECT *, properties AS _cursor_properties
|
|
367
467
|
FROM ${ctx.source.objectsTable}
|
|
368
468
|
WHERE project_id = ? AND object_type_id = ?
|
|
369
|
-
|
|
469
|
+
${ctx.startPredicate ? `AND (${ctx.startPredicate})` : ""}
|
|
470
|
+
${ctx.unordered ? "" : `ORDER BY ${order.sql}`}
|
|
471
|
+
${ctx.isolateSortOverflow ? "OFFSET 0" : ""}
|
|
370
472
|
`
|
|
371
473
|
|
|
372
474
|
return {
|
|
373
475
|
sql,
|
|
374
|
-
args: [projectId, query.objectTypeId, ...order.args],
|
|
476
|
+
args: [projectId, query.objectTypeId, ...(ctx.unordered ? [] : order.args)],
|
|
375
477
|
totalSql: `SELECT COUNT(*)::bigint AS total FROM ${ctx.source.objectsTable} WHERE project_id = ? AND object_type_id = ?`,
|
|
376
478
|
totalArgs: [projectId, query.objectTypeId],
|
|
377
479
|
order,
|
|
@@ -390,6 +492,13 @@ function compileRefs(
|
|
|
390
492
|
throw new Error("[SixbPg] PostgreSQL object storage requires at least one ref")
|
|
391
493
|
}
|
|
392
494
|
|
|
495
|
+
// Keep a concrete type visible to PostgreSQL even when identities arrive as JSON.
|
|
496
|
+
// Relationship joins can then use partial incoming-link indexes for that type.
|
|
497
|
+
const objectTypeId = query.refs[0]!.objectTypeId
|
|
498
|
+
const uniformType = query.refs.every((ref) => ref.objectTypeId === objectTypeId)
|
|
499
|
+
const typePredicate = uniformType ? "AND selected.object_type_id = ?" : ""
|
|
500
|
+
const typeArgs = uniformType ? [objectTypeId] : []
|
|
501
|
+
|
|
393
502
|
const order = compileOrder(identityOrderFields())
|
|
394
503
|
const selectedOrder = compileOrder(identityOrderFields(), "selected")
|
|
395
504
|
const refsJson = JSON.stringify(query.refs)
|
|
@@ -406,12 +515,13 @@ function compileRefs(
|
|
|
406
515
|
ON selected.project_id = ?
|
|
407
516
|
AND selected.object_type_id = requested.object_type_id
|
|
408
517
|
AND selected.primary_id = requested.primary_id
|
|
409
|
-
|
|
518
|
+
${typePredicate}
|
|
519
|
+
${ctx.unordered ? "" : `ORDER BY ${selectedOrder.sql}`}
|
|
410
520
|
`
|
|
411
521
|
|
|
412
522
|
return {
|
|
413
523
|
sql,
|
|
414
|
-
args: [refsJson, projectId, ...selectedOrder.args],
|
|
524
|
+
args: [refsJson, projectId, ...typeArgs, ...(ctx.unordered ? [] : selectedOrder.args)],
|
|
415
525
|
totalSql: `
|
|
416
526
|
SELECT COUNT(*)::bigint AS total
|
|
417
527
|
FROM (${requested}) AS requested
|
|
@@ -419,8 +529,9 @@ function compileRefs(
|
|
|
419
529
|
ON selected.project_id = ?
|
|
420
530
|
AND selected.object_type_id = requested.object_type_id
|
|
421
531
|
AND selected.primary_id = requested.primary_id
|
|
532
|
+
${typePredicate}
|
|
422
533
|
`,
|
|
423
|
-
totalArgs: [refsJson, projectId],
|
|
534
|
+
totalArgs: [refsJson, projectId, ...typeArgs],
|
|
424
535
|
order,
|
|
425
536
|
hasMore: () => false,
|
|
426
537
|
trimRows: identityRows,
|
|
@@ -443,17 +554,20 @@ function compileWhere(
|
|
|
443
554
|
predicate: CompiledPredicate,
|
|
444
555
|
ctx: CompileContext
|
|
445
556
|
): CompiledPgObjectQuery {
|
|
446
|
-
const input = compileObjectQueryInternal(projectId, inputQuery,
|
|
557
|
+
const input = compileObjectQueryInternal(projectId, inputQuery, {
|
|
558
|
+
...exactContext(ctx),
|
|
559
|
+
unordered: true,
|
|
560
|
+
})
|
|
447
561
|
const sql = `
|
|
448
562
|
SELECT *
|
|
449
563
|
FROM (${input.sql}) AS input
|
|
450
564
|
WHERE ${predicate.sql}
|
|
451
|
-
ORDER BY ${input.order.sql}
|
|
565
|
+
${ctx.unordered ? "" : `ORDER BY ${input.order.sql}`}
|
|
452
566
|
`
|
|
453
567
|
|
|
454
568
|
return {
|
|
455
569
|
sql,
|
|
456
|
-
args: [...input.args, ...predicate.args, ...input.order.args],
|
|
570
|
+
args: [...input.args, ...predicate.args, ...(ctx.unordered ? [] : input.order.args)],
|
|
457
571
|
totalSql: `
|
|
458
572
|
SELECT COUNT(*)::bigint AS total
|
|
459
573
|
FROM (${input.sql}) AS input
|
|
@@ -490,7 +604,10 @@ function compileSort(
|
|
|
490
604
|
fields: readonly ObjectQuerySortField[],
|
|
491
605
|
ctx: CompileContext
|
|
492
606
|
): CompiledPgObjectQuery {
|
|
493
|
-
const input = compileObjectQueryInternal(projectId, inputQuery,
|
|
607
|
+
const input = compileObjectQueryInternal(projectId, inputQuery, {
|
|
608
|
+
...exactContext(ctx),
|
|
609
|
+
unordered: true,
|
|
610
|
+
})
|
|
494
611
|
const probeInput =
|
|
495
612
|
ctx.probeLimit && needsLimitProbe(inputQuery)
|
|
496
613
|
? compileObjectQueryInternal(projectId, inputQuery, ctx)
|
|
@@ -510,9 +627,9 @@ function compileSort(
|
|
|
510
627
|
input.version,
|
|
511
628
|
input.last_commit_id${input.vectorProbe ? ", input._vector_score" : ""}
|
|
512
629
|
FROM (${input.sql}) AS input
|
|
513
|
-
ORDER BY ${order.sql}
|
|
630
|
+
${ctx.unordered ? "" : `ORDER BY ${order.sql}`}
|
|
514
631
|
`,
|
|
515
|
-
args: [...input.args, ...order.args],
|
|
632
|
+
args: [...input.args, ...(ctx.unordered ? [] : order.args)],
|
|
516
633
|
totalSql: input.totalSql,
|
|
517
634
|
totalArgs: input.totalArgs,
|
|
518
635
|
order,
|
|
@@ -529,10 +646,18 @@ function compileLimit(
|
|
|
529
646
|
rawLimit: number,
|
|
530
647
|
ctx: CompileContext
|
|
531
648
|
): CompiledPgObjectQuery {
|
|
532
|
-
const input = compileObjectQueryInternal(projectId, inputQuery,
|
|
649
|
+
const input = compileObjectQueryInternal(projectId, inputQuery, {
|
|
650
|
+
...exactContext(ctx),
|
|
651
|
+
unordered: true,
|
|
652
|
+
})
|
|
533
653
|
const limit = Math.max(0, rawLimit)
|
|
534
654
|
const rowLimit = ctx.probeLimit ? limit + 1 : limit
|
|
535
655
|
|
|
656
|
+
// A plan bounded for the page must not force correlated probes over the full total.
|
|
657
|
+
const totalInput = ctx.correlatedTraversal
|
|
658
|
+
? compileAggregateSource(projectId, inputQuery, ctx.source)
|
|
659
|
+
: input
|
|
660
|
+
|
|
536
661
|
return {
|
|
537
662
|
sql: `
|
|
538
663
|
SELECT *
|
|
@@ -543,9 +668,9 @@ function compileLimit(
|
|
|
543
668
|
args: [...input.args, ...input.order.args, rowLimit],
|
|
544
669
|
totalSql: `
|
|
545
670
|
SELECT COUNT(*)::bigint AS total
|
|
546
|
-
FROM (${
|
|
671
|
+
FROM (${totalInput.sql}) AS input
|
|
547
672
|
`,
|
|
548
|
-
totalArgs:
|
|
673
|
+
totalArgs: totalInput.args,
|
|
549
674
|
vectorProbe: input.vectorProbe,
|
|
550
675
|
order: input.order,
|
|
551
676
|
hasMore: (rowCount, total) =>
|
|
@@ -562,7 +687,10 @@ function compilePage(
|
|
|
562
687
|
pageToken: string | undefined,
|
|
563
688
|
ctx: CompileContext
|
|
564
689
|
): CompiledPgObjectQuery {
|
|
565
|
-
const input = compileObjectQueryInternal(projectId, inputQuery,
|
|
690
|
+
const input = compileObjectQueryInternal(projectId, inputQuery, {
|
|
691
|
+
...exactContext(ctx),
|
|
692
|
+
unordered: true,
|
|
693
|
+
})
|
|
566
694
|
const pageSize = Math.max(0, rawPageSize)
|
|
567
695
|
const cursor = pageToken ? decodePageToken(pageToken, input.order.fields) : undefined
|
|
568
696
|
const cursorPredicate = cursor
|
|
@@ -570,20 +698,50 @@ function compilePage(
|
|
|
570
698
|
: undefined
|
|
571
699
|
const whereSql = cursorPredicate ? `WHERE ${cursorPredicate.sql}` : ""
|
|
572
700
|
|
|
701
|
+
const ranges =
|
|
702
|
+
cursor && isIndexPageInput(inputQuery)
|
|
703
|
+
? compileKeysetRanges(input.order.fields, cursor, input.order.propertyColumn)
|
|
704
|
+
: undefined
|
|
705
|
+
const rangeSql = ranges
|
|
706
|
+
?.map(
|
|
707
|
+
(range) =>
|
|
708
|
+
`(SELECT * FROM (${input.sql}) AS input WHERE ${range.sql} ORDER BY ${input.order.sql} LIMIT ?)`
|
|
709
|
+
)
|
|
710
|
+
.join(" UNION ALL ")
|
|
711
|
+
|
|
712
|
+
// A plan bounded for the page must not force correlated probes over the full total.
|
|
713
|
+
const totalInput = ctx.correlatedTraversal
|
|
714
|
+
? compileAggregateSource(projectId, inputQuery, ctx.source)
|
|
715
|
+
: input
|
|
716
|
+
|
|
573
717
|
return {
|
|
574
|
-
sql:
|
|
718
|
+
sql: rangeSql
|
|
719
|
+
? `SELECT * FROM (${rangeSql}) AS input ORDER BY ${input.order.sql} LIMIT ?`
|
|
720
|
+
: `
|
|
575
721
|
SELECT *
|
|
576
722
|
FROM (${input.sql}) AS input
|
|
577
723
|
${whereSql}
|
|
578
724
|
ORDER BY ${input.order.sql}
|
|
579
725
|
LIMIT ?
|
|
580
726
|
`,
|
|
581
|
-
args:
|
|
727
|
+
args:
|
|
728
|
+
rangeSql && ranges
|
|
729
|
+
? [
|
|
730
|
+
...ranges.flatMap((range) => [
|
|
731
|
+
...input.args,
|
|
732
|
+
...range.args,
|
|
733
|
+
...input.order.args,
|
|
734
|
+
pageSize + 1,
|
|
735
|
+
]),
|
|
736
|
+
...input.order.args,
|
|
737
|
+
pageSize + 1,
|
|
738
|
+
]
|
|
739
|
+
: [...input.args, ...(cursorPredicate?.args ?? []), ...input.order.args, pageSize + 1],
|
|
582
740
|
totalSql: `
|
|
583
741
|
SELECT COUNT(*)::bigint AS total
|
|
584
|
-
FROM (${
|
|
742
|
+
FROM (${totalInput.sql}) AS input
|
|
585
743
|
`,
|
|
586
|
-
totalArgs:
|
|
744
|
+
totalArgs: totalInput.args,
|
|
587
745
|
vectorProbe: input.vectorProbe,
|
|
588
746
|
order: input.order,
|
|
589
747
|
hasMore: (rowCount) => rowCount > pageSize,
|
|
@@ -603,52 +761,28 @@ function compileTraversal(
|
|
|
603
761
|
sourceObjectTypeId: string | undefined,
|
|
604
762
|
ctx: CompileContext
|
|
605
763
|
): CompiledPgObjectQuery {
|
|
606
|
-
const input = compileObjectQueryInternal(projectId, inputQuery,
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
AND edge.link_id = ?
|
|
616
|
-
JOIN ${ctx.source.objectsTable} AS target_object
|
|
617
|
-
ON target_object.project_id = edge.project_id
|
|
618
|
-
AND target_object.object_type_id = edge.target_type_id
|
|
619
|
-
AND target_object.primary_id = edge.target_id
|
|
620
|
-
`
|
|
621
|
-
: `
|
|
622
|
-
JOIN ${ctx.source.linksTable} AS edge
|
|
623
|
-
ON edge.project_id = input.project_id
|
|
624
|
-
AND edge.target_type_id = input.object_type_id
|
|
625
|
-
AND edge.target_id = input.primary_id
|
|
626
|
-
AND edge.link_id = ?${sourceObjectTypeId === undefined ? "" : "\n AND edge.source_type_id = ?"}
|
|
627
|
-
JOIN ${ctx.source.objectsTable} AS source_object
|
|
628
|
-
ON source_object.project_id = edge.project_id
|
|
629
|
-
AND source_object.object_type_id = edge.source_type_id
|
|
630
|
-
AND source_object.primary_id = edge.source_id
|
|
631
|
-
`
|
|
632
|
-
const order = compileOrder(identityOrderFields())
|
|
633
|
-
const qualifiedOrder = compileOrder(identityOrderFields(), outputAlias)
|
|
634
|
-
const sql = `
|
|
635
|
-
SELECT DISTINCT ${outputAlias}.*, ${outputAlias}.properties AS _cursor_properties
|
|
636
|
-
FROM (${input.sql}) AS input
|
|
637
|
-
${joinSql}
|
|
638
|
-
ORDER BY ${qualifiedOrder.sql}
|
|
639
|
-
`
|
|
640
|
-
const args = [
|
|
641
|
-
...input.args,
|
|
764
|
+
const input = compileObjectQueryInternal(projectId, inputQuery, {
|
|
765
|
+
...exactContext(ctx),
|
|
766
|
+
unordered: true,
|
|
767
|
+
startPredicate: undefined,
|
|
768
|
+
isolateSortOverflow: false,
|
|
769
|
+
})
|
|
770
|
+
const relation = compileTraversalRelation(
|
|
771
|
+
projectId,
|
|
772
|
+
input,
|
|
642
773
|
linkId,
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
774
|
+
direction,
|
|
775
|
+
sourceObjectTypeId,
|
|
776
|
+
ctx.source,
|
|
777
|
+
ctx.correlatedTraversal,
|
|
778
|
+
ctx.startPredicate
|
|
779
|
+
)
|
|
780
|
+
const order = compileOrder(identityOrderFields())
|
|
647
781
|
return {
|
|
648
|
-
sql
|
|
649
|
-
args,
|
|
650
|
-
totalSql: `SELECT COUNT(*)::bigint AS total
|
|
651
|
-
totalArgs: args,
|
|
782
|
+
sql: `SELECT selected.*, selected.properties AS _cursor_properties ${relation.sql} ${ctx.unordered ? "" : `ORDER BY ${order.sql}`} ${ctx.isolateSortOverflow ? "OFFSET 0" : ""}`,
|
|
783
|
+
args: [...relation.args, ...(ctx.unordered ? [] : order.args)],
|
|
784
|
+
totalSql: `SELECT COUNT(*)::bigint AS total ${relation.sql}`,
|
|
785
|
+
totalArgs: relation.args,
|
|
652
786
|
order,
|
|
653
787
|
hasMore: () => false,
|
|
654
788
|
trimRows: identityRows,
|
|
@@ -656,6 +790,96 @@ function compileTraversal(
|
|
|
656
790
|
}
|
|
657
791
|
}
|
|
658
792
|
|
|
793
|
+
/** A traversal is a set of endpoints. EXISTS avoids sorting/deduplicating full JSON objects. */
|
|
794
|
+
function compileTraversalRelation(
|
|
795
|
+
projectId: string,
|
|
796
|
+
input: CompiledAggregateSource,
|
|
797
|
+
linkId: string,
|
|
798
|
+
direction: "outgoing" | "incoming",
|
|
799
|
+
sourceObjectTypeId: string | undefined,
|
|
800
|
+
source: PgObjectQuerySource,
|
|
801
|
+
correlated = false,
|
|
802
|
+
endpointPredicate?: string
|
|
803
|
+
): CompiledAggregateSource {
|
|
804
|
+
const incoming = direction === "incoming"
|
|
805
|
+
const parent = incoming ? "target" : "source"
|
|
806
|
+
const endpoint = incoming ? "source" : "target"
|
|
807
|
+
const constrainedType = incoming ? sourceObjectTypeId : undefined
|
|
808
|
+
return {
|
|
809
|
+
sql: `FROM ${source.objectsTable} AS selected
|
|
810
|
+
WHERE selected.project_id = ?
|
|
811
|
+
${constrainedType === undefined ? "" : "AND selected.object_type_id = ?"}
|
|
812
|
+
${endpointPredicate ? `AND (${endpointPredicate})` : ""}
|
|
813
|
+
AND EXISTS (
|
|
814
|
+
SELECT 1 FROM (${input.sql}) AS input
|
|
815
|
+
JOIN ${source.linksTable} AS edge
|
|
816
|
+
ON edge.project_id = input.project_id
|
|
817
|
+
AND edge.${parent}_type_id = input.object_type_id
|
|
818
|
+
AND edge.${parent}_id = input.primary_id
|
|
819
|
+
WHERE edge.project_id = selected.project_id
|
|
820
|
+
AND edge.${endpoint}_type_id = selected.object_type_id
|
|
821
|
+
AND edge.${endpoint}_id = selected.primary_id
|
|
822
|
+
AND edge.link_id = ?
|
|
823
|
+
${correlated ? "OFFSET 0" : ""}
|
|
824
|
+
)`,
|
|
825
|
+
args: [
|
|
826
|
+
projectId,
|
|
827
|
+
...(constrainedType === undefined ? [] : [constrainedType]),
|
|
828
|
+
...input.args,
|
|
829
|
+
linkId,
|
|
830
|
+
],
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/** Only a bounded page benefits from choosing an ordered, correlated endpoint scan. */
|
|
835
|
+
function boundedTraversal(
|
|
836
|
+
query: ObjectQuery,
|
|
837
|
+
bounded = false
|
|
838
|
+
): Extract<ObjectQuery, { kind: "traverse" }> | undefined {
|
|
839
|
+
switch (query.kind) {
|
|
840
|
+
case "page":
|
|
841
|
+
case "limit":
|
|
842
|
+
return boundedTraversal(query.input, true)
|
|
843
|
+
case "sort":
|
|
844
|
+
case "filter":
|
|
845
|
+
case "text":
|
|
846
|
+
case "project":
|
|
847
|
+
case "expand":
|
|
848
|
+
return boundedTraversal(query.input, bounded)
|
|
849
|
+
case "traverse":
|
|
850
|
+
return bounded ? query : undefined
|
|
851
|
+
default:
|
|
852
|
+
return undefined
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function compileTraversalProbe(
|
|
857
|
+
projectId: string,
|
|
858
|
+
traversal: Extract<ObjectQuery, { kind: "traverse" }>,
|
|
859
|
+
source: PgObjectQuerySource
|
|
860
|
+
): { sql: string; args: unknown[]; limit: number } {
|
|
861
|
+
const input = compileAggregateSource(projectId, traversal.input, source)
|
|
862
|
+
const parent = traversal.direction === "incoming" ? "target" : "source"
|
|
863
|
+
const limit = 1000
|
|
864
|
+
const statement = compilePgObjectStatement(
|
|
865
|
+
`SELECT 1 FROM (${input.sql}) AS input
|
|
866
|
+
JOIN ${source.linksTable} AS edge ON edge.project_id = input.project_id
|
|
867
|
+
AND edge.${parent}_type_id = input.object_type_id AND edge.${parent}_id = input.primary_id
|
|
868
|
+
WHERE edge.link_id = ? ${traversal.direction === "incoming" && traversal.sourceObjectTypeId ? "AND edge.source_type_id = ?" : ""}
|
|
869
|
+
LIMIT ?`,
|
|
870
|
+
[
|
|
871
|
+
...input.args,
|
|
872
|
+
traversal.linkId,
|
|
873
|
+
...(traversal.direction === "incoming" && traversal.sourceObjectTypeId
|
|
874
|
+
? [traversal.sourceObjectTypeId]
|
|
875
|
+
: []),
|
|
876
|
+
limit + 1,
|
|
877
|
+
],
|
|
878
|
+
source
|
|
879
|
+
)
|
|
880
|
+
return { ...statement, limit }
|
|
881
|
+
}
|
|
882
|
+
|
|
659
883
|
/** Correlation handle for an expansion: the parent row's identity columns. */
|
|
660
884
|
interface ExpansionParent {
|
|
661
885
|
project: string
|
|
@@ -849,7 +1073,7 @@ function compileExpansionOrder(
|
|
|
849
1073
|
`${
|
|
850
1074
|
field.scalarKind === "decimal"
|
|
851
1075
|
? `(${jsonTextExpression(propertyColumn)})::numeric`
|
|
852
|
-
: jsonValueExpression(propertyColumn)
|
|
1076
|
+
: `NULLIF(${jsonValueExpression(propertyColumn)}, 'null'::jsonb)`
|
|
853
1077
|
} ${direction}`
|
|
854
1078
|
)
|
|
855
1079
|
args.push(field.propertyId, field.propertyId, field.propertyId)
|
|
@@ -958,6 +1182,11 @@ function compileAggregateSource(
|
|
|
958
1182
|
source: PgObjectQuerySource
|
|
959
1183
|
): CompiledAggregateSource {
|
|
960
1184
|
switch (query.kind) {
|
|
1185
|
+
case "rerank":
|
|
1186
|
+
throw new ObjectQueryExecutionError(
|
|
1187
|
+
"reranking_requires_runtime",
|
|
1188
|
+
"Reranking must execute in the model runtime, not storage."
|
|
1189
|
+
)
|
|
961
1190
|
case "start":
|
|
962
1191
|
return compileAggregateStart(projectId, query, source)
|
|
963
1192
|
case "refs":
|
|
@@ -1029,6 +1258,13 @@ function compileAggregateRefs(
|
|
|
1029
1258
|
throw new Error("[SixbPg] PostgreSQL object storage requires at least one ref")
|
|
1030
1259
|
}
|
|
1031
1260
|
|
|
1261
|
+
// Keep a concrete type visible to PostgreSQL even when identities arrive as JSON.
|
|
1262
|
+
// Relationship joins can then use partial incoming-link indexes for that type.
|
|
1263
|
+
const objectTypeId = query.refs[0]!.objectTypeId
|
|
1264
|
+
const uniformType = query.refs.every((ref) => ref.objectTypeId === objectTypeId)
|
|
1265
|
+
const typePredicate = uniformType ? "AND selected.object_type_id = ?" : ""
|
|
1266
|
+
const typeArgs = uniformType ? [objectTypeId] : []
|
|
1267
|
+
|
|
1032
1268
|
return {
|
|
1033
1269
|
sql: `
|
|
1034
1270
|
SELECT selected.project_id, selected.object_type_id, selected.primary_id, selected.properties
|
|
@@ -1042,8 +1278,9 @@ function compileAggregateRefs(
|
|
|
1042
1278
|
ON selected.project_id = ?
|
|
1043
1279
|
AND selected.object_type_id = requested.object_type_id
|
|
1044
1280
|
AND selected.primary_id = requested.primary_id
|
|
1281
|
+
${typePredicate}
|
|
1045
1282
|
`,
|
|
1046
|
-
args: [JSON.stringify(query.refs), projectId],
|
|
1283
|
+
args: [JSON.stringify(query.refs), projectId, ...typeArgs],
|
|
1047
1284
|
}
|
|
1048
1285
|
}
|
|
1049
1286
|
|
|
@@ -1073,47 +1310,40 @@ function compileAggregateTraversal(
|
|
|
1073
1310
|
source: PgObjectQuerySource
|
|
1074
1311
|
): CompiledAggregateSource {
|
|
1075
1312
|
const input = compileAggregateSource(projectId, inputQuery, source)
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1313
|
+
if (direction === "incoming" && sourceObjectTypeId !== undefined) {
|
|
1314
|
+
// Deduplicate endpoint identities before fetching objects. Bounded arrays let PostgreSQL
|
|
1315
|
+
// visit primary keys in groups instead of doing one random B-tree descent per edge.
|
|
1316
|
+
// Each array contains at most 1024 IDs; larger traversals can spill their sort/group work.
|
|
1317
|
+
return {
|
|
1318
|
+
sql: `SELECT selected.project_id, selected.object_type_id, selected.primary_id, selected.properties
|
|
1319
|
+
FROM (
|
|
1320
|
+
SELECT array_agg(primary_id) AS ids FROM (
|
|
1321
|
+
SELECT primary_id, (row_number() OVER (ORDER BY primary_id) - 1) / 1024 AS batch
|
|
1322
|
+
FROM (
|
|
1323
|
+
SELECT DISTINCT edge.source_id AS primary_id
|
|
1324
|
+
FROM (${input.sql}) AS input
|
|
1325
|
+
JOIN ${source.linksTable} AS edge ON edge.project_id = input.project_id
|
|
1326
|
+
AND edge.target_type_id = input.object_type_id AND edge.target_id = input.primary_id
|
|
1327
|
+
WHERE edge.link_id = ? AND edge.source_type_id = ?
|
|
1328
|
+
) AS endpoints
|
|
1329
|
+
) AS numbered GROUP BY batch
|
|
1330
|
+
) AS batches
|
|
1331
|
+
JOIN ${source.objectsTable} AS selected ON selected.project_id = ?
|
|
1332
|
+
AND selected.object_type_id = ? AND selected.primary_id = ANY(batches.ids)`,
|
|
1333
|
+
args: [...input.args, linkId, sourceObjectTypeId, projectId, sourceObjectTypeId],
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
const relation = compileTraversalRelation(
|
|
1337
|
+
projectId,
|
|
1338
|
+
input,
|
|
1339
|
+
linkId,
|
|
1340
|
+
direction,
|
|
1341
|
+
sourceObjectTypeId,
|
|
1342
|
+
source
|
|
1343
|
+
)
|
|
1102
1344
|
return {
|
|
1103
|
-
sql: `
|
|
1104
|
-
|
|
1105
|
-
${outputAlias}.project_id,
|
|
1106
|
-
${outputAlias}.object_type_id,
|
|
1107
|
-
${outputAlias}.primary_id,
|
|
1108
|
-
${outputAlias}.properties
|
|
1109
|
-
FROM (${input.sql}) AS input
|
|
1110
|
-
${joinSql}
|
|
1111
|
-
`,
|
|
1112
|
-
args: [
|
|
1113
|
-
...input.args,
|
|
1114
|
-
linkId,
|
|
1115
|
-
...(sourceObjectTypeId === undefined ? [] : [sourceObjectTypeId]),
|
|
1116
|
-
],
|
|
1345
|
+
sql: `SELECT selected.project_id, selected.object_type_id, selected.primary_id, selected.properties ${relation.sql}`,
|
|
1346
|
+
args: relation.args,
|
|
1117
1347
|
}
|
|
1118
1348
|
}
|
|
1119
1349
|
|
|
@@ -1349,8 +1579,8 @@ function compileEqualityPredicate(
|
|
|
1349
1579
|
if (typeof value === "string") {
|
|
1350
1580
|
if (op === "eq") {
|
|
1351
1581
|
return {
|
|
1352
|
-
sql: `(${jsonTypeExpression()} = 'string' AND ${jsonTextExpression()} = ?::text)`,
|
|
1353
|
-
args: [propertyId, propertyId, value],
|
|
1582
|
+
sql: `(${jsonTypeExpression()} = 'string' AND ${jsonTextExpression()} = ?::text AND md5(${jsonTextExpression()}) = md5(?::text))`,
|
|
1583
|
+
args: [propertyId, propertyId, value, propertyId, value],
|
|
1354
1584
|
}
|
|
1355
1585
|
}
|
|
1356
1586
|
|
|
@@ -1391,9 +1621,11 @@ function compileInPredicate(propertyId: string, values: readonly unknown[]): Com
|
|
|
1391
1621
|
clauses.push(
|
|
1392
1622
|
`(${jsonTypeExpression()} = 'string' AND ${jsonTextExpression()} IN (${stringValues
|
|
1393
1623
|
.map(() => "?::text")
|
|
1394
|
-
.join(
|
|
1624
|
+
.join(
|
|
1625
|
+
", "
|
|
1626
|
+
)}) AND md5(${jsonTextExpression()}) IN (${stringValues.map(() => "md5(?::text)").join(", ")}))`
|
|
1395
1627
|
)
|
|
1396
|
-
args.push(propertyId, propertyId, ...stringValues)
|
|
1628
|
+
args.push(propertyId, propertyId, ...stringValues, propertyId, ...stringValues)
|
|
1397
1629
|
}
|
|
1398
1630
|
|
|
1399
1631
|
if (jsonbValues.length > 0) {
|
|
@@ -1441,11 +1673,13 @@ function compileTextPredicate(query: string, fields: readonly string[]): Compile
|
|
|
1441
1673
|
|
|
1442
1674
|
const clauses = terms.map(() => {
|
|
1443
1675
|
const fieldClauses = fields.map(
|
|
1444
|
-
() => `
|
|
1676
|
+
() => `lower(coalesce(${jsonTextExpression()}, '')) LIKE ?::text`
|
|
1445
1677
|
)
|
|
1446
1678
|
return `(${fieldClauses.join(" OR ")})`
|
|
1447
1679
|
})
|
|
1448
|
-
const args = terms.flatMap((term) =>
|
|
1680
|
+
const args = terms.flatMap((term) =>
|
|
1681
|
+
fields.flatMap((field) => [field, `%${term.replace(/[\\%_]/g, "\\$&")}%`])
|
|
1682
|
+
)
|
|
1449
1683
|
|
|
1450
1684
|
return { sql: `(${clauses.join(" AND ")})`, args }
|
|
1451
1685
|
}
|
|
@@ -1544,6 +1778,62 @@ function identityOrderFields(): readonly CompiledOrderField[] {
|
|
|
1544
1778
|
]
|
|
1545
1779
|
}
|
|
1546
1780
|
|
|
1781
|
+
/** Bound each disjoint cursor range before merging: OR would scan all preceding index entries. */
|
|
1782
|
+
function compileKeysetRanges(
|
|
1783
|
+
fields: readonly CompiledOrderField[],
|
|
1784
|
+
cursor: readonly EncodedCursorValue[],
|
|
1785
|
+
propertyColumn: string
|
|
1786
|
+
): CompiledPredicate[] {
|
|
1787
|
+
const ranges: CompiledPredicate[] = []
|
|
1788
|
+
const prefix: CompiledPredicate[] = []
|
|
1789
|
+
const addRange = (advance: CompiledPredicate) => {
|
|
1790
|
+
const parts = [...prefix, advance]
|
|
1791
|
+
ranges.push({
|
|
1792
|
+
sql: parts.map((part) => `(${part.sql})`).join(" AND "),
|
|
1793
|
+
args: parts.flatMap((part) => part.args),
|
|
1794
|
+
})
|
|
1795
|
+
}
|
|
1796
|
+
fields.forEach((field, index) => {
|
|
1797
|
+
const value = cursor[index]
|
|
1798
|
+
if (field.kind === "column") {
|
|
1799
|
+
const after = compileFieldAfter(field, value, propertyColumn)
|
|
1800
|
+
if (after) addRange(after)
|
|
1801
|
+
prefix.push(compileFieldEquality(field, value, propertyColumn))
|
|
1802
|
+
return
|
|
1803
|
+
}
|
|
1804
|
+
const rank = `CASE WHEN ${jsonTypeExpression(propertyColumn)} IS NULL OR ${jsonTypeExpression(propertyColumn)} = 'null' THEN 1 ELSE 0 END`
|
|
1805
|
+
if (!value.nullish) addRange({ sql: `${rank} = 1`, args: [field.propertyId, field.propertyId] })
|
|
1806
|
+
prefix.push({
|
|
1807
|
+
sql: `${rank} = ${value.nullish ? 1 : 0}`,
|
|
1808
|
+
args: [field.propertyId, field.propertyId],
|
|
1809
|
+
})
|
|
1810
|
+
if (value.nullish) return
|
|
1811
|
+
const expression = compiledPropertyValueExpression(field, propertyColumn)
|
|
1812
|
+
const parameter = field.scalarKind === "decimal" ? "?::numeric" : "?::text::jsonb"
|
|
1813
|
+
const args = [
|
|
1814
|
+
field.propertyId,
|
|
1815
|
+
field.scalarKind === "decimal" ? value.value : jsonbValue(value.value),
|
|
1816
|
+
]
|
|
1817
|
+
addRange({ sql: `${expression} ${field.direction === "desc" ? "<" : ">"} ${parameter}`, args })
|
|
1818
|
+
prefix.push({ sql: `${expression} = ${parameter}`, args })
|
|
1819
|
+
})
|
|
1820
|
+
return ranges
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
function isIndexPageInput(query: ObjectQuery): boolean {
|
|
1824
|
+
switch (query.kind) {
|
|
1825
|
+
case "start":
|
|
1826
|
+
return true
|
|
1827
|
+
case "filter":
|
|
1828
|
+
case "text":
|
|
1829
|
+
case "sort":
|
|
1830
|
+
case "traverse":
|
|
1831
|
+
return isIndexPageInput(query.input)
|
|
1832
|
+
default:
|
|
1833
|
+
return false
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1547
1837
|
function compileKeysetPredicate(
|
|
1548
1838
|
fields: readonly CompiledOrderField[],
|
|
1549
1839
|
cursor: readonly EncodedCursorValue[],
|
|
@@ -1722,7 +2012,7 @@ function compiledPropertyValueExpression(
|
|
|
1722
2012
|
): string {
|
|
1723
2013
|
return field.scalarKind === "decimal"
|
|
1724
2014
|
? `(${jsonTextExpression(propertyColumn)})::numeric`
|
|
1725
|
-
: jsonValueExpression(propertyColumn)
|
|
2015
|
+
: `NULLIF(${jsonValueExpression(propertyColumn)}, 'null'::jsonb)`
|
|
1726
2016
|
}
|
|
1727
2017
|
|
|
1728
2018
|
function columnExpression(
|