@mastra/clickhouse 1.26.0-alpha.3 → 1.26.0-alpha.5
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 +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/integrations-databases-clickhouse.md +1 -1
- package/dist/index.cjs +103 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +103 -70
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/observability/v-next/index.d.ts +1 -1
- package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/span-query.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/thread-query.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-aggregate.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-query.d.ts +44 -18
- package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/docs/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: mastra-clickhouse
|
|
|
3
3
|
description: Documentation for @mastra/clickhouse. Use when working with @mastra/clickhouse APIs, configuration, or implementation.
|
|
4
4
|
metadata:
|
|
5
5
|
package: "@mastra/clickhouse"
|
|
6
|
-
version: "1.26.0-alpha.
|
|
6
|
+
version: "1.26.0-alpha.5"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
## When to use
|
|
@@ -290,7 +290,7 @@ Don't set `replication` on ClickHouse Cloud. Cloud rewrites `MergeTree` to `Shar
|
|
|
290
290
|
|
|
291
291
|
### Observability domain options
|
|
292
292
|
|
|
293
|
-
`ObservabilityStorageClickhouse` and `ObservabilityStorageClickhouseVNext` accept the same connection options as `ClickhouseStore` (`url`, `username`, `password`, or a pre-configured `client`). `ObservabilityStorageClickhouseVNext` also accepts `retention`, the per-signal TTL in days described in [ClickHouse native TTL](https://mastra.ai/reference/storage/retention), and `traceQuery`, the execution limits for [trace queries](https://mastra.ai/reference/observability/tracing/
|
|
293
|
+
`ObservabilityStorageClickhouse` and `ObservabilityStorageClickhouseVNext` accept the same connection options as `ClickhouseStore` (`url`, `username`, `password`, or a pre-configured `client`). `ObservabilityStorageClickhouseVNext` also accepts `retention`, the per-signal TTL in days described in [ClickHouse native TTL](https://mastra.ai/reference/storage/retention), and `traceQuery`, the execution limits for [trace queries](https://mastra.ai/reference/observability/tracing/advanced-queries).
|
|
294
294
|
|
|
295
295
|
## Hosting options
|
|
296
296
|
|
package/dist/index.cjs
CHANGED
|
@@ -6869,7 +6869,9 @@ const TRACE_STATUS_SQL = `if(isNotNull(r.error), 'error', 'success')`;
|
|
|
6869
6869
|
* Core before 1.68 has no field registry at all.
|
|
6870
6870
|
*/
|
|
6871
6871
|
const timeToFirstChunkSupported = "timeToFirstChunkMs" in (_mastra_core_storage.TRACE_QUERY_FIELD_REGISTRY?.trace ?? {});
|
|
6872
|
-
|
|
6872
|
+
/** Core before the sort rejects `orderBy` on the field, so the sort is only declared when core plans it. */
|
|
6873
|
+
const timeToFirstChunkSortSupported = _mastra_core_storage.TRACE_QUERY_ORDER_FIELDS?.includes("timeToFirstChunkMs") === true;
|
|
6874
|
+
const timeToFirstChunkFeatures = timeToFirstChunkSupported ? timeToFirstChunkSortSupported ? ["trace-query-time-to-first-chunk", "trace-query-time-to-first-chunk-sort"] : ["trace-query-time-to-first-chunk"] : [];
|
|
6873
6875
|
function durationMsSql(startedAt, endedAt) {
|
|
6874
6876
|
return `dateDiff('millisecond', ${startedAt}, ${endedAt})`;
|
|
6875
6877
|
}
|
|
@@ -6922,6 +6924,10 @@ const TRACE_FIELDS = {
|
|
|
6922
6924
|
sql: "r.timeToFirstChunkMs",
|
|
6923
6925
|
parameterType: "Float64"
|
|
6924
6926
|
},
|
|
6927
|
+
entityId: {
|
|
6928
|
+
sql: "r.entityId",
|
|
6929
|
+
parameterType: "String"
|
|
6930
|
+
},
|
|
6925
6931
|
entityName: {
|
|
6926
6932
|
sql: "r.entityName",
|
|
6927
6933
|
parameterType: "String"
|
|
@@ -7212,7 +7218,7 @@ function fieldDefinition(registry, field) {
|
|
|
7212
7218
|
return definition;
|
|
7213
7219
|
}
|
|
7214
7220
|
function resolveOrderField(field) {
|
|
7215
|
-
if (field === "startedAt" || field === "endedAt") return field;
|
|
7221
|
+
if (field === "startedAt" || field === "endedAt" || field === "timeToFirstChunkMs") return field;
|
|
7216
7222
|
throw new Error(`Unsupported trusted trace-query field: ${field}`);
|
|
7217
7223
|
}
|
|
7218
7224
|
function isMetadataField(field) {
|
|
@@ -7320,6 +7326,28 @@ function compileTenantScope(scope, parameters) {
|
|
|
7320
7326
|
if (scope.resourceId !== void 0) sql += `\n AND resourceId = ${parameters.add(scope.resourceId, "String")}`;
|
|
7321
7327
|
return sql;
|
|
7322
7328
|
}
|
|
7329
|
+
/**
|
|
7330
|
+
* Order of the versions of one root span, current version first. `mastra_trace_roots` has no
|
|
7331
|
+
* version column, so until a background merge runs, every export of a root is its own row with the
|
|
7332
|
+
* same sort key, and merges never meet versions that ended on different days (it is partitioned by
|
|
7333
|
+
* `endedAt`). The version that ended last is current, as a resumed or completed root is exported
|
|
7334
|
+
* again with a later `endedAt`; a pending copy stores `endedAt = startedAt` and can lack `threadId`,
|
|
7335
|
+
* so a copy with a thread wins an equal-time tie. Remaining ties are broken by insertion order, last
|
|
7336
|
+
* insert first: the part's first block number, then the row's offset in the part. Tied versions
|
|
7337
|
+
* share a partition and sort key, so a merge meets them and keeps the last insert too, and reads
|
|
7338
|
+
* pick the same version before and after it. Both are virtual columns, so the order reads nothing
|
|
7339
|
+
* beyond `endedAt` and `threadId`.
|
|
7340
|
+
*
|
|
7341
|
+
* Merges don't follow this order across `endedAt`: within one day's partition a merge keeps the last
|
|
7342
|
+
* insert, not the version that ended last. If an outdated export of a root is redelivered after its
|
|
7343
|
+
* current version, on the same day, reads pick the current version until a merge, and the outdated
|
|
7344
|
+
* one afterwards (last write wins, as in the PG and DuckDB stores). Making merges keep the version
|
|
7345
|
+
* that ended last needs `ReplacingMergeTree(endedAt)`, a schema change.
|
|
7346
|
+
*
|
|
7347
|
+
* Every root collapse uses this order: trace rows, aggregates, thread rows and the discovery reads.
|
|
7348
|
+
*/
|
|
7349
|
+
const ROOT_VERSION_ORDER = `endedAt DESC, isNotNull(threadId) DESC, toUInt64(splitByChar('_', _part)[2]) DESC,
|
|
7350
|
+
_part_offset DESC`;
|
|
7323
7351
|
function compileClickHouseTraceScope(selection, relationCollections, parameters, scope, seedConjuncts = [], options = {}) {
|
|
7324
7352
|
const from = parameters.add(selection.timeRange.from, "DateTime64(3, 'UTC')");
|
|
7325
7353
|
const to = parameters.add(selection.timeRange.to, "DateTime64(3, 'UTC')");
|
|
@@ -7343,7 +7371,15 @@ function compileClickHouseTraceScope(selection, relationCollections, parameters,
|
|
|
7343
7371
|
SELECT *
|
|
7344
7372
|
FROM ${TABLE_TRACE_ROOTS} r
|
|
7345
7373
|
WHERE startedAt >= ${from}
|
|
7346
|
-
AND startedAt < ${to}${tenant}${
|
|
7374
|
+
AND startedAt < ${to}${tenant}${bound}${seedFilter ? `
|
|
7375
|
+
AND traceId IN (
|
|
7376
|
+
SELECT traceId
|
|
7377
|
+
FROM ${TABLE_TRACE_ROOTS} r
|
|
7378
|
+
WHERE startedAt >= ${from}
|
|
7379
|
+
AND startedAt < ${to}${tenant}${seedFilter}
|
|
7380
|
+
)` : ""}
|
|
7381
|
+
ORDER BY startedAt, traceId, spanId, ${ROOT_VERSION_ORDER}
|
|
7382
|
+
LIMIT 1 BY traceId
|
|
7347
7383
|
)`] : [`current_roots AS (
|
|
7348
7384
|
SELECT *
|
|
7349
7385
|
FROM ${TABLE_TRACE_ROOTS}
|
|
@@ -7353,7 +7389,7 @@ function compileClickHouseTraceScope(selection, relationCollections, parameters,
|
|
|
7353
7389
|
WHERE startedAt >= ${from}
|
|
7354
7390
|
AND startedAt < ${to}${tenant}${seedFilter}
|
|
7355
7391
|
)${tenant}
|
|
7356
|
-
ORDER BY traceId,
|
|
7392
|
+
ORDER BY traceId, startedAt, spanId, ${ROOT_VERSION_ORDER}
|
|
7357
7393
|
LIMIT 1 BY traceId
|
|
7358
7394
|
)`, `root_scope AS (
|
|
7359
7395
|
SELECT *
|
|
@@ -7483,69 +7519,61 @@ function traceOrder(plan) {
|
|
|
7483
7519
|
direction: plan.orderBy.direction === "asc" ? "ASC" : "DESC"
|
|
7484
7520
|
};
|
|
7485
7521
|
}
|
|
7486
|
-
/**
|
|
7487
|
-
* Keyset cursor on a trace's sort value. Applied after each trace is reduced to one sort value:
|
|
7488
|
-
* filtering root rows first would let a trace with several roots (or unmerged copies with
|
|
7489
|
-
* different `endedAt`) come back on a later page under a root that sorts after the cursor.
|
|
7490
|
-
*/
|
|
7522
|
+
/** Keyset cursor on a trace's sort value (the current root's), with traceId breaking ties. */
|
|
7491
7523
|
function keysetCursorCondition(plan, order, parameters) {
|
|
7492
7524
|
if (plan.paginationMode !== "keyset" || !plan.cursor) return "";
|
|
7525
|
+
if (plan.cursor.sortValue === null) return `(isNull(sortValue) AND traceId > ${parameters.add(plan.cursor.traceId, "String")})`;
|
|
7526
|
+
const numeric = order.field === "timeToFirstChunkMs";
|
|
7493
7527
|
const comparison = order.direction === "ASC" ? ">" : "<";
|
|
7494
|
-
const sortValue = parameters.add(plan.cursor.sortValue, "DateTime64(3, 'UTC')");
|
|
7495
|
-
return `(sortValue ${comparison} ${sortValue} OR (sortValue = ${sortValue} AND traceId > ${parameters.add(plan.cursor.traceId, "String")}))`;
|
|
7528
|
+
const sortValue = parameters.add(plan.cursor.sortValue, numeric ? "Float64" : "DateTime64(3, 'UTC')");
|
|
7529
|
+
return `(sortValue ${comparison} ${sortValue} OR (sortValue = ${sortValue} AND traceId > ${parameters.add(plan.cursor.traceId, "String")})${numeric ? " OR isNull(sortValue)" : ""})`;
|
|
7496
7530
|
}
|
|
7497
7531
|
/**
|
|
7498
7532
|
* First statement of keyset and page mode: the page's traces (after the keyset cursor, `limit + 1`
|
|
7499
|
-
* for keyset mode, `perPage` at the page offset for page mode) as
|
|
7500
|
-
* number of matching traces on every row.
|
|
7501
|
-
* value and the key, so the key always names a row with that sort value. No blob columns are read.
|
|
7533
|
+
* for keyset mode, `perPage` at the page offset for page mode) as keys of their current roots, and
|
|
7534
|
+
* the number of matching traces on every row. No blob columns are read.
|
|
7502
7535
|
*/
|
|
7503
7536
|
function compileClickHouseTraceQueryKeys(plan) {
|
|
7504
7537
|
if (plan.paginationMode === "delta") throw new Error("Delta trace queries have no keys statement");
|
|
7505
7538
|
const parameters = new ParameterBuilder();
|
|
7506
7539
|
const order = traceOrder(plan);
|
|
7507
|
-
const ctes = compileClickHouseTraceCandidates(plan, "r.traceId AS traceId, r.dedupeKey AS dedupeKey, r.startedAt AS startedAt, r.endedAt AS endedAt", parameters, QUERY_TRACES_SCOPE);
|
|
7540
|
+
const ctes = compileClickHouseTraceCandidates(plan, "r.traceId AS traceId, r.dedupeKey AS dedupeKey, r.startedAt AS startedAt, r.endedAt AS endedAt, r.timeToFirstChunkMs AS timeToFirstChunkMs", parameters, QUERY_TRACES_SCOPE);
|
|
7508
7541
|
const cursor = keysetCursorCondition(plan, order, parameters);
|
|
7509
|
-
const pick = order.direction === "DESC" ? "max" : "min";
|
|
7510
7542
|
const limit = plan.paginationMode === "page" ? `LIMIT ${parameters.add(plan.perPage, "UInt64")} OFFSET ${parameters.add(plan.page * plan.perPage, "UInt64")}` : `LIMIT ${parameters.add(plan.limit + 1, "UInt64")}`;
|
|
7511
7543
|
return {
|
|
7512
|
-
query: `WITH ${ctes.join(",\n")}
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
GROUP BY traceId${cursor ? `\n HAVING ${cursor}` : ""}
|
|
7518
|
-
)
|
|
7519
|
-
SELECT traceId, root.1 AS dedupeKey, sortValue, root.2 AS rootStartedAt, count() OVER () AS __total
|
|
7520
|
-
FROM per_trace
|
|
7521
|
-
ORDER BY sortValue ${order.direction}, traceId ASC
|
|
7544
|
+
query: `WITH ${ctes.join(",\n")}
|
|
7545
|
+
SELECT traceId, dedupeKey, ${order.field} AS sortValue, startedAt AS rootStartedAt, endedAt AS rootEndedAt,
|
|
7546
|
+
count() OVER () AS __total
|
|
7547
|
+
FROM candidates${cursor ? `\nWHERE ${cursor}` : ""}
|
|
7548
|
+
ORDER BY sortValue ${order.direction} NULLS LAST, traceId ASC
|
|
7522
7549
|
${limit}`,
|
|
7523
7550
|
query_params: parameters.params,
|
|
7524
7551
|
sharedSnapshot: true
|
|
7525
7552
|
};
|
|
7526
7553
|
}
|
|
7527
7554
|
/**
|
|
7528
|
-
* Second statement of keyset and page mode: the rows of the keyed roots, with `metadata`
|
|
7529
|
-
*
|
|
7530
|
-
*
|
|
7531
|
-
*
|
|
7555
|
+
* Second statement of keyset and page mode: the rows of the keyed roots, with `metadata` and the
|
|
7556
|
+
* stored previews, read by sort key and `endedAt`, so only the keyed version's rows match. Versions
|
|
7557
|
+
* that end at the same instant are ordered as in the keys statement (ROOT_VERSION_ORDER). The
|
|
7558
|
+
* relation-free `where` conjuncts are re-applied, so a version written between the two statements
|
|
7559
|
+
* that doesn't match isn't returned.
|
|
7560
|
+
*
|
|
7561
|
+
* Trade-off of matching on `endedAt`: if a newer version is written between the two statements and
|
|
7562
|
+
* a same-day merge then drops the keyed version, the trace has no row and drops off the page. Without
|
|
7563
|
+
* `endedAt` the lookup would find the replacement, but could also return a version other than the
|
|
7564
|
+
* keyed one whenever both are unmerged. The window is the gap between the two statements.
|
|
7532
7565
|
*/
|
|
7533
7566
|
function compileClickHouseTraceRowsByKey(plan, keys) {
|
|
7534
7567
|
const parameters = new ParameterBuilder();
|
|
7535
7568
|
const order = traceOrder(plan);
|
|
7536
7569
|
const ts = (value) => parameters.add(value, "DateTime64(3, 'UTC')");
|
|
7537
|
-
const
|
|
7538
|
-
const tuples = keys.map((key) => {
|
|
7539
|
-
const started = ts(byEnd ? key.rootStartedAt : key.sortValue);
|
|
7540
|
-
const id = `${parameters.add(key.traceId, "String")}, ${parameters.add(key.dedupeKey, "String")}`;
|
|
7541
|
-
return byEnd ? `(${started}, ${id}, ${ts(key.sortValue)})` : `(${started}, ${id})`;
|
|
7542
|
-
});
|
|
7570
|
+
const tuples = keys.map((key) => `(${ts(key.rootStartedAt)}, ${parameters.add(key.traceId, "String")}, ${parameters.add(key.dedupeKey, "String")}, ${ts(key.rootEndedAt)})`);
|
|
7543
7571
|
const predicate = (plan.where ? (plan.where.type === "boolean" && plan.where.operator === "and" ? plan.where.args : [plan.where]).filter((conjunct) => collectRelationCollections(conjunct).size === 0) : []).map((conjunct) => `\n AND (${compilePredicate(conjunct, parameters)})`).join("");
|
|
7544
7572
|
return {
|
|
7545
7573
|
query: `SELECT ${TRACE_SELECT}
|
|
7546
7574
|
FROM ${TABLE_TRACE_ROOTS} r
|
|
7547
|
-
WHERE (r.startedAt, r.traceId, r.dedupeKey
|
|
7548
|
-
ORDER BY ${order.field} ${order.direction}, traceId ASC
|
|
7575
|
+
WHERE (r.startedAt, r.traceId, r.dedupeKey, r.endedAt) IN (${tuples.join(", ")})${compileTenantScope(plan.scope, parameters)}${predicate}
|
|
7576
|
+
ORDER BY ${order.field} ${order.direction} NULLS LAST, traceId ASC, ${ROOT_VERSION_ORDER}
|
|
7549
7577
|
LIMIT 1 BY traceId`,
|
|
7550
7578
|
query_params: parameters.params
|
|
7551
7579
|
};
|
|
@@ -7564,9 +7592,7 @@ FROM candidates`,
|
|
|
7564
7592
|
/**
|
|
7565
7593
|
* The list statement of groups and delta mode (rows without `metadata` and the previews). Keyset
|
|
7566
7594
|
* and page mode list with compileClickHouseTraceQueryKeys and compileClickHouseTraceRowsByKey.
|
|
7567
|
-
*
|
|
7568
|
-
* mode under the row that sorts first in the list order, as in the keys statement; ties go to the
|
|
7569
|
-
* lowest dedupeKey, then the latest endedAt, so every poll picks the same row.
|
|
7595
|
+
* Candidates hold one current root per trace, as in the keys statement.
|
|
7570
7596
|
*/
|
|
7571
7597
|
function compileClickHouseTraceQuery(plan, deltaHead) {
|
|
7572
7598
|
const parameters = new ParameterBuilder();
|
|
@@ -7587,7 +7613,6 @@ LIMIT ${limit}`,
|
|
|
7587
7613
|
};
|
|
7588
7614
|
}
|
|
7589
7615
|
if (plan.paginationMode !== "delta") throw new Error("Keyset and page trace queries use compileClickHouseTraceQueryKeys and compileClickHouseTraceRowsByKey");
|
|
7590
|
-
const order = traceOrder(plan);
|
|
7591
7616
|
const watermark = _mastra_core_storage.getTraceQueryDeltaWatermark(plan, "clickhouse");
|
|
7592
7617
|
const after = watermark ? parseDeltaWatermark(watermark) : {
|
|
7593
7618
|
cursorId: "0",
|
|
@@ -7606,8 +7631,7 @@ LIMIT ${limit}`,
|
|
|
7606
7631
|
SELECT c.*, toString(d.latestCursorId) AS __delta_cursor
|
|
7607
7632
|
FROM candidates c
|
|
7608
7633
|
INNER JOIN delta_candidates d ON c.traceId = d.traceId
|
|
7609
|
-
ORDER BY d.latestCursorId ASC, c.traceId ASC
|
|
7610
|
-
LIMIT 1 BY c.traceId
|
|
7634
|
+
ORDER BY d.latestCursorId ASC, c.traceId ASC
|
|
7611
7635
|
LIMIT ${limit}`,
|
|
7612
7636
|
query_params: parameters.params,
|
|
7613
7637
|
sharedSnapshot: true
|
|
@@ -7651,6 +7675,7 @@ function compileClickHouseTraceRootPayloads(keys, scope, columns = "payload") {
|
|
|
7651
7675
|
query: `SELECT traceId, spanId AS rootSpanId, dedupeKey, organizationId, resourceId, ${columns === "payload" ? `metadataRaw AS metadata, ${PREVIEW_COLUMNS.join(", ")}` : "input, output, error"}
|
|
7652
7676
|
FROM ${TABLE_TRACE_ROOTS}
|
|
7653
7677
|
WHERE (startedAt, traceId, dedupeKey, endedAt, ifNull(organizationId, ''), isNull(organizationId), ifNull(resourceId, ''), isNull(resourceId)) IN (${tuples.join(", ")})${compileTenantScope(scope, parameters)}
|
|
7678
|
+
ORDER BY ${ROOT_VERSION_ORDER}
|
|
7654
7679
|
LIMIT 1 BY traceId, dedupeKey, organizationId, resourceId`,
|
|
7655
7680
|
query_params: parameters.params
|
|
7656
7681
|
};
|
|
@@ -7936,6 +7961,7 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
|
|
|
7936
7961
|
});
|
|
7937
7962
|
}
|
|
7938
7963
|
const keys = await run(compileClickHouseTraceQueryKeys(plan));
|
|
7964
|
+
const sortValue = (value) => plan.orderBy.field !== "timeToFirstChunkMs" ? asIsoTimestamp(value) : value == null ? null : Number(value);
|
|
7939
7965
|
let total = Number(keys[0]?.__total ?? 0);
|
|
7940
7966
|
if (plan.paginationMode === "page" && keys.length === 0 && plan.page > 0) {
|
|
7941
7967
|
const [stats] = await run(compileClickHouseTraceQueryTotal(plan));
|
|
@@ -7945,8 +7971,9 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
|
|
|
7945
7971
|
const rows = visible.length > 0 ? await run(compileClickHouseTraceRowsByKey(plan, visible.map((key) => ({
|
|
7946
7972
|
traceId: String(key.traceId),
|
|
7947
7973
|
dedupeKey: String(key.dedupeKey),
|
|
7948
|
-
sortValue:
|
|
7949
|
-
rootStartedAt: asIsoTimestamp(key.rootStartedAt)
|
|
7974
|
+
sortValue: sortValue(key.sortValue),
|
|
7975
|
+
rootStartedAt: asIsoTimestamp(key.rootStartedAt),
|
|
7976
|
+
rootEndedAt: asIsoTimestamp(key.rootEndedAt)
|
|
7950
7977
|
})))) : [];
|
|
7951
7978
|
if (plan.paginationMode === "page") return _mastra_core_storage.traceQueryResponseSchema.parse({
|
|
7952
7979
|
traces: await toTraceRecords(rows),
|
|
@@ -7963,7 +7990,7 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
|
|
|
7963
7990
|
traces: await toTraceRecords(rows),
|
|
7964
7991
|
page: { next: keys.length > plan.limit && lastKey ? _mastra_core_storage.encodeTraceQueryCursor(plan, {
|
|
7965
7992
|
result: "traces",
|
|
7966
|
-
sortValue:
|
|
7993
|
+
sortValue: sortValue(lastKey.sortValue),
|
|
7967
7994
|
traceId: String(lastKey.traceId)
|
|
7968
7995
|
}) : null }
|
|
7969
7996
|
});
|
|
@@ -8043,10 +8070,10 @@ const displayFields = [
|
|
|
8043
8070
|
];
|
|
8044
8071
|
var Parameters = class {
|
|
8045
8072
|
values = {};
|
|
8046
|
-
add(value,
|
|
8073
|
+
add(value, type = typeof value === "number" ? "UInt64" : "String") {
|
|
8047
8074
|
const name = `span_${Object.keys(this.values).length}`;
|
|
8048
|
-
this.values[name] =
|
|
8049
|
-
return `{${name}:${
|
|
8075
|
+
this.values[name] = type === "DateTime64(3, 'UTC')" ? new Date(value).toISOString().replace("T", " ").replace(/Z$/, "") : value;
|
|
8076
|
+
return `{${name}:${type}}`;
|
|
8050
8077
|
}
|
|
8051
8078
|
};
|
|
8052
8079
|
function identityValues(row) {
|
|
@@ -8062,10 +8089,17 @@ function identityValues(row) {
|
|
|
8062
8089
|
function afterCursor$1(plan, parameters) {
|
|
8063
8090
|
if (!plan.cursor) return "1";
|
|
8064
8091
|
const cursor = plan.cursor;
|
|
8065
|
-
const timestamp = parameters.add(cursor.sortValue, true);
|
|
8066
8092
|
const field = `s.${plan.orderBy.field}`;
|
|
8067
|
-
const terms = [
|
|
8068
|
-
const equal = [
|
|
8093
|
+
const terms = [];
|
|
8094
|
+
const equal = [];
|
|
8095
|
+
if (cursor.sortValue === null) equal.push(`isNull(${field})`);
|
|
8096
|
+
else {
|
|
8097
|
+
const numeric = plan.orderBy.field === "timeToFirstChunkMs";
|
|
8098
|
+
const value = parameters.add(cursor.sortValue, numeric ? "Float64" : "DateTime64(3, 'UTC')");
|
|
8099
|
+
terms.push(`${field} ${plan.orderBy.direction === "asc" ? ">" : "<"} ${value}`);
|
|
8100
|
+
if (numeric) terms.push(`isNull(${field})`);
|
|
8101
|
+
equal.push(`${field} = ${value}`);
|
|
8102
|
+
}
|
|
8069
8103
|
for (const key of identityFields) {
|
|
8070
8104
|
const value = cursor[key];
|
|
8071
8105
|
if (value === null) equal.push(`isNull(s.${key})`);
|
|
@@ -8093,8 +8127,8 @@ function fieldExpression(field) {
|
|
|
8093
8127
|
}
|
|
8094
8128
|
function compileClickHouseSpanQuery(plan) {
|
|
8095
8129
|
const parameters = new Parameters();
|
|
8096
|
-
const from = parameters.add(plan.timeRange.from,
|
|
8097
|
-
const to = parameters.add(plan.timeRange.to,
|
|
8130
|
+
const from = parameters.add(plan.timeRange.from, "DateTime64(3, 'UTC')");
|
|
8131
|
+
const to = parameters.add(plan.timeRange.to, "DateTime64(3, 'UTC')");
|
|
8098
8132
|
const scope = [];
|
|
8099
8133
|
if (plan.scope) {
|
|
8100
8134
|
scope.push(`organizationId = ${parameters.add(plan.scope.organizationId)}`);
|
|
@@ -8107,6 +8141,7 @@ function compileClickHouseSpanQuery(plan) {
|
|
|
8107
8141
|
const selectionFields = [.../* @__PURE__ */ new Set([
|
|
8108
8142
|
"startedAt",
|
|
8109
8143
|
"endedAt",
|
|
8144
|
+
plan.orderBy.field,
|
|
8110
8145
|
...predicateFields$1(plan.where)
|
|
8111
8146
|
])].filter((field) => !new Set(identityFields).has(field));
|
|
8112
8147
|
const expressions = selectionFields.map(fieldExpression);
|
|
@@ -8120,13 +8155,14 @@ function compileClickHouseSpanQuery(plan) {
|
|
|
8120
8155
|
ORDER BY ${identities}, e.endedAt DESC
|
|
8121
8156
|
LIMIT 1 BY ${identities}
|
|
8122
8157
|
)
|
|
8123
|
-
SELECT ${[
|
|
8158
|
+
SELECT ${[.../* @__PURE__ */ new Set([
|
|
8124
8159
|
...identityFields,
|
|
8125
8160
|
"startedAt",
|
|
8126
|
-
"endedAt"
|
|
8127
|
-
|
|
8161
|
+
"endedAt",
|
|
8162
|
+
plan.orderBy.field
|
|
8163
|
+
])].map((field) => `s.${field}`).join(", ")}
|
|
8128
8164
|
FROM candidates s WHERE s.startedAt >= ${from} AND s.startedAt < ${to} AND (${predicate.sql}) AND ${after}
|
|
8129
|
-
ORDER BY s.${plan.orderBy.field} ${plan.orderBy.direction === "asc" ? "ASC" : "DESC"}, ${identityFields.map((field) => `s.${field} ASC NULLS LAST`).join(", ")}
|
|
8165
|
+
ORDER BY s.${plan.orderBy.field} ${plan.orderBy.direction === "asc" ? "ASC" : "DESC"} NULLS LAST, ${identityFields.map((field) => `s.${field} ASC NULLS LAST`).join(", ")}
|
|
8130
8166
|
LIMIT ${parameters.add(plan.limit + 1)}`,
|
|
8131
8167
|
query_params: {
|
|
8132
8168
|
...parameters.values,
|
|
@@ -8218,7 +8254,7 @@ async function querySpans(client, plan, timeoutMs) {
|
|
|
8218
8254
|
resourceId: last.resourceId,
|
|
8219
8255
|
traceId: last.traceId,
|
|
8220
8256
|
spanId: last.spanId,
|
|
8221
|
-
sortValue: last[plan.orderBy.field]
|
|
8257
|
+
sortValue: last[plan.orderBy.field] ?? null
|
|
8222
8258
|
}) : null;
|
|
8223
8259
|
return result;
|
|
8224
8260
|
}
|
|
@@ -8346,7 +8382,7 @@ function compileClickHouseThreadRowsQuery(plan, strategy = "full") {
|
|
|
8346
8382
|
const sortedRoots = `roots AS (
|
|
8347
8383
|
SELECT organizationId, traceId, ${rootDataFields.map((field) => `${rootExpression(field)} AS ${field}`).join(", ")}
|
|
8348
8384
|
FROM ${TABLE_TRACE_ROOTS} ${scope.length || strategy === "candidates" ? `WHERE ${[...scope, ...strategy === "candidates" ? [candidateTracesFilter] : []].join(" AND ")}` : ""}
|
|
8349
|
-
ORDER BY organizationId, traceId, startedAt ASC, spanId,
|
|
8385
|
+
ORDER BY organizationId, traceId, startedAt ASC, spanId, ${ROOT_VERSION_ORDER}
|
|
8350
8386
|
LIMIT 1 BY organizationId, traceId
|
|
8351
8387
|
)`;
|
|
8352
8388
|
return {
|
|
@@ -8448,7 +8484,7 @@ async function queryThreadRows(client, plan, timeoutMs, strategy = "auto") {
|
|
|
8448
8484
|
${contextFields.join(", ")}, if(spanType = 'agent_run', timeToFirstChunkMs, NULL) AS timeToFirstChunkMs,
|
|
8449
8485
|
if(isNull(error), 'success', 'error') AS status, ${PREVIEW_COLUMNS.join(", ")}
|
|
8450
8486
|
FROM ${TABLE_TRACE_ROOTS} WHERE ${rootWhere}
|
|
8451
|
-
ORDER BY
|
|
8487
|
+
ORDER BY ${ROOT_VERSION_ORDER} LIMIT 1 BY organizationId, traceId, spanId`,
|
|
8452
8488
|
query_params: { roots: keys(batch) }
|
|
8453
8489
|
}));
|
|
8454
8490
|
const byRoot = new Map(stored.map((row) => [rootKey(row), row]));
|
|
@@ -8456,7 +8492,7 @@ async function queryThreadRows(client, plan, timeoutMs, strategy = "auto") {
|
|
|
8456
8492
|
for (const batch of batches(missing, 32)) {
|
|
8457
8493
|
const payloads = await run({
|
|
8458
8494
|
query: `SELECT organizationId, traceId, spanId, input, output, error FROM ${TABLE_TRACE_ROOTS}
|
|
8459
|
-
WHERE ${rootWhere} ORDER BY
|
|
8495
|
+
WHERE ${rootWhere} ORDER BY ${ROOT_VERSION_ORDER} LIMIT 1 BY organizationId, traceId, spanId`,
|
|
8460
8496
|
query_params: { roots: keys(batch) }
|
|
8461
8497
|
});
|
|
8462
8498
|
for (const payload of payloads) {
|
|
@@ -8525,6 +8561,7 @@ const DIMENSION_SQL = {
|
|
|
8525
8561
|
entityName: "r.entityName",
|
|
8526
8562
|
name: "r.name",
|
|
8527
8563
|
spanType: "r.spanType",
|
|
8564
|
+
entityId: "r.entityId",
|
|
8528
8565
|
environment: "r.environment",
|
|
8529
8566
|
status: TRACE_STATUS_SQL,
|
|
8530
8567
|
serviceName: "r.serviceName",
|
|
@@ -8563,12 +8600,6 @@ const COMPARISON_SQL = {
|
|
|
8563
8600
|
function compileClickHouseTraceAggregate(plan) {
|
|
8564
8601
|
const parameters = new ParameterBuilder();
|
|
8565
8602
|
const ctes = compileClickHouseTraceCandidates(plan, "*", parameters, { oneRootPerTrace: true });
|
|
8566
|
-
ctes.push(`trace_roots AS (
|
|
8567
|
-
SELECT *
|
|
8568
|
-
FROM candidates
|
|
8569
|
-
ORDER BY traceId, dedupeKey
|
|
8570
|
-
LIMIT 1 BY traceId
|
|
8571
|
-
)`);
|
|
8572
8603
|
const dimensionSql = (field) => {
|
|
8573
8604
|
if (field.startsWith("metadata.")) return traceMetadataValueSql(parameters.add(field.slice(9), "String"));
|
|
8574
8605
|
const sql = Object.hasOwn(DIMENSION_SQL, field) ? DIMENSION_SQL[field] : void 0;
|
|
@@ -8602,7 +8633,7 @@ function compileClickHouseTraceAggregate(plan) {
|
|
|
8602
8633
|
}
|
|
8603
8634
|
ctes.push(`facts AS (
|
|
8604
8635
|
SELECT ${factColumns.join(",\n ")}
|
|
8605
|
-
FROM
|
|
8636
|
+
FROM candidates r${usageJoin}
|
|
8606
8637
|
)`);
|
|
8607
8638
|
const mixedUnitsSql = "minOrNull(unitMin) != maxOrNull(unitMax)";
|
|
8608
8639
|
const measureSql = (name) => {
|
|
@@ -10108,6 +10139,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
|
|
|
10108
10139
|
"trace-query-context-ids",
|
|
10109
10140
|
...timeToFirstChunkFeatures,
|
|
10110
10141
|
"trace-query-root-span-fields",
|
|
10142
|
+
"trace-query-root-entity-id",
|
|
10111
10143
|
"trace-aggregate",
|
|
10112
10144
|
...spanQueryFeatures,
|
|
10113
10145
|
...threadQueryFeatures
|
|
@@ -10131,6 +10163,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
|
|
|
10131
10163
|
"trace-query-context-ids",
|
|
10132
10164
|
...timeToFirstChunkFeatures,
|
|
10133
10165
|
"trace-query-root-span-fields",
|
|
10166
|
+
"trace-query-root-entity-id",
|
|
10134
10167
|
"trace-aggregate",
|
|
10135
10168
|
...spanQueryFeatures,
|
|
10136
10169
|
...threadQueryFeatures
|