@mastra/clickhouse 1.23.0-alpha.0 → 1.23.0-alpha.1

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.23.0-alpha.0"
6
+ version: "1.23.0-alpha.1"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.23.0-alpha.0",
2
+ "version": "1.23.0-alpha.1",
3
3
  "package": "@mastra/clickhouse",
4
4
  "exports": {},
5
5
  "modules": {}
package/dist/index.cjs CHANGED
@@ -7443,6 +7443,201 @@ async function queryThreads(client, plan, timeoutMs) {
7443
7443
  }) : null }
7444
7444
  });
7445
7445
  }
7446
+ /** Compile a span-row filter with exactly the same rules as trace span predicates. */
7447
+ function compileSpanQueryPredicate(predicate) {
7448
+ const parameters = new ParameterBuilder();
7449
+ return {
7450
+ sql: compileScalarPredicate(predicate, SPAN_FIELDS, parameters),
7451
+ params: parameters.params
7452
+ };
7453
+ }
7454
+ //#endregion
7455
+ //#region src/storage/domains/observability/v-next/span-query.ts
7456
+ const identityFields = [
7457
+ "organizationId",
7458
+ "resourceId",
7459
+ "traceId",
7460
+ "spanId"
7461
+ ];
7462
+ const identities = identityFields.join(", ");
7463
+ const identitySetColumns = "isNull(organizationId), ifNull(organizationId, ''), isNull(resourceId), ifNull(resourceId, ''), traceId, spanId";
7464
+ const identitySetKey = `tuple(${identitySetColumns})`;
7465
+ const displayFields = [
7466
+ "parentSpanId",
7467
+ "name",
7468
+ "spanType",
7469
+ "entityType",
7470
+ "entityId",
7471
+ "entityName",
7472
+ "startedAt",
7473
+ "endedAt",
7474
+ "model",
7475
+ "provider",
7476
+ "durationMs",
7477
+ "status"
7478
+ ];
7479
+ var Parameters = class {
7480
+ values = {};
7481
+ add(value, timestamp = false) {
7482
+ const name = `span_${Object.keys(this.values).length}`;
7483
+ this.values[name] = timestamp ? new Date(value).toISOString().replace("T", " ").replace(/Z$/, "") : value;
7484
+ return `{${name}:${timestamp ? "DateTime64(3, 'UTC')" : typeof value === "number" ? "UInt64" : "String"}}`;
7485
+ }
7486
+ };
7487
+ function identityValues(row) {
7488
+ return [
7489
+ row.organizationId === null ? 1 : 0,
7490
+ row.organizationId ?? "",
7491
+ row.resourceId === null ? 1 : 0,
7492
+ row.resourceId ?? "",
7493
+ row.traceId,
7494
+ row.spanId
7495
+ ];
7496
+ }
7497
+ function afterCursor(plan, parameters) {
7498
+ if (!plan.cursor) return "1";
7499
+ const cursor = plan.cursor;
7500
+ const timestamp = parameters.add(cursor.sortValue, true);
7501
+ const field = `s.${plan.orderBy.field}`;
7502
+ const terms = [`${field} ${plan.orderBy.direction === "asc" ? ">" : "<"} ${timestamp}`];
7503
+ const equal = [`${field} = ${timestamp}`];
7504
+ for (const key of identityFields) {
7505
+ const value = cursor[key];
7506
+ if (value === null) equal.push(`isNull(s.${key})`);
7507
+ else {
7508
+ const parameter = parameters.add(value);
7509
+ terms.push(`(${equal.join(" AND ")} AND (s.${key} > ${parameter} OR isNull(s.${key})))`);
7510
+ equal.push(`s.${key} = ${parameter}`);
7511
+ }
7512
+ }
7513
+ return `(${terms.join(" OR ")})`;
7514
+ }
7515
+ function predicateFields(predicate, fields = /* @__PURE__ */ new Set()) {
7516
+ if (!predicate) return fields;
7517
+ if (predicate.type === "boolean") for (const arg of predicate.args) predicateFields(arg, fields);
7518
+ else if (predicate.type === "not") predicateFields(predicate.arg, fields);
7519
+ else fields.add(predicate.field);
7520
+ return fields;
7521
+ }
7522
+ function fieldExpression(field) {
7523
+ if (field === "model" || field === "provider") return `if(JSONType(e.attributes, '${field}') = 'String', JSONExtractString(e.attributes, '${field}'), NULL)`;
7524
+ if (field === "durationMs") return "dateDiff('millisecond', e.startedAt, e.endedAt)";
7525
+ if (field === "status") return "if(isNull(e.error), 'success', 'error')";
7526
+ if (field === "error") return "if(isNull(e.error), NULL, 'error')";
7527
+ return `e.${field}`;
7528
+ }
7529
+ function compileClickHouseSpanQuery(plan) {
7530
+ const parameters = new Parameters();
7531
+ const from = parameters.add(plan.timeRange.from, true);
7532
+ const to = parameters.add(plan.timeRange.to, true);
7533
+ const scope = [];
7534
+ if (plan.scope) {
7535
+ scope.push(`organizationId = ${parameters.add(plan.scope.organizationId)}`);
7536
+ if (plan.scope.resourceId !== void 0) scope.push(`resourceId = ${parameters.add(plan.scope.resourceId)}`);
7537
+ }
7538
+ const predicate = plan.where ? compileSpanQueryPredicate(plan.where) : {
7539
+ sql: "1",
7540
+ params: {}
7541
+ };
7542
+ const selectionFields = [.../* @__PURE__ */ new Set([
7543
+ "startedAt",
7544
+ "endedAt",
7545
+ ...predicateFields(plan.where)
7546
+ ])].filter((field) => !new Set(identityFields).has(field));
7547
+ const expressions = selectionFields.map(fieldExpression);
7548
+ const after = afterCursor(plan, parameters);
7549
+ return {
7550
+ query: `WITH candidate_ids AS (
7551
+ SELECT ${identitySetKey} AS identity FROM ${TABLE_SPAN_EVENTS}
7552
+ PREWHERE endedAt >= ${from}
7553
+ WHERE startedAt >= ${from} AND startedAt < ${to} ${scope.length ? `AND ${scope.join(" AND ")}` : ""}
7554
+ ), candidates AS (
7555
+ SELECT ${identities}, ${selectionFields.map((field, index) => `${expressions[index]} AS ${field}`).join(", ")}
7556
+ FROM ${TABLE_SPAN_EVENTS} e
7557
+ PREWHERE e.endedAt >= ${from}
7558
+ WHERE ${identitySetKey} IN (SELECT identity FROM candidate_ids) ${scope.length ? `AND ${scope.join(" AND ")}` : ""}
7559
+ ORDER BY ${identities}, e.endedAt DESC
7560
+ LIMIT 1 BY ${identities}
7561
+ )
7562
+ SELECT ${[
7563
+ ...identityFields,
7564
+ "startedAt",
7565
+ "endedAt"
7566
+ ].map((field) => `s.${field}`).join(", ")}
7567
+ FROM candidates s WHERE s.startedAt >= ${from} AND s.startedAt < ${to} AND (${predicate.sql}) AND ${after}
7568
+ ORDER BY s.${plan.orderBy.field} ${plan.orderBy.direction === "asc" ? "ASC" : "DESC"}, ${identityFields.map((field) => `s.${field} ASC NULLS LAST`).join(", ")}
7569
+ LIMIT ${parameters.add(plan.limit + 1)}`,
7570
+ query_params: {
7571
+ ...parameters.values,
7572
+ ...predicate.params
7573
+ }
7574
+ };
7575
+ }
7576
+ function iso(value) {
7577
+ return new Date(value.includes("T") ? value : `${value.replace(" ", "T")}Z`).toISOString();
7578
+ }
7579
+ async function querySpans(client, plan, timeoutMs) {
7580
+ const deadline = performance.now() + _mastra_core_storage.resolveTraceQueryTimeoutMs(timeoutMs);
7581
+ const run = async (compiled) => {
7582
+ const remaining = Math.floor(deadline - performance.now());
7583
+ if (remaining <= 0) throw new _mastra_core_storage.TraceQueryExecutionError();
7584
+ return await runWithClickHouseTraceQueryTimeout(client, {
7585
+ timeoutMs: remaining,
7586
+ memoryLimitBytes: 512 * 1024 * 1024
7587
+ }, compiled);
7588
+ };
7589
+ const selected = (await run(compileClickHouseSpanQuery(plan))).map((row) => ({
7590
+ ...row,
7591
+ startedAt: iso(row.startedAt),
7592
+ endedAt: iso(row.endedAt)
7593
+ }));
7594
+ const page = selected.slice(0, plan.limit);
7595
+ if (!page.length) return {
7596
+ spans: [],
7597
+ page: { next: null }
7598
+ };
7599
+ const payloadWhere = `tuple(${identitySetColumns}, endedAt) IN {page:Array(Tuple(UInt8, String, UInt8, String, String, String, DateTime64(3, 'UTC')))}`;
7600
+ const payloads = await run({
7601
+ query: `SELECT ${identities}, ${displayFields.map((field, index) => `data.${index + 1} AS ${field}`).join(", ")},
7602
+ data.${displayFields.length + 1} AS inputPreview, data.${displayFields.length + 2} AS outputPreview
7603
+ FROM (SELECT ${identities}, any(tuple(${displayFields.map(fieldExpression).join(", ")}, substringUTF8(input, 1, ${_mastra_core_storage.SPAN_QUERY_MAX_PREVIEW_CHARACTERS + 1}), substringUTF8(output, 1, ${_mastra_core_storage.SPAN_QUERY_MAX_PREVIEW_CHARACTERS + 1}))) AS data
7604
+ FROM ${TABLE_SPAN_EVENTS} e WHERE ${payloadWhere} GROUP BY ${identities})`,
7605
+ query_params: { page: page.map((row) => new _clickhouse_client.TupleParam([...identityValues(row), row.endedAt.replace("T", " ").replace(/Z$/, "")])) }
7606
+ });
7607
+ const metricWhere = `${identitySetKey} IN {page:Array(Tuple(UInt8, String, UInt8, String, String, String))}`;
7608
+ const metrics = await run({
7609
+ query: `SELECT ${identities}, data.1 AS name, data.2 AS estimatedCost,
7610
+ data.3 AS costUnit, data.4 AS allocation, data.5 AS costError
7611
+ FROM (SELECT ${identities}, metricId, argMax(tuple(name, estimatedCost, costUnit,
7612
+ JSONExtractString(costMetadata, 'allocation'), JSONHas(costMetadata, 'error')), timestamp) AS data
7613
+ FROM ${TABLE_METRIC_EVENTS} WHERE (${metricWhere}) AND name LIKE 'mastra_model_%'
7614
+ GROUP BY ${identities}, metricId)
7615
+ LIMIT ${_mastra_core_storage.SPAN_QUERY_MAX_COST_METRICS + 1}`,
7616
+ query_params: { page: page.map((row) => new _clickhouse_client.TupleParam(identityValues(row))) }
7617
+ });
7618
+ if (metrics.length > _mastra_core_storage.SPAN_QUERY_MAX_COST_METRICS) throw new _mastra_core_storage.TraceQueryResourceLimitError();
7619
+ const byIdentity = new Map(payloads.map((row) => [_mastra_core_storage.spanQueryIdentityKey(row), row]));
7620
+ const hydrated = page.flatMap((identity) => {
7621
+ const row = byIdentity.get(_mastra_core_storage.spanQueryIdentityKey(identity));
7622
+ if (!row) return [];
7623
+ const { inputPreview: _input, outputPreview: _output, ...metadata } = row;
7624
+ return [{
7625
+ ...metadata,
7626
+ ...identity,
7627
+ durationMs: Number(row.durationMs)
7628
+ }];
7629
+ });
7630
+ const result = _mastra_core_storage.buildSpanQueryResponse(plan, hydrated, payloads, metrics);
7631
+ const last = page.at(-1);
7632
+ result.page.next = selected.length > plan.limit ? _mastra_core_storage.encodeSpanQueryCursor(plan, {
7633
+ organizationId: last.organizationId,
7634
+ resourceId: last.resourceId,
7635
+ traceId: last.traceId,
7636
+ spanId: last.spanId,
7637
+ sortValue: last[plan.orderBy.field]
7638
+ }) : null;
7639
+ return result;
7640
+ }
7446
7641
  //#endregion
7447
7642
  //#region src/storage/domains/observability/v-next/trace-aggregate.ts
7448
7643
  /** Trace-root column expressions (alias `r`) for each groupable dimension; same values `where` sees. */
@@ -8434,6 +8629,7 @@ function buildBranchCursor(row) {
8434
8629
  }
8435
8630
  //#endregion
8436
8631
  //#region src/storage/domains/observability/v-next/index.ts
8632
+ const spanQueryFeatures = typeof _mastra_core_storage.planSpanQuery === "function" ? ["span-query"] : [];
8437
8633
  function buildSignalMigrationRequiredMessage(args) {
8438
8634
  const tableList = args.tables.map((table) => ` - ${table.table} (${table.engine})`).join("\n");
8439
8635
  return `
@@ -8865,6 +9061,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
8865
9061
  "tag-discovery",
8866
9062
  "metric-discovery",
8867
9063
  "trace-query",
9064
+ ...spanQueryFeatures,
8868
9065
  "trace-query-root-duration",
8869
9066
  "trace-query-discovery",
8870
9067
  "thread-query",
@@ -8884,6 +9081,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
8884
9081
  "metric-discovery",
8885
9082
  "delta-polling",
8886
9083
  "trace-query",
9084
+ ...spanQueryFeatures,
8887
9085
  "trace-query-root-duration",
8888
9086
  "trace-query-discovery",
8889
9087
  "thread-query",
@@ -9005,6 +9203,18 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
9005
9203
  }, error);
9006
9204
  }
9007
9205
  }
9206
+ async querySpans(plan) {
9207
+ try {
9208
+ return await querySpans(this.#client, plan, this.#traceQueryTimeoutMs);
9209
+ } catch (error) {
9210
+ if (error instanceof _mastra_core_error.MastraError || error instanceof _mastra_core_storage.TraceQueryExecutionError || error instanceof _mastra_core_storage.TraceQueryCursorError || error instanceof _mastra_core_storage.TraceQueryResourceLimitError) throw error;
9211
+ throw new _mastra_core_error.MastraError({
9212
+ id: (0, _mastra_core_storage.createStorageErrorId)("CLICKHOUSE", "QUERY_SPANS", "FAILED"),
9213
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
9214
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY
9215
+ }, error);
9216
+ }
9217
+ }
9008
9218
  async queryTraces(plan) {
9009
9219
  try {
9010
9220
  return await queryTraces(this.#client, plan, this.#traceQueryTimeoutMs, this.#deltaCursorStrategy);