@mastra/clickhouse 1.7.1-alpha.1 → 1.7.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.
package/dist/index.js CHANGED
@@ -3422,44 +3422,56 @@ var ALL_TABLE_DDL = [
3422
3422
  ];
3423
3423
  var ALL_MV_DDL = [TRACE_ROOTS_MV_DDL, TRACE_BRANCHES_MV_DDL];
3424
3424
  var DISCOVERY_MV_DDL = [DISCOVERY_VALUES_MV_DDL, DISCOVERY_PAIRS_MV_DDL];
3425
+ var addColumn = (table, name, type) => ({
3426
+ kind: "column",
3427
+ table,
3428
+ name,
3429
+ sql: `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${name} ${type}`
3430
+ });
3431
+ var addBloomIndex = (table, name, column) => ({
3432
+ kind: "index",
3433
+ table,
3434
+ name,
3435
+ sql: `ALTER TABLE ${table} ADD INDEX IF NOT EXISTS ${name} ${column} TYPE bloom_filter(0.01) GRANULARITY 2`
3436
+ });
3425
3437
  var ALL_MIGRATIONS = [
3426
3438
  // Span events
3427
- `ALTER TABLE ${TABLE_SPAN_EVENTS} ADD COLUMN IF NOT EXISTS entityVersionId Nullable(String)`,
3428
- `ALTER TABLE ${TABLE_SPAN_EVENTS} ADD COLUMN IF NOT EXISTS parentEntityVersionId Nullable(String)`,
3429
- `ALTER TABLE ${TABLE_SPAN_EVENTS} ADD COLUMN IF NOT EXISTS rootEntityVersionId Nullable(String)`,
3439
+ addColumn(TABLE_SPAN_EVENTS, "entityVersionId", "Nullable(String)"),
3440
+ addColumn(TABLE_SPAN_EVENTS, "parentEntityVersionId", "Nullable(String)"),
3441
+ addColumn(TABLE_SPAN_EVENTS, "rootEntityVersionId", "Nullable(String)"),
3430
3442
  // Trace roots
3431
- `ALTER TABLE ${TABLE_TRACE_ROOTS} ADD COLUMN IF NOT EXISTS entityVersionId Nullable(String)`,
3432
- `ALTER TABLE ${TABLE_TRACE_ROOTS} ADD COLUMN IF NOT EXISTS parentEntityVersionId Nullable(String)`,
3433
- `ALTER TABLE ${TABLE_TRACE_ROOTS} ADD COLUMN IF NOT EXISTS rootEntityVersionId Nullable(String)`,
3443
+ addColumn(TABLE_TRACE_ROOTS, "entityVersionId", "Nullable(String)"),
3444
+ addColumn(TABLE_TRACE_ROOTS, "parentEntityVersionId", "Nullable(String)"),
3445
+ addColumn(TABLE_TRACE_ROOTS, "rootEntityVersionId", "Nullable(String)"),
3434
3446
  // Metrics
3435
- `ALTER TABLE ${TABLE_METRIC_EVENTS} ADD COLUMN IF NOT EXISTS entityVersionId Nullable(String)`,
3436
- `ALTER TABLE ${TABLE_METRIC_EVENTS} ADD COLUMN IF NOT EXISTS parentEntityVersionId Nullable(String)`,
3437
- `ALTER TABLE ${TABLE_METRIC_EVENTS} ADD COLUMN IF NOT EXISTS rootEntityVersionId Nullable(String)`,
3447
+ addColumn(TABLE_METRIC_EVENTS, "entityVersionId", "Nullable(String)"),
3448
+ addColumn(TABLE_METRIC_EVENTS, "parentEntityVersionId", "Nullable(String)"),
3449
+ addColumn(TABLE_METRIC_EVENTS, "rootEntityVersionId", "Nullable(String)"),
3438
3450
  // Logs
3439
- `ALTER TABLE ${TABLE_LOG_EVENTS} ADD COLUMN IF NOT EXISTS entityVersionId Nullable(String)`,
3440
- `ALTER TABLE ${TABLE_LOG_EVENTS} ADD COLUMN IF NOT EXISTS parentEntityVersionId Nullable(String)`,
3441
- `ALTER TABLE ${TABLE_LOG_EVENTS} ADD COLUMN IF NOT EXISTS rootEntityVersionId Nullable(String)`,
3451
+ addColumn(TABLE_LOG_EVENTS, "entityVersionId", "Nullable(String)"),
3452
+ addColumn(TABLE_LOG_EVENTS, "parentEntityVersionId", "Nullable(String)"),
3453
+ addColumn(TABLE_LOG_EVENTS, "rootEntityVersionId", "Nullable(String)"),
3442
3454
  // Scores
3443
- `ALTER TABLE ${TABLE_SCORE_EVENTS} ADD COLUMN IF NOT EXISTS entityVersionId Nullable(String)`,
3444
- `ALTER TABLE ${TABLE_SCORE_EVENTS} ADD COLUMN IF NOT EXISTS parentEntityVersionId Nullable(String)`,
3445
- `ALTER TABLE ${TABLE_SCORE_EVENTS} ADD COLUMN IF NOT EXISTS rootEntityVersionId Nullable(String)`,
3455
+ addColumn(TABLE_SCORE_EVENTS, "entityVersionId", "Nullable(String)"),
3456
+ addColumn(TABLE_SCORE_EVENTS, "parentEntityVersionId", "Nullable(String)"),
3457
+ addColumn(TABLE_SCORE_EVENTS, "rootEntityVersionId", "Nullable(String)"),
3446
3458
  // Feedback
3447
- `ALTER TABLE ${TABLE_FEEDBACK_EVENTS} ADD COLUMN IF NOT EXISTS entityVersionId Nullable(String)`,
3448
- `ALTER TABLE ${TABLE_FEEDBACK_EVENTS} ADD COLUMN IF NOT EXISTS parentEntityVersionId Nullable(String)`,
3449
- `ALTER TABLE ${TABLE_FEEDBACK_EVENTS} ADD COLUMN IF NOT EXISTS rootEntityVersionId Nullable(String)`,
3459
+ addColumn(TABLE_FEEDBACK_EVENTS, "entityVersionId", "Nullable(String)"),
3460
+ addColumn(TABLE_FEEDBACK_EVENTS, "parentEntityVersionId", "Nullable(String)"),
3461
+ addColumn(TABLE_FEEDBACK_EVENTS, "rootEntityVersionId", "Nullable(String)"),
3450
3462
  // Metric skip indexes — additive, instant DDL. Existing parts keep no index
3451
3463
  // until merged or `MATERIALIZE INDEX` is run; new parts are bloom-filtered
3452
3464
  // immediately. With normal retention turning over the table, the index
3453
3465
  // converges to full coverage without an explicit backfill.
3454
- `ALTER TABLE ${TABLE_METRIC_EVENTS} ADD INDEX IF NOT EXISTS idx_traceId traceId TYPE bloom_filter(0.01) GRANULARITY 2`,
3455
- `ALTER TABLE ${TABLE_METRIC_EVENTS} ADD INDEX IF NOT EXISTS idx_threadId threadId TYPE bloom_filter(0.01) GRANULARITY 2`,
3456
- `ALTER TABLE ${TABLE_METRIC_EVENTS} ADD INDEX IF NOT EXISTS idx_resourceId resourceId TYPE bloom_filter(0.01) GRANULARITY 2`,
3457
- `ALTER TABLE ${TABLE_METRIC_EVENTS} ADD INDEX IF NOT EXISTS idx_userId userId TYPE bloom_filter(0.01) GRANULARITY 2`,
3458
- `ALTER TABLE ${TABLE_METRIC_EVENTS} ADD INDEX IF NOT EXISTS idx_organizationId organizationId TYPE bloom_filter(0.01) GRANULARITY 2`,
3459
- `ALTER TABLE ${TABLE_METRIC_EVENTS} ADD INDEX IF NOT EXISTS idx_experimentId experimentId TYPE bloom_filter(0.01) GRANULARITY 2`,
3460
- `ALTER TABLE ${TABLE_METRIC_EVENTS} ADD INDEX IF NOT EXISTS idx_runId runId TYPE bloom_filter(0.01) GRANULARITY 2`,
3461
- `ALTER TABLE ${TABLE_METRIC_EVENTS} ADD INDEX IF NOT EXISTS idx_sessionId sessionId TYPE bloom_filter(0.01) GRANULARITY 2`,
3462
- `ALTER TABLE ${TABLE_METRIC_EVENTS} ADD INDEX IF NOT EXISTS idx_requestId requestId TYPE bloom_filter(0.01) GRANULARITY 2`
3466
+ addBloomIndex(TABLE_METRIC_EVENTS, "idx_traceId", "traceId"),
3467
+ addBloomIndex(TABLE_METRIC_EVENTS, "idx_threadId", "threadId"),
3468
+ addBloomIndex(TABLE_METRIC_EVENTS, "idx_resourceId", "resourceId"),
3469
+ addBloomIndex(TABLE_METRIC_EVENTS, "idx_userId", "userId"),
3470
+ addBloomIndex(TABLE_METRIC_EVENTS, "idx_organizationId", "organizationId"),
3471
+ addBloomIndex(TABLE_METRIC_EVENTS, "idx_experimentId", "experimentId"),
3472
+ addBloomIndex(TABLE_METRIC_EVENTS, "idx_runId", "runId"),
3473
+ addBloomIndex(TABLE_METRIC_EVENTS, "idx_sessionId", "sessionId"),
3474
+ addBloomIndex(TABLE_METRIC_EVENTS, "idx_requestId", "requestId")
3463
3475
  ];
3464
3476
  var ALL_TABLE_NAMES = [
3465
3477
  TABLE_SPAN_EVENTS,
@@ -3488,8 +3500,8 @@ var SIGNAL_TO_TABLES = {
3488
3500
  scores: [TABLE_SCORE_EVENTS],
3489
3501
  feedback: [TABLE_FEEDBACK_EVENTS]
3490
3502
  };
3491
- function buildRetentionDDL(retention) {
3492
- const statements = [];
3503
+ function buildRetentionEntries(retention) {
3504
+ const entries = [];
3493
3505
  for (const [signal, days] of Object.entries(retention)) {
3494
3506
  const safeDays = Math.floor(Number(days));
3495
3507
  if (!Number.isFinite(safeDays) || safeDays <= 0) continue;
@@ -3498,10 +3510,24 @@ function buildRetentionDDL(retention) {
3498
3510
  for (const table of tables) {
3499
3511
  const col = SIGNAL_TTL_COLUMNS[table];
3500
3512
  if (!col) continue;
3501
- statements.push(`ALTER TABLE ${table} MODIFY TTL ${col} + INTERVAL ${safeDays} DAY`);
3513
+ entries.push({
3514
+ table,
3515
+ column: col,
3516
+ days: safeDays,
3517
+ sql: `ALTER TABLE ${table} MODIFY TTL ${col} + INTERVAL ${safeDays} DAY`
3518
+ });
3502
3519
  }
3503
3520
  }
3504
- return statements;
3521
+ return entries;
3522
+ }
3523
+ function parseTtlExpression(expr) {
3524
+ const match = expr.match(/TTL\s+(?:`([^`]+)`|(\w+))\s*\+\s*(?:toIntervalDay\((\d+)\)|INTERVAL\s+(\d+)\s+DAY)/i);
3525
+ if (!match) return null;
3526
+ const column = match[1] ?? match[2];
3527
+ if (!column) return null;
3528
+ const days = Number(match[3] ?? match[4]);
3529
+ if (!Number.isFinite(days)) return null;
3530
+ return { column, days };
3505
3531
  }
3506
3532
  var CH_SETTINGS = {
3507
3533
  date_time_input_format: "best_effort",
@@ -5962,6 +5988,74 @@ time for large databases. Please ensure you have a backup before proceeding.
5962
5988
  ===========================================================================
5963
5989
  `;
5964
5990
  }
5991
+ async function filterAppliedMigrations(client, migrations) {
5992
+ if (migrations.length === 0) return migrations;
5993
+ const tables = [...new Set(migrations.map((m) => m.table))];
5994
+ let existingColumns;
5995
+ let existingIndices;
5996
+ try {
5997
+ [existingColumns, existingIndices] = await Promise.all([
5998
+ queryNamesByTable(
5999
+ client,
6000
+ `SELECT table, name FROM system.columns WHERE database = currentDatabase() AND table IN ({tables:Array(String)})`,
6001
+ tables
6002
+ ),
6003
+ queryNamesByTable(
6004
+ client,
6005
+ `SELECT table, name FROM system.data_skipping_indices WHERE database = currentDatabase() AND table IN ({tables:Array(String)})`,
6006
+ tables
6007
+ )
6008
+ ]);
6009
+ } catch {
6010
+ return migrations;
6011
+ }
6012
+ return migrations.filter((m) => {
6013
+ const present = m.kind === "column" ? existingColumns.get(m.table) : existingIndices.get(m.table);
6014
+ if (!present) return true;
6015
+ return !present.has(m.name);
6016
+ });
6017
+ }
6018
+ async function filterAppliedRetention(client, entries) {
6019
+ if (entries.length === 0) return entries;
6020
+ const tables = [...new Set(entries.map((e) => e.table))];
6021
+ let createQueries;
6022
+ try {
6023
+ const result = await client.query({
6024
+ query: `SELECT name, create_table_query FROM system.tables WHERE database = currentDatabase() AND name IN ({tables:Array(String)})`,
6025
+ query_params: { tables },
6026
+ format: "JSONEachRow"
6027
+ });
6028
+ const rows = await result.json();
6029
+ createQueries = new Map(rows.map((r) => [r.name, r.create_table_query ?? ""]));
6030
+ } catch {
6031
+ return entries;
6032
+ }
6033
+ return entries.filter((e) => {
6034
+ const createQuery = createQueries.get(e.table);
6035
+ if (!createQuery) return true;
6036
+ const current = parseTtlExpression(createQuery);
6037
+ if (!current) return true;
6038
+ return current.column !== e.column || current.days !== e.days;
6039
+ });
6040
+ }
6041
+ async function queryNamesByTable(client, query, tables) {
6042
+ const result = await client.query({
6043
+ query,
6044
+ query_params: { tables },
6045
+ format: "JSONEachRow"
6046
+ });
6047
+ const rows = await result.json();
6048
+ const out = /* @__PURE__ */ new Map();
6049
+ for (const row of rows) {
6050
+ let set = out.get(row.table);
6051
+ if (!set) {
6052
+ set = /* @__PURE__ */ new Set();
6053
+ out.set(row.table, set);
6054
+ }
6055
+ set.add(row.name);
6056
+ }
6057
+ return out;
6058
+ }
5965
6059
  var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
5966
6060
  #client;
5967
6061
  #retention;
@@ -5991,13 +6085,14 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
5991
6085
  for (const ddl of [...ALL_TABLE_DDL, ...ALL_MV_DDL]) {
5992
6086
  await this.#client.command({ query: ddl });
5993
6087
  }
5994
- for (const migration of ALL_MIGRATIONS) {
5995
- await this.#client.command({ query: migration });
6088
+ const pendingMigrations = await filterAppliedMigrations(this.#client, ALL_MIGRATIONS);
6089
+ for (const migration of pendingMigrations) {
6090
+ await this.#client.command({ query: migration.sql });
5996
6091
  }
5997
6092
  if (this.#retention) {
5998
- const ttlStatements = buildRetentionDDL(this.#retention);
5999
- for (const stmt of ttlStatements) {
6000
- await this.#client.command({ query: stmt });
6093
+ const pendingRetention = await filterAppliedRetention(this.#client, buildRetentionEntries(this.#retention));
6094
+ for (const entry of pendingRetention) {
6095
+ await this.#client.command({ query: entry.sql });
6001
6096
  }
6002
6097
  }
6003
6098
  } catch (error) {