@mastra/clickhouse 1.26.0-alpha.4 → 1.26.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.
@@ -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.4"
6
+ version: "1.26.0"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.26.0-alpha.4",
2
+ "version": "1.26.0",
3
3
  "package": "@mastra/clickhouse",
4
4
  "exports": {},
5
5
  "modules": {}
package/dist/index.cjs CHANGED
@@ -16,7 +16,7 @@ var __copyProps = (to, from, except, desc) => {
16
16
  }
17
17
  return to;
18
18
  };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
20
20
  value: mod,
21
21
  enumerable: true
22
22
  }) : target, mod));
@@ -1610,6 +1610,7 @@ var MemoryStorageClickhouse = class extends _mastra_core_storage.MemoryStorage {
1610
1610
  metadataIndex++;
1611
1611
  }
1612
1612
  }
1613
+ const idScope = filter?.resourceId ? `WHERE id IN (SELECT id FROM ${_mastra_core_storage.TABLE_THREADS} WHERE resourceId = {resourceId:String})` : "";
1613
1614
  const latestThreads = `
1614
1615
  latest_threads AS (
1615
1616
  SELECT
@@ -1620,7 +1621,7 @@ var MemoryStorageClickhouse = class extends _mastra_core_storage.MemoryStorage {
1620
1621
  toDateTime64(createdAt, 3) as createdAt,
1621
1622
  toDateTime64(updatedAt, 3) as updatedAt
1622
1623
  FROM ${_mastra_core_storage.TABLE_THREADS}
1623
- ${filter?.resourceId ? `WHERE id IN (SELECT id FROM ${_mastra_core_storage.TABLE_THREADS} WHERE resourceId = {resourceId:String})` : ""}
1624
+ ${idScope}
1624
1625
  ORDER BY updatedAt DESC
1625
1626
  LIMIT 1 BY id
1626
1627
  )`;
@@ -2441,9 +2442,7 @@ var ObservabilityStorageClickhouse = class extends _mastra_core_storage.Observab
2441
2442
  case _mastra_core_storage.TraceStatus.RUNNING:
2442
2443
  conditions.push(`endedAt IS NULL AND (error IS NULL OR error = '')`);
2443
2444
  break;
2444
- case _mastra_core_storage.TraceStatus.SUCCESS:
2445
- conditions.push(`endedAt IS NOT NULL AND (error IS NULL OR error = '')`);
2446
- break;
2445
+ case _mastra_core_storage.TraceStatus.SUCCESS: conditions.push(`endedAt IS NOT NULL AND (error IS NULL OR error = '')`);
2447
2446
  }
2448
2447
  if (filters.hasChildError !== void 0) {
2449
2448
  const finalClause = (TABLE_ENGINES[_mastra_core_storage.TABLE_SPANS] ?? "MergeTree()").startsWith("ReplacingMergeTree") ? "FINAL" : "";
@@ -5284,7 +5283,8 @@ async function getFeedbackAggregate(client, args) {
5284
5283
  const aggSql = getAggregationSql$2(args.aggregation);
5285
5284
  const identity = buildFeedbackIdentityFilter(args);
5286
5285
  const combined = mergeFilters$2(identity, buildFeedbackFilterConditions(args.filters));
5287
- const result = await queryJson$2(client, `SELECT ${aggSql} AS value FROM ${TABLE_FEEDBACK_EVENTS} FINAL ${toWhereClause$2(combined)}`, combined.params);
5286
+ const whereClause = toWhereClause$2(combined);
5287
+ const result = await queryJson$2(client, `SELECT ${aggSql} AS value FROM ${TABLE_FEEDBACK_EVENTS} FINAL ${whereClause}`, combined.params);
5288
5288
  const value = result[0]?.value == null ? null : Number(result[0]?.value);
5289
5289
  if (args.comparePeriod && args.filters?.timestamp) {
5290
5290
  const ts = args.filters.timestamp;
@@ -5318,7 +5318,8 @@ async function getFeedbackAggregate(client, args) {
5318
5318
  endExclusive: ts.endExclusive
5319
5319
  }
5320
5320
  }));
5321
- const prevResult = await queryJson$2(client, `SELECT ${aggSql} AS value FROM ${TABLE_FEEDBACK_EVENTS} FINAL ${toWhereClause$2(prevCombined)}`, prevCombined.params);
5321
+ const prevWhereClause = toWhereClause$2(prevCombined);
5322
+ const prevResult = await queryJson$2(client, `SELECT ${aggSql} AS value FROM ${TABLE_FEEDBACK_EVENTS} FINAL ${prevWhereClause}`, prevCombined.params);
5322
5323
  const previousValue = prevResult[0]?.value == null ? null : Number(prevResult[0]?.value);
5323
5324
  let changePercent = null;
5324
5325
  if (previousValue !== null && previousValue !== 0 && value !== null) changePercent = (value - previousValue) / Math.abs(previousValue) * 100;
@@ -6225,8 +6226,10 @@ function buildLegacySpanSelectExprs(legacyColumnTypes) {
6225
6226
  const promotedIn = PROMOTED_METADATA_KEYS.map((k) => `'${k}'`).join(", ");
6226
6227
  const metadataSearchExpr = has("metadata") ? `CAST(arrayFilter(x -> x.1 NOT IN (${promotedIn}) AND trim(BOTH ' ' FROM x.2) != '', arrayMap(x -> (x.1, trim(BOTH ' ' FROM x.2)), JSONExtractKeysAndValues(COALESCE("metadata", '{}'), 'String'))), 'Map(String, String)')` : `CAST(map(), 'Map(String, String)')`;
6227
6228
  let tagsExpr = "[]";
6228
- if (has("tags")) if (legacyColumnTypes.get("tags").includes("Array")) tagsExpr = `arrayFilter(x -> x != '', arrayDistinct(arrayMap(x -> trim(BOTH ' ' FROM x), "tags")))`;
6229
- else tagsExpr = `arrayFilter(x -> x != '', arrayDistinct(arrayMap(x -> trim(BOTH ' ' FROM x), JSONExtract(COALESCE("tags", '[]'), 'Array(String)'))))`;
6229
+ if (has("tags")) {
6230
+ if (legacyColumnTypes.get("tags").includes("Array")) tagsExpr = `arrayFilter(x -> x != '', arrayDistinct(arrayMap(x -> trim(BOTH ' ' FROM x), "tags")))`;
6231
+ else tagsExpr = `arrayFilter(x -> x != '', arrayDistinct(arrayMap(x -> trim(BOTH ' ' FROM x), JSONExtract(COALESCE("tags", '[]'), 'Array(String)'))))`;
6232
+ }
6230
6233
  const exprs = {
6231
6234
  dedupeKey: `concat("traceId", ':', "spanId")`,
6232
6235
  traceId: `"traceId"`,
@@ -6473,10 +6476,11 @@ function toWhereClause(filter) {
6473
6476
  return filter.conditions.length ? `WHERE ${filter.conditions.join(" AND ")}` : "";
6474
6477
  }
6475
6478
  function currentScoresRelation(sourcePredicate) {
6479
+ const whereClause = sourcePredicate ? `WHERE ${sourcePredicate}` : "";
6476
6480
  return `(
6477
6481
  SELECT *
6478
6482
  FROM ${TABLE_SCORE_EVENTS_CURRENT} FINAL
6479
- ${sourcePredicate ? `WHERE ${sourcePredicate}` : ""}
6483
+ ${whereClause}
6480
6484
  )`;
6481
6485
  }
6482
6486
  /**
@@ -6924,6 +6928,10 @@ const TRACE_FIELDS = {
6924
6928
  sql: "r.timeToFirstChunkMs",
6925
6929
  parameterType: "Float64"
6926
6930
  },
6931
+ entityId: {
6932
+ sql: "r.entityId",
6933
+ parameterType: "String"
6934
+ },
6927
6935
  entityName: {
6928
6936
  sql: "r.entityName",
6929
6937
  parameterType: "String"
@@ -7322,6 +7330,28 @@ function compileTenantScope(scope, parameters) {
7322
7330
  if (scope.resourceId !== void 0) sql += `\n AND resourceId = ${parameters.add(scope.resourceId, "String")}`;
7323
7331
  return sql;
7324
7332
  }
7333
+ /**
7334
+ * Order of the versions of one root span, current version first. `mastra_trace_roots` has no
7335
+ * version column, so until a background merge runs, every export of a root is its own row with the
7336
+ * same sort key, and merges never meet versions that ended on different days (it is partitioned by
7337
+ * `endedAt`). The version that ended last is current, as a resumed or completed root is exported
7338
+ * again with a later `endedAt`; a pending copy stores `endedAt = startedAt` and can lack `threadId`,
7339
+ * so a copy with a thread wins an equal-time tie. Remaining ties are broken by insertion order, last
7340
+ * insert first: the part's first block number, then the row's offset in the part. Tied versions
7341
+ * share a partition and sort key, so a merge meets them and keeps the last insert too, and reads
7342
+ * pick the same version before and after it. Both are virtual columns, so the order reads nothing
7343
+ * beyond `endedAt` and `threadId`.
7344
+ *
7345
+ * Merges don't follow this order across `endedAt`: within one day's partition a merge keeps the last
7346
+ * insert, not the version that ended last. If an outdated export of a root is redelivered after its
7347
+ * current version, on the same day, reads pick the current version until a merge, and the outdated
7348
+ * one afterwards (last write wins, as in the PG and DuckDB stores). Making merges keep the version
7349
+ * that ended last needs `ReplacingMergeTree(endedAt)`, a schema change.
7350
+ *
7351
+ * Every root collapse uses this order: trace rows, aggregates, thread rows and the discovery reads.
7352
+ */
7353
+ const ROOT_VERSION_ORDER = `endedAt DESC, isNotNull(threadId) DESC, toUInt64(splitByChar('_', _part)[2]) DESC,
7354
+ _part_offset DESC`;
7325
7355
  function compileClickHouseTraceScope(selection, relationCollections, parameters, scope, seedConjuncts = [], options = {}) {
7326
7356
  const from = parameters.add(selection.timeRange.from, "DateTime64(3, 'UTC')");
7327
7357
  const to = parameters.add(selection.timeRange.to, "DateTime64(3, 'UTC')");
@@ -7345,7 +7375,15 @@ function compileClickHouseTraceScope(selection, relationCollections, parameters,
7345
7375
  SELECT *
7346
7376
  FROM ${TABLE_TRACE_ROOTS} r
7347
7377
  WHERE startedAt >= ${from}
7348
- AND startedAt < ${to}${tenant}${seedFilter}${bound}
7378
+ AND startedAt < ${to}${tenant}${bound}${seedFilter ? `
7379
+ AND traceId IN (
7380
+ SELECT traceId
7381
+ FROM ${TABLE_TRACE_ROOTS} r
7382
+ WHERE startedAt >= ${from}
7383
+ AND startedAt < ${to}${tenant}${seedFilter}
7384
+ )` : ""}
7385
+ ORDER BY startedAt, traceId, spanId, ${ROOT_VERSION_ORDER}
7386
+ LIMIT 1 BY traceId
7349
7387
  )`] : [`current_roots AS (
7350
7388
  SELECT *
7351
7389
  FROM ${TABLE_TRACE_ROOTS}
@@ -7355,7 +7393,7 @@ function compileClickHouseTraceScope(selection, relationCollections, parameters,
7355
7393
  WHERE startedAt >= ${from}
7356
7394
  AND startedAt < ${to}${tenant}${seedFilter}
7357
7395
  )${tenant}
7358
- ORDER BY traceId, dedupeKey
7396
+ ORDER BY traceId, startedAt, spanId, ${ROOT_VERSION_ORDER}
7359
7397
  LIMIT 1 BY traceId
7360
7398
  )`, `root_scope AS (
7361
7399
  SELECT *
@@ -7485,11 +7523,7 @@ function traceOrder(plan) {
7485
7523
  direction: plan.orderBy.direction === "asc" ? "ASC" : "DESC"
7486
7524
  };
7487
7525
  }
7488
- /**
7489
- * Keyset cursor on a trace's sort value. Applied after each trace is reduced to one sort value:
7490
- * filtering root rows first would let a trace with several roots (or unmerged copies with
7491
- * different `endedAt`) come back on a later page under a root that sorts after the cursor.
7492
- */
7526
+ /** Keyset cursor on a trace's sort value (the current root's), with traceId breaking ties. */
7493
7527
  function keysetCursorCondition(plan, order, parameters) {
7494
7528
  if (plan.paginationMode !== "keyset" || !plan.cursor) return "";
7495
7529
  if (plan.cursor.sortValue === null) return `(isNull(sortValue) AND traceId > ${parameters.add(plan.cursor.traceId, "String")})`;
@@ -7500,9 +7534,8 @@ function keysetCursorCondition(plan, order, parameters) {
7500
7534
  }
7501
7535
  /**
7502
7536
  * First statement of keyset and page mode: the page's traces (after the keyset cursor, `limit + 1`
7503
- * for keyset mode, `perPage` at the page offset for page mode) as root keys, and the
7504
- * number of matching traces on every row. Per trace, the root row that sorts first gives the sort
7505
- * value and the key, so the key always names a row with that sort value. No blob columns are read.
7537
+ * for keyset mode, `perPage` at the page offset for page mode) as keys of their current roots, and
7538
+ * the number of matching traces on every row. No blob columns are read.
7506
7539
  */
7507
7540
  function compileClickHouseTraceQueryKeys(plan) {
7508
7541
  if (plan.paginationMode === "delta") throw new Error("Delta trace queries have no keys statement");
@@ -7510,19 +7543,12 @@ function compileClickHouseTraceQueryKeys(plan) {
7510
7543
  const order = traceOrder(plan);
7511
7544
  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);
7512
7545
  const cursor = keysetCursorCondition(plan, order, parameters);
7513
- const pick = order.direction === "DESC" ? "max" : "min";
7514
- const rootOrder = order.field === "timeToFirstChunkMs" ? `ifNull(${order.field}, ${pick === "max" ? "-inf" : "inf"})` : order.field;
7515
7546
  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")}`;
7516
7547
  return {
7517
- query: `WITH ${ctes.join(",\n")},
7518
- per_trace AS (
7519
- SELECT traceId, ${pick}(${order.field}) AS sortValue,
7520
- ${pick === "max" ? "argMax" : "argMin"}((dedupeKey, startedAt), ${rootOrder}) AS root
7521
- FROM candidates
7522
- GROUP BY traceId${cursor ? `\n HAVING ${cursor}` : ""}
7523
- )
7524
- SELECT traceId, root.1 AS dedupeKey, sortValue, root.2 AS rootStartedAt, count() OVER () AS __total
7525
- FROM per_trace
7548
+ query: `WITH ${ctes.join(",\n")}
7549
+ SELECT traceId, dedupeKey, ${order.field} AS sortValue, startedAt AS rootStartedAt, endedAt AS rootEndedAt,
7550
+ count() OVER () AS __total
7551
+ FROM candidates${cursor ? `\nWHERE ${cursor}` : ""}
7526
7552
  ORDER BY sortValue ${order.direction} NULLS LAST, traceId ASC
7527
7553
  ${limit}`,
7528
7554
  query_params: parameters.params,
@@ -7530,27 +7556,28 @@ ${limit}`,
7530
7556
  };
7531
7557
  }
7532
7558
  /**
7533
- * Second statement of keyset and page mode: the rows of the keyed roots, with `metadata`
7534
- * and the stored previews, read by sort-key tuple. Unmerged copies of a root key are redeliveries of one
7535
- * span; the relation-free `where` conjuncts are re-applied so a copy that doesn't match isn't
7536
- * returned, and the copy that sorts first is kept, as in the keys statement.
7559
+ * Second statement of keyset and page mode: the rows of the keyed roots, with `metadata` and the
7560
+ * stored previews, read by sort key and `endedAt`, so only the keyed version's rows match. Versions
7561
+ * that end at the same instant are ordered as in the keys statement (ROOT_VERSION_ORDER). The
7562
+ * relation-free `where` conjuncts are re-applied, so a version written between the two statements
7563
+ * that doesn't match isn't returned.
7564
+ *
7565
+ * Trade-off of matching on `endedAt`: if a newer version is written between the two statements and
7566
+ * a same-day merge then drops the keyed version, the trace has no row and drops off the page. Without
7567
+ * `endedAt` the lookup would find the replacement, but could also return a version other than the
7568
+ * keyed one whenever both are unmerged. The window is the gap between the two statements.
7537
7569
  */
7538
7570
  function compileClickHouseTraceRowsByKey(plan, keys) {
7539
7571
  const parameters = new ParameterBuilder();
7540
7572
  const order = traceOrder(plan);
7541
7573
  const ts = (value) => parameters.add(value, "DateTime64(3, 'UTC')");
7542
- const byEnd = order.field === "endedAt";
7543
- const tuples = keys.map((key) => {
7544
- const started = ts(key.rootStartedAt);
7545
- const id = `${parameters.add(key.traceId, "String")}, ${parameters.add(key.dedupeKey, "String")}`;
7546
- return byEnd ? `(${started}, ${id}, ${ts(String(key.sortValue))})` : `(${started}, ${id})`;
7547
- });
7574
+ const tuples = keys.map((key) => `(${ts(key.rootStartedAt)}, ${parameters.add(key.traceId, "String")}, ${parameters.add(key.dedupeKey, "String")}, ${ts(key.rootEndedAt)})`);
7548
7575
  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("");
7549
7576
  return {
7550
7577
  query: `SELECT ${TRACE_SELECT}
7551
7578
  FROM ${TABLE_TRACE_ROOTS} r
7552
- WHERE (r.startedAt, r.traceId, r.dedupeKey${byEnd ? ", r.endedAt" : ""}) IN (${tuples.join(", ")})${compileTenantScope(plan.scope, parameters)}${predicate}
7553
- ORDER BY ${order.field} ${order.direction} NULLS LAST, traceId ASC
7579
+ WHERE (r.startedAt, r.traceId, r.dedupeKey, r.endedAt) IN (${tuples.join(", ")})${compileTenantScope(plan.scope, parameters)}${predicate}
7580
+ ORDER BY ${order.field} ${order.direction} NULLS LAST, traceId ASC, ${ROOT_VERSION_ORDER}
7554
7581
  LIMIT 1 BY traceId`,
7555
7582
  query_params: parameters.params
7556
7583
  };
@@ -7569,9 +7596,7 @@ FROM candidates`,
7569
7596
  /**
7570
7597
  * The list statement of groups and delta mode (rows without `metadata` and the previews). Keyset
7571
7598
  * and page mode list with compileClickHouseTraceQueryKeys and compileClickHouseTraceRowsByKey.
7572
- * A trace with several candidate rows (several roots, or unmerged copies of one) is listed in delta
7573
- * mode under the row that sorts first in the list order, as in the keys statement; ties go to the
7574
- * lowest dedupeKey, then the latest endedAt, so every poll picks the same row.
7599
+ * Candidates hold one current root per trace, as in the keys statement.
7575
7600
  */
7576
7601
  function compileClickHouseTraceQuery(plan, deltaHead) {
7577
7602
  const parameters = new ParameterBuilder();
@@ -7592,7 +7617,6 @@ LIMIT ${limit}`,
7592
7617
  };
7593
7618
  }
7594
7619
  if (plan.paginationMode !== "delta") throw new Error("Keyset and page trace queries use compileClickHouseTraceQueryKeys and compileClickHouseTraceRowsByKey");
7595
- const order = traceOrder(plan);
7596
7620
  const watermark = _mastra_core_storage.getTraceQueryDeltaWatermark(plan, "clickhouse");
7597
7621
  const after = watermark ? parseDeltaWatermark(watermark) : {
7598
7622
  cursorId: "0",
@@ -7611,8 +7635,7 @@ LIMIT ${limit}`,
7611
7635
  SELECT c.*, toString(d.latestCursorId) AS __delta_cursor
7612
7636
  FROM candidates c
7613
7637
  INNER JOIN delta_candidates d ON c.traceId = d.traceId
7614
- ORDER BY d.latestCursorId ASC, c.traceId ASC, c.${order.field} ${order.direction}, c.dedupeKey ASC, c.endedAt DESC
7615
- LIMIT 1 BY c.traceId
7638
+ ORDER BY d.latestCursorId ASC, c.traceId ASC
7616
7639
  LIMIT ${limit}`,
7617
7640
  query_params: parameters.params,
7618
7641
  sharedSnapshot: true
@@ -7656,6 +7679,7 @@ function compileClickHouseTraceRootPayloads(keys, scope, columns = "payload") {
7656
7679
  query: `SELECT traceId, spanId AS rootSpanId, dedupeKey, organizationId, resourceId, ${columns === "payload" ? `metadataRaw AS metadata, ${PREVIEW_COLUMNS.join(", ")}` : "input, output, error"}
7657
7680
  FROM ${TABLE_TRACE_ROOTS}
7658
7681
  WHERE (startedAt, traceId, dedupeKey, endedAt, ifNull(organizationId, ''), isNull(organizationId), ifNull(resourceId, ''), isNull(resourceId)) IN (${tuples.join(", ")})${compileTenantScope(scope, parameters)}
7682
+ ORDER BY ${ROOT_VERSION_ORDER}
7659
7683
  LIMIT 1 BY traceId, dedupeKey, organizationId, resourceId`,
7660
7684
  query_params: parameters.params
7661
7685
  };
@@ -7952,7 +7976,8 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
7952
7976
  traceId: String(key.traceId),
7953
7977
  dedupeKey: String(key.dedupeKey),
7954
7978
  sortValue: sortValue(key.sortValue),
7955
- rootStartedAt: asIsoTimestamp(key.rootStartedAt)
7979
+ rootStartedAt: asIsoTimestamp(key.rootStartedAt),
7980
+ rootEndedAt: asIsoTimestamp(key.rootEndedAt)
7956
7981
  })))) : [];
7957
7982
  if (plan.paginationMode === "page") return _mastra_core_storage.traceQueryResponseSchema.parse({
7958
7983
  traces: await toTraceRecords(rows),
@@ -8160,7 +8185,7 @@ async function querySpans(client, plan, timeoutMs) {
8160
8185
  if (remaining <= 0) throw new _mastra_core_storage.TraceQueryExecutionError();
8161
8186
  return await runWithClickHouseTraceQueryTimeout(client, {
8162
8187
  timeoutMs: remaining,
8163
- memoryLimitBytes: 512 * 1024 * 1024
8188
+ memoryLimitBytes: 536870912
8164
8189
  }, compiled);
8165
8190
  };
8166
8191
  const selected = (await run(compileClickHouseSpanQuery(plan))).map((row) => ({
@@ -8277,14 +8302,14 @@ function afterCursor(plan, parameters) {
8277
8302
  return `(${terms.join(" OR ")})`;
8278
8303
  }
8279
8304
  const THREAD_QUERY_EXECUTION_LIMITS = {
8280
- memoryLimitBytes: 512 * 1024 * 1024,
8305
+ memoryLimitBytes: 536870912,
8281
8306
  maxThreads: 2,
8282
8307
  maxBlockSize: 4096,
8283
- spillThresholdBytes: 32 * 1024 * 1024
8308
+ spillThresholdBytes: 33554432
8284
8309
  };
8285
8310
  const MAX_CANDIDATE_THREADS = 16384;
8286
8311
  const MAX_CANDIDATE_TRACES = 1e5;
8287
- const MAX_CANDIDATE_ID_BYTES = 4 * 1024 * 1024;
8312
+ const MAX_CANDIDATE_ID_BYTES = 4194304;
8288
8313
  const MAX_FULL_HISTORY_ROOTS = 25e4;
8289
8314
  function canUseThreadCandidates(probe) {
8290
8315
  return Number(probe.threadCount) <= MAX_CANDIDATE_THREADS && Number(probe.traceCount) <= MAX_CANDIDATE_TRACES && Number(probe.threadBytes) + Number(probe.traceBytes) <= MAX_CANDIDATE_ID_BYTES;
@@ -8361,7 +8386,7 @@ function compileClickHouseThreadRowsQuery(plan, strategy = "full") {
8361
8386
  const sortedRoots = `roots AS (
8362
8387
  SELECT organizationId, traceId, ${rootDataFields.map((field) => `${rootExpression(field)} AS ${field}`).join(", ")}
8363
8388
  FROM ${TABLE_TRACE_ROOTS} ${scope.length || strategy === "candidates" ? `WHERE ${[...scope, ...strategy === "candidates" ? [candidateTracesFilter] : []].join(" AND ")}` : ""}
8364
- ORDER BY organizationId, traceId, startedAt ASC, spanId, endedAt DESC, isNotNull(threadId) DESC, dedupeKey
8389
+ ORDER BY organizationId, traceId, startedAt ASC, spanId, ${ROOT_VERSION_ORDER}
8365
8390
  LIMIT 1 BY organizationId, traceId
8366
8391
  )`;
8367
8392
  return {
@@ -8394,7 +8419,7 @@ function* batches(rows, maxRows = 256) {
8394
8419
  let bytes = 0;
8395
8420
  for (const row of rows) {
8396
8421
  const size = Buffer.byteLength(JSON.stringify(row), "utf8") * 2;
8397
- if (batch.length && (batch.length >= maxRows || bytes + size > 48 * 1024)) {
8422
+ if (batch.length && (batch.length >= maxRows || bytes + size > 49152)) {
8398
8423
  yield batch;
8399
8424
  batch = [];
8400
8425
  bytes = 0;
@@ -8463,7 +8488,7 @@ async function queryThreadRows(client, plan, timeoutMs, strategy = "auto") {
8463
8488
  ${contextFields.join(", ")}, if(spanType = 'agent_run', timeToFirstChunkMs, NULL) AS timeToFirstChunkMs,
8464
8489
  if(isNull(error), 'success', 'error') AS status, ${PREVIEW_COLUMNS.join(", ")}
8465
8490
  FROM ${TABLE_TRACE_ROOTS} WHERE ${rootWhere}
8466
- ORDER BY isNotNull(threadId) DESC LIMIT 1 BY organizationId, traceId, spanId`,
8491
+ ORDER BY ${ROOT_VERSION_ORDER} LIMIT 1 BY organizationId, traceId, spanId`,
8467
8492
  query_params: { roots: keys(batch) }
8468
8493
  }));
8469
8494
  const byRoot = new Map(stored.map((row) => [rootKey(row), row]));
@@ -8471,7 +8496,7 @@ async function queryThreadRows(client, plan, timeoutMs, strategy = "auto") {
8471
8496
  for (const batch of batches(missing, 32)) {
8472
8497
  const payloads = await run({
8473
8498
  query: `SELECT organizationId, traceId, spanId, input, output, error FROM ${TABLE_TRACE_ROOTS}
8474
- WHERE ${rootWhere} ORDER BY isNotNull(threadId) DESC LIMIT 1 BY organizationId, traceId, spanId`,
8499
+ WHERE ${rootWhere} ORDER BY ${ROOT_VERSION_ORDER} LIMIT 1 BY organizationId, traceId, spanId`,
8475
8500
  query_params: { roots: keys(batch) }
8476
8501
  });
8477
8502
  for (const payload of payloads) {
@@ -8540,6 +8565,7 @@ const DIMENSION_SQL = {
8540
8565
  entityName: "r.entityName",
8541
8566
  name: "r.name",
8542
8567
  spanType: "r.spanType",
8568
+ entityId: "r.entityId",
8543
8569
  environment: "r.environment",
8544
8570
  status: TRACE_STATUS_SQL,
8545
8571
  serviceName: "r.serviceName",
@@ -8578,12 +8604,6 @@ const COMPARISON_SQL = {
8578
8604
  function compileClickHouseTraceAggregate(plan) {
8579
8605
  const parameters = new ParameterBuilder();
8580
8606
  const ctes = compileClickHouseTraceCandidates(plan, "*", parameters, { oneRootPerTrace: true });
8581
- ctes.push(`trace_roots AS (
8582
- SELECT *
8583
- FROM candidates
8584
- ORDER BY traceId, dedupeKey
8585
- LIMIT 1 BY traceId
8586
- )`);
8587
8607
  const dimensionSql = (field) => {
8588
8608
  if (field.startsWith("metadata.")) return traceMetadataValueSql(parameters.add(field.slice(9), "String"));
8589
8609
  const sql = Object.hasOwn(DIMENSION_SQL, field) ? DIMENSION_SQL[field] : void 0;
@@ -8617,7 +8637,7 @@ function compileClickHouseTraceAggregate(plan) {
8617
8637
  }
8618
8638
  ctes.push(`facts AS (
8619
8639
  SELECT ${factColumns.join(",\n ")}
8620
- FROM trace_roots r${usageJoin}
8640
+ FROM candidates r${usageJoin}
8621
8641
  )`);
8622
8642
  const mixedUnitsSql = "minOrNull(unitMin) != maxOrNull(unitMax)";
8623
8643
  const measureSql = (name) => {
@@ -9969,7 +9989,7 @@ async function detectExistingDeltaCursorStrategy(client) {
9969
9989
  }
9970
9990
  }
9971
9991
  const TRACE_QUERY_DISCOVERY_DEFAULT_TIMEOUT_MS = 5e3;
9972
- const TRACE_QUERY_DISCOVERY_DEFAULT_MEMORY_LIMIT_BYTES = 256 * 1024 * 1024;
9992
+ const TRACE_QUERY_DISCOVERY_DEFAULT_MEMORY_LIMIT_BYTES = 268435456;
9973
9993
  function resolveTraceQueryDiscoveryMemoryLimitBytes(memoryLimitBytes = TRACE_QUERY_DISCOVERY_DEFAULT_MEMORY_LIMIT_BYTES) {
9974
9994
  if (!Number.isSafeInteger(memoryLimitBytes) || memoryLimitBytes <= 0) throw new RangeError("traceQuery.discovery.memoryLimitBytes must be a positive safe integer");
9975
9995
  return memoryLimitBytes;
@@ -10123,6 +10143,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
10123
10143
  "trace-query-context-ids",
10124
10144
  ...timeToFirstChunkFeatures,
10125
10145
  "trace-query-root-span-fields",
10146
+ "trace-query-root-entity-id",
10126
10147
  "trace-aggregate",
10127
10148
  ...spanQueryFeatures,
10128
10149
  ...threadQueryFeatures
@@ -10146,6 +10167,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
10146
10167
  "trace-query-context-ids",
10147
10168
  ...timeToFirstChunkFeatures,
10148
10169
  "trace-query-root-span-fields",
10170
+ "trace-query-root-entity-id",
10149
10171
  "trace-aggregate",
10150
10172
  ...spanQueryFeatures,
10151
10173
  ...threadQueryFeatures
@@ -11360,10 +11382,12 @@ var WorkflowsStorageClickhouse = class extends _mastra_core_storage.WorkflowsSto
11360
11382
  conditions.push(`JSONExtractString(snapshot, 'status') = {var_status:String}`);
11361
11383
  values.var_status = status;
11362
11384
  }
11363
- if (resourceId) if (await this.#db.hasColumn(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "resourceId")) {
11364
- conditions.push(`resourceId = {var_resourceId:String}`);
11365
- values.var_resourceId = resourceId;
11366
- } else this.logger.warn(`[${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
11385
+ if (resourceId) {
11386
+ if (await this.#db.hasColumn(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "resourceId")) {
11387
+ conditions.push(`resourceId = {var_resourceId:String}`);
11388
+ values.var_resourceId = resourceId;
11389
+ } else this.logger.warn(`[${_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
11390
+ }
11367
11391
  if (fromDate) {
11368
11392
  conditions.push(`createdAt >= {var_from_date:DateTime64(3)}`);
11369
11393
  values.var_from_date = fromDate.getTime() / 1e3;