@mastra/clickhouse 1.26.0-alpha.3 → 1.26.0-alpha.4

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/index.js CHANGED
@@ -6846,7 +6846,9 @@ const TRACE_STATUS_SQL = `if(isNotNull(r.error), 'error', 'success')`;
6846
6846
  * Core before 1.68 has no field registry at all.
6847
6847
  */
6848
6848
  const timeToFirstChunkSupported = "timeToFirstChunkMs" in (coreStorage.TRACE_QUERY_FIELD_REGISTRY?.trace ?? {});
6849
- const timeToFirstChunkFeatures = timeToFirstChunkSupported ? ["trace-query-time-to-first-chunk"] : [];
6849
+ /** Core before the sort rejects `orderBy` on the field, so the sort is only declared when core plans it. */
6850
+ const timeToFirstChunkSortSupported = coreStorage.TRACE_QUERY_ORDER_FIELDS?.includes("timeToFirstChunkMs") === true;
6851
+ const timeToFirstChunkFeatures = timeToFirstChunkSupported ? timeToFirstChunkSortSupported ? ["trace-query-time-to-first-chunk", "trace-query-time-to-first-chunk-sort"] : ["trace-query-time-to-first-chunk"] : [];
6850
6852
  function durationMsSql(startedAt, endedAt) {
6851
6853
  return `dateDiff('millisecond', ${startedAt}, ${endedAt})`;
6852
6854
  }
@@ -7189,7 +7191,7 @@ function fieldDefinition(registry, field) {
7189
7191
  return definition;
7190
7192
  }
7191
7193
  function resolveOrderField(field) {
7192
- if (field === "startedAt" || field === "endedAt") return field;
7194
+ if (field === "startedAt" || field === "endedAt" || field === "timeToFirstChunkMs") return field;
7193
7195
  throw new Error(`Unsupported trusted trace-query field: ${field}`);
7194
7196
  }
7195
7197
  function isMetadataField(field) {
@@ -7467,9 +7469,11 @@ function traceOrder(plan) {
7467
7469
  */
7468
7470
  function keysetCursorCondition(plan, order, parameters) {
7469
7471
  if (plan.paginationMode !== "keyset" || !plan.cursor) return "";
7472
+ if (plan.cursor.sortValue === null) return `(isNull(sortValue) AND traceId > ${parameters.add(plan.cursor.traceId, "String")})`;
7473
+ const numeric = order.field === "timeToFirstChunkMs";
7470
7474
  const comparison = order.direction === "ASC" ? ">" : "<";
7471
- const sortValue = parameters.add(plan.cursor.sortValue, "DateTime64(3, 'UTC')");
7472
- return `(sortValue ${comparison} ${sortValue} OR (sortValue = ${sortValue} AND traceId > ${parameters.add(plan.cursor.traceId, "String")}))`;
7475
+ const sortValue = parameters.add(plan.cursor.sortValue, numeric ? "Float64" : "DateTime64(3, 'UTC')");
7476
+ return `(sortValue ${comparison} ${sortValue} OR (sortValue = ${sortValue} AND traceId > ${parameters.add(plan.cursor.traceId, "String")})${numeric ? " OR isNull(sortValue)" : ""})`;
7473
7477
  }
7474
7478
  /**
7475
7479
  * First statement of keyset and page mode: the page's traces (after the keyset cursor, `limit + 1`
@@ -7481,21 +7485,22 @@ function compileClickHouseTraceQueryKeys(plan) {
7481
7485
  if (plan.paginationMode === "delta") throw new Error("Delta trace queries have no keys statement");
7482
7486
  const parameters = new ParameterBuilder();
7483
7487
  const order = traceOrder(plan);
7484
- const ctes = compileClickHouseTraceCandidates(plan, "r.traceId AS traceId, r.dedupeKey AS dedupeKey, r.startedAt AS startedAt, r.endedAt AS endedAt", parameters, QUERY_TRACES_SCOPE);
7488
+ 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);
7485
7489
  const cursor = keysetCursorCondition(plan, order, parameters);
7486
7490
  const pick = order.direction === "DESC" ? "max" : "min";
7491
+ const rootOrder = order.field === "timeToFirstChunkMs" ? `ifNull(${order.field}, ${pick === "max" ? "-inf" : "inf"})` : order.field;
7487
7492
  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")}`;
7488
7493
  return {
7489
7494
  query: `WITH ${ctes.join(",\n")},
7490
7495
  per_trace AS (
7491
7496
  SELECT traceId, ${pick}(${order.field}) AS sortValue,
7492
- ${pick === "max" ? "argMax" : "argMin"}((dedupeKey, startedAt), ${order.field}) AS root
7497
+ ${pick === "max" ? "argMax" : "argMin"}((dedupeKey, startedAt), ${rootOrder}) AS root
7493
7498
  FROM candidates
7494
7499
  GROUP BY traceId${cursor ? `\n HAVING ${cursor}` : ""}
7495
7500
  )
7496
7501
  SELECT traceId, root.1 AS dedupeKey, sortValue, root.2 AS rootStartedAt, count() OVER () AS __total
7497
7502
  FROM per_trace
7498
- ORDER BY sortValue ${order.direction}, traceId ASC
7503
+ ORDER BY sortValue ${order.direction} NULLS LAST, traceId ASC
7499
7504
  ${limit}`,
7500
7505
  query_params: parameters.params,
7501
7506
  sharedSnapshot: true
@@ -7513,16 +7518,16 @@ function compileClickHouseTraceRowsByKey(plan, keys) {
7513
7518
  const ts = (value) => parameters.add(value, "DateTime64(3, 'UTC')");
7514
7519
  const byEnd = order.field === "endedAt";
7515
7520
  const tuples = keys.map((key) => {
7516
- const started = ts(byEnd ? key.rootStartedAt : key.sortValue);
7521
+ const started = ts(key.rootStartedAt);
7517
7522
  const id = `${parameters.add(key.traceId, "String")}, ${parameters.add(key.dedupeKey, "String")}`;
7518
- return byEnd ? `(${started}, ${id}, ${ts(key.sortValue)})` : `(${started}, ${id})`;
7523
+ return byEnd ? `(${started}, ${id}, ${ts(String(key.sortValue))})` : `(${started}, ${id})`;
7519
7524
  });
7520
7525
  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("");
7521
7526
  return {
7522
7527
  query: `SELECT ${TRACE_SELECT}
7523
7528
  FROM ${TABLE_TRACE_ROOTS} r
7524
7529
  WHERE (r.startedAt, r.traceId, r.dedupeKey${byEnd ? ", r.endedAt" : ""}) IN (${tuples.join(", ")})${compileTenantScope(plan.scope, parameters)}${predicate}
7525
- ORDER BY ${order.field} ${order.direction}, traceId ASC
7530
+ ORDER BY ${order.field} ${order.direction} NULLS LAST, traceId ASC
7526
7531
  LIMIT 1 BY traceId`,
7527
7532
  query_params: parameters.params
7528
7533
  };
@@ -7913,6 +7918,7 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
7913
7918
  });
7914
7919
  }
7915
7920
  const keys = await run(compileClickHouseTraceQueryKeys(plan));
7921
+ const sortValue = (value) => plan.orderBy.field !== "timeToFirstChunkMs" ? asIsoTimestamp(value) : value == null ? null : Number(value);
7916
7922
  let total = Number(keys[0]?.__total ?? 0);
7917
7923
  if (plan.paginationMode === "page" && keys.length === 0 && plan.page > 0) {
7918
7924
  const [stats] = await run(compileClickHouseTraceQueryTotal(plan));
@@ -7922,7 +7928,7 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
7922
7928
  const rows = visible.length > 0 ? await run(compileClickHouseTraceRowsByKey(plan, visible.map((key) => ({
7923
7929
  traceId: String(key.traceId),
7924
7930
  dedupeKey: String(key.dedupeKey),
7925
- sortValue: asIsoTimestamp(key.sortValue),
7931
+ sortValue: sortValue(key.sortValue),
7926
7932
  rootStartedAt: asIsoTimestamp(key.rootStartedAt)
7927
7933
  })))) : [];
7928
7934
  if (plan.paginationMode === "page") return coreStorage.traceQueryResponseSchema.parse({
@@ -7940,7 +7946,7 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
7940
7946
  traces: await toTraceRecords(rows),
7941
7947
  page: { next: keys.length > plan.limit && lastKey ? coreStorage.encodeTraceQueryCursor(plan, {
7942
7948
  result: "traces",
7943
- sortValue: asIsoTimestamp(lastKey.sortValue),
7949
+ sortValue: sortValue(lastKey.sortValue),
7944
7950
  traceId: String(lastKey.traceId)
7945
7951
  }) : null }
7946
7952
  });
@@ -8020,10 +8026,10 @@ const displayFields = [
8020
8026
  ];
8021
8027
  var Parameters = class {
8022
8028
  values = {};
8023
- add(value, timestamp = false) {
8029
+ add(value, type = typeof value === "number" ? "UInt64" : "String") {
8024
8030
  const name = `span_${Object.keys(this.values).length}`;
8025
- this.values[name] = timestamp ? new Date(value).toISOString().replace("T", " ").replace(/Z$/, "") : value;
8026
- return `{${name}:${timestamp ? "DateTime64(3, 'UTC')" : typeof value === "number" ? "UInt64" : "String"}}`;
8031
+ this.values[name] = type === "DateTime64(3, 'UTC')" ? new Date(value).toISOString().replace("T", " ").replace(/Z$/, "") : value;
8032
+ return `{${name}:${type}}`;
8027
8033
  }
8028
8034
  };
8029
8035
  function identityValues(row) {
@@ -8039,10 +8045,17 @@ function identityValues(row) {
8039
8045
  function afterCursor$1(plan, parameters) {
8040
8046
  if (!plan.cursor) return "1";
8041
8047
  const cursor = plan.cursor;
8042
- const timestamp = parameters.add(cursor.sortValue, true);
8043
8048
  const field = `s.${plan.orderBy.field}`;
8044
- const terms = [`${field} ${plan.orderBy.direction === "asc" ? ">" : "<"} ${timestamp}`];
8045
- const equal = [`${field} = ${timestamp}`];
8049
+ const terms = [];
8050
+ const equal = [];
8051
+ if (cursor.sortValue === null) equal.push(`isNull(${field})`);
8052
+ else {
8053
+ const numeric = plan.orderBy.field === "timeToFirstChunkMs";
8054
+ const value = parameters.add(cursor.sortValue, numeric ? "Float64" : "DateTime64(3, 'UTC')");
8055
+ terms.push(`${field} ${plan.orderBy.direction === "asc" ? ">" : "<"} ${value}`);
8056
+ if (numeric) terms.push(`isNull(${field})`);
8057
+ equal.push(`${field} = ${value}`);
8058
+ }
8046
8059
  for (const key of identityFields) {
8047
8060
  const value = cursor[key];
8048
8061
  if (value === null) equal.push(`isNull(s.${key})`);
@@ -8070,8 +8083,8 @@ function fieldExpression(field) {
8070
8083
  }
8071
8084
  function compileClickHouseSpanQuery(plan) {
8072
8085
  const parameters = new Parameters();
8073
- const from = parameters.add(plan.timeRange.from, true);
8074
- const to = parameters.add(plan.timeRange.to, true);
8086
+ const from = parameters.add(plan.timeRange.from, "DateTime64(3, 'UTC')");
8087
+ const to = parameters.add(plan.timeRange.to, "DateTime64(3, 'UTC')");
8075
8088
  const scope = [];
8076
8089
  if (plan.scope) {
8077
8090
  scope.push(`organizationId = ${parameters.add(plan.scope.organizationId)}`);
@@ -8084,6 +8097,7 @@ function compileClickHouseSpanQuery(plan) {
8084
8097
  const selectionFields = [.../* @__PURE__ */ new Set([
8085
8098
  "startedAt",
8086
8099
  "endedAt",
8100
+ plan.orderBy.field,
8087
8101
  ...predicateFields$1(plan.where)
8088
8102
  ])].filter((field) => !new Set(identityFields).has(field));
8089
8103
  const expressions = selectionFields.map(fieldExpression);
@@ -8097,13 +8111,14 @@ function compileClickHouseSpanQuery(plan) {
8097
8111
  ORDER BY ${identities}, e.endedAt DESC
8098
8112
  LIMIT 1 BY ${identities}
8099
8113
  )
8100
- SELECT ${[
8114
+ SELECT ${[.../* @__PURE__ */ new Set([
8101
8115
  ...identityFields,
8102
8116
  "startedAt",
8103
- "endedAt"
8104
- ].map((field) => `s.${field}`).join(", ")}
8117
+ "endedAt",
8118
+ plan.orderBy.field
8119
+ ])].map((field) => `s.${field}`).join(", ")}
8105
8120
  FROM candidates s WHERE s.startedAt >= ${from} AND s.startedAt < ${to} AND (${predicate.sql}) AND ${after}
8106
- ORDER BY s.${plan.orderBy.field} ${plan.orderBy.direction === "asc" ? "ASC" : "DESC"}, ${identityFields.map((field) => `s.${field} ASC NULLS LAST`).join(", ")}
8121
+ ORDER BY s.${plan.orderBy.field} ${plan.orderBy.direction === "asc" ? "ASC" : "DESC"} NULLS LAST, ${identityFields.map((field) => `s.${field} ASC NULLS LAST`).join(", ")}
8107
8122
  LIMIT ${parameters.add(plan.limit + 1)}`,
8108
8123
  query_params: {
8109
8124
  ...parameters.values,
@@ -8195,7 +8210,7 @@ async function querySpans(client, plan, timeoutMs) {
8195
8210
  resourceId: last.resourceId,
8196
8211
  traceId: last.traceId,
8197
8212
  spanId: last.spanId,
8198
- sortValue: last[plan.orderBy.field]
8213
+ sortValue: last[plan.orderBy.field] ?? null
8199
8214
  }) : null;
8200
8215
  return result;
8201
8216
  }