@mastra/pg 1.28.1 → 1.30.0-alpha.0
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/docs/SKILL.md +2 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-memory-semantic-recall.md +35 -1
- package/dist/docs/references/docs-storage.md +1 -0
- package/dist/docs/references/integrations-databases-clickhouse-managed-postgres.md +82 -0
- package/dist/index.cjs +300 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +300 -30
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +1 -0
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/index.d.ts +3 -2
- package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-aggregate.d.ts +18 -0
- package/dist/storage/domains/observability/v-next/trace-aggregate.d.ts.map +1 -0
- package/dist/storage/domains/observability/v-next/trace-query.d.ts +31 -1
- package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -10455,6 +10455,7 @@ function dedupeMessagesForSave(messages) {
|
|
|
10455
10455
|
var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
10456
10456
|
supportsPartialThreadUpdate = true;
|
|
10457
10457
|
supportsObservationalMemory = true;
|
|
10458
|
+
supportsObservationalMemoryHistorySearch = true;
|
|
10458
10459
|
/**
|
|
10459
10460
|
* Retention-eligible tables. `threads`, `messages`, and `resources` all anchor
|
|
10460
10461
|
* on the timezone-aware `createdAtZ` mirror column (kept in sync by triggers),
|
|
@@ -12061,6 +12062,10 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
12061
12062
|
const conditions = [`"lookupKey" = $1`];
|
|
12062
12063
|
const params = [lookupKey];
|
|
12063
12064
|
let paramIndex = 2;
|
|
12065
|
+
if (options?.recordId !== void 0) {
|
|
12066
|
+
conditions.push(`id = $${paramIndex++}`);
|
|
12067
|
+
params.push(options.recordId);
|
|
12068
|
+
}
|
|
12064
12069
|
if (options?.from) {
|
|
12065
12070
|
conditions.push(`"createdAtZ" >= $${paramIndex}`);
|
|
12066
12071
|
params.push(options.from.toISOString());
|
|
@@ -12071,8 +12076,27 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
|
|
|
12071
12076
|
params.push(options.to.toISOString());
|
|
12072
12077
|
paramIndex++;
|
|
12073
12078
|
}
|
|
12079
|
+
if (options?.groupId !== void 0) {
|
|
12080
|
+
conditions.push(`(strpos("activeObservations", $${paramIndex}) > 0 OR EXISTS (
|
|
12081
|
+
SELECT 1 FROM jsonb_array_elements(
|
|
12082
|
+
CASE WHEN jsonb_typeof("bufferedObservationChunks") = 'array' THEN "bufferedObservationChunks" ELSE '[]'::jsonb END
|
|
12083
|
+
) AS chunk
|
|
12084
|
+
WHERE strpos(chunk->>'observations', $${paramIndex}) > 0
|
|
12085
|
+
))`);
|
|
12086
|
+
paramIndex++;
|
|
12087
|
+
params.push(`<observation-group id="${options.groupId}"`);
|
|
12088
|
+
}
|
|
12089
|
+
if (options?.beforeGeneration !== void 0) {
|
|
12090
|
+
conditions.push(`"generationCount" < $${paramIndex++}`);
|
|
12091
|
+
params.push(options.beforeGeneration);
|
|
12092
|
+
}
|
|
12093
|
+
if (options?.afterGeneration !== void 0) {
|
|
12094
|
+
conditions.push(`"generationCount" > $${paramIndex++}`);
|
|
12095
|
+
params.push(options.afterGeneration);
|
|
12096
|
+
}
|
|
12097
|
+
const order = options?.sortDirection === "ASC" ? `"generationCount" ASC, "createdAt" ASC, id ASC` : OM_GENERATION_ORDER;
|
|
12074
12098
|
params.push(limit);
|
|
12075
|
-
let sql = `SELECT * FROM ${tableName} WHERE ${conditions.join(" AND ")} ORDER BY ${
|
|
12099
|
+
let sql = `SELECT * FROM ${tableName} WHERE ${conditions.join(" AND ")} ORDER BY ${order} LIMIT $${paramIndex}`;
|
|
12076
12100
|
paramIndex++;
|
|
12077
12101
|
if (options?.offset != null) {
|
|
12078
12102
|
params.push(options.offset);
|
|
@@ -17460,6 +17484,16 @@ function fieldSql(registry, field) {
|
|
|
17460
17484
|
if (sql === void 0) throw new Error(`Unsupported trusted trace-query field: ${field}`);
|
|
17461
17485
|
return sql;
|
|
17462
17486
|
}
|
|
17487
|
+
/**
|
|
17488
|
+
* Value of trace-root `metadata.<key>` (alias `r`) as filtering, discovery, and aggregate grouping
|
|
17489
|
+
* all see it: the normalized search value, falling back to the trimmed raw string.
|
|
17490
|
+
*/
|
|
17491
|
+
function traceMetadataValueSql(keyParameter) {
|
|
17492
|
+
return `COALESCE(
|
|
17493
|
+
CASE WHEN jsonb_typeof(r."metadataSearch" -> ${keyParameter}) = 'string' THEN r."metadataSearch" ->> ${keyParameter} END,
|
|
17494
|
+
CASE WHEN jsonb_typeof(r."metadataRaw" -> ${keyParameter}) = 'string' THEN NULLIF(btrim(r."metadataRaw" ->> ${keyParameter}), '') END
|
|
17495
|
+
)`;
|
|
17496
|
+
}
|
|
17463
17497
|
function isMetadataField(field) {
|
|
17464
17498
|
return field.startsWith("metadata.");
|
|
17465
17499
|
}
|
|
@@ -17489,11 +17523,7 @@ function compileScalarPredicate(predicate, registry, parameterOffset, allowMetad
|
|
|
17489
17523
|
let fieldValues = [];
|
|
17490
17524
|
if (isMetadataField(predicate.field)) {
|
|
17491
17525
|
if (!allowMetadata) throw new Error(`Unsupported trusted trace-query field: ${predicate.field}`);
|
|
17492
|
-
|
|
17493
|
-
field = `COALESCE(
|
|
17494
|
-
CASE WHEN jsonb_typeof(r."metadataSearch" -> ${keyParameter}) = 'string' THEN r."metadataSearch" ->> ${keyParameter} END,
|
|
17495
|
-
CASE WHEN jsonb_typeof(r."metadataRaw" -> ${keyParameter}) = 'string' THEN NULLIF(btrim(r."metadataRaw" ->> ${keyParameter}), '') END
|
|
17496
|
-
)`;
|
|
17526
|
+
field = traceMetadataValueSql(`$${parameterOffset++}`);
|
|
17497
17527
|
fieldValues = [predicate.field.slice(9)];
|
|
17498
17528
|
} else field = fieldSql(registry, predicate.field);
|
|
17499
17529
|
if (predicate.type === "presence") return {
|
|
@@ -17613,6 +17643,54 @@ function collectThreadRelationCollections(predicate, collections) {
|
|
|
17613
17643
|
else collectThreadRelationCollections(predicate.arg, collections);
|
|
17614
17644
|
return collections;
|
|
17615
17645
|
}
|
|
17646
|
+
const ROOT_PUSHDOWN_FIELDS = /* @__PURE__ */ new Set([
|
|
17647
|
+
"traceId",
|
|
17648
|
+
"threadId",
|
|
17649
|
+
"resourceId",
|
|
17650
|
+
"runId",
|
|
17651
|
+
"sessionId",
|
|
17652
|
+
"userId",
|
|
17653
|
+
"organizationId",
|
|
17654
|
+
"startedAt",
|
|
17655
|
+
"endedAt",
|
|
17656
|
+
"entityName",
|
|
17657
|
+
"entityType",
|
|
17658
|
+
"environment"
|
|
17659
|
+
]);
|
|
17660
|
+
function isRootPushdownPredicate(predicate) {
|
|
17661
|
+
if (predicate.type === "relation") return false;
|
|
17662
|
+
if (predicate.type === "boolean") return predicate.args.every(isRootPushdownPredicate);
|
|
17663
|
+
if (predicate.type === "not") return isRootPushdownPredicate(predicate.arg);
|
|
17664
|
+
return ROOT_PUSHDOWN_FIELDS.has(predicate.field);
|
|
17665
|
+
}
|
|
17666
|
+
/**
|
|
17667
|
+
* Splits `where` into top-level AND conjuncts that can filter `root_scope` directly and the rest,
|
|
17668
|
+
* which is applied in `candidates`. The latest-root check compares against the unfiltered span
|
|
17669
|
+
* table, so filtering a root earlier cannot change which root is current, but it stops long windows
|
|
17670
|
+
* from materializing every root (and running the latest-root check on it) before filtering.
|
|
17671
|
+
*/
|
|
17672
|
+
function splitRootPredicate(predicate) {
|
|
17673
|
+
if (!predicate) return {
|
|
17674
|
+
root: [],
|
|
17675
|
+
rest: []
|
|
17676
|
+
};
|
|
17677
|
+
const conjuncts = predicate.type === "boolean" && predicate.operator === "and" ? predicate.args : [predicate];
|
|
17678
|
+
const root = [];
|
|
17679
|
+
const rest = [];
|
|
17680
|
+
for (const conjunct of conjuncts) (isRootPushdownPredicate(conjunct) ? root : rest).push(conjunct);
|
|
17681
|
+
return {
|
|
17682
|
+
root,
|
|
17683
|
+
rest
|
|
17684
|
+
};
|
|
17685
|
+
}
|
|
17686
|
+
function compileConjunction(predicates, values) {
|
|
17687
|
+
if (predicates.length === 0) return "TRUE";
|
|
17688
|
+
return predicates.map((predicate) => {
|
|
17689
|
+
const compiled = compilePredicate(predicate, values.length + 1);
|
|
17690
|
+
values.push(...compiled.values);
|
|
17691
|
+
return `(${compiled.sql})`;
|
|
17692
|
+
}).join(" AND ");
|
|
17693
|
+
}
|
|
17616
17694
|
function compilePredicate(predicate, parameterOffset) {
|
|
17617
17695
|
if (predicate.type === "relation") {
|
|
17618
17696
|
const compiled = predicate.collection === "feedback" ? compileFeedbackScalarPredicate(predicate.predicate, parameterOffset) : compileScalarPredicate(predicate.predicate, predicate.collection === "spans" ? SPAN_FIELDS : SCORE_FIELDS, parameterOffset);
|
|
@@ -17677,7 +17755,7 @@ function compileThreadPredicate(predicate, parameterOffset) {
|
|
|
17677
17755
|
values: compiled.values
|
|
17678
17756
|
};
|
|
17679
17757
|
}
|
|
17680
|
-
function compilePostgresTraceScope(schema, selection, relationCollections, scope, deltaWindow) {
|
|
17758
|
+
function compilePostgresTraceScope(schema, selection, relationCollections, scope, deltaWindow, rootPredicates = []) {
|
|
17681
17759
|
const spanTable = qualifiedTable(schema, TABLE_SPAN_EVENTS);
|
|
17682
17760
|
const scoreTable = qualifiedTable(schema, TABLE_SCORE_EVENTS);
|
|
17683
17761
|
const feedbackTable = qualifiedTable(schema, TABLE_FEEDBACK_EVENTS);
|
|
@@ -17697,10 +17775,7 @@ function compilePostgresTraceScope(schema, selection, relationCollections, scope
|
|
|
17697
17775
|
}
|
|
17698
17776
|
}
|
|
17699
17777
|
const scopeSql = (alias) => scopeConditions.map((condition) => `\n AND ${alias}.${condition}`).join("");
|
|
17700
|
-
const
|
|
17701
|
-
SELECT *
|
|
17702
|
-
FROM ${spanTable} r
|
|
17703
|
-
WHERE ${[
|
|
17778
|
+
const rootConditions = [
|
|
17704
17779
|
`r."parentSpanId" IS NULL`,
|
|
17705
17780
|
latestRootPredicate$1(spanTable),
|
|
17706
17781
|
`NOT r."isPending"`,
|
|
@@ -17709,7 +17784,12 @@ function compilePostgresTraceScope(schema, selection, relationCollections, scope
|
|
|
17709
17784
|
`r."startedAt" < $2`,
|
|
17710
17785
|
...deltaConditions,
|
|
17711
17786
|
...scopeConditions.map((condition) => `r.${condition}`)
|
|
17712
|
-
]
|
|
17787
|
+
];
|
|
17788
|
+
if (rootPredicates.length > 0) rootConditions.push(compileConjunction(rootPredicates, values));
|
|
17789
|
+
const ctes = [`root_scope AS MATERIALIZED (
|
|
17790
|
+
SELECT *
|
|
17791
|
+
FROM ${spanTable} r
|
|
17792
|
+
WHERE ${rootConditions.join("\n AND ")}
|
|
17713
17793
|
)`];
|
|
17714
17794
|
if (relationCollections.has("spans")) ctes.push(`current_spans AS MATERIALIZED (
|
|
17715
17795
|
SELECT
|
|
@@ -17782,8 +17862,27 @@ function compilePostgresTraceScope(schema, selection, relationCollections, scope
|
|
|
17782
17862
|
values
|
|
17783
17863
|
};
|
|
17784
17864
|
}
|
|
17865
|
+
/**
|
|
17866
|
+
* Builds the CTE chain ending in `candidates`: the completed, current trace roots in the
|
|
17867
|
+
* selection's time range and tenant scope that match its `where` predicate. Trace queries and
|
|
17868
|
+
* trace aggregates both select from this CTE so they always see the same population.
|
|
17869
|
+
*/
|
|
17870
|
+
function compilePostgresTraceCandidates(schema, selection, columns, deltaWindow) {
|
|
17871
|
+
const relationCollections = collectRelationCollections(selection.where);
|
|
17872
|
+
const { root, rest } = splitRootPredicate(selection.where);
|
|
17873
|
+
const { ctes, values } = compilePostgresTraceScope(schema, selection, relationCollections, selection.scope, deltaWindow, root);
|
|
17874
|
+
const predicateSql = compileConjunction(rest, values);
|
|
17875
|
+
ctes.push(`candidates AS (
|
|
17876
|
+
SELECT ${columns}
|
|
17877
|
+
FROM root_scope r
|
|
17878
|
+
WHERE ${predicateSql}
|
|
17879
|
+
)`);
|
|
17880
|
+
return {
|
|
17881
|
+
ctes,
|
|
17882
|
+
values
|
|
17883
|
+
};
|
|
17884
|
+
}
|
|
17785
17885
|
function compilePostgresTraceQuery(schema, plan, mode = "data", safeHorizon) {
|
|
17786
|
-
const relationCollections = collectRelationCollections(plan.where);
|
|
17787
17886
|
let deltaWindow;
|
|
17788
17887
|
if (plan.paginationMode === "delta") {
|
|
17789
17888
|
const watermark = coreStorage.getTraceQueryDeltaWatermark(plan, "pg");
|
|
@@ -17793,18 +17892,7 @@ function compilePostgresTraceQuery(schema, plan, mode = "data", safeHorizon) {
|
|
|
17793
17892
|
safeHorizon
|
|
17794
17893
|
};
|
|
17795
17894
|
}
|
|
17796
|
-
const { ctes, values } =
|
|
17797
|
-
let predicateSql = "TRUE";
|
|
17798
|
-
if (plan.where) {
|
|
17799
|
-
const predicate = compilePredicate(plan.where, values.length + 1);
|
|
17800
|
-
predicateSql = predicate.sql;
|
|
17801
|
-
values.push(...predicate.values);
|
|
17802
|
-
}
|
|
17803
|
-
ctes.push(`candidates AS (
|
|
17804
|
-
SELECT ${TRACE_SELECT}${plan.paginationMode === "delta" ? ", r.\"xactId\", r.\"cursorId\"" : ""}
|
|
17805
|
-
FROM root_scope r
|
|
17806
|
-
WHERE ${predicateSql}
|
|
17807
|
-
)`);
|
|
17895
|
+
const { ctes, values } = compilePostgresTraceCandidates(schema, plan, `${TRACE_SELECT}${plan.paginationMode === "delta" ? ", r.\"xactId\", r.\"cursorId\"" : ""}`, deltaWindow);
|
|
17808
17896
|
const candidates = `WITH ${ctes.join(",\n")}`;
|
|
17809
17897
|
if (plan.paginationMode === "page" && mode === "count") return {
|
|
17810
17898
|
text: `${candidates}
|
|
@@ -17957,11 +18045,7 @@ function compilePostgresTraceQueryValues(schema, plan) {
|
|
|
17957
18045
|
let extracted;
|
|
17958
18046
|
if (plan.predicateScope === "trace" && plan.path === "tags") extracted = `SELECT value FROM (SELECT DISTINCT r."traceId", UNNEST(r."tags") AS value FROM root_scope r) t`;
|
|
17959
18047
|
else if (plan.predicateScope === "trace" && plan.path.startsWith("metadata.")) {
|
|
17960
|
-
|
|
17961
|
-
extracted = `SELECT COALESCE(
|
|
17962
|
-
CASE WHEN jsonb_typeof(r."metadataSearch" -> ${keyParameter}) = 'string' THEN r."metadataSearch" ->> ${keyParameter} END,
|
|
17963
|
-
CASE WHEN jsonb_typeof(r."metadataRaw" -> ${keyParameter}) = 'string' THEN NULLIF(btrim(r."metadataRaw" ->> ${keyParameter}), '') END
|
|
17964
|
-
)::text AS value FROM root_scope r`;
|
|
18048
|
+
extracted = `SELECT ${traceMetadataValueSql(`$${values.length + 1}`)}::text AS value FROM root_scope r`;
|
|
17965
18049
|
values.push(plan.path.slice(9));
|
|
17966
18050
|
} else extracted = `SELECT ${fieldSql(discoveryRegistry(plan.predicateScope), plan.path)}::text AS value FROM ${discoverySource(plan.predicateScope)}`;
|
|
17967
18051
|
const searchParameter = values.length + 1;
|
|
@@ -18173,6 +18257,187 @@ async function queryThreads(client, schema, plan, timeoutMs) {
|
|
|
18173
18257
|
});
|
|
18174
18258
|
}
|
|
18175
18259
|
//#endregion
|
|
18260
|
+
//#region src/storage/domains/observability/v-next/trace-aggregate.ts
|
|
18261
|
+
/** Trace-root column expressions (alias `r`) for each groupable dimension; same values `where` sees. */
|
|
18262
|
+
const DIMENSION_SQL = {
|
|
18263
|
+
entityType: "r.\"entityType\"",
|
|
18264
|
+
entityName: "r.\"entityName\"",
|
|
18265
|
+
environment: "r.\"environment\"",
|
|
18266
|
+
status: TRACE_STATUS_SQL,
|
|
18267
|
+
serviceName: "r.\"serviceName\"",
|
|
18268
|
+
executionSource: "r.\"executionSource\"",
|
|
18269
|
+
threadId: "r.\"threadId\"",
|
|
18270
|
+
resourceId: "r.\"resourceId\"",
|
|
18271
|
+
userId: "r.\"userId\"",
|
|
18272
|
+
sessionId: "r.\"sessionId\"",
|
|
18273
|
+
organizationId: "r.\"organizationId\"",
|
|
18274
|
+
experimentId: "r.\"experimentId\""
|
|
18275
|
+
};
|
|
18276
|
+
const PERCENTILES = {
|
|
18277
|
+
"duration.p50": .5,
|
|
18278
|
+
"duration.p90": .9,
|
|
18279
|
+
"duration.p95": .95,
|
|
18280
|
+
"duration.p99": .99
|
|
18281
|
+
};
|
|
18282
|
+
const COMPARISON_SQL = {
|
|
18283
|
+
eq: "=",
|
|
18284
|
+
ne: "<>",
|
|
18285
|
+
lt: "<",
|
|
18286
|
+
lte: "<=",
|
|
18287
|
+
gt: ">",
|
|
18288
|
+
gte: ">="
|
|
18289
|
+
};
|
|
18290
|
+
/**
|
|
18291
|
+
* Compiles a trusted aggregate plan. The plan's caps are enforced by the planner and trusted here.
|
|
18292
|
+
*
|
|
18293
|
+
* Every candidate root is first reduced to a `facts` row (dimension values, duration, error flag,
|
|
18294
|
+
* countDistinct inputs, bucket) so grouping, measures, and joins only reference plain columns.
|
|
18295
|
+
* Without `interval`, groups are filtered, ordered, and limited in one grouped select. With
|
|
18296
|
+
* `interval`, a `ranked` CTE ranks groups on whole-window measures, then only the top `limit`
|
|
18297
|
+
* groups are expanded into per-bucket rows.
|
|
18298
|
+
*/
|
|
18299
|
+
function compilePostgresTraceAggregate(schema, plan) {
|
|
18300
|
+
const { ctes, values } = compilePostgresTraceCandidates(schema, plan, "r.*");
|
|
18301
|
+
const param = (value) => {
|
|
18302
|
+
values.push(value);
|
|
18303
|
+
return `$${values.length}`;
|
|
18304
|
+
};
|
|
18305
|
+
const dimensionSql = (field) => {
|
|
18306
|
+
if (field.startsWith("metadata.")) return traceMetadataValueSql(param(field.slice(9)));
|
|
18307
|
+
const sql = Object.hasOwn(DIMENSION_SQL, field) ? DIMENSION_SQL[field] : void 0;
|
|
18308
|
+
if (sql === void 0) throw new Error(`Unsupported trusted trace-aggregate field: ${field}`);
|
|
18309
|
+
return sql;
|
|
18310
|
+
};
|
|
18311
|
+
const distinctSql = (field) => field === "traceId" ? "r.\"traceId\"" : dimensionSql(field);
|
|
18312
|
+
const dimensionColumns = plan.dimensions.map((_, index) => `"d${index}"`);
|
|
18313
|
+
const distinctColumns = /* @__PURE__ */ new Map();
|
|
18314
|
+
const factColumns = plan.dimensions.map((dimension, index) => `(${dimensionSql(dimension)})::text AS "d${index}"`);
|
|
18315
|
+
for (const measure of plan.measures) {
|
|
18316
|
+
if (measure.type !== "countDistinct" || distinctColumns.has(measure.field)) continue;
|
|
18317
|
+
const column = `"cd${distinctColumns.size}"`;
|
|
18318
|
+
distinctColumns.set(measure.field, column);
|
|
18319
|
+
factColumns.push(`(${distinctSql(measure.field)})::text AS ${column}`);
|
|
18320
|
+
}
|
|
18321
|
+
factColumns.push(`(${durationMsSql("r.\"startedAt\"", "r.\"endedAt\"")})::float8 AS "durationMs"`, `r."error" IS NOT NULL AS "isError"`);
|
|
18322
|
+
if (plan.interval !== void 0) {
|
|
18323
|
+
const intervalMs = coreStorage.TRACE_AGGREGATE_INTERVAL_MS[plan.interval];
|
|
18324
|
+
if (intervalMs === void 0) throw new Error(`Unsupported trusted trace-aggregate interval: ${plan.interval}`);
|
|
18325
|
+
factColumns.push(`to_timestamp(floor(EXTRACT(EPOCH FROM r."startedAt") * 1000 / ${intervalMs}) * ${intervalMs} / 1000.0) AS "bucket"`);
|
|
18326
|
+
}
|
|
18327
|
+
ctes.push(`facts AS (
|
|
18328
|
+
SELECT ${factColumns.join(",\n ")}
|
|
18329
|
+
FROM candidates r
|
|
18330
|
+
)`);
|
|
18331
|
+
const measureSql = (name, alias = "") => {
|
|
18332
|
+
const column = (name) => `${alias}"${name}"`;
|
|
18333
|
+
if (name === "count") return "COUNT(*)::float8";
|
|
18334
|
+
if (name === "errorCount") return `(COUNT(*) FILTER (WHERE ${column("isError")}))::float8`;
|
|
18335
|
+
if (name === "errorRate") return `(COUNT(*) FILTER (WHERE ${column("isError")}))::float8 / COUNT(*)`;
|
|
18336
|
+
if (name === "duration.avg") return `AVG(${column("durationMs")})`;
|
|
18337
|
+
if (name === "duration.min") return `MIN(${column("durationMs")})`;
|
|
18338
|
+
if (name === "duration.max") return `MAX(${column("durationMs")})`;
|
|
18339
|
+
const percentile = PERCENTILES[name];
|
|
18340
|
+
if (percentile !== void 0) return `percentile_cont(${percentile}) WITHIN GROUP (ORDER BY ${column("durationMs")})`;
|
|
18341
|
+
const measure = plan.measures.find((candidate) => candidate.type === "countDistinct" && candidate.name === name);
|
|
18342
|
+
const distinctColumn = measure && distinctColumns.get(measure.field);
|
|
18343
|
+
if (distinctColumn === void 0) throw new Error(`Unsupported trusted trace-aggregate measure: ${name}`);
|
|
18344
|
+
return `(COUNT(DISTINCT ${alias}${distinctColumn}))::float8`;
|
|
18345
|
+
};
|
|
18346
|
+
const compileHaving = (predicate) => {
|
|
18347
|
+
if (predicate.type === "boolean") return predicate.args.map((arg) => `(${compileHaving(arg)})`).join(predicate.operator === "and" ? " AND " : " OR ");
|
|
18348
|
+
if (predicate.type === "not") return `NOT (${compileHaving(predicate.arg)})`;
|
|
18349
|
+
const measure = measureSql(predicate.measure);
|
|
18350
|
+
if (predicate.type === "membership") {
|
|
18351
|
+
if (predicate.values.length === 0) return predicate.operator === "in" ? "FALSE" : "TRUE";
|
|
18352
|
+
const list = predicate.values.map((value) => `${param(value)}::float8`).join(", ");
|
|
18353
|
+
return `${measure} ${predicate.operator === "in" ? "IN" : "NOT IN"} (${list})`;
|
|
18354
|
+
}
|
|
18355
|
+
const operator = COMPARISON_SQL[predicate.operator];
|
|
18356
|
+
if (operator === void 0) throw new Error(`Unsupported trusted trace-aggregate operator: ${predicate.operator}`);
|
|
18357
|
+
return `${measure} ${operator} ${param(predicate.value)}::float8`;
|
|
18358
|
+
};
|
|
18359
|
+
const havingSql = ["COUNT(*) > 0", ...plan.having ? [`(${compileHaving(plan.having)})`] : []].join(" AND ");
|
|
18360
|
+
const direction = plan.orderBy.direction === "asc" ? "ASC" : "DESC";
|
|
18361
|
+
let orderTarget;
|
|
18362
|
+
if (plan.orderBy.target === "measure") orderTarget = measureSql(plan.orderBy.measure);
|
|
18363
|
+
else {
|
|
18364
|
+
const index = plan.dimensions.indexOf(plan.orderBy.dimension);
|
|
18365
|
+
if (index === -1) throw new Error(`Unsupported trusted trace-aggregate order dimension: ${plan.orderBy.dimension}`);
|
|
18366
|
+
orderTarget = `${dimensionColumns[index]} COLLATE "C"`;
|
|
18367
|
+
}
|
|
18368
|
+
const orderSql = [`${orderTarget} ${direction} NULLS LAST`, ...dimensionColumns.map((column) => `${column} COLLATE "C" ASC NULLS LAST`)].join(", ");
|
|
18369
|
+
const groupSql = dimensionColumns.length > 0 ? dimensionColumns.join(", ") : "()";
|
|
18370
|
+
const limitParameter = param(plan.limit + 1);
|
|
18371
|
+
if (plan.interval === void 0) {
|
|
18372
|
+
const projected = [...dimensionColumns, ...plan.measures.map((measure, index) => `${measureSql(measure.name)} AS "m${index}"`)];
|
|
18373
|
+
return {
|
|
18374
|
+
text: `WITH ${ctes.join(",\n")}
|
|
18375
|
+
SELECT ${projected.join(", ")}
|
|
18376
|
+
FROM facts
|
|
18377
|
+
GROUP BY ${groupSql}
|
|
18378
|
+
HAVING ${havingSql}
|
|
18379
|
+
ORDER BY ${orderSql}
|
|
18380
|
+
LIMIT ${limitParameter}`,
|
|
18381
|
+
values
|
|
18382
|
+
};
|
|
18383
|
+
}
|
|
18384
|
+
const maxRankParameter = param(plan.limit);
|
|
18385
|
+
ctes.push(`ranked AS (
|
|
18386
|
+
SELECT ${[...dimensionColumns, `row_number() OVER (ORDER BY ${orderSql}) AS "rank"`].join(", ")}
|
|
18387
|
+
FROM facts
|
|
18388
|
+
GROUP BY ${groupSql}
|
|
18389
|
+
HAVING ${havingSql}
|
|
18390
|
+
ORDER BY ${orderSql}
|
|
18391
|
+
LIMIT ${limitParameter}
|
|
18392
|
+
)`);
|
|
18393
|
+
const joinSql = dimensionColumns.length > 0 ? dimensionColumns.map((column) => `COALESCE(f.${column}, '') = COALESCE(g.${column}, '') AND (f.${column} IS NULL) = (g.${column} IS NULL)`).join(" AND ") : "TRUE";
|
|
18394
|
+
const projected = [
|
|
18395
|
+
...dimensionColumns.map((column) => `f.${column}`),
|
|
18396
|
+
"f.\"bucket\"",
|
|
18397
|
+
...plan.measures.map((measure, index) => `${measureSql(measure.name, "f.")} AS "m${index}"`),
|
|
18398
|
+
`(SELECT COUNT(*) FROM ranked) > ${maxRankParameter} AS "truncated"`
|
|
18399
|
+
];
|
|
18400
|
+
return {
|
|
18401
|
+
text: `WITH ${ctes.join(",\n")}
|
|
18402
|
+
SELECT ${projected.join(", ")}
|
|
18403
|
+
FROM facts f
|
|
18404
|
+
JOIN ranked g ON ${joinSql}
|
|
18405
|
+
WHERE g."rank" <= ${maxRankParameter}
|
|
18406
|
+
GROUP BY ${[
|
|
18407
|
+
"g.\"rank\"",
|
|
18408
|
+
...dimensionColumns.map((column) => `f.${column}`),
|
|
18409
|
+
"f.\"bucket\""
|
|
18410
|
+
].join(", ")}
|
|
18411
|
+
ORDER BY g."rank" ASC, f."bucket" ASC`,
|
|
18412
|
+
values
|
|
18413
|
+
};
|
|
18414
|
+
}
|
|
18415
|
+
function toIsoTimestamp(value) {
|
|
18416
|
+
return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
|
|
18417
|
+
}
|
|
18418
|
+
async function aggregateTraces(client, schema, plan, timeoutMs) {
|
|
18419
|
+
const compiled = compilePostgresTraceAggregate(schema, plan);
|
|
18420
|
+
const result = await runWithPostgresTraceQueryTimeout(client, timeoutMs, (transaction) => transaction.any(compiled.text, compiled.values));
|
|
18421
|
+
let truncated;
|
|
18422
|
+
let resultRows;
|
|
18423
|
+
if (plan.interval === void 0) {
|
|
18424
|
+
truncated = result.length > plan.limit;
|
|
18425
|
+
resultRows = result.slice(0, plan.limit);
|
|
18426
|
+
} else {
|
|
18427
|
+
truncated = result[0]?.truncated === true;
|
|
18428
|
+
resultRows = result;
|
|
18429
|
+
}
|
|
18430
|
+
return {
|
|
18431
|
+
rows: resultRows.map((row) => {
|
|
18432
|
+
const shaped = { measures: Object.fromEntries(plan.measures.map((measure, index) => [measure.name, Number(row[`m${index}`])])) };
|
|
18433
|
+
if (plan.dimensions.length > 0) shaped.dimensions = Object.fromEntries(plan.dimensions.map((dimension, index) => [dimension, row[`d${index}`] ?? null]));
|
|
18434
|
+
if (plan.interval !== void 0) shaped.bucket = toIsoTimestamp(row.bucket);
|
|
18435
|
+
return shaped;
|
|
18436
|
+
}),
|
|
18437
|
+
truncated
|
|
18438
|
+
};
|
|
18439
|
+
}
|
|
18440
|
+
//#endregion
|
|
18176
18441
|
//#region src/storage/domains/observability/v-next/traces.ts
|
|
18177
18442
|
/**
|
|
18178
18443
|
* Trace / branch list reads for the v-next Postgres observability domain.
|
|
@@ -19074,6 +19339,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
19074
19339
|
"tag-discovery",
|
|
19075
19340
|
"metric-discovery",
|
|
19076
19341
|
"trace-query",
|
|
19342
|
+
"trace-aggregate",
|
|
19077
19343
|
"trace-query-root-duration",
|
|
19078
19344
|
"trace-query-discovery",
|
|
19079
19345
|
"thread-query",
|
|
@@ -19092,6 +19358,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
19092
19358
|
"metric-discovery",
|
|
19093
19359
|
"delta-polling",
|
|
19094
19360
|
"trace-query",
|
|
19361
|
+
"trace-aggregate",
|
|
19095
19362
|
"trace-query-root-duration",
|
|
19096
19363
|
"trace-query-discovery",
|
|
19097
19364
|
"thread-query",
|
|
@@ -19143,6 +19410,9 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
19143
19410
|
async queryTraces(plan) {
|
|
19144
19411
|
return this.#run("QUERY_TRACES", () => queryTraces(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
|
|
19145
19412
|
}
|
|
19413
|
+
async aggregateTraces(plan) {
|
|
19414
|
+
return this.#run("AGGREGATE_TRACES", () => aggregateTraces(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
|
|
19415
|
+
}
|
|
19146
19416
|
async getTraceQueryObservedFields(plan) {
|
|
19147
19417
|
return this.#run("GET_TRACE_QUERY_OBSERVED_FIELDS", () => getTraceQueryObservedFields(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
|
|
19148
19418
|
}
|