@mastra/clickhouse 1.11.1 → 1.12.0-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.
- package/CHANGELOG.md +11 -0
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/reference-storage-clickhouse.md +41 -9
- package/dist/docs/references/reference-storage-composite.md +65 -7
- package/dist/index.cjs +257 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +257 -21
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/observability/v-next/index.d.ts +3 -3
- package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/migration.d.ts +12 -4
- package/dist/storage/domains/observability/v-next/migration.d.ts.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -5947,6 +5947,11 @@ async function getTableColumns(client, table) {
|
|
|
5947
5947
|
const rows = await result.json();
|
|
5948
5948
|
return rows.map((r) => r.name);
|
|
5949
5949
|
}
|
|
5950
|
+
async function getTableColumnTypes(client, table) {
|
|
5951
|
+
const result = await client.query({ query: `DESCRIBE TABLE ${table}`, format: "JSONEachRow" });
|
|
5952
|
+
const rows = await result.json();
|
|
5953
|
+
return new Map(rows.map((r) => [r.name, r.type]));
|
|
5954
|
+
}
|
|
5950
5955
|
function buildTemporaryTableDDL(createDDL, table, tempTable) {
|
|
5951
5956
|
return createDDL.replace(`CREATE TABLE IF NOT EXISTS ${table}`, `CREATE TABLE ${tempTable}`);
|
|
5952
5957
|
}
|
|
@@ -5980,6 +5985,218 @@ async function checkSignalTablesMigrationStatus(client) {
|
|
|
5980
5985
|
tables
|
|
5981
5986
|
};
|
|
5982
5987
|
}
|
|
5988
|
+
var TABLE_LEGACY_SPANS = "mastra_ai_spans";
|
|
5989
|
+
var PROMOTED_METADATA_KEYS = [
|
|
5990
|
+
"experimentId",
|
|
5991
|
+
"entityType",
|
|
5992
|
+
"entityId",
|
|
5993
|
+
"entityName",
|
|
5994
|
+
"entityVersionId",
|
|
5995
|
+
"parentEntityVersionId",
|
|
5996
|
+
"rootEntityVersionId",
|
|
5997
|
+
"userId",
|
|
5998
|
+
"organizationId",
|
|
5999
|
+
"resourceId",
|
|
6000
|
+
"runId",
|
|
6001
|
+
"sessionId",
|
|
6002
|
+
"threadId",
|
|
6003
|
+
"requestId",
|
|
6004
|
+
"environment",
|
|
6005
|
+
"executionSource",
|
|
6006
|
+
"serviceName"
|
|
6007
|
+
];
|
|
6008
|
+
var TABLE_LEGACY_MIGRATION_DONE = "mastra_legacy_span_migration_done";
|
|
6009
|
+
async function hasMarkerRow(client) {
|
|
6010
|
+
const engine = await getTableEngine(client, TABLE_LEGACY_MIGRATION_DONE);
|
|
6011
|
+
if (!engine) return false;
|
|
6012
|
+
const result = await client.query({
|
|
6013
|
+
query: `SELECT count() as cnt FROM ${TABLE_LEGACY_MIGRATION_DONE}`,
|
|
6014
|
+
format: "JSONEachRow"
|
|
6015
|
+
});
|
|
6016
|
+
const rows = await result.json();
|
|
6017
|
+
return Number(rows[0]?.cnt ?? 0) > 0;
|
|
6018
|
+
}
|
|
6019
|
+
async function checkLegacySpanMigrationStatus(client) {
|
|
6020
|
+
if (await hasMarkerRow(client)) {
|
|
6021
|
+
return { needsMigration: false };
|
|
6022
|
+
}
|
|
6023
|
+
const engine = await getTableEngine(client, TABLE_LEGACY_SPANS);
|
|
6024
|
+
if (!engine) {
|
|
6025
|
+
return { needsMigration: false };
|
|
6026
|
+
}
|
|
6027
|
+
const countResult = await client.query({
|
|
6028
|
+
query: `SELECT count() as cnt FROM ${TABLE_LEGACY_SPANS}`,
|
|
6029
|
+
format: "JSONEachRow"
|
|
6030
|
+
});
|
|
6031
|
+
const countRows = await countResult.json();
|
|
6032
|
+
const legacyRowCount = Number(countRows[0]?.cnt ?? 0);
|
|
6033
|
+
if (legacyRowCount === 0) {
|
|
6034
|
+
return { needsMigration: false };
|
|
6035
|
+
}
|
|
6036
|
+
return { needsMigration: true };
|
|
6037
|
+
}
|
|
6038
|
+
var VNEXT_SPAN_COLUMNS = [
|
|
6039
|
+
"dedupeKey",
|
|
6040
|
+
"traceId",
|
|
6041
|
+
"spanId",
|
|
6042
|
+
"parentSpanId",
|
|
6043
|
+
"experimentId",
|
|
6044
|
+
"entityType",
|
|
6045
|
+
"entityId",
|
|
6046
|
+
"entityName",
|
|
6047
|
+
"entityVersionId",
|
|
6048
|
+
"parentEntityVersionId",
|
|
6049
|
+
"parentEntityType",
|
|
6050
|
+
"parentEntityId",
|
|
6051
|
+
"parentEntityName",
|
|
6052
|
+
"rootEntityVersionId",
|
|
6053
|
+
"rootEntityType",
|
|
6054
|
+
"rootEntityId",
|
|
6055
|
+
"rootEntityName",
|
|
6056
|
+
"userId",
|
|
6057
|
+
"organizationId",
|
|
6058
|
+
"resourceId",
|
|
6059
|
+
"runId",
|
|
6060
|
+
"sessionId",
|
|
6061
|
+
"threadId",
|
|
6062
|
+
"requestId",
|
|
6063
|
+
"environment",
|
|
6064
|
+
"executionSource",
|
|
6065
|
+
"serviceName",
|
|
6066
|
+
"name",
|
|
6067
|
+
"spanType",
|
|
6068
|
+
"isEvent",
|
|
6069
|
+
"startedAt",
|
|
6070
|
+
"endedAt",
|
|
6071
|
+
"tags",
|
|
6072
|
+
"metadataSearch",
|
|
6073
|
+
"attributes",
|
|
6074
|
+
"scope",
|
|
6075
|
+
"links",
|
|
6076
|
+
"input",
|
|
6077
|
+
"output",
|
|
6078
|
+
"error",
|
|
6079
|
+
"metadataRaw",
|
|
6080
|
+
"requestContext"
|
|
6081
|
+
];
|
|
6082
|
+
function buildLegacySpanSelectExprs(legacyColumnTypes) {
|
|
6083
|
+
const has = (col) => legacyColumnTypes.has(col);
|
|
6084
|
+
const colOrNull = (legacy) => has(legacy) ? `"${legacy}"` : "NULL";
|
|
6085
|
+
const promotedIn = PROMOTED_METADATA_KEYS.map((k) => `'${k}'`).join(", ");
|
|
6086
|
+
const metadataSearchExpr = has("metadata") ? `CAST(arrayFilter(x -> x.1 NOT IN (${promotedIn}) AND trim(BOTH ' ' FROM x.2) != '', arrayMap(x -> (x.1, trim(BOTH ' ' FROM x.2)), JSONExtractKeysAndValues(COALESCE("metadata", '{}'), 'String'))), 'Map(String, String)')` : `CAST(map(), 'Map(String, String)')`;
|
|
6087
|
+
let tagsExpr = "[]";
|
|
6088
|
+
if (has("tags")) {
|
|
6089
|
+
const tagsType = legacyColumnTypes.get("tags");
|
|
6090
|
+
if (tagsType.includes("Array")) {
|
|
6091
|
+
tagsExpr = `arrayFilter(x -> x != '', arrayDistinct(arrayMap(x -> trim(BOTH ' ' FROM x), "tags")))`;
|
|
6092
|
+
} else {
|
|
6093
|
+
tagsExpr = `arrayFilter(x -> x != '', arrayDistinct(arrayMap(x -> trim(BOTH ' ' FROM x), JSONExtract(COALESCE("tags", '[]'), 'Array(String)'))))`;
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
const exprs = {
|
|
6097
|
+
dedupeKey: `concat("traceId", ':', "spanId")`,
|
|
6098
|
+
traceId: `"traceId"`,
|
|
6099
|
+
spanId: `"spanId"`,
|
|
6100
|
+
// Legacy stores '' for root spans; VNext MV filters on IS NULL.
|
|
6101
|
+
parentSpanId: has("parentSpanId") ? `nullIf("parentSpanId", '')` : "NULL",
|
|
6102
|
+
experimentId: colOrNull("experimentId"),
|
|
6103
|
+
entityType: colOrNull("entityType"),
|
|
6104
|
+
entityId: colOrNull("entityId"),
|
|
6105
|
+
entityName: colOrNull("entityName"),
|
|
6106
|
+
entityVersionId: colOrNull("entityVersionId"),
|
|
6107
|
+
parentEntityVersionId: colOrNull("parentEntityVersionId"),
|
|
6108
|
+
parentEntityType: colOrNull("parentEntityType"),
|
|
6109
|
+
parentEntityId: colOrNull("parentEntityId"),
|
|
6110
|
+
parentEntityName: colOrNull("parentEntityName"),
|
|
6111
|
+
rootEntityVersionId: colOrNull("rootEntityVersionId"),
|
|
6112
|
+
rootEntityType: colOrNull("rootEntityType"),
|
|
6113
|
+
rootEntityId: colOrNull("rootEntityId"),
|
|
6114
|
+
rootEntityName: colOrNull("rootEntityName"),
|
|
6115
|
+
userId: colOrNull("userId"),
|
|
6116
|
+
organizationId: colOrNull("organizationId"),
|
|
6117
|
+
resourceId: colOrNull("resourceId"),
|
|
6118
|
+
runId: colOrNull("runId"),
|
|
6119
|
+
sessionId: colOrNull("sessionId"),
|
|
6120
|
+
threadId: colOrNull("threadId"),
|
|
6121
|
+
requestId: colOrNull("requestId"),
|
|
6122
|
+
environment: colOrNull("environment"),
|
|
6123
|
+
executionSource: has("source") ? `"source"` : "NULL",
|
|
6124
|
+
serviceName: colOrNull("serviceName"),
|
|
6125
|
+
name: `"name"`,
|
|
6126
|
+
spanType: `"spanType"`,
|
|
6127
|
+
isEvent: `"isEvent"`,
|
|
6128
|
+
startedAt: `"startedAt"`,
|
|
6129
|
+
// VNext requires non-null endedAt; events always use startedAt.
|
|
6130
|
+
endedAt: `if("isEvent", "startedAt", COALESCE("endedAt", "startedAt"))`,
|
|
6131
|
+
tags: tagsExpr,
|
|
6132
|
+
metadataSearch: metadataSearchExpr,
|
|
6133
|
+
attributes: colOrNull("attributes"),
|
|
6134
|
+
scope: colOrNull("scope"),
|
|
6135
|
+
links: colOrNull("links"),
|
|
6136
|
+
input: colOrNull("input"),
|
|
6137
|
+
output: colOrNull("output"),
|
|
6138
|
+
error: colOrNull("error"),
|
|
6139
|
+
metadataRaw: has("metadata") ? `"metadata"` : "NULL",
|
|
6140
|
+
requestContext: colOrNull("requestContext")
|
|
6141
|
+
};
|
|
6142
|
+
return VNEXT_SPAN_COLUMNS.map((c) => `${exprs[c]} AS "${c}"`).join(",\n ");
|
|
6143
|
+
}
|
|
6144
|
+
async function migrateLegacySpans(client, logger) {
|
|
6145
|
+
if (await hasMarkerRow(client)) {
|
|
6146
|
+
return { migratedRows: 0, batches: 0 };
|
|
6147
|
+
}
|
|
6148
|
+
const engine = await getTableEngine(client, TABLE_LEGACY_SPANS);
|
|
6149
|
+
if (!engine) {
|
|
6150
|
+
return { migratedRows: 0, batches: 0 };
|
|
6151
|
+
}
|
|
6152
|
+
const legacyColumnTypes = await getTableColumnTypes(client, TABLE_LEGACY_SPANS);
|
|
6153
|
+
const hasUpdatedAt = legacyColumnTypes.has("updatedAt");
|
|
6154
|
+
await client.command({ query: SPAN_EVENTS_DDL });
|
|
6155
|
+
await client.command({ query: TRACE_ROOTS_DDL });
|
|
6156
|
+
await client.command({ query: TRACE_BRANCHES_DDL });
|
|
6157
|
+
await client.command({ query: TRACE_ROOTS_MV_DDL });
|
|
6158
|
+
await client.command({ query: TRACE_BRANCHES_MV_DDL });
|
|
6159
|
+
const selectExprs = buildLegacySpanSelectExprs(legacyColumnTypes);
|
|
6160
|
+
const columnList = VNEXT_SPAN_COLUMNS.map((c) => `"${c}"`).join(", ");
|
|
6161
|
+
const dedupOrder = hasUpdatedAt ? `ORDER BY "traceId", "spanId", COALESCE("updatedAt", "createdAt") DESC` : `ORDER BY "traceId", "spanId", "createdAt" DESC`;
|
|
6162
|
+
const daysResult = await client.query({
|
|
6163
|
+
query: `SELECT
|
|
6164
|
+
toString(toDate(COALESCE(endedAt, startedAt))) as day,
|
|
6165
|
+
count() as cnt
|
|
6166
|
+
FROM ${TABLE_LEGACY_SPANS}
|
|
6167
|
+
GROUP BY day
|
|
6168
|
+
ORDER BY day`,
|
|
6169
|
+
format: "JSONEachRow"
|
|
6170
|
+
});
|
|
6171
|
+
const days = await daysResult.json();
|
|
6172
|
+
let migratedRows = 0;
|
|
6173
|
+
let batches = 0;
|
|
6174
|
+
for (const { day, cnt } of days) {
|
|
6175
|
+
const dayCount = Number(cnt);
|
|
6176
|
+
await client.command({
|
|
6177
|
+
query: `INSERT INTO ${TABLE_SPAN_EVENTS} (${columnList})
|
|
6178
|
+
SELECT ${selectExprs}
|
|
6179
|
+
FROM ${TABLE_LEGACY_SPANS}
|
|
6180
|
+
WHERE toDate(COALESCE(endedAt, startedAt)) = {batchDate:Date}
|
|
6181
|
+
${dedupOrder}
|
|
6182
|
+
LIMIT 1 BY "traceId", "spanId"`,
|
|
6183
|
+
query_params: { batchDate: day }
|
|
6184
|
+
});
|
|
6185
|
+
migratedRows += dayCount;
|
|
6186
|
+
batches++;
|
|
6187
|
+
logger?.info?.(`Migrated batch ${day}: ${dayCount} rows`);
|
|
6188
|
+
}
|
|
6189
|
+
await client.command({
|
|
6190
|
+
query: `CREATE TABLE IF NOT EXISTS ${TABLE_LEGACY_MIGRATION_DONE} (completedAt DateTime64(3, 'UTC')) ENGINE = MergeTree ORDER BY completedAt`
|
|
6191
|
+
});
|
|
6192
|
+
await client.command({
|
|
6193
|
+
query: `INSERT INTO ${TABLE_LEGACY_MIGRATION_DONE} VALUES (now64(3))`
|
|
6194
|
+
});
|
|
6195
|
+
logger?.info?.(
|
|
6196
|
+
`Legacy span migration complete: ${migratedRows} rows in ${batches} batches. The legacy table '${TABLE_LEGACY_SPANS}' has been preserved. Drop it manually after verifying the migration.`
|
|
6197
|
+
);
|
|
6198
|
+
return { migratedRows, batches };
|
|
6199
|
+
}
|
|
5983
6200
|
async function migrateSignalTables(client, logger) {
|
|
5984
6201
|
for (const { table, createDDL, idColumn } of SIGNAL_MIGRATIONS) {
|
|
5985
6202
|
const engine = await getTableEngine(client, table);
|
|
@@ -7294,6 +7511,15 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
|
|
|
7294
7511
|
})
|
|
7295
7512
|
});
|
|
7296
7513
|
}
|
|
7514
|
+
try {
|
|
7515
|
+
const legacyStatus = await checkLegacySpanMigrationStatus(this.#client);
|
|
7516
|
+
if (legacyStatus.needsMigration) {
|
|
7517
|
+
this.logger?.warn?.(
|
|
7518
|
+
`Legacy span table 'mastra_ai_spans' detected. Run 'npx mastra migrate' to migrate historical spans to the v-next schema.`
|
|
7519
|
+
);
|
|
7520
|
+
}
|
|
7521
|
+
} catch {
|
|
7522
|
+
}
|
|
7297
7523
|
try {
|
|
7298
7524
|
await assertExistingTablesCompatibleWithReplication(this.#client, this.#replication);
|
|
7299
7525
|
const existingStrategy = await detectExistingDeltaCursorStrategy(this.#client);
|
|
@@ -7368,34 +7594,44 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
|
|
|
7368
7594
|
}
|
|
7369
7595
|
}
|
|
7370
7596
|
/**
|
|
7371
|
-
* Manually migrate legacy
|
|
7372
|
-
*
|
|
7373
|
-
*
|
|
7597
|
+
* Manually migrate legacy tables to the v-next schema.
|
|
7598
|
+
* Handles both signal table migrations (MergeTree → ReplacingMergeTree)
|
|
7599
|
+
* and legacy span migration (mastra_ai_spans → mastra_span_events).
|
|
7374
7600
|
*/
|
|
7375
7601
|
async migrateSpans() {
|
|
7376
|
-
const
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7602
|
+
const messages = [];
|
|
7603
|
+
const signalStatus = await checkSignalTablesMigrationStatus(this.#client);
|
|
7604
|
+
if (signalStatus.needsMigration) {
|
|
7605
|
+
if (isReplicationConfigured(this.#replication)) {
|
|
7606
|
+
throw new MastraError({
|
|
7607
|
+
id: createStorageErrorId("CLICKHOUSE", "REPLICATION", "SIGNAL_TABLES_MIGRATION_UNSUPPORTED"),
|
|
7608
|
+
domain: ErrorDomain.STORAGE,
|
|
7609
|
+
category: ErrorCategory.USER,
|
|
7610
|
+
text: "ClickHouse replication is enabled, so Mastra will not run copy-and-swap signal table migrations automatically. Migrate existing local signal tables manually before enabling replication."
|
|
7611
|
+
});
|
|
7612
|
+
}
|
|
7613
|
+
await migrateSignalTables(this.#client, this.logger);
|
|
7614
|
+
messages.push(`Migrated signal tables: ${signalStatus.tables.map((t) => t.table).join(", ")}.`);
|
|
7384
7615
|
}
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7616
|
+
const legacyStatus = await checkLegacySpanMigrationStatus(this.#client);
|
|
7617
|
+
if (legacyStatus.needsMigration) {
|
|
7618
|
+
if (isReplicationConfigured(this.#replication)) {
|
|
7619
|
+
throw new MastraError({
|
|
7620
|
+
id: createStorageErrorId("CLICKHOUSE", "REPLICATION", "LEGACY_SPAN_MIGRATION_UNSUPPORTED"),
|
|
7621
|
+
domain: ErrorDomain.STORAGE,
|
|
7622
|
+
category: ErrorCategory.USER,
|
|
7623
|
+
text: "ClickHouse replication is enabled. Migrate legacy mastra_ai_spans manually before enabling replication."
|
|
7624
|
+
});
|
|
7625
|
+
}
|
|
7626
|
+
const result = await migrateLegacySpans(this.#client, this.logger);
|
|
7627
|
+
messages.push(`Migrated ${result.migratedRows} legacy spans in ${result.batches} batches.`);
|
|
7392
7628
|
}
|
|
7393
|
-
|
|
7629
|
+
const alreadyMigrated = !signalStatus.needsMigration && !legacyStatus.needsMigration;
|
|
7394
7630
|
return {
|
|
7395
7631
|
success: true,
|
|
7396
|
-
alreadyMigrated
|
|
7632
|
+
alreadyMigrated,
|
|
7397
7633
|
duplicatesRemoved: 0,
|
|
7398
|
-
message:
|
|
7634
|
+
message: alreadyMigrated ? "Migration already complete." : `Migration complete. ${messages.join(" ")}`
|
|
7399
7635
|
};
|
|
7400
7636
|
}
|
|
7401
7637
|
// -------------------------------------------------------------------------
|