@mastra/clickhouse 1.26.0-alpha.4 → 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.
@@ -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-alpha.5"
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-alpha.5",
3
3
  "package": "@mastra/clickhouse",
4
4
  "exports": {},
5
5
  "modules": {}
package/dist/index.cjs CHANGED
@@ -6924,6 +6924,10 @@ const TRACE_FIELDS = {
6924
6924
  sql: "r.timeToFirstChunkMs",
6925
6925
  parameterType: "Float64"
6926
6926
  },
6927
+ entityId: {
6928
+ sql: "r.entityId",
6929
+ parameterType: "String"
6930
+ },
6927
6931
  entityName: {
6928
6932
  sql: "r.entityName",
6929
6933
  parameterType: "String"
@@ -7322,6 +7326,28 @@ function compileTenantScope(scope, parameters) {
7322
7326
  if (scope.resourceId !== void 0) sql += `\n AND resourceId = ${parameters.add(scope.resourceId, "String")}`;
7323
7327
  return sql;
7324
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`;
7325
7351
  function compileClickHouseTraceScope(selection, relationCollections, parameters, scope, seedConjuncts = [], options = {}) {
7326
7352
  const from = parameters.add(selection.timeRange.from, "DateTime64(3, 'UTC')");
7327
7353
  const to = parameters.add(selection.timeRange.to, "DateTime64(3, 'UTC')");
@@ -7345,7 +7371,15 @@ function compileClickHouseTraceScope(selection, relationCollections, parameters,
7345
7371
  SELECT *
7346
7372
  FROM ${TABLE_TRACE_ROOTS} r
7347
7373
  WHERE startedAt >= ${from}
7348
- AND startedAt < ${to}${tenant}${seedFilter}${bound}
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
7349
7383
  )`] : [`current_roots AS (
7350
7384
  SELECT *
7351
7385
  FROM ${TABLE_TRACE_ROOTS}
@@ -7355,7 +7389,7 @@ function compileClickHouseTraceScope(selection, relationCollections, parameters,
7355
7389
  WHERE startedAt >= ${from}
7356
7390
  AND startedAt < ${to}${tenant}${seedFilter}
7357
7391
  )${tenant}
7358
- ORDER BY traceId, dedupeKey
7392
+ ORDER BY traceId, startedAt, spanId, ${ROOT_VERSION_ORDER}
7359
7393
  LIMIT 1 BY traceId
7360
7394
  )`, `root_scope AS (
7361
7395
  SELECT *
@@ -7485,11 +7519,7 @@ function traceOrder(plan) {
7485
7519
  direction: plan.orderBy.direction === "asc" ? "ASC" : "DESC"
7486
7520
  };
7487
7521
  }
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
- */
7522
+ /** Keyset cursor on a trace's sort value (the current root's), with traceId breaking ties. */
7493
7523
  function keysetCursorCondition(plan, order, parameters) {
7494
7524
  if (plan.paginationMode !== "keyset" || !plan.cursor) return "";
7495
7525
  if (plan.cursor.sortValue === null) return `(isNull(sortValue) AND traceId > ${parameters.add(plan.cursor.traceId, "String")})`;
@@ -7500,9 +7530,8 @@ function keysetCursorCondition(plan, order, parameters) {
7500
7530
  }
7501
7531
  /**
7502
7532
  * 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.
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.
7506
7535
  */
7507
7536
  function compileClickHouseTraceQueryKeys(plan) {
7508
7537
  if (plan.paginationMode === "delta") throw new Error("Delta trace queries have no keys statement");
@@ -7510,19 +7539,12 @@ function compileClickHouseTraceQueryKeys(plan) {
7510
7539
  const order = traceOrder(plan);
7511
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);
7512
7541
  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
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")}`;
7516
7543
  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
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}` : ""}
7526
7548
  ORDER BY sortValue ${order.direction} NULLS LAST, traceId ASC
7527
7549
  ${limit}`,
7528
7550
  query_params: parameters.params,
@@ -7530,27 +7552,28 @@ ${limit}`,
7530
7552
  };
7531
7553
  }
7532
7554
  /**
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.
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.
7537
7565
  */
7538
7566
  function compileClickHouseTraceRowsByKey(plan, keys) {
7539
7567
  const parameters = new ParameterBuilder();
7540
7568
  const order = traceOrder(plan);
7541
7569
  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
- });
7570
+ const tuples = keys.map((key) => `(${ts(key.rootStartedAt)}, ${parameters.add(key.traceId, "String")}, ${parameters.add(key.dedupeKey, "String")}, ${ts(key.rootEndedAt)})`);
7548
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("");
7549
7572
  return {
7550
7573
  query: `SELECT ${TRACE_SELECT}
7551
7574
  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
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}
7554
7577
  LIMIT 1 BY traceId`,
7555
7578
  query_params: parameters.params
7556
7579
  };
@@ -7569,9 +7592,7 @@ FROM candidates`,
7569
7592
  /**
7570
7593
  * The list statement of groups and delta mode (rows without `metadata` and the previews). Keyset
7571
7594
  * 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.
7595
+ * Candidates hold one current root per trace, as in the keys statement.
7575
7596
  */
7576
7597
  function compileClickHouseTraceQuery(plan, deltaHead) {
7577
7598
  const parameters = new ParameterBuilder();
@@ -7592,7 +7613,6 @@ LIMIT ${limit}`,
7592
7613
  };
7593
7614
  }
7594
7615
  if (plan.paginationMode !== "delta") throw new Error("Keyset and page trace queries use compileClickHouseTraceQueryKeys and compileClickHouseTraceRowsByKey");
7595
- const order = traceOrder(plan);
7596
7616
  const watermark = _mastra_core_storage.getTraceQueryDeltaWatermark(plan, "clickhouse");
7597
7617
  const after = watermark ? parseDeltaWatermark(watermark) : {
7598
7618
  cursorId: "0",
@@ -7611,8 +7631,7 @@ LIMIT ${limit}`,
7611
7631
  SELECT c.*, toString(d.latestCursorId) AS __delta_cursor
7612
7632
  FROM candidates c
7613
7633
  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
7634
+ ORDER BY d.latestCursorId ASC, c.traceId ASC
7616
7635
  LIMIT ${limit}`,
7617
7636
  query_params: parameters.params,
7618
7637
  sharedSnapshot: true
@@ -7656,6 +7675,7 @@ function compileClickHouseTraceRootPayloads(keys, scope, columns = "payload") {
7656
7675
  query: `SELECT traceId, spanId AS rootSpanId, dedupeKey, organizationId, resourceId, ${columns === "payload" ? `metadataRaw AS metadata, ${PREVIEW_COLUMNS.join(", ")}` : "input, output, error"}
7657
7676
  FROM ${TABLE_TRACE_ROOTS}
7658
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}
7659
7679
  LIMIT 1 BY traceId, dedupeKey, organizationId, resourceId`,
7660
7680
  query_params: parameters.params
7661
7681
  };
@@ -7952,7 +7972,8 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
7952
7972
  traceId: String(key.traceId),
7953
7973
  dedupeKey: String(key.dedupeKey),
7954
7974
  sortValue: sortValue(key.sortValue),
7955
- rootStartedAt: asIsoTimestamp(key.rootStartedAt)
7975
+ rootStartedAt: asIsoTimestamp(key.rootStartedAt),
7976
+ rootEndedAt: asIsoTimestamp(key.rootEndedAt)
7956
7977
  })))) : [];
7957
7978
  if (plan.paginationMode === "page") return _mastra_core_storage.traceQueryResponseSchema.parse({
7958
7979
  traces: await toTraceRecords(rows),
@@ -8361,7 +8382,7 @@ function compileClickHouseThreadRowsQuery(plan, strategy = "full") {
8361
8382
  const sortedRoots = `roots AS (
8362
8383
  SELECT organizationId, traceId, ${rootDataFields.map((field) => `${rootExpression(field)} AS ${field}`).join(", ")}
8363
8384
  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
8385
+ ORDER BY organizationId, traceId, startedAt ASC, spanId, ${ROOT_VERSION_ORDER}
8365
8386
  LIMIT 1 BY organizationId, traceId
8366
8387
  )`;
8367
8388
  return {
@@ -8463,7 +8484,7 @@ async function queryThreadRows(client, plan, timeoutMs, strategy = "auto") {
8463
8484
  ${contextFields.join(", ")}, if(spanType = 'agent_run', timeToFirstChunkMs, NULL) AS timeToFirstChunkMs,
8464
8485
  if(isNull(error), 'success', 'error') AS status, ${PREVIEW_COLUMNS.join(", ")}
8465
8486
  FROM ${TABLE_TRACE_ROOTS} WHERE ${rootWhere}
8466
- ORDER BY isNotNull(threadId) DESC LIMIT 1 BY organizationId, traceId, spanId`,
8487
+ ORDER BY ${ROOT_VERSION_ORDER} LIMIT 1 BY organizationId, traceId, spanId`,
8467
8488
  query_params: { roots: keys(batch) }
8468
8489
  }));
8469
8490
  const byRoot = new Map(stored.map((row) => [rootKey(row), row]));
@@ -8471,7 +8492,7 @@ async function queryThreadRows(client, plan, timeoutMs, strategy = "auto") {
8471
8492
  for (const batch of batches(missing, 32)) {
8472
8493
  const payloads = await run({
8473
8494
  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`,
8495
+ WHERE ${rootWhere} ORDER BY ${ROOT_VERSION_ORDER} LIMIT 1 BY organizationId, traceId, spanId`,
8475
8496
  query_params: { roots: keys(batch) }
8476
8497
  });
8477
8498
  for (const payload of payloads) {
@@ -8540,6 +8561,7 @@ const DIMENSION_SQL = {
8540
8561
  entityName: "r.entityName",
8541
8562
  name: "r.name",
8542
8563
  spanType: "r.spanType",
8564
+ entityId: "r.entityId",
8543
8565
  environment: "r.environment",
8544
8566
  status: TRACE_STATUS_SQL,
8545
8567
  serviceName: "r.serviceName",
@@ -8578,12 +8600,6 @@ const COMPARISON_SQL = {
8578
8600
  function compileClickHouseTraceAggregate(plan) {
8579
8601
  const parameters = new ParameterBuilder();
8580
8602
  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
8603
  const dimensionSql = (field) => {
8588
8604
  if (field.startsWith("metadata.")) return traceMetadataValueSql(parameters.add(field.slice(9), "String"));
8589
8605
  const sql = Object.hasOwn(DIMENSION_SQL, field) ? DIMENSION_SQL[field] : void 0;
@@ -8617,7 +8633,7 @@ function compileClickHouseTraceAggregate(plan) {
8617
8633
  }
8618
8634
  ctes.push(`facts AS (
8619
8635
  SELECT ${factColumns.join(",\n ")}
8620
- FROM trace_roots r${usageJoin}
8636
+ FROM candidates r${usageJoin}
8621
8637
  )`);
8622
8638
  const mixedUnitsSql = "minOrNull(unitMin) != maxOrNull(unitMax)";
8623
8639
  const measureSql = (name) => {
@@ -10123,6 +10139,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
10123
10139
  "trace-query-context-ids",
10124
10140
  ...timeToFirstChunkFeatures,
10125
10141
  "trace-query-root-span-fields",
10142
+ "trace-query-root-entity-id",
10126
10143
  "trace-aggregate",
10127
10144
  ...spanQueryFeatures,
10128
10145
  ...threadQueryFeatures
@@ -10146,6 +10163,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
10146
10163
  "trace-query-context-ids",
10147
10164
  ...timeToFirstChunkFeatures,
10148
10165
  "trace-query-root-span-fields",
10166
+ "trace-query-root-entity-id",
10149
10167
  "trace-aggregate",
10150
10168
  ...spanQueryFeatures,
10151
10169
  ...threadQueryFeatures