@mastra/clickhouse 1.20.0-alpha.2 → 1.20.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.
@@ -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.20.0-alpha.2"
6
+ version: "1.20.0-alpha.4"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.20.0-alpha.2",
2
+ "version": "1.20.0-alpha.4",
3
3
  "package": "@mastra/clickhouse",
4
4
  "exports": {},
5
5
  "modules": {}
package/dist/index.cjs CHANGED
@@ -30,6 +30,7 @@ let _mastra_core_base = require("@mastra/core/base");
30
30
  let crypto$1 = require("crypto");
31
31
  let _mastra_core_utils = require("@mastra/core/utils");
32
32
  let _mastra_core_features = require("@mastra/core/features");
33
+ let zod_v4 = require("zod/v4");
33
34
  let _mastra_core_evals = require("@mastra/core/evals");
34
35
  //#region src/storage/db/replication.ts
35
36
  const DEFAULT_ZOOKEEPER_PATH = "/clickhouse/tables/{shard}/{database}/{table}";
@@ -2569,6 +2570,8 @@ const TABLE_TRACE_BRANCHES_DELTA = "mastra_trace_branches_delta";
2569
2570
  const TABLE_METRIC_EVENTS = "mastra_metric_events";
2570
2571
  const TABLE_LOG_EVENTS = "mastra_log_events";
2571
2572
  const TABLE_SCORE_EVENTS = "mastra_score_events";
2573
+ const TABLE_SCORE_EVENTS_CURRENT = "mastra_score_events_current";
2574
+ const TABLE_SCORE_EVENTS_CURRENT_BACKFILL = "mastra_score_events_current_backfill";
2572
2575
  const TABLE_FEEDBACK_EVENTS = "mastra_feedback_events";
2573
2576
  const TABLE_DELETION_REQUESTS = "mastra_deletion_requests";
2574
2577
  const TABLE_METRIC_EVENTS_DELTA = "mastra_metric_events_delta";
@@ -2584,6 +2587,7 @@ const MV_TRACE_BRANCHES_DELTA = "mastra_mv_trace_branches_delta";
2584
2587
  const MV_METRIC_EVENTS_DELTA = "mastra_mv_metric_events_delta";
2585
2588
  const MV_LOG_EVENTS_DELTA = "mastra_mv_log_events_delta";
2586
2589
  const MV_SCORE_EVENTS_DELTA = "mastra_mv_score_events_delta";
2590
+ const MV_SCORE_EVENTS_CURRENT = "mastra_mv_score_events_current";
2587
2591
  const MV_FEEDBACK_EVENTS_DELTA = "mastra_mv_feedback_events_delta";
2588
2592
  const MV_DISCOVERY_VALUES = "mastra_mv_discovery_values";
2589
2593
  const MV_DISCOVERY_PAIRS = "mastra_mv_discovery_pairs";
@@ -3101,19 +3105,53 @@ FROM (
3101
3105
  )
3102
3106
  `;
3103
3107
  }
3104
- const SCORE_EVENTS_DDL = `
3105
- CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS} (
3106
- -- Timestamp
3108
+ const SCORE_EVENT_COLUMN_NAMES = [
3109
+ "timestamp",
3110
+ "scoreId",
3111
+ "writeVersion",
3112
+ "traceId",
3113
+ "spanId",
3114
+ "experimentId",
3115
+ "scoreTraceId",
3116
+ "entityType",
3117
+ "entityId",
3118
+ "entityName",
3119
+ "entityVersionId",
3120
+ "parentEntityVersionId",
3121
+ "parentEntityType",
3122
+ "parentEntityId",
3123
+ "parentEntityName",
3124
+ "rootEntityVersionId",
3125
+ "rootEntityType",
3126
+ "rootEntityId",
3127
+ "rootEntityName",
3128
+ "userId",
3129
+ "organizationId",
3130
+ "resourceId",
3131
+ "runId",
3132
+ "sessionId",
3133
+ "threadId",
3134
+ "requestId",
3135
+ "environment",
3136
+ "executionSource",
3137
+ "serviceName",
3138
+ "scorerId",
3139
+ "scorerVersion",
3140
+ "scoreSource",
3141
+ "score",
3142
+ "reason",
3143
+ "tags",
3144
+ "metadata",
3145
+ "scope"
3146
+ ];
3147
+ const SCORE_EVENT_COLUMNS_DDL = `
3107
3148
  timestamp DateTime64(3, 'UTC'),
3108
-
3109
- -- IDs
3110
3149
  scoreId String,
3150
+ writeVersion UInt64 DEFAULT 0,
3111
3151
  traceId Nullable(String),
3112
3152
  spanId Nullable(String),
3113
3153
  experimentId Nullable(String),
3114
3154
  scoreTraceId Nullable(String),
3115
-
3116
- -- Entity hierarchy
3117
3155
  entityType LowCardinality(Nullable(String)),
3118
3156
  entityId Nullable(String),
3119
3157
  entityName Nullable(String),
@@ -3126,8 +3164,6 @@ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS} (
3126
3164
  rootEntityType LowCardinality(Nullable(String)),
3127
3165
  rootEntityId Nullable(String),
3128
3166
  rootEntityName Nullable(String),
3129
-
3130
- -- Context
3131
3167
  userId Nullable(String),
3132
3168
  organizationId Nullable(String),
3133
3169
  resourceId Nullable(String),
@@ -3138,30 +3174,48 @@ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS} (
3138
3174
  environment LowCardinality(Nullable(String)),
3139
3175
  executionSource LowCardinality(Nullable(String)),
3140
3176
  serviceName LowCardinality(Nullable(String)),
3141
-
3142
- -- Scorer identity
3143
3177
  scorerId LowCardinality(String),
3144
3178
  scorerVersion LowCardinality(Nullable(String)),
3145
3179
  scoreSource LowCardinality(Nullable(String)),
3146
-
3147
- -- Score value
3148
3180
  score Float64,
3149
-
3150
- -- Information-only
3151
3181
  reason Nullable(String),
3152
-
3153
- -- Query-relevant flexible fields
3154
3182
  tags Array(LowCardinality(String)) DEFAULT [],
3155
-
3156
- -- Information-only JSON payloads
3157
3183
  metadata Nullable(String),
3158
3184
  scope Nullable(String)
3185
+ `;
3186
+ const SCORE_EVENTS_DDL = `
3187
+ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS} (
3188
+ ${SCORE_EVENT_COLUMNS_DDL},
3189
+ INDEX idx_scoreId scoreId TYPE bloom_filter(0.01) GRANULARITY 1
3159
3190
  )
3160
3191
  ENGINE = ReplacingMergeTree
3161
3192
  PARTITION BY toDate(timestamp)
3162
3193
  ORDER BY (traceId, timestamp, scoreId)
3163
3194
  SETTINGS allow_nullable_key = 1
3164
3195
  `;
3196
+ const SCORE_EVENTS_CURRENT_DDL = `
3197
+ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS_CURRENT} (
3198
+ ${SCORE_EVENT_COLUMNS_DDL}
3199
+ )
3200
+ ENGINE = ReplacingMergeTree(writeVersion)
3201
+ PARTITION BY cityHash64(scoreId) % 64
3202
+ ORDER BY scoreId
3203
+ `;
3204
+ const SCORE_EVENTS_CURRENT_BACKFILL_DDL = `
3205
+ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS_CURRENT_BACKFILL} (
3206
+ marker String,
3207
+ completedAt DateTime64(3, 'UTC')
3208
+ )
3209
+ ENGINE = ReplacingMergeTree(completedAt)
3210
+ ORDER BY marker
3211
+ `;
3212
+ const SCORE_EVENTS_CURRENT_MV_DDL = `
3213
+ CREATE MATERIALIZED VIEW IF NOT EXISTS ${MV_SCORE_EVENTS_CURRENT}
3214
+ TO ${TABLE_SCORE_EVENTS_CURRENT}
3215
+ AS
3216
+ SELECT ${SCORE_EVENT_COLUMN_NAMES.join(", ")}
3217
+ FROM ${TABLE_SCORE_EVENTS}
3218
+ `;
3165
3219
  function buildScoreEventsDeltaDDL() {
3166
3220
  return `
3167
3221
  CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS_DELTA} (
@@ -3435,6 +3489,8 @@ const BASE_TABLE_DDL = [
3435
3489
  METRIC_EVENTS_DDL,
3436
3490
  LOG_EVENTS_DDL,
3437
3491
  SCORE_EVENTS_DDL,
3492
+ SCORE_EVENTS_CURRENT_DDL,
3493
+ SCORE_EVENTS_CURRENT_BACKFILL_DDL,
3438
3494
  FEEDBACK_EVENTS_DDL,
3439
3495
  DELETION_REQUESTS_DDL,
3440
3496
  DISCOVERY_VALUES_DDL,
@@ -3453,7 +3509,11 @@ function buildDeltaTableDDL() {
3453
3509
  function buildAllTableDDL() {
3454
3510
  return [...BASE_TABLE_DDL, ...buildDeltaTableDDL()];
3455
3511
  }
3456
- const BASE_MV_DDL = [TRACE_ROOTS_MV_DDL, TRACE_BRANCHES_MV_DDL];
3512
+ const BASE_MV_DDL = [
3513
+ TRACE_ROOTS_MV_DDL,
3514
+ TRACE_BRANCHES_MV_DDL,
3515
+ SCORE_EVENTS_CURRENT_MV_DDL
3516
+ ];
3457
3517
  function buildDeltaMvDDL(strategy) {
3458
3518
  return [
3459
3519
  buildTraceRootsDeltaMvDDL(strategy),
@@ -3475,11 +3535,11 @@ const addColumn = (table, name, type) => ({
3475
3535
  name,
3476
3536
  sql: `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${name} ${type}`
3477
3537
  });
3478
- const addBloomIndex = (table, name, column) => ({
3538
+ const addBloomIndex = (table, name, column, granularity = 2) => ({
3479
3539
  kind: "index",
3480
3540
  table,
3481
3541
  name,
3482
- sql: `ALTER TABLE ${table} ADD INDEX IF NOT EXISTS ${name} ${column} TYPE bloom_filter(0.01) GRANULARITY 2`
3542
+ sql: `ALTER TABLE ${table} ADD INDEX IF NOT EXISTS ${name} ${column} TYPE bloom_filter(0.01) GRANULARITY ${granularity}`
3483
3543
  });
3484
3544
  const ALL_MIGRATIONS = [
3485
3545
  addColumn(TABLE_SPAN_EVENTS, "entityVersionId", "Nullable(String)"),
@@ -3494,9 +3554,11 @@ const ALL_MIGRATIONS = [
3494
3554
  addColumn(TABLE_LOG_EVENTS, "entityVersionId", "Nullable(String)"),
3495
3555
  addColumn(TABLE_LOG_EVENTS, "parentEntityVersionId", "Nullable(String)"),
3496
3556
  addColumn(TABLE_LOG_EVENTS, "rootEntityVersionId", "Nullable(String)"),
3557
+ addColumn(TABLE_SCORE_EVENTS, "writeVersion", "UInt64 DEFAULT 0"),
3497
3558
  addColumn(TABLE_SCORE_EVENTS, "entityVersionId", "Nullable(String)"),
3498
3559
  addColumn(TABLE_SCORE_EVENTS, "parentEntityVersionId", "Nullable(String)"),
3499
3560
  addColumn(TABLE_SCORE_EVENTS, "rootEntityVersionId", "Nullable(String)"),
3561
+ addBloomIndex(TABLE_SCORE_EVENTS, "idx_scoreId", "scoreId", 1),
3500
3562
  addColumn(TABLE_FEEDBACK_EVENTS, "writeVersion", "UInt64 DEFAULT 0"),
3501
3563
  addColumn(TABLE_FEEDBACK_EVENTS, "entityVersionId", "Nullable(String)"),
3502
3564
  addColumn(TABLE_FEEDBACK_EVENTS, "reviewStatus", "LowCardinality(String) DEFAULT 'needs-review'"),
@@ -3522,6 +3584,8 @@ const ALL_TABLE_NAMES = [
3522
3584
  TABLE_METRIC_EVENTS,
3523
3585
  TABLE_LOG_EVENTS,
3524
3586
  TABLE_SCORE_EVENTS,
3587
+ TABLE_SCORE_EVENTS_CURRENT,
3588
+ TABLE_SCORE_EVENTS_CURRENT_BACKFILL,
3525
3589
  TABLE_FEEDBACK_EVENTS,
3526
3590
  TABLE_DELETION_REQUESTS,
3527
3591
  TABLE_METRIC_EVENTS_DELTA,
@@ -3539,6 +3603,7 @@ const SIGNAL_TTL_COLUMNS = {
3539
3603
  [TABLE_METRIC_EVENTS]: "timestamp",
3540
3604
  [TABLE_LOG_EVENTS]: "timestamp",
3541
3605
  [TABLE_SCORE_EVENTS]: "timestamp",
3606
+ [TABLE_SCORE_EVENTS_CURRENT]: "timestamp",
3542
3607
  [TABLE_FEEDBACK_EVENTS]: "timestamp"
3543
3608
  };
3544
3609
  /** Maps each signal key to the table(s) it controls. */
@@ -3550,7 +3615,7 @@ const SIGNAL_TO_TABLES = {
3550
3615
  ],
3551
3616
  logs: [TABLE_LOG_EVENTS],
3552
3617
  metrics: [TABLE_METRIC_EVENTS],
3553
- scores: [TABLE_SCORE_EVENTS],
3618
+ scores: [TABLE_SCORE_EVENTS, TABLE_SCORE_EVENTS_CURRENT],
3554
3619
  feedback: [TABLE_FEEDBACK_EVENTS]
3555
3620
  };
3556
3621
  const RETENTION_MANAGED_TABLES = [...Object.keys(SIGNAL_TTL_COLUMNS), TABLE_DELETION_REQUESTS];
@@ -5970,6 +6035,36 @@ async function migrateSignalTables(client, logger) {
5970
6035
  }
5971
6036
  }
5972
6037
  //#endregion
6038
+ //#region src/storage/domains/observability/v-next/score-current.ts
6039
+ const SCORE_CURRENT_BACKFILL_MARKER = "v1";
6040
+ async function backfillCurrentScores(client) {
6041
+ const markerRows = await (await client.query({
6042
+ query: `SELECT count() AS count
6043
+ FROM ${TABLE_SCORE_EVENTS_CURRENT_BACKFILL} FINAL
6044
+ WHERE marker = {marker:String}`,
6045
+ query_params: { marker: SCORE_CURRENT_BACKFILL_MARKER },
6046
+ format: "JSONEachRow"
6047
+ })).json();
6048
+ if (Number(markerRows[0]?.count ?? 0) > 0) return;
6049
+ const columns = SCORE_EVENT_COLUMN_NAMES.join(", ");
6050
+ await client.command({
6051
+ query: `INSERT INTO ${TABLE_SCORE_EVENTS_CURRENT} (${columns})
6052
+ SELECT ${columns}
6053
+ FROM (
6054
+ SELECT *, cityHash64(tuple(*)) AS _currentScoreFingerprint
6055
+ FROM ${TABLE_SCORE_EVENTS} FINAL
6056
+ ORDER BY scoreId, writeVersion DESC, timestamp DESC, _currentScoreFingerprint DESC
6057
+ LIMIT 1 BY scoreId
6058
+ )`,
6059
+ clickhouse_settings: { max_bytes_ratio_before_external_sort: .5 }
6060
+ });
6061
+ await client.command({
6062
+ query: `INSERT INTO ${TABLE_SCORE_EVENTS_CURRENT_BACKFILL} (marker, completedAt)
6063
+ VALUES ({marker:String}, now64(3, 'UTC'))`,
6064
+ query_params: { marker: SCORE_CURRENT_BACKFILL_MARKER }
6065
+ });
6066
+ }
6067
+ //#endregion
5973
6068
  //#region src/storage/domains/observability/v-next/scores.ts
5974
6069
  const SCORE_TYPED_COLUMNS = /* @__PURE__ */ new Set([
5975
6070
  "timestamp",
@@ -6046,6 +6141,13 @@ function mergeFilters(...parts) {
6046
6141
  function toWhereClause(filter) {
6047
6142
  return filter.conditions.length ? `WHERE ${filter.conditions.join(" AND ")}` : "";
6048
6143
  }
6144
+ function currentScoresRelation(sourcePredicate) {
6145
+ return `(
6146
+ SELECT *
6147
+ FROM ${TABLE_SCORE_EVENTS_CURRENT} FINAL
6148
+ ${sourcePredicate ? `WHERE ${sourcePredicate}` : ""}
6149
+ )`;
6150
+ }
6049
6151
  function buildScoreIdentityFilter(args) {
6050
6152
  const conditions = ["scorerId = {olapScorerId:String}"];
6051
6153
  const params = { olapScorerId: args.scorerId };
@@ -6081,6 +6183,24 @@ async function queryJson(client, query, params) {
6081
6183
  clickhouse_settings: CH_SETTINGS
6082
6184
  })).json();
6083
6185
  }
6186
+ async function scoreRowsWithWriteVersions(client, scores) {
6187
+ const scoreIds = [...new Set(scores.flatMap((score) => score.scoreId ? [score.scoreId] : []))];
6188
+ const existingVersions = [];
6189
+ const lookupBatchSize = 1e3;
6190
+ for (let offset = 0; offset < scoreIds.length; offset += lookupBatchSize) existingVersions.push(...await queryJson(client, `SELECT scoreId, toString(max(writeVersion)) AS writeVersion
6191
+ FROM ${TABLE_SCORE_EVENTS}
6192
+ WHERE scoreId IN ({scoreIds:Array(String)})
6193
+ GROUP BY scoreId`, { scoreIds: scoreIds.slice(offset, offset + lookupBatchSize) }));
6194
+ const versions = new Map(existingVersions.map((row) => [row.scoreId, BigInt(row.writeVersion)]));
6195
+ return scores.map((score) => {
6196
+ const writeVersion = score.scoreId ? (versions.get(score.scoreId) ?? 0n) + 1n : 1n;
6197
+ if (score.scoreId) versions.set(score.scoreId, writeVersion);
6198
+ return {
6199
+ ...scoreRecordToRow(score),
6200
+ writeVersion: writeVersion.toString()
6201
+ };
6202
+ });
6203
+ }
6084
6204
  async function createScore(client, args) {
6085
6205
  await batchCreateScores(client, { scores: [args.score] });
6086
6206
  }
@@ -6088,7 +6208,7 @@ async function batchCreateScores(client, args) {
6088
6208
  if (args.scores.length === 0) return;
6089
6209
  await client.insert({
6090
6210
  table: TABLE_SCORE_EVENTS,
6091
- values: args.scores.map(scoreRecordToRow),
6211
+ values: await scoreRowsWithWriteVersions(client, args.scores),
6092
6212
  format: "JSONEachRow",
6093
6213
  clickhouse_settings: CH_INSERT_SETTINGS
6094
6214
  });
@@ -6131,10 +6251,11 @@ async function deleteScores(client, args, replication) {
6131
6251
  conditions.push("resourceId = {delResourceId:String}");
6132
6252
  params.delResourceId = args.resourceId;
6133
6253
  }
6134
- await client.command({
6135
- query: `DELETE FROM ${TABLE_SCORE_EVENTS} WHERE ${conditions.join(" AND ")}`,
6254
+ const clickhouse_settings = { lightweight_deletes_sync: isReplicationConfigured(replication) ? "2" : "1" };
6255
+ for (const table of [TABLE_SCORE_EVENTS_CURRENT, TABLE_SCORE_EVENTS]) await client.command({
6256
+ query: `DELETE FROM ${table} WHERE ${conditions.join(" AND ")}`,
6136
6257
  query_params: params,
6137
- clickhouse_settings: { lightweight_deletes_sync: isReplicationConfigured(replication) ? "2" : "1" }
6258
+ clickhouse_settings
6138
6259
  });
6139
6260
  }
6140
6261
  async function listScores(client, args, strategy) {
@@ -6168,8 +6289,9 @@ async function listScores(client, args, strategy) {
6168
6289
  };
6169
6290
  }
6170
6291
  const currentDeltaCursor = deltaCursorEnabled ? await getDeltaCursor$2(client, whereClause, filter.params) : void 0;
6171
- const countResult = await queryJson(client, `SELECT count() AS total FROM ${TABLE_SCORE_EVENTS} AS s ${whereClause}`, filter.params);
6172
- const rows = await queryJson(client, `SELECT * FROM ${TABLE_SCORE_EVENTS} AS s ${whereClause} ORDER BY ${orderBy} LIMIT {limit:UInt32} OFFSET {offset:UInt32}`, {
6292
+ const scoresRelation = currentScoresRelation();
6293
+ const countResult = await queryJson(client, `SELECT count() AS total FROM ${scoresRelation} AS s ${whereClause}`, filter.params);
6294
+ const rows = await queryJson(client, `SELECT * FROM ${scoresRelation} AS s ${whereClause} ORDER BY ${orderBy} LIMIT {limit:UInt32} OFFSET {offset:UInt32}`, {
6173
6295
  ...filter.params,
6174
6296
  limit: pagination.limit,
6175
6297
  offset: pagination.offset
@@ -6228,14 +6350,15 @@ function buildScoresCursor(row) {
6228
6350
  return row.cursorId ?? "0";
6229
6351
  }
6230
6352
  async function getScoreById(client, scoreId) {
6231
- const rows = await queryJson(client, `SELECT * FROM ${TABLE_SCORE_EVENTS} WHERE scoreId = {scoreId:String} LIMIT 1`, { scoreId });
6353
+ const rows = await queryJson(client, `SELECT * FROM ${currentScoresRelation("scoreId = {scoreId:String}")} LIMIT 1`, { scoreId });
6232
6354
  return rows[0] ? rowToScoreRecord(rows[0]) : null;
6233
6355
  }
6234
6356
  async function getScoreAggregate(client, args) {
6235
6357
  const aggSql = getAggregationSql(args.aggregation);
6236
6358
  const identity = buildScoreIdentityFilter(args);
6237
6359
  const combined = mergeFilters(identity, buildScoresFilterConditions(args.filters));
6238
- const result = await queryJson(client, `SELECT ${aggSql} AS value FROM ${TABLE_SCORE_EVENTS} ${toWhereClause(combined)}`, combined.params);
6360
+ const whereClause = toWhereClause(combined);
6361
+ const result = await queryJson(client, `SELECT ${aggSql} AS value FROM ${currentScoresRelation()} ${whereClause}`, combined.params);
6239
6362
  const value = result[0]?.value == null ? null : Number(result[0]?.value);
6240
6363
  if (args.comparePeriod && args.filters?.timestamp) {
6241
6364
  const ts = args.filters.timestamp;
@@ -6269,7 +6392,8 @@ async function getScoreAggregate(client, args) {
6269
6392
  endExclusive: ts.endExclusive
6270
6393
  }
6271
6394
  }));
6272
- const prevResult = await queryJson(client, `SELECT ${aggSql} AS value FROM ${TABLE_SCORE_EVENTS} ${toWhereClause(prevCombined)}`, prevCombined.params);
6395
+ const prevWhereClause = toWhereClause(prevCombined);
6396
+ const prevResult = await queryJson(client, `SELECT ${aggSql} AS value FROM ${currentScoresRelation()} ${prevWhereClause}`, prevCombined.params);
6273
6397
  const previousValue = prevResult[0]?.value == null ? null : Number(prevResult[0]?.value);
6274
6398
  let changePercent = null;
6275
6399
  if (previousValue !== null && previousValue !== 0 && value !== null) changePercent = (value - previousValue) / Math.abs(previousValue) * 100;
@@ -6287,7 +6411,7 @@ async function getScoreBreakdown(client, args) {
6287
6411
  const combined = mergeFilters(buildScoreIdentityFilter(args), buildScoresFilterConditions(args.filters));
6288
6412
  const whereClause = toWhereClause(combined);
6289
6413
  const resolved = resolveScoreGroupBy(args.groupBy);
6290
- return { groups: (await queryJson(client, `SELECT ${resolved.map((e) => e.selectSql).join(", ")}, ${aggSql} AS value FROM ${TABLE_SCORE_EVENTS} ${whereClause} GROUP BY ${resolved.map((e) => e.groupSql).join(", ")} ORDER BY value DESC`, combined.params)).map((row) => ({
6414
+ return { groups: (await queryJson(client, `SELECT ${resolved.map((e) => e.selectSql).join(", ")}, ${aggSql} AS value FROM ${currentScoresRelation()} ${whereClause} GROUP BY ${resolved.map((e) => e.groupSql).join(", ")} ORDER BY value DESC`, combined.params)).map((row) => ({
6291
6415
  dimensions: Object.fromEntries(resolved.map((entry, index) => {
6292
6416
  const v = row[`group_by_${index}`];
6293
6417
  return [entry.key, v == null ? null : String(v)];
@@ -6306,7 +6430,7 @@ async function getScoreTimeSeries(client, args) {
6306
6430
  SELECT toStartOfInterval(timestamp, ${intervalSql}) AS bucket,
6307
6431
  ${resolved.map((e) => e.selectSql).join(", ")},
6308
6432
  ${aggSql} AS value
6309
- FROM ${TABLE_SCORE_EVENTS} ${whereClause}
6433
+ FROM ${currentScoresRelation()} ${whereClause}
6310
6434
  GROUP BY bucket, ${resolved.map((e) => e.groupSql).join(", ")}
6311
6435
  ORDER BY bucket
6312
6436
  `, combined.params);
@@ -6328,7 +6452,7 @@ async function getScoreTimeSeries(client, args) {
6328
6452
  const rows = await queryJson(client, `
6329
6453
  SELECT toStartOfInterval(timestamp, ${intervalSql}) AS bucket,
6330
6454
  ${aggSql} AS value
6331
- FROM ${TABLE_SCORE_EVENTS} ${whereClause}
6455
+ FROM ${currentScoresRelation()} ${whereClause}
6332
6456
  GROUP BY bucket
6333
6457
  ORDER BY bucket
6334
6458
  `, combined.params);
@@ -6351,7 +6475,7 @@ async function getScorePercentiles(client, args) {
6351
6475
  const rows = await queryJson(client, `
6352
6476
  SELECT toStartOfInterval(timestamp, ${intervalSql}) AS bucket,
6353
6477
  quantile(${p})(score) AS pvalue
6354
- FROM ${TABLE_SCORE_EVENTS}
6478
+ FROM ${currentScoresRelation()}
6355
6479
  ${whereClause}
6356
6480
  GROUP BY bucket
6357
6481
  ORDER BY bucket
@@ -6720,11 +6844,9 @@ function compileClickHouseTraceScope(selection, relationCollections, parameters)
6720
6844
  entityVersionId,
6721
6845
  parentEntityVersionId,
6722
6846
  rootEntityVersionId
6723
- FROM ${TABLE_SCORE_EVENTS}
6724
- WHERE isNotNull(traceId)
6725
- AND traceId IN (SELECT traceId FROM root_scope)
6726
- ORDER BY scoreId, timestamp DESC
6727
- LIMIT 1 BY scoreId
6847
+ FROM ${currentScoresRelation()} AS current
6848
+ WHERE isNotNull(current.traceId)
6849
+ AND current.traceId IN (SELECT traceId FROM root_scope)
6728
6850
  )`);
6729
6851
  if (relationCollections.has("feedback")) ctes.push(`current_feedback AS (
6730
6852
  SELECT
@@ -6751,7 +6873,18 @@ function compileClickHouseTraceScope(selection, relationCollections, parameters)
6751
6873
  )`);
6752
6874
  return ctes;
6753
6875
  }
6754
- function compileClickHouseTraceQuery(plan) {
6876
+ const deltaWatermarkSchema = zod_v4.z.object({
6877
+ cursorId: zod_v4.z.string().regex(/^\d+$/).refine((value) => BigInt(value) <= 18446744073709551615n),
6878
+ traceId: zod_v4.z.string()
6879
+ }).strict();
6880
+ function parseDeltaWatermark(value) {
6881
+ try {
6882
+ return deltaWatermarkSchema.parse(JSON.parse(value));
6883
+ } catch {
6884
+ throw new _mastra_core_storage.TraceQueryCursorError("TRACE_QUERY_CURSOR_MALFORMED");
6885
+ }
6886
+ }
6887
+ function compileClickHouseTraceQuery(plan, deltaHead) {
6755
6888
  const parameters = new ParameterBuilder();
6756
6889
  const ctes = compileClickHouseTraceScope(plan, collectRelationCollections(plan.where), parameters);
6757
6890
  const predicate = plan.where ? compilePredicate(plan.where, parameters) : "1";
@@ -6771,8 +6904,70 @@ ORDER BY threadId ASC
6771
6904
  LIMIT ${parameters.add(plan.limit + 1, "UInt64")}`,
6772
6905
  query_params: parameters.params
6773
6906
  };
6907
+ if (plan.paginationMode === "delta") {
6908
+ const watermark = _mastra_core_storage.getTraceQueryDeltaWatermark(plan, "clickhouse");
6909
+ const after = watermark ? parseDeltaWatermark(watermark) : {
6910
+ cursorId: "0",
6911
+ traceId: ""
6912
+ };
6913
+ return {
6914
+ query: `${candidates}, delta_candidates AS (
6915
+ SELECT traceId, max(cursorId) AS latestCursorId
6916
+ FROM ${TABLE_TRACE_ROOTS_DELTA}
6917
+ WHERE tuple(cursorId, traceId) > ${`tuple(${parameters.add(after.cursorId, "UInt64")}, ${parameters.add(after.traceId, "String")})`} ${deltaHead ? `AND tuple(cursorId, traceId) <= tuple(${parameters.add(deltaHead.cursorId, "UInt64")}, ${parameters.add(deltaHead.traceId, "String")})` : ""}
6918
+ GROUP BY traceId
6919
+ )
6920
+ SELECT c.*, toString(d.latestCursorId) AS __delta_cursor
6921
+ FROM candidates c
6922
+ INNER JOIN delta_candidates d ON c.traceId = d.traceId
6923
+ ORDER BY d.latestCursorId ASC, c.traceId ASC
6924
+ LIMIT ${parameters.add(plan.limit + 1, "UInt64")}`,
6925
+ query_params: parameters.params,
6926
+ sharedSnapshot: true
6927
+ };
6928
+ }
6774
6929
  const orderField = resolveOrderField(plan.orderBy.field);
6775
6930
  const direction = plan.orderBy.direction === "asc" ? "ASC" : "DESC";
6931
+ if (plan.paginationMode === "page") return {
6932
+ query: `${candidates},
6933
+ page_rows AS (
6934
+ SELECT *, row_number() OVER (ORDER BY ${orderField} ${direction}, traceId ASC) AS __row_position
6935
+ FROM candidates
6936
+ ORDER BY ${orderField} ${direction}, traceId ASC
6937
+ LIMIT ${parameters.add(plan.perPage, "UInt64")} OFFSET ${parameters.add(plan.page * plan.perPage, "UInt64")}
6938
+ ),
6939
+ page_total AS (
6940
+ SELECT count() AS total
6941
+ FROM candidates
6942
+ )
6943
+ SELECT page_rows.*, page_total.total, 0 AS __metadata
6944
+ FROM page_rows
6945
+ CROSS JOIN page_total
6946
+ UNION ALL
6947
+ SELECT
6948
+ '' AS traceId,
6949
+ '' AS rootSpanId,
6950
+ '' AS name,
6951
+ CAST(NULL, 'Nullable(String)') AS entityId,
6952
+ CAST(NULL, 'Nullable(String)') AS parentSpanId,
6953
+ CAST(NULL, 'Nullable(String)') AS metadata,
6954
+ CAST(NULL, 'Nullable(String)') AS input,
6955
+ CAST(NULL, 'Nullable(String)') AS threadId,
6956
+ CAST(NULL, 'Nullable(String)') AS resourceId,
6957
+ toDateTime64(0, 3, 'UTC') AS startedAt,
6958
+ toDateTime64(0, 3, 'UTC') AS endedAt,
6959
+ CAST(NULL, 'Nullable(String)') AS entityName,
6960
+ CAST(NULL, 'Nullable(String)') AS entityType,
6961
+ CAST(NULL, 'Nullable(String)') AS environment,
6962
+ '' AS status,
6963
+ 0 AS __row_position,
6964
+ page_total.total AS total,
6965
+ 1 AS __metadata
6966
+ FROM page_total
6967
+ ORDER BY __metadata ASC, __row_position ASC`,
6968
+ query_params: parameters.params,
6969
+ sharedSnapshot: true
6970
+ };
6776
6971
  let pageCondition = "";
6777
6972
  if (plan.cursor) {
6778
6973
  const comparison = plan.orderBy.direction === "asc" ? ">" : "<";
@@ -6919,7 +7114,8 @@ async function runWithClickHouseTraceQueryTimeout(client, limits, compiled, quer
6919
7114
  clickhouse_settings: {
6920
7115
  ...CH_SETTINGS,
6921
7116
  max_execution_time: resolvedTimeoutMs / 1e3,
6922
- ...limits.memoryLimitBytes === void 0 ? {} : { max_memory_usage: String(limits.memoryLimitBytes) }
7117
+ ...limits.memoryLimitBytes === void 0 ? {} : { max_memory_usage: String(limits.memoryLimitBytes) },
7118
+ ...compiled.sharedSnapshot ? { enable_shared_storage_snapshot_in_query: 1 } : {}
6923
7119
  }
6924
7120
  })).json();
6925
7121
  } catch (error) {
@@ -6949,8 +7145,70 @@ async function getTraceQueryValues(client, plan, limits) {
6949
7145
  valuesTruncated: rows.length > plan.limit
6950
7146
  });
6951
7147
  }
6952
- async function queryTraces(client, plan, timeoutMs) {
6953
- const rows = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs }, compileClickHouseTraceQuery(plan));
7148
+ async function queryTraces(client, plan, timeoutMs, strategy = null) {
7149
+ const deadline = performance.now() + _mastra_core_storage.resolveTraceQueryTimeoutMs(timeoutMs);
7150
+ const remaining = () => {
7151
+ const value = Math.floor(deadline - performance.now());
7152
+ if (value <= 0) throw new _mastra_core_storage.TraceQueryExecutionError();
7153
+ return value;
7154
+ };
7155
+ let deltaHead;
7156
+ if (plan.paginationMode === "delta") {
7157
+ assertDeltaPollingSupported(strategy);
7158
+ const watermark = _mastra_core_storage.getTraceQueryDeltaWatermark(plan, "clickhouse");
7159
+ if (watermark) parseDeltaWatermark(watermark);
7160
+ }
7161
+ if (plan.paginationMode === "delta" || plan.paginationMode === "page" && deltaPollingSupported(strategy) && typeof _mastra_core_storage.encodeTraceQueryDeltaCursor === "function") {
7162
+ const head = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs: remaining() }, {
7163
+ query: `SELECT toString(cursorId) AS cursorId, traceId FROM ${TABLE_TRACE_ROOTS_DELTA} ORDER BY cursorId DESC, traceId DESC LIMIT 1`,
7164
+ query_params: {}
7165
+ });
7166
+ deltaHead = head[0] ? parseDeltaWatermark(JSON.stringify(head[0])) : {
7167
+ cursorId: "0",
7168
+ traceId: ""
7169
+ };
7170
+ if (plan.paginationMode === "delta" && !plan.deltaCursor) return _mastra_core_storage.traceQueryResponseSchema.parse({
7171
+ traces: [],
7172
+ delta: {
7173
+ limit: plan.limit,
7174
+ hasMore: false
7175
+ },
7176
+ deltaCursor: _mastra_core_storage.encodeTraceQueryDeltaCursor(plan, "clickhouse", JSON.stringify(deltaHead))
7177
+ });
7178
+ }
7179
+ if (plan.paginationMode === "page") {
7180
+ const rows = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs: deltaHead ? remaining() : timeoutMs }, compileClickHouseTraceQuery(plan, deltaHead));
7181
+ const total = Number(rows.at(-1)?.total ?? 0);
7182
+ const traces = rows.filter((row) => Number(row.__metadata) === 0).map((row) => ({
7183
+ traceId: String(row.traceId),
7184
+ rootSpanId: String(row.rootSpanId),
7185
+ name: row.name,
7186
+ entityId: row.entityId ?? null,
7187
+ parentSpanId: row.parentSpanId ?? null,
7188
+ createdAt: asIsoTimestamp(row.startedAt),
7189
+ metadata: parseJson(row.metadata) ?? null,
7190
+ inputPreview: _mastra_core_storage.buildInputPreview(row.input) ?? null,
7191
+ threadId: row.threadId == null ? null : String(row.threadId),
7192
+ resourceId: row.resourceId == null ? null : String(row.resourceId),
7193
+ startedAt: asIsoTimestamp(row.startedAt),
7194
+ endedAt: asIsoTimestamp(row.endedAt),
7195
+ entityName: row.entityName == null ? null : String(row.entityName),
7196
+ entityType: row.entityType == null ? null : String(row.entityType),
7197
+ environment: row.environment == null ? null : String(row.environment),
7198
+ status: row.status
7199
+ }));
7200
+ return _mastra_core_storage.traceQueryResponseSchema.parse({
7201
+ traces,
7202
+ ...deltaHead ? { deltaCursor: _mastra_core_storage.encodeTraceQueryDeltaCursor(plan, "clickhouse", JSON.stringify(deltaHead)) } : {},
7203
+ pagination: {
7204
+ total,
7205
+ page: plan.page,
7206
+ perPage: plan.perPage,
7207
+ hasMore: (plan.page + 1) * plan.perPage < total
7208
+ }
7209
+ });
7210
+ }
7211
+ const rows = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs: deltaHead ? remaining() : timeoutMs }, compileClickHouseTraceQuery(plan, deltaHead));
6954
7212
  const visibleRows = rows.slice(0, plan.limit);
6955
7213
  if (plan.result === "groups") {
6956
7214
  const groups = visibleRows.map((row) => ({ threadId: String(row.threadId) }));
@@ -6982,6 +7240,23 @@ async function queryTraces(client, plan, timeoutMs) {
6982
7240
  status: row.status
6983
7241
  }));
6984
7242
  const last = traces.at(-1);
7243
+ if (plan.paginationMode === "delta") {
7244
+ const lastRow = visibleRows.at(-1);
7245
+ let watermark = lastRow ? {
7246
+ cursorId: String(lastRow.__delta_cursor),
7247
+ traceId: String(lastRow.traceId)
7248
+ } : deltaHead;
7249
+ const previous = parseDeltaWatermark(_mastra_core_storage.getTraceQueryDeltaWatermark(plan, "clickhouse"));
7250
+ if (BigInt(previous.cursorId) > BigInt(watermark.cursorId) || previous.cursorId === watermark.cursorId && previous.traceId > watermark.traceId) watermark = previous;
7251
+ return _mastra_core_storage.traceQueryResponseSchema.parse({
7252
+ traces,
7253
+ delta: {
7254
+ limit: plan.limit,
7255
+ hasMore: rows.length > plan.limit
7256
+ },
7257
+ deltaCursor: _mastra_core_storage.encodeTraceQueryDeltaCursor(plan, "clickhouse", JSON.stringify(watermark))
7258
+ });
7259
+ }
6985
7260
  return _mastra_core_storage.traceQueryResponseSchema.parse({
6986
7261
  traces,
6987
7262
  page: { next: rows.length > plan.limit && last ? _mastra_core_storage.encodeTraceQueryCursor(plan, {
@@ -7455,6 +7730,7 @@ async function batchDeleteTraces(client, args, replication) {
7455
7730
  TABLE_METRIC_EVENTS,
7456
7731
  TABLE_LOG_EVENTS,
7457
7732
  TABLE_SCORE_EVENTS,
7733
+ TABLE_SCORE_EVENTS_CURRENT,
7458
7734
  TABLE_FEEDBACK_EVENTS
7459
7735
  ];
7460
7736
  await Promise.all([...tracingTables.map((table) => client.command({
@@ -8134,10 +8410,13 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
8134
8410
  else if (existingStrategy) this.#deltaCursorStrategy = existingStrategy;
8135
8411
  else this.#deltaCursorStrategy = await detectDeltaCursorStrategy(this.#client, void 0, existingStrategy);
8136
8412
  await reconcileDiscoveryTables(this.#client, this.#replication);
8137
- const coreDdl = this.#deltaCursorStrategy === null ? [...BASE_TABLE_DDL, ...BASE_MV_DDL] : [...buildAllTableDDL(), ...buildAllMvDDL(this.#deltaCursorStrategy)];
8138
- for (const ddl of coreDdl) await this.#client.command({ query: applyReplicationToDDL(ddl, this.#replication) });
8413
+ const coreTableDdl = this.#deltaCursorStrategy === null ? BASE_TABLE_DDL : buildAllTableDDL();
8414
+ for (const ddl of coreTableDdl) await this.#client.command({ query: applyReplicationToDDL(ddl, this.#replication) });
8139
8415
  const pendingMigrations = await filterAppliedMigrations(this.#client, ALL_MIGRATIONS);
8140
8416
  for (const migration of pendingMigrations) await this.#client.command({ query: addOnClusterToDDL(migration.sql, this.#replication) });
8417
+ const coreMvDdl = this.#deltaCursorStrategy === null ? BASE_MV_DDL : buildAllMvDDL(this.#deltaCursorStrategy);
8418
+ for (const ddl of coreMvDdl) await this.#client.command({ query: applyReplicationToDDL(ddl, this.#replication) });
8419
+ await backfillCurrentScores(this.#client);
8141
8420
  if (this.#retention) await this.applyRetention();
8142
8421
  if (this.#deltaCursorStrategy === "serial") for (const counterName of DELTA_CURSOR_COUNTER_NAMES) await this.#client.query({
8143
8422
  query: `SELECT generateSerialID({counterName:String}) AS cursorId`,
@@ -8336,9 +8615,9 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
8336
8615
  }
8337
8616
  async queryTraces(plan) {
8338
8617
  try {
8339
- return await queryTraces(this.#client, plan, this.#traceQueryTimeoutMs);
8618
+ return await queryTraces(this.#client, plan, this.#traceQueryTimeoutMs, this.#deltaCursorStrategy);
8340
8619
  } catch (error) {
8341
- if (error instanceof _mastra_core_error.MastraError || error instanceof _mastra_core_storage.TraceQueryExecutionError || error instanceof _mastra_core_storage.TraceQueryResourceLimitError) throw error;
8620
+ 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;
8342
8621
  throw new _mastra_core_error.MastraError({
8343
8622
  id: (0, _mastra_core_storage.createStorageErrorId)("CLICKHOUSE", "QUERY_TRACES", "FAILED"),
8344
8623
  domain: _mastra_core_error.ErrorDomain.STORAGE,