@mastra/clickhouse 1.17.0-alpha.0 → 1.17.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.
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import { MessageList } from "@mastra/core/agent";
6
6
  import { MastraBase } from "@mastra/core/base";
7
7
  import { parseFieldKey } from "@mastra/core/utils";
8
8
  import { coreFeatures } from "@mastra/core/features";
9
+ import { randomUUID } from "crypto";
9
10
  import { saveScorePayloadSchema } from "@mastra/core/evals";
10
11
  //#region src/storage/db/replication.ts
11
12
  const DEFAULT_ZOOKEEPER_PATH = "/clickhouse/tables/{shard}/{database}/{table}";
@@ -71,12 +72,22 @@ function getEngineNameAndArgs(engine) {
71
72
  }
72
73
  function hasBalancedParens(s) {
73
74
  let depth = 0;
74
- for (const c of s) if (c === "(") depth++;
75
- else if (c === ")") {
76
- depth--;
77
- if (depth < 0) return false;
75
+ let quote;
76
+ for (let i = 0; i < s.length; i++) {
77
+ const c = s[i];
78
+ if (quote) {
79
+ if (c === "\\") i++;
80
+ else if (c === quote) quote = void 0;
81
+ continue;
82
+ }
83
+ if (c === "`" || c === "\"" || c === "'") quote = c;
84
+ else if (c === "(") depth++;
85
+ else if (c === ")") {
86
+ depth--;
87
+ if (depth < 0) return false;
88
+ }
78
89
  }
79
- return depth === 0;
90
+ return depth === 0 && quote === void 0;
80
91
  }
81
92
  function buildReplicatedTableEngine(engine, replication) {
82
93
  if (!replication) return engine;
@@ -148,20 +159,51 @@ function addOnClusterToDDL(sql, replication) {
148
159
  return out;
149
160
  }
150
161
  function rewriteEngineClauses(sql, replication) {
151
- return sql.replace(/ENGINE\s*=\s*(\w+)\s*/gi, (match, engineName, offset, source) => {
152
- const argsStart = offset + match.length;
153
- if (source[argsStart] !== "(") return `ENGINE = ${buildReplicatedTableEngine(engineName, replication)}`;
154
- let depth = 0;
155
- for (let i = argsStart; i < source.length; i++) {
156
- const char = source[i];
157
- if (char === "(") depth++;
158
- else if (char === ")") {
159
- depth--;
160
- if (depth === 0) return `ENGINE = ${buildReplicatedTableEngine(`${engineName}${source.slice(argsStart, i + 1)}`, replication)}`;
162
+ const enginePattern = /ENGINE\s*=\s*(\w+)/gi;
163
+ let result = "";
164
+ let consumedUntil = 0;
165
+ for (let match = enginePattern.exec(sql); match; match = enginePattern.exec(sql)) {
166
+ const engineName = match[1];
167
+ if (!engineName) continue;
168
+ let engineEnd = enginePattern.lastIndex;
169
+ let argsStart = engineEnd;
170
+ while (/\s/.test(sql[argsStart] ?? "")) argsStart++;
171
+ let engine = engineName;
172
+ if (sql[argsStart] === "(") {
173
+ let depth = 0;
174
+ let closingParenEnd;
175
+ let quote;
176
+ for (let i = argsStart; i < sql.length; i++) {
177
+ const char = sql[i];
178
+ if (quote) {
179
+ if (char === "\\") i++;
180
+ else if (char === quote) quote = void 0;
181
+ continue;
182
+ }
183
+ if (char === "`" || char === "\"" || char === "'") quote = char;
184
+ else if (char === "(") depth++;
185
+ else if (char === ")") {
186
+ depth--;
187
+ if (depth === 0) {
188
+ closingParenEnd = i + 1;
189
+ break;
190
+ }
191
+ }
161
192
  }
193
+ if (closingParenEnd === void 0) {
194
+ result += sql.slice(consumedUntil, enginePattern.lastIndex);
195
+ consumedUntil = enginePattern.lastIndex;
196
+ continue;
197
+ }
198
+ engineEnd = closingParenEnd;
199
+ engine = `${engineName}${sql.slice(argsStart, engineEnd)}`;
162
200
  }
163
- return match;
164
- });
201
+ result += sql.slice(consumedUntil, match.index);
202
+ result += `ENGINE = ${buildReplicatedTableEngine(engine, replication)}`;
203
+ consumedUntil = engineEnd;
204
+ enginePattern.lastIndex = engineEnd;
205
+ }
206
+ return result + sql.slice(consumedUntil);
165
207
  }
166
208
  function applyReplicationToDDL(sql, replication) {
167
209
  return addOnClusterToDDL(replication ? rewriteEngineClauses(sql, replication) : sql, replication);
@@ -2478,6 +2520,7 @@ var ObservabilityStorageClickhouse = class extends ObservabilityStorage {
2478
2520
  }
2479
2521
  }
2480
2522
  async batchDeleteTraces(args) {
2523
+ this.assertUnscopedBatchDeleteTraces(args);
2481
2524
  try {
2482
2525
  if (args.traceIds.length === 0) return;
2483
2526
  await this.client.command({
@@ -2504,6 +2547,7 @@ const TABLE_METRIC_EVENTS = "mastra_metric_events";
2504
2547
  const TABLE_LOG_EVENTS = "mastra_log_events";
2505
2548
  const TABLE_SCORE_EVENTS = "mastra_score_events";
2506
2549
  const TABLE_FEEDBACK_EVENTS = "mastra_feedback_events";
2550
+ const TABLE_DELETION_REQUESTS = "mastra_deletion_requests";
2507
2551
  const TABLE_METRIC_EVENTS_DELTA = "mastra_metric_events_delta";
2508
2552
  const TABLE_LOG_EVENTS_DELTA = "mastra_log_events_delta";
2509
2553
  const TABLE_SCORE_EVENTS_DELTA = "mastra_score_events_delta";
@@ -3199,6 +3243,23 @@ PARTITION BY toDate(timestamp)
3199
3243
  ORDER BY (traceId, timestamp, feedbackId)
3200
3244
  SETTINGS allow_nullable_key = 1
3201
3245
  `;
3246
+ const DELETION_REQUESTS_DDL = `
3247
+ CREATE TABLE IF NOT EXISTS ${TABLE_DELETION_REQUESTS} (
3248
+ requestId String,
3249
+ organizationId String DEFAULT '',
3250
+ resourceId String DEFAULT '',
3251
+ signal LowCardinality(String),
3252
+ predicateType LowCardinality(String),
3253
+ predicateValues Array(String),
3254
+ requestedAt DateTime64(3),
3255
+ requestedBy String DEFAULT '',
3256
+ lastAppliedAt DateTime64(3) DEFAULT 0,
3257
+ purgeVerifiedAt DateTime64(3) DEFAULT 0,
3258
+ updatedAt DateTime64(3)
3259
+ )
3260
+ ENGINE = ReplacingMergeTree(updatedAt)
3261
+ ORDER BY (organizationId, resourceId, requestId)
3262
+ `;
3202
3263
  function buildFeedbackEventsDeltaDDL() {
3203
3264
  return `
3204
3265
  CREATE TABLE IF NOT EXISTS ${TABLE_FEEDBACK_EVENTS_DELTA} (
@@ -3348,6 +3409,7 @@ const BASE_TABLE_DDL = [
3348
3409
  LOG_EVENTS_DDL,
3349
3410
  SCORE_EVENTS_DDL,
3350
3411
  FEEDBACK_EVENTS_DDL,
3412
+ DELETION_REQUESTS_DDL,
3351
3413
  DISCOVERY_VALUES_DDL,
3352
3414
  DISCOVERY_PAIRS_DDL
3353
3415
  ];
@@ -3432,6 +3494,7 @@ const ALL_TABLE_NAMES = [
3432
3494
  TABLE_LOG_EVENTS,
3433
3495
  TABLE_SCORE_EVENTS,
3434
3496
  TABLE_FEEDBACK_EVENTS,
3497
+ TABLE_DELETION_REQUESTS,
3435
3498
  TABLE_METRIC_EVENTS_DELTA,
3436
3499
  TABLE_LOG_EVENTS_DELTA,
3437
3500
  TABLE_SCORE_EVENTS_DELTA,
@@ -4089,6 +4152,35 @@ function feedbackRecordToRow(feedback) {
4089
4152
  };
4090
4153
  }
4091
4154
  //#endregion
4155
+ //#region src/storage/domains/observability/v-next/deletion-requests.ts
4156
+ const EPOCH = "1970-01-01T00:00:00.000Z";
4157
+ async function recordDeletionRequest(client, args) {
4158
+ const row = {
4159
+ requestId: args.requestId,
4160
+ organizationId: args.organizationId ?? "",
4161
+ resourceId: args.resourceId ?? "",
4162
+ signal: args.signal,
4163
+ predicateType: args.predicateType,
4164
+ predicateValues: args.predicateValues,
4165
+ requestedAt: args.requestedAt,
4166
+ requestedBy: args.requestedBy ?? "",
4167
+ lastAppliedAt: EPOCH,
4168
+ purgeVerifiedAt: EPOCH,
4169
+ updatedAt: args.requestedAt
4170
+ };
4171
+ await client.insert({
4172
+ table: TABLE_DELETION_REQUESTS,
4173
+ values: [row],
4174
+ format: "JSONEachRow",
4175
+ clickhouse_settings: isReplicationConfigured(args.replication) ? {
4176
+ ...CH_INSERT_SETTINGS,
4177
+ insert_quorum: "auto",
4178
+ insert_quorum_parallel: 1
4179
+ } : CH_INSERT_SETTINGS
4180
+ });
4181
+ return row;
4182
+ }
4183
+ //#endregion
4092
4184
  //#region src/storage/domains/observability/v-next/discovery.ts
4093
4185
  async function queryJson$3(client, query, params = {}) {
4094
4186
  return await (await client.query({
@@ -6591,6 +6683,17 @@ function buildTraceCursor(row) {
6591
6683
  }
6592
6684
  //#endregion
6593
6685
  //#region src/storage/domains/observability/v-next/tracing.ts
6686
+ /**
6687
+ * Tracing operations for ClickHouse v-next observability.
6688
+ *
6689
+ * Owns: batchCreateSpans, getSpan, getSpans, getTrace, getTraceLight,
6690
+ * listBranches, batchDeleteTraces, dangerouslyClearSpanEvents.
6691
+ * Delegates to trace-roots.ts: listTraces, getRootSpan.
6692
+ *
6693
+ * `listBranches` reads from the MV-fed `mastra_trace_branches` table (one row
6694
+ * per branch anchor span). It lives here -- alongside the other read paths
6695
+ * over the trace data -- since branches are conceptually a subset of traces.
6696
+ */
6594
6697
  const BRANCH_SPAN_TYPE_SQL_LIST = BRANCH_SPAN_TYPES.map((t) => `'${t}'`).join(", ");
6595
6698
  /** Insert a single completed span. */
6596
6699
  async function createSpan(client, args) {
@@ -6730,16 +6833,40 @@ async function getTraceLight(client, args) {
6730
6833
  };
6731
6834
  }
6732
6835
  /**
6733
- * Delete traces by traceId.
6734
- * Issues lightweight DELETE against both span_events and trace_roots.
6836
+ * Delete traces by traceId, cascading to trace-derived tables and trace-linked
6837
+ * signal events (metrics, logs, scores, feedback). Signal rows with a NULL
6838
+ * traceId are never affected.
6839
+ *
6840
+ * On the tracing tables, rows are targeted by tracing identity: traceId +
6841
+ * dedupeKey (which starts with traceId). The dedupeKey condition is redundant
6842
+ * for correctness (dedupeKey = traceId:spanId) but satisfies the design-doc
6843
+ * requirement that trace deletes reference dedupeKey and helps the engine
6844
+ * narrow within the sorted ORDER BY key. Signal tables key on their own event
6845
+ * ids, so they are targeted by traceId alone.
6846
+ *
6847
+ * `trace_branches` must be deleted explicitly: its MV fires on insert only,
6848
+ * so span deletes never propagate to it. Delta tables self-expire via TTL and
6849
+ * discovery tables self-heal, so neither needs explicit deletes.
6735
6850
  *
6736
- * Targets rows by tracing identity: traceId + dedupeKey (which starts with traceId).
6737
- * The dedupeKey condition is redundant for correctness (dedupeKey = traceId:spanId)
6738
- * but satisfies the design-doc requirement that trace deletes reference dedupeKey
6739
- * and helps the engine narrow within the sorted ORDER BY key.
6851
+ * Records the predicate before using lightweight DELETE FROM on every table.
6852
+ * Lightweight deletes hide rows through ClickHouse's delete mask; physical
6853
+ * removal depends on the deployment's configured retention and merge policy.
6854
+ *
6855
+ * When the optional tenant scope (`organizationId` / `resourceId`) is set,
6856
+ * every DELETE additionally requires the row's tenant columns to match.
6740
6857
  */
6741
- async function batchDeleteTraces(client, args) {
6858
+ async function batchDeleteTraces(client, args, replication) {
6742
6859
  if (args.traceIds.length === 0) return;
6860
+ await recordDeletionRequest(client, {
6861
+ requestId: randomUUID(),
6862
+ organizationId: args.organizationId,
6863
+ resourceId: args.resourceId,
6864
+ signal: "traces",
6865
+ predicateType: "traceIds",
6866
+ predicateValues: [...args.traceIds],
6867
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
6868
+ replication
6869
+ });
6743
6870
  const params = {};
6744
6871
  const traceInPlaceholders = [];
6745
6872
  const dedupeOrParts = [];
@@ -6753,13 +6880,35 @@ async function batchDeleteTraces(client, args) {
6753
6880
  }
6754
6881
  const traceInList = traceInPlaceholders.join(", ");
6755
6882
  const dedupeCondition = dedupeOrParts.length === 1 ? dedupeOrParts[0] : `(${dedupeOrParts.join(" OR ")})`;
6756
- await Promise.all([client.command({
6757
- query: `DELETE FROM ${TABLE_SPAN_EVENTS} WHERE traceId IN (${traceInList}) AND ${dedupeCondition}`,
6758
- query_params: params
6759
- }), client.command({
6760
- query: `DELETE FROM ${TABLE_TRACE_ROOTS} WHERE traceId IN (${traceInList}) AND ${dedupeCondition}`,
6761
- query_params: params
6762
- })]);
6883
+ let scopeCondition = "";
6884
+ if (args.organizationId !== void 0) {
6885
+ params.scope_org = args.organizationId;
6886
+ scopeCondition += ` AND organizationId = {scope_org:String}`;
6887
+ }
6888
+ if (args.resourceId !== void 0) {
6889
+ params.scope_res = args.resourceId;
6890
+ scopeCondition += ` AND resourceId = {scope_res:String}`;
6891
+ }
6892
+ const tracingTables = [
6893
+ TABLE_SPAN_EVENTS,
6894
+ TABLE_TRACE_ROOTS,
6895
+ TABLE_TRACE_BRANCHES
6896
+ ];
6897
+ const signalTables = [
6898
+ TABLE_METRIC_EVENTS,
6899
+ TABLE_LOG_EVENTS,
6900
+ TABLE_SCORE_EVENTS,
6901
+ TABLE_FEEDBACK_EVENTS
6902
+ ];
6903
+ await Promise.all([...tracingTables.map((table) => client.command({
6904
+ query: `DELETE FROM ${table} WHERE traceId IN (${traceInList}) AND ${dedupeCondition}${scopeCondition}`,
6905
+ query_params: params,
6906
+ clickhouse_settings: { lightweight_deletes_sync: "2" }
6907
+ })), ...signalTables.map((table) => client.command({
6908
+ query: `DELETE FROM ${table} WHERE traceId IN (${traceInList})${scopeCondition}`,
6909
+ query_params: params,
6910
+ clickhouse_settings: { lightweight_deletes_sync: "2" }
6911
+ }))]);
6763
6912
  }
6764
6913
  /**
6765
6914
  * List trace branches with optional filtering, pagination, and ordering.
@@ -7962,7 +8111,7 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
7962
8111
  }
7963
8112
  async batchDeleteTraces(args) {
7964
8113
  try {
7965
- await batchDeleteTraces(this.#client, args);
8114
+ await batchDeleteTraces(this.#client, args, this.#replication);
7966
8115
  } catch (error) {
7967
8116
  if (error instanceof MastraError) throw error;
7968
8117
  throw new MastraError({
@@ -8832,6 +8981,6 @@ var ClickhouseStoreVNext = class extends ClickhouseStore {
8832
8981
  }
8833
8982
  };
8834
8983
  //#endregion
8835
- export { COLUMN_TYPES, ClickhouseStore, ClickhouseStoreVNext, MemoryStorageClickhouse, ObservabilityStorageClickhouse, ObservabilityStorageClickhouseVNext, ScoresStorageClickhouse, TABLE_ENGINES, WorkflowsStorageClickhouse };
8984
+ export { COLUMN_TYPES, ClickhouseStore, ClickhouseStoreVNext, MemoryStorageClickhouse, ObservabilityStorageClickhouse, ObservabilityStorageClickhouseVNext, ScoresStorageClickhouse, TABLE_DELETION_REQUESTS, TABLE_ENGINES, WorkflowsStorageClickhouse, recordDeletionRequest };
8836
8985
 
8837
8986
  //# sourceMappingURL=index.js.map