@mastra/pg 1.29.0 → 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 +275 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +275 -29
- package/dist/index.js.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 +3 -3
package/dist/index.js
CHANGED
|
@@ -17484,6 +17484,16 @@ function fieldSql(registry, field) {
|
|
|
17484
17484
|
if (sql === void 0) throw new Error(`Unsupported trusted trace-query field: ${field}`);
|
|
17485
17485
|
return sql;
|
|
17486
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
|
+
}
|
|
17487
17497
|
function isMetadataField(field) {
|
|
17488
17498
|
return field.startsWith("metadata.");
|
|
17489
17499
|
}
|
|
@@ -17513,11 +17523,7 @@ function compileScalarPredicate(predicate, registry, parameterOffset, allowMetad
|
|
|
17513
17523
|
let fieldValues = [];
|
|
17514
17524
|
if (isMetadataField(predicate.field)) {
|
|
17515
17525
|
if (!allowMetadata) throw new Error(`Unsupported trusted trace-query field: ${predicate.field}`);
|
|
17516
|
-
|
|
17517
|
-
field = `COALESCE(
|
|
17518
|
-
CASE WHEN jsonb_typeof(r."metadataSearch" -> ${keyParameter}) = 'string' THEN r."metadataSearch" ->> ${keyParameter} END,
|
|
17519
|
-
CASE WHEN jsonb_typeof(r."metadataRaw" -> ${keyParameter}) = 'string' THEN NULLIF(btrim(r."metadataRaw" ->> ${keyParameter}), '') END
|
|
17520
|
-
)`;
|
|
17526
|
+
field = traceMetadataValueSql(`$${parameterOffset++}`);
|
|
17521
17527
|
fieldValues = [predicate.field.slice(9)];
|
|
17522
17528
|
} else field = fieldSql(registry, predicate.field);
|
|
17523
17529
|
if (predicate.type === "presence") return {
|
|
@@ -17637,6 +17643,54 @@ function collectThreadRelationCollections(predicate, collections) {
|
|
|
17637
17643
|
else collectThreadRelationCollections(predicate.arg, collections);
|
|
17638
17644
|
return collections;
|
|
17639
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
|
+
}
|
|
17640
17694
|
function compilePredicate(predicate, parameterOffset) {
|
|
17641
17695
|
if (predicate.type === "relation") {
|
|
17642
17696
|
const compiled = predicate.collection === "feedback" ? compileFeedbackScalarPredicate(predicate.predicate, parameterOffset) : compileScalarPredicate(predicate.predicate, predicate.collection === "spans" ? SPAN_FIELDS : SCORE_FIELDS, parameterOffset);
|
|
@@ -17701,7 +17755,7 @@ function compileThreadPredicate(predicate, parameterOffset) {
|
|
|
17701
17755
|
values: compiled.values
|
|
17702
17756
|
};
|
|
17703
17757
|
}
|
|
17704
|
-
function compilePostgresTraceScope(schema, selection, relationCollections, scope, deltaWindow) {
|
|
17758
|
+
function compilePostgresTraceScope(schema, selection, relationCollections, scope, deltaWindow, rootPredicates = []) {
|
|
17705
17759
|
const spanTable = qualifiedTable(schema, TABLE_SPAN_EVENTS);
|
|
17706
17760
|
const scoreTable = qualifiedTable(schema, TABLE_SCORE_EVENTS);
|
|
17707
17761
|
const feedbackTable = qualifiedTable(schema, TABLE_FEEDBACK_EVENTS);
|
|
@@ -17721,10 +17775,7 @@ function compilePostgresTraceScope(schema, selection, relationCollections, scope
|
|
|
17721
17775
|
}
|
|
17722
17776
|
}
|
|
17723
17777
|
const scopeSql = (alias) => scopeConditions.map((condition) => `\n AND ${alias}.${condition}`).join("");
|
|
17724
|
-
const
|
|
17725
|
-
SELECT *
|
|
17726
|
-
FROM ${spanTable} r
|
|
17727
|
-
WHERE ${[
|
|
17778
|
+
const rootConditions = [
|
|
17728
17779
|
`r."parentSpanId" IS NULL`,
|
|
17729
17780
|
latestRootPredicate$1(spanTable),
|
|
17730
17781
|
`NOT r."isPending"`,
|
|
@@ -17733,7 +17784,12 @@ function compilePostgresTraceScope(schema, selection, relationCollections, scope
|
|
|
17733
17784
|
`r."startedAt" < $2`,
|
|
17734
17785
|
...deltaConditions,
|
|
17735
17786
|
...scopeConditions.map((condition) => `r.${condition}`)
|
|
17736
|
-
]
|
|
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 ")}
|
|
17737
17793
|
)`];
|
|
17738
17794
|
if (relationCollections.has("spans")) ctes.push(`current_spans AS MATERIALIZED (
|
|
17739
17795
|
SELECT
|
|
@@ -17806,8 +17862,27 @@ function compilePostgresTraceScope(schema, selection, relationCollections, scope
|
|
|
17806
17862
|
values
|
|
17807
17863
|
};
|
|
17808
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
|
+
}
|
|
17809
17885
|
function compilePostgresTraceQuery(schema, plan, mode = "data", safeHorizon) {
|
|
17810
|
-
const relationCollections = collectRelationCollections(plan.where);
|
|
17811
17886
|
let deltaWindow;
|
|
17812
17887
|
if (plan.paginationMode === "delta") {
|
|
17813
17888
|
const watermark = coreStorage.getTraceQueryDeltaWatermark(plan, "pg");
|
|
@@ -17817,18 +17892,7 @@ function compilePostgresTraceQuery(schema, plan, mode = "data", safeHorizon) {
|
|
|
17817
17892
|
safeHorizon
|
|
17818
17893
|
};
|
|
17819
17894
|
}
|
|
17820
|
-
const { ctes, values } =
|
|
17821
|
-
let predicateSql = "TRUE";
|
|
17822
|
-
if (plan.where) {
|
|
17823
|
-
const predicate = compilePredicate(plan.where, values.length + 1);
|
|
17824
|
-
predicateSql = predicate.sql;
|
|
17825
|
-
values.push(...predicate.values);
|
|
17826
|
-
}
|
|
17827
|
-
ctes.push(`candidates AS (
|
|
17828
|
-
SELECT ${TRACE_SELECT}${plan.paginationMode === "delta" ? ", r.\"xactId\", r.\"cursorId\"" : ""}
|
|
17829
|
-
FROM root_scope r
|
|
17830
|
-
WHERE ${predicateSql}
|
|
17831
|
-
)`);
|
|
17895
|
+
const { ctes, values } = compilePostgresTraceCandidates(schema, plan, `${TRACE_SELECT}${plan.paginationMode === "delta" ? ", r.\"xactId\", r.\"cursorId\"" : ""}`, deltaWindow);
|
|
17832
17896
|
const candidates = `WITH ${ctes.join(",\n")}`;
|
|
17833
17897
|
if (plan.paginationMode === "page" && mode === "count") return {
|
|
17834
17898
|
text: `${candidates}
|
|
@@ -17981,11 +18045,7 @@ function compilePostgresTraceQueryValues(schema, plan) {
|
|
|
17981
18045
|
let extracted;
|
|
17982
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`;
|
|
17983
18047
|
else if (plan.predicateScope === "trace" && plan.path.startsWith("metadata.")) {
|
|
17984
|
-
|
|
17985
|
-
extracted = `SELECT COALESCE(
|
|
17986
|
-
CASE WHEN jsonb_typeof(r."metadataSearch" -> ${keyParameter}) = 'string' THEN r."metadataSearch" ->> ${keyParameter} END,
|
|
17987
|
-
CASE WHEN jsonb_typeof(r."metadataRaw" -> ${keyParameter}) = 'string' THEN NULLIF(btrim(r."metadataRaw" ->> ${keyParameter}), '') END
|
|
17988
|
-
)::text AS value FROM root_scope r`;
|
|
18048
|
+
extracted = `SELECT ${traceMetadataValueSql(`$${values.length + 1}`)}::text AS value FROM root_scope r`;
|
|
17989
18049
|
values.push(plan.path.slice(9));
|
|
17990
18050
|
} else extracted = `SELECT ${fieldSql(discoveryRegistry(plan.predicateScope), plan.path)}::text AS value FROM ${discoverySource(plan.predicateScope)}`;
|
|
17991
18051
|
const searchParameter = values.length + 1;
|
|
@@ -18197,6 +18257,187 @@ async function queryThreads(client, schema, plan, timeoutMs) {
|
|
|
18197
18257
|
});
|
|
18198
18258
|
}
|
|
18199
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
|
|
18200
18441
|
//#region src/storage/domains/observability/v-next/traces.ts
|
|
18201
18442
|
/**
|
|
18202
18443
|
* Trace / branch list reads for the v-next Postgres observability domain.
|
|
@@ -19098,6 +19339,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
19098
19339
|
"tag-discovery",
|
|
19099
19340
|
"metric-discovery",
|
|
19100
19341
|
"trace-query",
|
|
19342
|
+
"trace-aggregate",
|
|
19101
19343
|
"trace-query-root-duration",
|
|
19102
19344
|
"trace-query-discovery",
|
|
19103
19345
|
"thread-query",
|
|
@@ -19116,6 +19358,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
19116
19358
|
"metric-discovery",
|
|
19117
19359
|
"delta-polling",
|
|
19118
19360
|
"trace-query",
|
|
19361
|
+
"trace-aggregate",
|
|
19119
19362
|
"trace-query-root-duration",
|
|
19120
19363
|
"trace-query-discovery",
|
|
19121
19364
|
"thread-query",
|
|
@@ -19167,6 +19410,9 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
19167
19410
|
async queryTraces(plan) {
|
|
19168
19411
|
return this.#run("QUERY_TRACES", () => queryTraces(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
|
|
19169
19412
|
}
|
|
19413
|
+
async aggregateTraces(plan) {
|
|
19414
|
+
return this.#run("AGGREGATE_TRACES", () => aggregateTraces(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
|
|
19415
|
+
}
|
|
19170
19416
|
async getTraceQueryObservedFields(plan) {
|
|
19171
19417
|
return this.#run("GET_TRACE_QUERY_OBSERVED_FIELDS", () => getTraceQueryObservedFields(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
|
|
19172
19418
|
}
|