@mastra/clickhouse 1.20.0 → 1.20.1-alpha.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.
@@ -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"
6
+ version: "1.20.1-alpha.0"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.20.0",
2
+ "version": "1.20.1-alpha.0",
3
3
  "package": "@mastra/clickhouse",
4
4
  "exports": {},
5
5
  "modules": {}
package/dist/index.cjs CHANGED
@@ -3223,7 +3223,8 @@ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS_DELTA} (
3223
3223
  ingestedAt ${DELTA_INGESTED_AT_TYPE},
3224
3224
  traceId Nullable(String),
3225
3225
  timestamp DateTime64(3, 'UTC'),
3226
- scoreId String
3226
+ scoreId String,
3227
+ INDEX idx_scoreId scoreId TYPE bloom_filter(0.01) GRANULARITY 1
3227
3228
  )
3228
3229
  ENGINE = MergeTree
3229
3230
  PARTITION BY toDate(ingestedAt)
@@ -3232,12 +3233,8 @@ TTL ingestedAt + toIntervalDay(2)
3232
3233
  SETTINGS allow_nullable_key = 1
3233
3234
  `;
3234
3235
  }
3235
- function buildScoreEventsDeltaMvDDL(strategy) {
3236
- return `
3237
- CREATE MATERIALIZED VIEW IF NOT EXISTS ${MV_SCORE_EVENTS_DELTA}
3238
- TO ${TABLE_SCORE_EVENTS_DELTA}
3239
- AS
3240
- SELECT
3236
+ function buildScoreEventsDeltaMvQuery(strategy) {
3237
+ return `SELECT
3241
3238
  ${buildDeltaCursorExpr(strategy, "mastra_score_events_delta_cursor", "scoreId")} AS cursorId,
3242
3239
  ingestedAt,
3243
3240
  traceId,
@@ -3250,7 +3247,19 @@ FROM (
3250
3247
  timestamp,
3251
3248
  scoreId
3252
3249
  FROM ${TABLE_SCORE_EVENTS}
3253
- )
3250
+ WHERE scoreId NOT IN (
3251
+ SELECT scoreId FROM ${TABLE_SCORE_EVENTS_DELTA}
3252
+ WHERE scoreId IN (SELECT scoreId FROM ${TABLE_SCORE_EVENTS})
3253
+ )
3254
+ LIMIT 1 BY scoreId
3255
+ )`;
3256
+ }
3257
+ function buildScoreEventsDeltaMvDDL(strategy) {
3258
+ return `
3259
+ CREATE MATERIALIZED VIEW IF NOT EXISTS ${MV_SCORE_EVENTS_DELTA}
3260
+ TO ${TABLE_SCORE_EVENTS_DELTA}
3261
+ AS
3262
+ ${buildScoreEventsDeltaMvQuery(strategy)}
3254
3263
  `;
3255
3264
  }
3256
3265
  const FEEDBACK_EVENTS_DDL = `
@@ -3559,6 +3568,7 @@ const ALL_MIGRATIONS = [
3559
3568
  addColumn(TABLE_SCORE_EVENTS, "parentEntityVersionId", "Nullable(String)"),
3560
3569
  addColumn(TABLE_SCORE_EVENTS, "rootEntityVersionId", "Nullable(String)"),
3561
3570
  addBloomIndex(TABLE_SCORE_EVENTS, "idx_scoreId", "scoreId", 1),
3571
+ addBloomIndex(TABLE_SCORE_EVENTS_DELTA, "idx_scoreId", "scoreId", 1),
3562
3572
  addColumn(TABLE_FEEDBACK_EVENTS, "writeVersion", "UInt64 DEFAULT 0"),
3563
3573
  addColumn(TABLE_FEEDBACK_EVENTS, "entityVersionId", "Nullable(String)"),
3564
3574
  addColumn(TABLE_FEEDBACK_EVENTS, "reviewStatus", "LowCardinality(String) DEFAULT 'needs-review'"),
@@ -6308,6 +6318,17 @@ async function listScores(client, args, strategy) {
6308
6318
  ...deltaCursorEnabled ? { deltaCursor: currentDeltaCursor } : {}
6309
6319
  };
6310
6320
  }
6321
+ /**
6322
+ * Delta reads join the delta stream to the current-state table by scoreId, so
6323
+ * a poll or replay always returns the latest write of a score (a rewrite may
6324
+ * change traceId or timestamp, which the delta row does not track).
6325
+ *
6326
+ * A score's first delta row is its only cursor: rows after `afterCursor` are
6327
+ * dropped when the same scoreId already has a row at or before it, and
6328
+ * `LIMIT 1 BY` keeps the lowest cursor within the page. Duplicate delta rows
6329
+ * (a concurrent-insert race past the MV check) therefore never surface, in
6330
+ * this poll or a later one.
6331
+ */
6311
6332
  async function queryScoresAfterCursor(client, whereClause, params, limit, cursorId) {
6312
6333
  return await queryJson(client, `
6313
6334
  SELECT
@@ -6317,12 +6338,16 @@ async function queryScoresAfterCursor(client, whereClause, params, limit, cursor
6317
6338
  s.scoreId AS scoreId,
6318
6339
  toString(d.cursorId) AS cursorId
6319
6340
  FROM ${TABLE_SCORE_EVENTS_DELTA} d
6320
- INNER JOIN ${TABLE_SCORE_EVENTS} s
6321
- ON ((s.traceId = d.traceId) OR (s.traceId IS NULL AND d.traceId IS NULL))
6322
- AND s.timestamp = d.timestamp
6323
- AND s.scoreId = d.scoreId
6324
- ${whereClause ? `${whereClause} AND d.cursorId > {afterCursor:UInt64}` : "WHERE d.cursorId > {afterCursor:UInt64}"}
6341
+ INNER JOIN ${TABLE_SCORE_EVENTS_CURRENT} s FINAL
6342
+ ON s.scoreId = d.scoreId
6343
+ ${whereClause ? `${whereClause} AND` : "WHERE"} d.cursorId > {afterCursor:UInt64}
6344
+ AND d.scoreId NOT IN (
6345
+ SELECT scoreId FROM ${TABLE_SCORE_EVENTS_DELTA}
6346
+ WHERE cursorId <= {afterCursor:UInt64}
6347
+ AND scoreId IN (SELECT scoreId FROM ${TABLE_SCORE_EVENTS_DELTA} WHERE cursorId > {afterCursor:UInt64})
6348
+ )
6325
6349
  ORDER BY d.cursorId ASC
6350
+ LIMIT 1 BY s.scoreId
6326
6351
  LIMIT {fetchLimit:UInt32}
6327
6352
  `, {
6328
6353
  ...params,
@@ -6334,10 +6359,8 @@ async function getDeltaCursor$2(client, whereClause, params) {
6334
6359
  const cursorId = (await queryJson(client, `
6335
6360
  SELECT toString(max(d.cursorId)) AS cursorId
6336
6361
  FROM mastra_score_events_delta d
6337
- INNER JOIN mastra_score_events s
6338
- ON ((s.traceId = d.traceId) OR (s.traceId IS NULL AND d.traceId IS NULL))
6339
- AND s.timestamp = d.timestamp
6340
- AND s.scoreId = d.scoreId
6362
+ INNER JOIN mastra_score_events_current s FINAL
6363
+ ON s.scoreId = d.scoreId
6341
6364
  ${whereClause}
6342
6365
  `, params))[0]?.cursorId ?? null;
6343
6366
  if (cursorId) return cursorId;
@@ -8307,6 +8330,37 @@ async function queryNamesByTable(client, query, tables) {
8307
8330
  }
8308
8331
  return out;
8309
8332
  }
8333
+ /**
8334
+ * Upgrades a score delta MV created before per-scoreId deduplication with
8335
+ * `ALTER TABLE ... MODIFY QUERY`. The view is changed in place, so there is no
8336
+ * window without a view in which score writes would miss their delta row (a
8337
+ * drop-and-recreate would leave such writes out of delta polling for good).
8338
+ *
8339
+ * With a cluster configured the definition is read from every replica via
8340
+ * `clusterAllReplicas`, since each host stores its own copy of the view; a
8341
+ * single legacy copy is enough to alter `ON CLUSTER`.
8342
+ *
8343
+ * Introspection failures are logged and skipped, like the other schema checks
8344
+ * in init(): the existing view keeps working and the next boot retries.
8345
+ */
8346
+ async function reconcileScoreDeltaMv(client, replication, strategy, logger) {
8347
+ const cluster = replication?.cluster?.trim();
8348
+ let createQueries;
8349
+ try {
8350
+ createQueries = (await (await client.query({
8351
+ query: cluster ? `SELECT create_table_query FROM clusterAllReplicas({cluster:String}, system.tables) WHERE database = currentDatabase() AND name = {name:String}` : `SELECT create_table_query FROM system.tables WHERE database = currentDatabase() AND name = {name:String}`,
8352
+ query_params: cluster ? {
8353
+ cluster,
8354
+ name: MV_SCORE_EVENTS_DELTA
8355
+ } : { name: MV_SCORE_EVENTS_DELTA },
8356
+ format: "JSONEachRow"
8357
+ })).json()).map((row) => row.create_table_query ?? "");
8358
+ } catch (error) {
8359
+ logger?.warn?.(`Could not verify the ${MV_SCORE_EVENTS_DELTA} definition; leaving the existing view in place: ${error instanceof Error ? error.message : String(error)}`);
8360
+ return;
8361
+ }
8362
+ if (createQueries.some((createQuery) => createQuery.length > 0 && !/NOT IN/i.test(createQuery))) await client.command({ query: addOnClusterToDDL(`ALTER TABLE ${MV_SCORE_EVENTS_DELTA} MODIFY QUERY ${buildScoreEventsDeltaMvQuery(strategy)}`, replication) });
8363
+ }
8310
8364
  async function detectDeltaCursorStrategy(client, override, existingStrategy) {
8311
8365
  if (override) return override;
8312
8366
  if (existingStrategy && existingStrategy !== "mixed") return existingStrategy;
@@ -8416,6 +8470,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
8416
8470
  for (const migration of pendingMigrations) await this.#client.command({ query: addOnClusterToDDL(migration.sql, this.#replication) });
8417
8471
  const coreMvDdl = this.#deltaCursorStrategy === null ? BASE_MV_DDL : buildAllMvDDL(this.#deltaCursorStrategy);
8418
8472
  for (const ddl of coreMvDdl) await this.#client.command({ query: applyReplicationToDDL(ddl, this.#replication) });
8473
+ if (this.#deltaCursorStrategy !== null) await reconcileScoreDeltaMv(this.#client, this.#replication, this.#deltaCursorStrategy, this.logger);
8419
8474
  await backfillCurrentScores(this.#client);
8420
8475
  if (this.#retention) await this.applyRetention();
8421
8476
  if (this.#deltaCursorStrategy === "serial") for (const counterName of DELTA_CURSOR_COUNTER_NAMES) await this.#client.query({