@mastra/clickhouse 1.15.2 → 1.16.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.
package/dist/index.js CHANGED
@@ -3519,7 +3519,7 @@ function unionDistinctFromSignals(kind, key1Expr, valueExpr, extraJoin = "", ext
3519
3519
  }
3520
3520
  const DISCOVERY_VALUES_MV_DDL = `
3521
3521
  CREATE MATERIALIZED VIEW IF NOT EXISTS ${MV_DISCOVERY_VALUES}
3522
- REFRESH EVERY 1 MINUTE
3522
+ REFRESH EVERY 1 MINUTE APPEND
3523
3523
  TO ${TABLE_DISCOVERY_VALUES}
3524
3524
  AS
3525
3525
  SELECT DISTINCT kind, key1, value FROM (
@@ -3544,7 +3544,7 @@ SELECT DISTINCT kind, key1, value FROM (
3544
3544
  `;
3545
3545
  const DISCOVERY_PAIRS_MV_DDL = `
3546
3546
  CREATE MATERIALIZED VIEW IF NOT EXISTS ${MV_DISCOVERY_PAIRS}
3547
- REFRESH EVERY 5 MINUTE
3547
+ REFRESH EVERY 5 MINUTE APPEND
3548
3548
  TO ${TABLE_DISCOVERY_PAIRS}
3549
3549
  AS
3550
3550
  SELECT DISTINCT kind, key1, key2, value FROM (
@@ -4380,6 +4380,64 @@ function addStringMapFilters(column, values, keyPrefix, valuePrefix, out) {
4380
4380
  }
4381
4381
  }
4382
4382
  /**
4383
+ * Adds conditions asserting the JSON value at `pathRefs` inside `src` deep-equals `value`.
4384
+ *
4385
+ * Scalars (string/number/boolean/null) use type-checked typed extraction. Objects and
4386
+ * arrays are decomposed recursively into per-leaf comparisons plus length checks, so
4387
+ * nested values match structurally regardless of key serialization order while still
4388
+ * requiring complete (exact, not partial) equality — mirroring the in-memory
4389
+ * deep-equality semantics.
4390
+ */
4391
+ function addJsonValueConditions(src, pathRefs, value, valuePrefix, nextId, out) {
4392
+ const path = pathRefs.join(", ");
4393
+ if (value === null || value === void 0) out.conditions.push(`JSONHas(${src}, ${path}) AND JSONType(${src}, ${path}) = 'Null'`);
4394
+ else if (typeof value === "string") {
4395
+ const p = `${valuePrefix}_${nextId()}`;
4396
+ out.conditions.push(`JSONType(${src}, ${path}) = 'String' AND JSONExtractString(${src}, ${path}) = {${p}:String}`);
4397
+ out.params[p] = value;
4398
+ } else if (typeof value === "number") {
4399
+ const p = `${valuePrefix}_${nextId()}`;
4400
+ out.conditions.push(`JSONType(${src}, ${path}) IN ('Int64', 'UInt64', 'Double') AND JSONExtractFloat(${src}, ${path}) = {${p}:Float64}`);
4401
+ out.params[p] = value;
4402
+ } else if (typeof value === "boolean") {
4403
+ const p = `${valuePrefix}_${nextId()}`;
4404
+ out.conditions.push(`JSONType(${src}, ${path}) = 'Bool' AND JSONExtractBool(${src}, ${path}) = {${p}:UInt8}`);
4405
+ out.params[p] = value ? 1 : 0;
4406
+ } else if (Array.isArray(value)) {
4407
+ out.conditions.push(`JSONType(${src}, ${path}) = 'Array' AND JSONLength(${src}, ${path}) = ${value.length}`);
4408
+ value.forEach((item, idx) => {
4409
+ addJsonValueConditions(src, [...pathRefs, String(idx + 1)], item, valuePrefix, nextId, out);
4410
+ });
4411
+ } else {
4412
+ const entries = Object.entries(value).filter(([, v]) => v !== void 0);
4413
+ out.conditions.push(`JSONType(${src}, ${path}) = 'Object' AND JSONLength(${src}, ${path}) = ${entries.length}`);
4414
+ for (const [subKey, subValue] of entries) {
4415
+ const p = `${valuePrefix}_k_${nextId()}`;
4416
+ out.params[p] = subKey;
4417
+ addJsonValueConditions(src, [...pathRefs, `{${p}:String}`], subValue, valuePrefix, nextId, out);
4418
+ }
4419
+ }
4420
+ }
4421
+ /**
4422
+ * Adds key/value filters against a JSON-encoded string column (e.g. scores.metadata).
4423
+ * Emits predicates per requested key with exact-value deep equality for any JSON value,
4424
+ * mirroring the in-memory deep-equality semantics (structural, key-order independent).
4425
+ */
4426
+ function addJsonStringFilters(column, values, keyPrefix, valuePrefix, out) {
4427
+ if (values == null || typeof values !== "object") return;
4428
+ let id = 0;
4429
+ const nextId = () => id++;
4430
+ let i = 0;
4431
+ for (const [rawKey, rawValue] of Object.entries(values)) {
4432
+ if (rawValue === void 0) continue;
4433
+ const keyParam = `${keyPrefix}_${i}`;
4434
+ const src = `ifNull(${column}, '{}')`;
4435
+ out.params[keyParam] = rawKey;
4436
+ addJsonValueConditions(src, [`{${keyParam}:String}`], rawValue, valuePrefix, nextId, out);
4437
+ i++;
4438
+ }
4439
+ }
4440
+ /**
4383
4441
  * Adds shared context filter conditions (commonFilterFields) to the output.
4384
4442
  * Used by logs, metrics, scores, and feedback filter builders.
4385
4443
  */
@@ -4492,6 +4550,7 @@ function buildScoresFilterConditions(filters, tableAlias) {
4492
4550
  addEq(col("scoreSource"), filters.scoreSource, "scoreSource", "String", out);
4493
4551
  if (typeof filters.scorerId === "string") addEq(col("scorerId"), filters.scorerId, "scorerId", "String", out);
4494
4552
  else if (Array.isArray(filters.scorerId)) addIn(col("scorerId"), filters.scorerId, "scorerIds", out);
4553
+ addJsonStringFilters(col("metadata"), filters.metadata, "score_meta_k", "score_meta_v", out);
4495
4554
  return out;
4496
4555
  }
4497
4556
  function buildFeedbackFilterConditions(filters, tableAlias) {
@@ -7023,6 +7082,12 @@ async function filterAppliedRetention(client, entries) {
7023
7082
  * Init's subsequent `CREATE TABLE IF NOT EXISTS` and discovery MV bootstrap
7024
7083
  * recreate both with the current definitions.
7025
7084
  *
7085
+ * Separately, a refreshable MV whose stored definition lacks the `APPEND`
7086
+ * modifier (created by older releases) is dropped — keeping its target
7087
+ * table — so the MV bootstrap recreates it with the current APPEND
7088
+ * definition. Non-APPEND refreshes swap the target table atomically, which
7089
+ * fails when the target is Replicated inside a non-Replicated database.
7090
+ *
7026
7091
  * Silently returns if `system.tables` can't be queried — the rest of init
7027
7092
  * will still run and leave any existing tables untouched.
7028
7093
  */
@@ -7037,13 +7102,20 @@ async function assertExistingTablesCompatibleWithReplication(client, replication
7037
7102
  }
7038
7103
  async function reconcileDiscoveryTables(client, replication) {
7039
7104
  let engines;
7105
+ let mvCreateQueries;
7040
7106
  try {
7041
7107
  const rows = await (await client.query({
7042
- query: `SELECT name, engine FROM system.tables WHERE database = currentDatabase() AND name IN ({tables:Array(String)})`,
7043
- query_params: { tables: [TABLE_DISCOVERY_VALUES, TABLE_DISCOVERY_PAIRS] },
7108
+ query: `SELECT name, engine, create_table_query FROM system.tables WHERE database = currentDatabase() AND name IN ({tables:Array(String)})`,
7109
+ query_params: { tables: [
7110
+ TABLE_DISCOVERY_VALUES,
7111
+ TABLE_DISCOVERY_PAIRS,
7112
+ MV_DISCOVERY_VALUES,
7113
+ MV_DISCOVERY_PAIRS
7114
+ ] },
7044
7115
  format: "JSONEachRow"
7045
7116
  })).json();
7046
7117
  engines = new Map(rows.map((r) => [r.name, r.engine]));
7118
+ mvCreateQueries = new Map(rows.map((r) => [r.name, r.create_table_query]));
7047
7119
  } catch {
7048
7120
  return;
7049
7121
  }
@@ -7056,9 +7128,13 @@ async function reconcileDiscoveryTables(client, replication) {
7056
7128
  }];
7057
7129
  for (const { table, mv } of targets) {
7058
7130
  const engine = engines.get(table);
7059
- if (!engine || isReplacingMergeTreeEngine(engine)) continue;
7060
- await client.command({ query: addOnClusterToDDL(`DROP VIEW IF EXISTS ${mv}`, replication) });
7061
- await client.command({ query: addOnClusterToDDL(`DROP TABLE IF EXISTS ${table}`, replication) });
7131
+ if (engine && !isReplacingMergeTreeEngine(engine)) {
7132
+ await client.command({ query: addOnClusterToDDL(`DROP VIEW IF EXISTS ${mv}`, replication) });
7133
+ await client.command({ query: addOnClusterToDDL(`DROP TABLE IF EXISTS ${table}`, replication) });
7134
+ continue;
7135
+ }
7136
+ const createQuery = mvCreateQueries.get(mv);
7137
+ if (createQuery && /REFRESH EVERY/i.test(createQuery) && !/\bAPPEND\b/i.test(createQuery)) await client.command({ query: addOnClusterToDDL(`DROP VIEW IF EXISTS ${mv}`, replication) });
7062
7138
  }
7063
7139
  }
7064
7140
  async function queryNamesByTable(client, query, tables) {