@mastra/clickhouse 1.23.0-alpha.0 → 1.23.0-alpha.2
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/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/index.cjs +815 -386
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +816 -387
- package/dist/index.js.map +1 -1
- package/dist/storage/db/index.d.ts +8 -0
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/db/utils.d.ts +11 -0
- package/dist/storage/db/utils.d.ts.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +2 -0
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/ddl.d.ts +5 -5
- package/dist/storage/domains/observability/v-next/ddl.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/feedback.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/index.d.ts +3 -2
- package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/metrics.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/polling.d.ts +2 -0
- package/dist/storage/domains/observability/v-next/polling.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/scores.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/span-query.d.ts +45 -0
- package/dist/storage/domains/observability/v-next/span-query.d.ts.map +1 -0
- package/dist/storage/domains/observability/v-next/trace-query.d.ts +21 -2
- package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-roots.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/tracing.d.ts.map +1 -1
- package/dist/storage/domains/scores/index.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -279,6 +279,35 @@ const TABLE_ENGINES = {
|
|
|
279
279
|
[_mastra_core_storage.TABLE_KNOWLEDGE_ACTIVITY]: `ReplacingMergeTree()`,
|
|
280
280
|
[_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX]: `ReplacingMergeTree()`
|
|
281
281
|
};
|
|
282
|
+
/**
|
|
283
|
+
* Bloom-filter skip indexes for lookups on columns outside the sort key.
|
|
284
|
+
*
|
|
285
|
+
* These tables are keyed `(createdAt, id)` (or `(createdAt, run_id, ...)` for
|
|
286
|
+
* workflow snapshots), so every lookup by id, thread, resource or run would
|
|
287
|
+
* otherwise scan every granule. Added idempotently at init; existing parts are
|
|
288
|
+
* covered as merges and retention rewrite them (or via `MATERIALIZE INDEX`).
|
|
289
|
+
*/
|
|
290
|
+
const TABLE_SKIP_INDEXES = {
|
|
291
|
+
[_mastra_core_storage.TABLE_MESSAGES]: [
|
|
292
|
+
"id",
|
|
293
|
+
"thread_id",
|
|
294
|
+
"resourceId"
|
|
295
|
+
],
|
|
296
|
+
[_mastra_core_storage.TABLE_THREADS]: ["id", "resourceId"],
|
|
297
|
+
[_mastra_core_storage.TABLE_RESOURCES]: ["id"],
|
|
298
|
+
[_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT]: ["run_id"],
|
|
299
|
+
[_mastra_core_storage.TABLE_SCORERS]: [
|
|
300
|
+
"id",
|
|
301
|
+
"runId",
|
|
302
|
+
"scorerId",
|
|
303
|
+
"entityId",
|
|
304
|
+
"traceId"
|
|
305
|
+
]
|
|
306
|
+
};
|
|
307
|
+
/** Name of the skip index {@link TABLE_SKIP_INDEXES} declares for `column`. */
|
|
308
|
+
function skipIndexName(column) {
|
|
309
|
+
return `idx_${column}`;
|
|
310
|
+
}
|
|
282
311
|
const COLUMN_TYPES = {
|
|
283
312
|
text: "String",
|
|
284
313
|
timestamp: "DateTime64(3)",
|
|
@@ -391,10 +420,39 @@ var ClickhouseDB = class extends _mastra_core_base.MastraBase {
|
|
|
391
420
|
return filtered;
|
|
392
421
|
}
|
|
393
422
|
async hasColumn(table, column) {
|
|
394
|
-
return (await
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
423
|
+
return (await this.getTableColumns(table)).has(column);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Adds the bloom-filter skip indexes declared in {@link TABLE_SKIP_INDEXES}
|
|
427
|
+
* for `tableName`. Call after createTable/alterTable so optional columns
|
|
428
|
+
* exist. Indexes (or columns) already present are skipped: on Replicated
|
|
429
|
+
* tables every issued ALTER bumps the metadata version even with
|
|
430
|
+
* `IF NOT EXISTS`.
|
|
431
|
+
*/
|
|
432
|
+
async ensureSkipIndexes(tableName) {
|
|
433
|
+
const columns = TABLE_SKIP_INDEXES[tableName];
|
|
434
|
+
if (!columns?.length) return;
|
|
435
|
+
try {
|
|
436
|
+
const existingColumns = await this.getTableColumns(tableName);
|
|
437
|
+
const result = await this.client.query({
|
|
438
|
+
query: `SELECT name FROM system.data_skipping_indices WHERE database = currentDatabase() AND table = {table:String}`,
|
|
439
|
+
query_params: { table: tableName },
|
|
440
|
+
format: "JSONEachRow"
|
|
441
|
+
});
|
|
442
|
+
const existingIndexes = new Set((await result.json()).map((row) => row.name));
|
|
443
|
+
for (const column of columns) {
|
|
444
|
+
const name = skipIndexName(column);
|
|
445
|
+
if (existingIndexes.has(name) || !existingColumns.has(column)) continue;
|
|
446
|
+
await this.client.command({ query: addOnClusterToDDL(`ALTER TABLE ${tableName} ADD INDEX IF NOT EXISTS ${name} "${column}" TYPE bloom_filter(0.01) GRANULARITY 1`, this.replication) });
|
|
447
|
+
}
|
|
448
|
+
} catch (error) {
|
|
449
|
+
throw new _mastra_core_error.MastraError({
|
|
450
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("CLICKHOUSE", "ENSURE_SKIP_INDEXES", "FAILED"),
|
|
451
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
452
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
453
|
+
details: { tableName }
|
|
454
|
+
}, error);
|
|
455
|
+
}
|
|
398
456
|
}
|
|
399
457
|
/**
|
|
400
458
|
* Checks if a table exists in the database.
|
|
@@ -787,7 +845,8 @@ var ClickhouseDB = class extends _mastra_core_base.MastraBase {
|
|
|
787
845
|
date_time_input_format: "best_effort",
|
|
788
846
|
date_time_output_format: "iso",
|
|
789
847
|
use_client_time_zone: 1,
|
|
790
|
-
output_format_json_quote_64bit_integers: 0
|
|
848
|
+
output_format_json_quote_64bit_integers: 0,
|
|
849
|
+
...tableName === _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT ? { use_skip_indexes_if_final: 1 } : {}
|
|
791
850
|
}
|
|
792
851
|
});
|
|
793
852
|
if (!result) return null;
|
|
@@ -884,6 +943,9 @@ var MemoryStorageClickhouse = class extends _mastra_core_storage.MemoryStorage {
|
|
|
884
943
|
schema: _mastra_core_storage.TABLE_SCHEMAS[_mastra_core_storage.TABLE_MESSAGES],
|
|
885
944
|
ifNotExists: ["resourceId"]
|
|
886
945
|
});
|
|
946
|
+
await this.#db.ensureSkipIndexes(_mastra_core_storage.TABLE_THREADS);
|
|
947
|
+
await this.#db.ensureSkipIndexes(_mastra_core_storage.TABLE_MESSAGES);
|
|
948
|
+
await this.#db.ensureSkipIndexes(_mastra_core_storage.TABLE_RESOURCES);
|
|
887
949
|
}
|
|
888
950
|
async dangerouslyClearAll() {
|
|
889
951
|
await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_MESSAGES });
|
|
@@ -1226,16 +1288,9 @@ var MemoryStorageClickhouse = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1226
1288
|
for (const message of messages) {
|
|
1227
1289
|
if (!message.resourceId) throw new Error("Resource ID is required");
|
|
1228
1290
|
if (!message.threadId) throw new Error("Thread ID is required");
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
const
|
|
1232
|
-
await Promise.all(messages.map(async (m) => {
|
|
1233
|
-
if (!m.resourceId) throw new Error("Resource ID is required");
|
|
1234
|
-
if (!m.threadId) throw new Error("Thread ID is required");
|
|
1235
|
-
const thread = await this.getThreadById({ threadId: m.threadId });
|
|
1236
|
-
if (!thread) throw new Error(`Thread ${m.threadId} not found`);
|
|
1237
|
-
threadIdSet.set(m.threadId, thread);
|
|
1238
|
-
}));
|
|
1291
|
+
}
|
|
1292
|
+
const threadIdSet = await this.getLatestThreads([...new Set(messages.map((m) => m.threadId))]);
|
|
1293
|
+
for (const message of messages) if (!threadIdSet.has(message.threadId)) throw new Error(`Thread ${message.threadId} not found`);
|
|
1239
1294
|
try {
|
|
1240
1295
|
const existingRows = await (await this.client.query({
|
|
1241
1296
|
query: `SELECT id, thread_id FROM ${_mastra_core_storage.TABLE_MESSAGES} WHERE id IN ({ids:Array(String)})`,
|
|
@@ -1338,6 +1393,38 @@ var MemoryStorageClickhouse = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1338
1393
|
}, error);
|
|
1339
1394
|
}
|
|
1340
1395
|
}
|
|
1396
|
+
/** Newest version of each requested thread, keyed by id. Missing threads are absent. */
|
|
1397
|
+
async getLatestThreads(threadIds) {
|
|
1398
|
+
const rows = await (await this.client.query({
|
|
1399
|
+
query: `SELECT
|
|
1400
|
+
id,
|
|
1401
|
+
"resourceId",
|
|
1402
|
+
title,
|
|
1403
|
+
metadata,
|
|
1404
|
+
toDateTime64(createdAt, 3) as createdAt,
|
|
1405
|
+
toDateTime64(updatedAt, 3) as updatedAt
|
|
1406
|
+
FROM "${_mastra_core_storage.TABLE_THREADS}"
|
|
1407
|
+
WHERE id IN {threadIds:Array(String)}
|
|
1408
|
+
ORDER BY updatedAt DESC
|
|
1409
|
+
LIMIT 1 BY id`,
|
|
1410
|
+
query_params: { threadIds },
|
|
1411
|
+
clickhouse_settings: {
|
|
1412
|
+
date_time_input_format: "best_effort",
|
|
1413
|
+
date_time_output_format: "iso",
|
|
1414
|
+
use_client_time_zone: 1,
|
|
1415
|
+
output_format_json_quote_64bit_integers: 0
|
|
1416
|
+
}
|
|
1417
|
+
})).json();
|
|
1418
|
+
const threads = /* @__PURE__ */ new Map();
|
|
1419
|
+
for (const row of rows.data) {
|
|
1420
|
+
const thread = transformRow(row);
|
|
1421
|
+
threads.set(thread.id, {
|
|
1422
|
+
...thread,
|
|
1423
|
+
metadata: parseMetadata(thread.metadata)
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
return threads;
|
|
1427
|
+
}
|
|
1341
1428
|
async getThreadById({ threadId, resourceId }) {
|
|
1342
1429
|
try {
|
|
1343
1430
|
const thread = transformRow((await (await this.client.query({
|
|
@@ -1512,19 +1599,27 @@ var MemoryStorageClickhouse = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1512
1599
|
metadataIndex++;
|
|
1513
1600
|
}
|
|
1514
1601
|
}
|
|
1602
|
+
const latestThreads = `
|
|
1603
|
+
latest_threads AS (
|
|
1604
|
+
SELECT
|
|
1605
|
+
id,
|
|
1606
|
+
resourceId,
|
|
1607
|
+
title,
|
|
1608
|
+
metadata,
|
|
1609
|
+
toDateTime64(createdAt, 3) as createdAt,
|
|
1610
|
+
toDateTime64(updatedAt, 3) as updatedAt
|
|
1611
|
+
FROM ${_mastra_core_storage.TABLE_THREADS}
|
|
1612
|
+
${filter?.resourceId ? `WHERE id IN (SELECT id FROM ${_mastra_core_storage.TABLE_THREADS} WHERE resourceId = {resourceId:String})` : ""}
|
|
1613
|
+
ORDER BY updatedAt DESC
|
|
1614
|
+
LIMIT 1 BY id
|
|
1615
|
+
)`;
|
|
1616
|
+
const latestWhere = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
|
|
1515
1617
|
const total = (await (await this.client.query({
|
|
1516
1618
|
query: `
|
|
1517
|
-
WITH
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
metadata,
|
|
1522
|
-
ROW_NUMBER() OVER (PARTITION BY id ORDER BY updatedAt DESC) as row_num
|
|
1523
|
-
FROM ${_mastra_core_storage.TABLE_THREADS}
|
|
1524
|
-
)
|
|
1525
|
-
SELECT count(*) as total
|
|
1526
|
-
FROM ranked_threads
|
|
1527
|
-
WHERE row_num = 1 ${whereClauses.length > 0 ? `AND ${whereClauses.join(" AND ")}` : ""}
|
|
1619
|
+
WITH ${latestThreads}
|
|
1620
|
+
SELECT count(*) as total
|
|
1621
|
+
FROM latest_threads
|
|
1622
|
+
${latestWhere}
|
|
1528
1623
|
`,
|
|
1529
1624
|
query_params: queryParams,
|
|
1530
1625
|
clickhouse_settings: {
|
|
@@ -1544,17 +1639,7 @@ var MemoryStorageClickhouse = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1544
1639
|
return {
|
|
1545
1640
|
threads: transformRows((await (await this.client.query({
|
|
1546
1641
|
query: `
|
|
1547
|
-
WITH
|
|
1548
|
-
SELECT
|
|
1549
|
-
id,
|
|
1550
|
-
resourceId,
|
|
1551
|
-
title,
|
|
1552
|
-
metadata,
|
|
1553
|
-
toDateTime64(createdAt, 3) as createdAt,
|
|
1554
|
-
toDateTime64(updatedAt, 3) as updatedAt,
|
|
1555
|
-
ROW_NUMBER() OVER (PARTITION BY id ORDER BY updatedAt DESC) as row_num
|
|
1556
|
-
FROM ${_mastra_core_storage.TABLE_THREADS}
|
|
1557
|
-
)
|
|
1642
|
+
WITH ${latestThreads}
|
|
1558
1643
|
SELECT
|
|
1559
1644
|
id,
|
|
1560
1645
|
resourceId,
|
|
@@ -1562,8 +1647,8 @@ var MemoryStorageClickhouse = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1562
1647
|
metadata,
|
|
1563
1648
|
createdAt,
|
|
1564
1649
|
updatedAt
|
|
1565
|
-
FROM
|
|
1566
|
-
|
|
1650
|
+
FROM latest_threads
|
|
1651
|
+
${latestWhere}
|
|
1567
1652
|
ORDER BY "${field}" ${direction === "DESC" ? "DESC" : "ASC"}
|
|
1568
1653
|
LIMIT {perPage:Int64} OFFSET {offset:Int64}
|
|
1569
1654
|
`,
|
|
@@ -1676,37 +1761,32 @@ var MemoryStorageClickhouse = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1676
1761
|
clickhouse_settings: {
|
|
1677
1762
|
date_time_input_format: "best_effort",
|
|
1678
1763
|
use_client_time_zone: 1,
|
|
1679
|
-
output_format_json_quote_64bit_integers: 0
|
|
1764
|
+
output_format_json_quote_64bit_integers: 0,
|
|
1765
|
+
mutations_sync: "1"
|
|
1680
1766
|
}
|
|
1681
1767
|
}));
|
|
1682
1768
|
}
|
|
1683
1769
|
}
|
|
1684
1770
|
if (updatePromises.length > 0) await Promise.all(updatePromises);
|
|
1685
|
-
await this.client.
|
|
1686
|
-
query: `
|
|
1771
|
+
const verifyResult = await this.client.query({
|
|
1772
|
+
query: `SELECT id, content, role, type, "createdAt", thread_id AS "threadId", "resourceId" FROM ${_mastra_core_storage.TABLE_MESSAGES} WHERE id IN {messageIds:Array(String)}`,
|
|
1773
|
+
query_params: { messageIds },
|
|
1687
1774
|
clickhouse_settings: {
|
|
1688
1775
|
date_time_input_format: "best_effort",
|
|
1776
|
+
date_time_output_format: "iso",
|
|
1689
1777
|
use_client_time_zone: 1,
|
|
1690
1778
|
output_format_json_quote_64bit_integers: 0
|
|
1691
1779
|
}
|
|
1692
1780
|
});
|
|
1781
|
+
const verifiedById = /* @__PURE__ */ new Map();
|
|
1782
|
+
for (const row of transformRows((await verifyResult.json()).data)) if (!verifiedById.has(row.id)) verifiedById.set(row.id, row);
|
|
1693
1783
|
for (const existingMessage of parsedExistingMessages) {
|
|
1694
1784
|
const updatePayload = messages.find((m) => m.id === existingMessage.id);
|
|
1695
1785
|
if (!updatePayload) continue;
|
|
1696
1786
|
const { id, ...fieldsToUpdate } = updatePayload;
|
|
1697
1787
|
if (Object.keys(fieldsToUpdate).length === 0) continue;
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
query_params: { messageId: id },
|
|
1701
|
-
clickhouse_settings: {
|
|
1702
|
-
date_time_input_format: "best_effort",
|
|
1703
|
-
date_time_output_format: "iso",
|
|
1704
|
-
use_client_time_zone: 1,
|
|
1705
|
-
output_format_json_quote_64bit_integers: 0
|
|
1706
|
-
}
|
|
1707
|
-
})).json();
|
|
1708
|
-
if (verifyRows.data.length > 0) {
|
|
1709
|
-
const updatedMessage = transformRows(verifyRows.data)[0];
|
|
1788
|
+
if (verifiedById.has(id)) {
|
|
1789
|
+
const updatedMessage = verifiedById.get(id);
|
|
1710
1790
|
if (updatedMessage) {
|
|
1711
1791
|
let needsRetry = false;
|
|
1712
1792
|
for (const [key, value] of Object.entries(fieldsToUpdate)) if (key === "content") {
|
|
@@ -1773,68 +1853,42 @@ var MemoryStorageClickhouse = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1773
1853
|
}
|
|
1774
1854
|
if (threadIdsToUpdate.size > 0) {
|
|
1775
1855
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
1776
|
-
const now =
|
|
1777
|
-
const
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
const existingThread = threadRows.data[0];
|
|
1790
|
-
await this.client.command({
|
|
1791
|
-
query: `DELETE FROM ${_mastra_core_storage.TABLE_THREADS} WHERE id = {threadId:String}`,
|
|
1792
|
-
query_params: { threadId },
|
|
1793
|
-
clickhouse_settings: {
|
|
1794
|
-
date_time_input_format: "best_effort",
|
|
1795
|
-
use_client_time_zone: 1,
|
|
1796
|
-
output_format_json_quote_64bit_integers: 0
|
|
1797
|
-
}
|
|
1798
|
-
});
|
|
1799
|
-
await this.client.insert({
|
|
1800
|
-
table: _mastra_core_storage.TABLE_THREADS,
|
|
1801
|
-
format: "JSONEachRow",
|
|
1802
|
-
values: [{
|
|
1803
|
-
id: existingThread.id,
|
|
1804
|
-
resourceId: existingThread.resourceId,
|
|
1805
|
-
title: existingThread.title,
|
|
1806
|
-
metadata: typeof existingThread.metadata === "string" ? existingThread.metadata : serializeMetadata(existingThread.metadata),
|
|
1807
|
-
createdAt: existingThread.createdAt,
|
|
1808
|
-
updatedAt: now
|
|
1809
|
-
}],
|
|
1810
|
-
clickhouse_settings: {
|
|
1811
|
-
date_time_input_format: "best_effort",
|
|
1812
|
-
use_client_time_zone: 1,
|
|
1813
|
-
output_format_json_quote_64bit_integers: 0
|
|
1814
|
-
}
|
|
1815
|
-
});
|
|
1816
|
-
}
|
|
1817
|
-
});
|
|
1818
|
-
await Promise.all(threadUpdatePromises);
|
|
1819
|
-
}
|
|
1820
|
-
const updatedMessages = [];
|
|
1821
|
-
for (const messageId of messageIds) {
|
|
1822
|
-
const updatedRows = await (await this.client.query({
|
|
1823
|
-
query: `SELECT id, content, role, type, "createdAt", thread_id AS "threadId", "resourceId" FROM ${_mastra_core_storage.TABLE_MESSAGES} WHERE id = {messageId:String}`,
|
|
1824
|
-
query_params: { messageId },
|
|
1856
|
+
const now = Date.now();
|
|
1857
|
+
const existingThreads = await this.getLatestThreads(Array.from(threadIdsToUpdate));
|
|
1858
|
+
if (existingThreads.size > 0) await this.client.insert({
|
|
1859
|
+
table: _mastra_core_storage.TABLE_THREADS,
|
|
1860
|
+
format: "JSONEachRow",
|
|
1861
|
+
values: Array.from(existingThreads.values()).map((existingThread) => ({
|
|
1862
|
+
id: existingThread.id,
|
|
1863
|
+
resourceId: existingThread.resourceId,
|
|
1864
|
+
title: existingThread.title,
|
|
1865
|
+
metadata: serializeMetadata(existingThread.metadata),
|
|
1866
|
+
createdAt: existingThread.createdAt,
|
|
1867
|
+
updatedAt: new Date(Math.max(now, new Date(existingThread.updatedAt).getTime() + 1)).toISOString().replace("Z", "")
|
|
1868
|
+
})),
|
|
1825
1869
|
clickhouse_settings: {
|
|
1826
1870
|
date_time_input_format: "best_effort",
|
|
1827
|
-
date_time_output_format: "iso",
|
|
1828
1871
|
use_client_time_zone: 1,
|
|
1829
1872
|
output_format_json_quote_64bit_integers: 0
|
|
1830
1873
|
}
|
|
1831
|
-
})
|
|
1832
|
-
if (updatedRows.data.length > 0) {
|
|
1833
|
-
const message = transformRows(updatedRows.data)[0];
|
|
1834
|
-
if (message) updatedMessages.push(message);
|
|
1835
|
-
}
|
|
1874
|
+
});
|
|
1836
1875
|
}
|
|
1837
|
-
|
|
1876
|
+
const updatedResult = await this.client.query({
|
|
1877
|
+
query: `SELECT id, content, role, type, "createdAt", thread_id AS "threadId", "resourceId" FROM ${_mastra_core_storage.TABLE_MESSAGES} WHERE id IN {messageIds:Array(String)}`,
|
|
1878
|
+
query_params: { messageIds },
|
|
1879
|
+
clickhouse_settings: {
|
|
1880
|
+
date_time_input_format: "best_effort",
|
|
1881
|
+
date_time_output_format: "iso",
|
|
1882
|
+
use_client_time_zone: 1,
|
|
1883
|
+
output_format_json_quote_64bit_integers: 0
|
|
1884
|
+
}
|
|
1885
|
+
});
|
|
1886
|
+
const updatedById = /* @__PURE__ */ new Map();
|
|
1887
|
+
for (const row of transformRows((await updatedResult.json()).data)) if (!updatedById.has(row.id)) updatedById.set(row.id, row);
|
|
1888
|
+
return messageIds.flatMap((messageId) => {
|
|
1889
|
+
const message = updatedById.get(messageId);
|
|
1890
|
+
return message ? [message] : [];
|
|
1891
|
+
}).map((message) => {
|
|
1838
1892
|
if (typeof message.content === "string") try {
|
|
1839
1893
|
message.content = JSON.parse(message.content);
|
|
1840
1894
|
} catch {}
|
|
@@ -1930,35 +1984,9 @@ var MemoryStorageClickhouse = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1930
1984
|
...existingResource.metadata,
|
|
1931
1985
|
...metadata
|
|
1932
1986
|
},
|
|
1933
|
-
updatedAt:
|
|
1987
|
+
updatedAt: new Date(Math.max(Date.now(), existingResource.updatedAt.getTime() + 1))
|
|
1934
1988
|
};
|
|
1935
|
-
|
|
1936
|
-
ALTER TABLE ${_mastra_core_storage.TABLE_RESOURCES}
|
|
1937
|
-
UPDATE workingMemory = {workingMemory:String}, metadata = {metadata:String}, updatedAt = {updatedAt:String}
|
|
1938
|
-
WHERE id = {resourceId:String}
|
|
1939
|
-
`;
|
|
1940
|
-
await this.client.command({
|
|
1941
|
-
query: updateQuery,
|
|
1942
|
-
query_params: {
|
|
1943
|
-
workingMemory: updatedResource.workingMemory,
|
|
1944
|
-
metadata: JSON.stringify(updatedResource.metadata),
|
|
1945
|
-
updatedAt: updatedResource.updatedAt.toISOString().replace("Z", ""),
|
|
1946
|
-
resourceId
|
|
1947
|
-
},
|
|
1948
|
-
clickhouse_settings: {
|
|
1949
|
-
date_time_input_format: "best_effort",
|
|
1950
|
-
use_client_time_zone: 1,
|
|
1951
|
-
output_format_json_quote_64bit_integers: 0
|
|
1952
|
-
}
|
|
1953
|
-
});
|
|
1954
|
-
await this.client.command({
|
|
1955
|
-
query: `OPTIMIZE TABLE ${_mastra_core_storage.TABLE_RESOURCES} FINAL`,
|
|
1956
|
-
clickhouse_settings: {
|
|
1957
|
-
date_time_input_format: "best_effort",
|
|
1958
|
-
use_client_time_zone: 1,
|
|
1959
|
-
output_format_json_quote_64bit_integers: 0
|
|
1960
|
-
}
|
|
1961
|
-
});
|
|
1989
|
+
await this.saveResource({ resource: updatedResource });
|
|
1962
1990
|
return updatedResource;
|
|
1963
1991
|
} catch (error) {
|
|
1964
1992
|
throw new _mastra_core_error.MastraError({
|
|
@@ -2406,14 +2434,10 @@ var ObservabilityStorageClickhouse = class extends _mastra_core_storage.Observab
|
|
|
2406
2434
|
}
|
|
2407
2435
|
if (filters.hasChildError !== void 0) {
|
|
2408
2436
|
const finalClause = (TABLE_ENGINES[_mastra_core_storage.TABLE_SPANS] ?? "MergeTree()").startsWith("ReplacingMergeTree") ? "FINAL" : "";
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
else conditions.push(`NOT EXISTS (
|
|
2414
|
-
SELECT 1 FROM ${_mastra_core_storage.TABLE_SPANS} ${finalClause} c
|
|
2415
|
-
WHERE c.traceId = ${_mastra_core_storage.TABLE_SPANS}.traceId AND c.error IS NOT NULL AND c.error != ''
|
|
2416
|
-
)`);
|
|
2437
|
+
conditions.push(`traceId ${filters.hasChildError ? "IN" : "NOT IN"} (
|
|
2438
|
+
SELECT c.traceId FROM ${_mastra_core_storage.TABLE_SPANS} ${finalClause} c
|
|
2439
|
+
WHERE c.error IS NOT NULL AND c.error != ''
|
|
2440
|
+
)`);
|
|
2417
2441
|
}
|
|
2418
2442
|
}
|
|
2419
2443
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
@@ -2505,35 +2529,58 @@ var ObservabilityStorageClickhouse = class extends _mastra_core_storage.Observab
|
|
|
2505
2529
|
async batchUpdateSpans(args) {
|
|
2506
2530
|
try {
|
|
2507
2531
|
const now = Date.now();
|
|
2532
|
+
if (args.records.length === 0) return;
|
|
2533
|
+
const keyParams = {};
|
|
2534
|
+
const keyTuples = args.records.map((record, i) => {
|
|
2535
|
+
keyParams[`t_${i}`] = record.traceId;
|
|
2536
|
+
keyParams[`s_${i}`] = record.spanId;
|
|
2537
|
+
return `({t_${i}:String}, {s_${i}:String})`;
|
|
2538
|
+
});
|
|
2539
|
+
const existingResult = await this.client.query({
|
|
2540
|
+
query: `SELECT *, toDateTime64(createdAt, 3) as createdAt, toDateTime64(updatedAt, 3) as updatedAt
|
|
2541
|
+
FROM ${_mastra_core_storage.TABLE_SPANS}
|
|
2542
|
+
WHERE (traceId, spanId) IN (${keyTuples.join(", ")})
|
|
2543
|
+
-- Newest version per span, which is what ReplacingMergeTree(updatedAt)
|
|
2544
|
+
-- keeps. FINAL would merge every part the batch's keys touch, which
|
|
2545
|
+
-- costs far more than reading the unmerged versions and sorting them.
|
|
2546
|
+
ORDER BY updatedAt DESC
|
|
2547
|
+
LIMIT 1 BY traceId, spanId`,
|
|
2548
|
+
query_params: keyParams,
|
|
2549
|
+
clickhouse_settings: {
|
|
2550
|
+
date_time_input_format: "best_effort",
|
|
2551
|
+
date_time_output_format: "iso",
|
|
2552
|
+
use_client_time_zone: 1,
|
|
2553
|
+
output_format_json_quote_64bit_integers: 0
|
|
2554
|
+
}
|
|
2555
|
+
});
|
|
2556
|
+
const spanKey = (traceId, spanId) => `${traceId}\u0000${spanId}`;
|
|
2557
|
+
const current = /* @__PURE__ */ new Map();
|
|
2558
|
+
for (const row of transformRows((await existingResult.json()).data)) current.set(spanKey(row.traceId, row.spanId), row);
|
|
2508
2559
|
for (const record of args.records) {
|
|
2509
|
-
const
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2560
|
+
const key = spanKey(record.traceId, record.spanId);
|
|
2561
|
+
const existing = current.get(key);
|
|
2562
|
+
if (!existing) continue;
|
|
2563
|
+
const updates = { ...record.updates };
|
|
2564
|
+
if (updates.startedAt instanceof Date) updates.startedAt = updates.startedAt.getTime();
|
|
2565
|
+
if (updates.endedAt instanceof Date) updates.endedAt = updates.endedAt.getTime();
|
|
2566
|
+
current.set(key, {
|
|
2567
|
+
...existing,
|
|
2568
|
+
...updates,
|
|
2569
|
+
updatedAt: now,
|
|
2570
|
+
__updated: true
|
|
2515
2571
|
});
|
|
2516
|
-
if (existing) {
|
|
2517
|
-
const updates = { ...record.updates };
|
|
2518
|
-
if (updates.startedAt instanceof Date) updates.startedAt = updates.startedAt.getTime();
|
|
2519
|
-
if (updates.endedAt instanceof Date) updates.endedAt = updates.endedAt.getTime();
|
|
2520
|
-
const updated = {
|
|
2521
|
-
...existing,
|
|
2522
|
-
...updates,
|
|
2523
|
-
updatedAt: now
|
|
2524
|
-
};
|
|
2525
|
-
await this.client.insert({
|
|
2526
|
-
table: _mastra_core_storage.TABLE_SPANS,
|
|
2527
|
-
values: [updated],
|
|
2528
|
-
format: "JSONEachRow",
|
|
2529
|
-
clickhouse_settings: {
|
|
2530
|
-
date_time_input_format: "best_effort",
|
|
2531
|
-
use_client_time_zone: 1,
|
|
2532
|
-
output_format_json_quote_64bit_integers: 0
|
|
2533
|
-
}
|
|
2534
|
-
});
|
|
2535
|
-
}
|
|
2536
2572
|
}
|
|
2573
|
+
const updatedRows = Array.from(current.values()).filter((row) => row.__updated).map(({ __updated: _updated, ...row }) => row);
|
|
2574
|
+
if (updatedRows.length > 0) await this.client.insert({
|
|
2575
|
+
table: _mastra_core_storage.TABLE_SPANS,
|
|
2576
|
+
values: updatedRows,
|
|
2577
|
+
format: "JSONEachRow",
|
|
2578
|
+
clickhouse_settings: {
|
|
2579
|
+
date_time_input_format: "best_effort",
|
|
2580
|
+
use_client_time_zone: 1,
|
|
2581
|
+
output_format_json_quote_64bit_integers: 0
|
|
2582
|
+
}
|
|
2583
|
+
});
|
|
2537
2584
|
} catch (error) {
|
|
2538
2585
|
throw new _mastra_core_error.MastraError({
|
|
2539
2586
|
id: (0, _mastra_core_storage.createStorageErrorId)("CLICKHOUSE", "BATCH_UPDATE_SPANS", "FAILED"),
|
|
@@ -2762,7 +2809,12 @@ CREATE TABLE IF NOT EXISTS ${TABLE_TRACE_ROOTS} (
|
|
|
2762
2809
|
output Nullable(String),
|
|
2763
2810
|
error Nullable(String),
|
|
2764
2811
|
metadataRaw Nullable(String),
|
|
2765
|
-
requestContext Nullable(String)
|
|
2812
|
+
requestContext Nullable(String),
|
|
2813
|
+
|
|
2814
|
+
-- traceId is second in the sort key, behind startedAt, so point lookups by
|
|
2815
|
+
-- traceId (getRootSpan, trace deletes, trace-query root dedupe) need a skip
|
|
2816
|
+
-- index to avoid scanning every granule.
|
|
2817
|
+
INDEX idx_traceId traceId TYPE bloom_filter(0.01) GRANULARITY 1
|
|
2766
2818
|
)
|
|
2767
2819
|
ENGINE = ReplacingMergeTree
|
|
2768
2820
|
PARTITION BY toDate(endedAt)
|
|
@@ -2836,7 +2888,9 @@ CREATE TABLE IF NOT EXISTS ${TABLE_TRACE_BRANCHES} (
|
|
|
2836
2888
|
output Nullable(String),
|
|
2837
2889
|
error Nullable(String),
|
|
2838
2890
|
metadataRaw Nullable(String),
|
|
2839
|
-
requestContext Nullable(String)
|
|
2891
|
+
requestContext Nullable(String),
|
|
2892
|
+
|
|
2893
|
+
INDEX idx_traceId traceId TYPE bloom_filter(0.01) GRANULARITY 1
|
|
2840
2894
|
)
|
|
2841
2895
|
ENGINE = ReplacingMergeTree
|
|
2842
2896
|
PARTITION BY toDate(endedAt)
|
|
@@ -3065,7 +3119,19 @@ CREATE TABLE IF NOT EXISTS ${TABLE_LOG_EVENTS} (
|
|
|
3065
3119
|
-- Information-only JSON payloads
|
|
3066
3120
|
data Nullable(String),
|
|
3067
3121
|
metadata Nullable(String),
|
|
3068
|
-
scope Nullable(String)
|
|
3122
|
+
scope Nullable(String),
|
|
3123
|
+
|
|
3124
|
+
-- Same ID drilldown indexes as metric_events: these columns are outside
|
|
3125
|
+
-- the sort key, so without them every drilldown scans the time range.
|
|
3126
|
+
INDEX idx_traceId traceId TYPE bloom_filter(0.01) GRANULARITY 2,
|
|
3127
|
+
INDEX idx_threadId threadId TYPE bloom_filter(0.01) GRANULARITY 2,
|
|
3128
|
+
INDEX idx_resourceId resourceId TYPE bloom_filter(0.01) GRANULARITY 2,
|
|
3129
|
+
INDEX idx_userId userId TYPE bloom_filter(0.01) GRANULARITY 2,
|
|
3130
|
+
INDEX idx_organizationId organizationId TYPE bloom_filter(0.01) GRANULARITY 2,
|
|
3131
|
+
INDEX idx_experimentId experimentId TYPE bloom_filter(0.01) GRANULARITY 2,
|
|
3132
|
+
INDEX idx_runId runId TYPE bloom_filter(0.01) GRANULARITY 2,
|
|
3133
|
+
INDEX idx_sessionId sessionId TYPE bloom_filter(0.01) GRANULARITY 2,
|
|
3134
|
+
INDEX idx_requestId requestId TYPE bloom_filter(0.01) GRANULARITY 2
|
|
3069
3135
|
)
|
|
3070
3136
|
ENGINE = ReplacingMergeTree
|
|
3071
3137
|
PARTITION BY toDate(timestamp)
|
|
@@ -3194,7 +3260,8 @@ SETTINGS allow_nullable_key = 1
|
|
|
3194
3260
|
`;
|
|
3195
3261
|
const SCORE_EVENTS_CURRENT_DDL = `
|
|
3196
3262
|
CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS_CURRENT} (
|
|
3197
|
-
${SCORE_EVENT_COLUMNS_DDL}
|
|
3263
|
+
${SCORE_EVENT_COLUMNS_DDL},
|
|
3264
|
+
INDEX idx_traceId traceId TYPE bloom_filter(0.01) GRANULARITY 1
|
|
3198
3265
|
)
|
|
3199
3266
|
ENGINE = ReplacingMergeTree(writeVersion)
|
|
3200
3267
|
PARTITION BY cityHash64(scoreId) % 64
|
|
@@ -3322,7 +3389,11 @@ CREATE TABLE IF NOT EXISTS ${TABLE_FEEDBACK_EVENTS} (
|
|
|
3322
3389
|
|
|
3323
3390
|
-- Information-only JSON payloads
|
|
3324
3391
|
metadata Nullable(String),
|
|
3325
|
-
scope Nullable(String)
|
|
3392
|
+
scope Nullable(String),
|
|
3393
|
+
|
|
3394
|
+
-- feedbackId is last in the sort key; id lookups (write versions, review
|
|
3395
|
+
-- updates, deletes, trace-query dedupe) need a skip index.
|
|
3396
|
+
INDEX idx_feedbackId feedbackId TYPE bloom_filter(0.01) GRANULARITY 1
|
|
3326
3397
|
)
|
|
3327
3398
|
ENGINE = ReplacingMergeTree
|
|
3328
3399
|
PARTITION BY toDate(timestamp)
|
|
@@ -3568,11 +3639,15 @@ const ALL_MIGRATIONS = [
|
|
|
3568
3639
|
addColumn(TABLE_SCORE_EVENTS, "rootEntityVersionId", "Nullable(String)"),
|
|
3569
3640
|
addBloomIndex(TABLE_SCORE_EVENTS, "idx_scoreId", "scoreId", 1),
|
|
3570
3641
|
addBloomIndex(TABLE_SCORE_EVENTS_DELTA, "idx_scoreId", "scoreId", 1),
|
|
3642
|
+
addBloomIndex(TABLE_SCORE_EVENTS_CURRENT, "idx_traceId", "traceId", 1),
|
|
3571
3643
|
addColumn(TABLE_FEEDBACK_EVENTS, "writeVersion", "UInt64 DEFAULT 0"),
|
|
3572
3644
|
addColumn(TABLE_FEEDBACK_EVENTS, "entityVersionId", "Nullable(String)"),
|
|
3573
3645
|
addColumn(TABLE_FEEDBACK_EVENTS, "reviewStatus", "LowCardinality(String) DEFAULT 'needs-review'"),
|
|
3574
3646
|
addColumn(TABLE_FEEDBACK_EVENTS, "parentEntityVersionId", "Nullable(String)"),
|
|
3575
3647
|
addColumn(TABLE_FEEDBACK_EVENTS, "rootEntityVersionId", "Nullable(String)"),
|
|
3648
|
+
addBloomIndex(TABLE_FEEDBACK_EVENTS, "idx_feedbackId", "feedbackId", 1),
|
|
3649
|
+
addBloomIndex(TABLE_TRACE_ROOTS, "idx_traceId", "traceId", 1),
|
|
3650
|
+
addBloomIndex(TABLE_TRACE_BRANCHES, "idx_traceId", "traceId", 1),
|
|
3576
3651
|
addBloomIndex(TABLE_METRIC_EVENTS, "idx_traceId", "traceId"),
|
|
3577
3652
|
addBloomIndex(TABLE_METRIC_EVENTS, "idx_threadId", "threadId"),
|
|
3578
3653
|
addBloomIndex(TABLE_METRIC_EVENTS, "idx_resourceId", "resourceId"),
|
|
@@ -3582,6 +3657,17 @@ const ALL_MIGRATIONS = [
|
|
|
3582
3657
|
addBloomIndex(TABLE_METRIC_EVENTS, "idx_runId", "runId"),
|
|
3583
3658
|
addBloomIndex(TABLE_METRIC_EVENTS, "idx_sessionId", "sessionId"),
|
|
3584
3659
|
addBloomIndex(TABLE_METRIC_EVENTS, "idx_requestId", "requestId"),
|
|
3660
|
+
...[
|
|
3661
|
+
"traceId",
|
|
3662
|
+
"threadId",
|
|
3663
|
+
"resourceId",
|
|
3664
|
+
"userId",
|
|
3665
|
+
"organizationId",
|
|
3666
|
+
"experimentId",
|
|
3667
|
+
"runId",
|
|
3668
|
+
"sessionId",
|
|
3669
|
+
"requestId"
|
|
3670
|
+
].map((column) => addBloomIndex(TABLE_LOG_EVENTS, `idx_${column}`, column)),
|
|
3585
3671
|
addBloomIndex(TABLE_DELETION_REQUESTS, "idx_predicateValues", "predicateValues")
|
|
3586
3672
|
];
|
|
3587
3673
|
const ALL_TABLE_NAMES = [
|
|
@@ -4677,6 +4763,11 @@ function validateCursorId(cursor) {
|
|
|
4677
4763
|
text: "Invalid observability delta cursor"
|
|
4678
4764
|
});
|
|
4679
4765
|
}
|
|
4766
|
+
/** ANDs `conditions` onto a `WHERE ...` clause, or starts one when `whereClause` is empty. */
|
|
4767
|
+
function appendWhere(whereClause, ...conditions) {
|
|
4768
|
+
const parts = [whereClause.replace(/^\s*WHERE\s+/i, ""), ...conditions].filter(Boolean);
|
|
4769
|
+
return parts.length > 0 ? `WHERE ${parts.join(" AND ")}` : "";
|
|
4770
|
+
}
|
|
4680
4771
|
//#endregion
|
|
4681
4772
|
//#region src/storage/domains/observability/v-next/feedback.ts
|
|
4682
4773
|
const FEEDBACK_TYPED_COLUMNS = /* @__PURE__ */ new Set([
|
|
@@ -4783,12 +4874,21 @@ function resolveFeedbackGroupBy(groupBy) {
|
|
|
4783
4874
|
function toSeriesName$1(values) {
|
|
4784
4875
|
return values.map((v) => v == null ? "" : String(v)).join("|");
|
|
4785
4876
|
}
|
|
4786
|
-
|
|
4877
|
+
/**
|
|
4878
|
+
* Lets `FINAL` reads use skip indexes on older servers, where it defaults off.
|
|
4879
|
+
* Only for lookups on a column shared by every version of a sort key (so a
|
|
4880
|
+
* skipped granule can never hold a newer version of a matching row).
|
|
4881
|
+
*/
|
|
4882
|
+
const FINAL_SKIP_INDEX_SETTINGS = { use_skip_indexes_if_final: 1 };
|
|
4883
|
+
async function queryJson$2(client, query, params, settings = {}) {
|
|
4787
4884
|
return await (await client.query({
|
|
4788
4885
|
query,
|
|
4789
4886
|
query_params: params,
|
|
4790
4887
|
format: "JSONEachRow",
|
|
4791
|
-
clickhouse_settings:
|
|
4888
|
+
clickhouse_settings: {
|
|
4889
|
+
...CH_SETTINGS,
|
|
4890
|
+
...settings
|
|
4891
|
+
}
|
|
4792
4892
|
})).json();
|
|
4793
4893
|
}
|
|
4794
4894
|
async function feedbackRowsWithWriteVersions(client, feedbacks) {
|
|
@@ -4897,7 +4997,7 @@ async function hasFeedbackDeletionRequest(client, feedbackId, organizationId, re
|
|
|
4897
4997
|
feedbackId,
|
|
4898
4998
|
organizationId: organizationId ?? "",
|
|
4899
4999
|
resourceId: resourceId ?? ""
|
|
4900
|
-
})).length > 0;
|
|
5000
|
+
}, FINAL_SKIP_INDEX_SETTINGS)).length > 0;
|
|
4901
5001
|
}
|
|
4902
5002
|
/**
|
|
4903
5003
|
* Review updates insert a replacement row, so they need only `INSERT` and the
|
|
@@ -4918,7 +5018,7 @@ async function updateFeedbackReviewStatus(client, args, replication) {
|
|
|
4918
5018
|
const existingRow = (await queryJson$2(client, `SELECT * FROM ${TABLE_FEEDBACK_EVENTS} FINAL
|
|
4919
5019
|
WHERE feedbackId = {feedbackId:String}
|
|
4920
5020
|
ORDER BY writeVersion DESC, timestamp DESC
|
|
4921
|
-
LIMIT 1`, { feedbackId }))[0];
|
|
5021
|
+
LIMIT 1`, { feedbackId }, FINAL_SKIP_INDEX_SETTINGS))[0];
|
|
4922
5022
|
if (!existingRow) throw feedbackNotFoundError(feedbackId);
|
|
4923
5023
|
const { organizationId, resourceId } = existingRow;
|
|
4924
5024
|
const redelete = async () => {
|
|
@@ -4995,12 +5095,16 @@ async function queryFeedbackAfterCursor(client, whereClause, params, limit, curs
|
|
|
4995
5095
|
f.timestamp AS timestamp,
|
|
4996
5096
|
f.feedbackId AS feedbackId,
|
|
4997
5097
|
toString(d.cursorId) AS cursorId
|
|
4998
|
-
FROM ${
|
|
4999
|
-
INNER JOIN
|
|
5000
|
-
|
|
5098
|
+
FROM ${TABLE_FEEDBACK_EVENTS} f FINAL
|
|
5099
|
+
INNER JOIN (
|
|
5100
|
+
SELECT cursorId, traceId, timestamp, feedbackId
|
|
5101
|
+
FROM ${TABLE_FEEDBACK_EVENTS_DELTA}
|
|
5102
|
+
WHERE cursorId > {afterCursor:UInt64}
|
|
5103
|
+
) d
|
|
5104
|
+
ON isNotDistinctFrom(f.traceId, d.traceId)
|
|
5001
5105
|
AND f.timestamp = d.timestamp
|
|
5002
5106
|
AND f.feedbackId = d.feedbackId
|
|
5003
|
-
${whereClause
|
|
5107
|
+
${appendWhere(whereClause, `f.timestamp >= (SELECT min(timestamp) FROM ${TABLE_FEEDBACK_EVENTS_DELTA} WHERE cursorId > {afterCursor:UInt64})`, `(f.timestamp, f.feedbackId) IN (${`SELECT timestamp, feedbackId FROM ${TABLE_FEEDBACK_EVENTS_DELTA} WHERE cursorId > {afterCursor:UInt64}`})`)}
|
|
5004
5108
|
ORDER BY d.cursorId ASC
|
|
5005
5109
|
LIMIT {fetchLimit:UInt32}
|
|
5006
5110
|
`, {
|
|
@@ -5009,18 +5113,24 @@ async function queryFeedbackAfterCursor(client, whereClause, params, limit, curs
|
|
|
5009
5113
|
fetchLimit: limit + 1
|
|
5010
5114
|
});
|
|
5011
5115
|
}
|
|
5116
|
+
/**
|
|
5117
|
+
* Newest delta cursor whose feedback matches the filters. Without filters
|
|
5118
|
+
* this is the stream head; with filters, the feedback scan is bounded below by
|
|
5119
|
+
* the oldest `timestamp` still in the delta table.
|
|
5120
|
+
*/
|
|
5012
5121
|
async function getDeltaCursor$5(client, whereClause, params) {
|
|
5122
|
+
if (!whereClause) return getStreamHeadCursor$5(client);
|
|
5013
5123
|
const cursorId = (await queryJson$2(client, `
|
|
5014
5124
|
SELECT toString(max(d.cursorId)) AS cursorId
|
|
5015
5125
|
FROM mastra_feedback_events_delta d
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5126
|
+
WHERE (d.timestamp, d.feedbackId) IN (
|
|
5127
|
+
SELECT f.timestamp, f.feedbackId
|
|
5128
|
+
FROM mastra_feedback_events f FINAL
|
|
5129
|
+
${appendWhere(whereClause, `f.timestamp >= (SELECT min(timestamp) FROM mastra_feedback_events_delta)`)}
|
|
5130
|
+
)
|
|
5021
5131
|
`, params))[0]?.cursorId ?? null;
|
|
5022
5132
|
if (cursorId) return cursorId;
|
|
5023
|
-
return
|
|
5133
|
+
return getStreamHeadCursor$5(client);
|
|
5024
5134
|
}
|
|
5025
5135
|
async function getStreamHeadCursor$5(client) {
|
|
5026
5136
|
return (await queryJson$2(client, `SELECT toString(max(cursorId)) AS cursorId FROM mastra_feedback_events_delta`, {}))[0]?.cursorId ?? "0";
|
|
@@ -5142,26 +5252,22 @@ async function getFeedbackPercentiles(client, args) {
|
|
|
5142
5252
|
const combined = mergeFilters$2(buildFeedbackIdentityFilter(args), buildFeedbackFilterConditions(args.filters));
|
|
5143
5253
|
const whereClause = toWhereClause$2(combined);
|
|
5144
5254
|
if (!Array.isArray(args.percentiles) || args.percentiles.length === 0) throw new Error("Percentiles must include at least one value between 0 and 1.");
|
|
5145
|
-
const
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
}))
|
|
5162
|
-
});
|
|
5163
|
-
}
|
|
5164
|
-
return { series };
|
|
5255
|
+
for (const p of args.percentiles) if (!Number.isFinite(p) || p < 0 || p > 1) throw new Error(`Percentile value must be a finite number between 0 and 1, got ${p}`);
|
|
5256
|
+
const rows = await queryJson$2(client, `
|
|
5257
|
+
SELECT toStartOfInterval(timestamp, ${intervalSql}) AS bucket,
|
|
5258
|
+
quantiles(${args.percentiles.join(", ")})(valueNumber) AS pvalues
|
|
5259
|
+
FROM ${TABLE_FEEDBACK_EVENTS} FINAL
|
|
5260
|
+
${whereClause}
|
|
5261
|
+
GROUP BY bucket
|
|
5262
|
+
ORDER BY bucket
|
|
5263
|
+
`, combined.params);
|
|
5264
|
+
return { series: args.percentiles.map((percentile, index) => ({
|
|
5265
|
+
percentile,
|
|
5266
|
+
points: rows.map((row) => ({
|
|
5267
|
+
timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)),
|
|
5268
|
+
value: Number(row.pvalues?.[index] ?? 0)
|
|
5269
|
+
}))
|
|
5270
|
+
})) };
|
|
5165
5271
|
}
|
|
5166
5272
|
//#endregion
|
|
5167
5273
|
//#region src/storage/domains/observability/v-next/logs.ts
|
|
@@ -5240,6 +5346,7 @@ async function listLogs(client, args, strategy) {
|
|
|
5240
5346
|
};
|
|
5241
5347
|
}
|
|
5242
5348
|
async function queryLogsAfterCursor(client, whereClause, params, limit, cursorId) {
|
|
5349
|
+
const deltaKeys = `SELECT timestamp, logId FROM ${TABLE_LOG_EVENTS_DELTA} WHERE cursorId > {afterCursor:UInt64}`;
|
|
5243
5350
|
return await (await client.query({
|
|
5244
5351
|
query: `
|
|
5245
5352
|
SELECT
|
|
@@ -5247,11 +5354,15 @@ async function queryLogsAfterCursor(client, whereClause, params, limit, cursorId
|
|
|
5247
5354
|
l.timestamp AS timestamp,
|
|
5248
5355
|
l.logId AS logId,
|
|
5249
5356
|
toString(d.cursorId) AS cursorId
|
|
5250
|
-
FROM ${
|
|
5251
|
-
INNER JOIN
|
|
5357
|
+
FROM ${TABLE_LOG_EVENTS} l
|
|
5358
|
+
INNER JOIN (
|
|
5359
|
+
SELECT cursorId, timestamp, logId
|
|
5360
|
+
FROM ${TABLE_LOG_EVENTS_DELTA}
|
|
5361
|
+
WHERE cursorId > {afterCursor:UInt64}
|
|
5362
|
+
) d
|
|
5252
5363
|
ON l.timestamp = d.timestamp
|
|
5253
5364
|
AND l.logId = d.logId
|
|
5254
|
-
${whereClause
|
|
5365
|
+
${appendWhere(whereClause, `(l.timestamp, l.logId) IN (${deltaKeys})`)}
|
|
5255
5366
|
ORDER BY d.cursorId ASC
|
|
5256
5367
|
LIMIT {fetchLimit:UInt32}
|
|
5257
5368
|
`,
|
|
@@ -5264,26 +5375,29 @@ async function queryLogsAfterCursor(client, whereClause, params, limit, cursorId
|
|
|
5264
5375
|
clickhouse_settings: CH_SETTINGS
|
|
5265
5376
|
})).json();
|
|
5266
5377
|
}
|
|
5378
|
+
/**
|
|
5379
|
+
* Newest delta cursor whose log matches the filters. Without filters this is
|
|
5380
|
+
* the stream head; with filters, the log scan is bounded below by the oldest
|
|
5381
|
+
* `timestamp` still in the delta table.
|
|
5382
|
+
*/
|
|
5267
5383
|
async function getDeltaCursor$4(client, whereClause, params) {
|
|
5384
|
+
if (!whereClause) return getStreamHeadCursor$4(client);
|
|
5268
5385
|
const cursorId = (await (await client.query({
|
|
5269
5386
|
query: `
|
|
5270
5387
|
SELECT toString(max(d.cursorId)) AS cursorId
|
|
5271
5388
|
FROM mastra_log_events_delta d
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5389
|
+
WHERE (d.timestamp, d.logId) IN (
|
|
5390
|
+
SELECT l.timestamp, l.logId
|
|
5391
|
+
FROM mastra_log_events l
|
|
5392
|
+
${appendWhere(whereClause, `l.timestamp >= (SELECT min(timestamp) FROM mastra_log_events_delta)`)}
|
|
5393
|
+
)
|
|
5276
5394
|
`,
|
|
5277
5395
|
query_params: params,
|
|
5278
5396
|
format: "JSONEachRow",
|
|
5279
5397
|
clickhouse_settings: CH_SETTINGS
|
|
5280
5398
|
})).json())[0]?.cursorId ?? null;
|
|
5281
5399
|
if (cursorId) return cursorId;
|
|
5282
|
-
return (
|
|
5283
|
-
query: `SELECT toString(max(cursorId)) AS cursorId FROM mastra_log_events_delta`,
|
|
5284
|
-
format: "JSONEachRow",
|
|
5285
|
-
clickhouse_settings: CH_SETTINGS
|
|
5286
|
-
})).json())[0]?.cursorId ?? "0";
|
|
5400
|
+
return getStreamHeadCursor$4(client);
|
|
5287
5401
|
}
|
|
5288
5402
|
async function getStreamHeadCursor$4(client) {
|
|
5289
5403
|
return (await (await client.query({
|
|
@@ -5509,12 +5623,16 @@ async function queryMetricsAfterCursor(client, whereClause, params, limit, curso
|
|
|
5509
5623
|
m.timestamp AS timestamp,
|
|
5510
5624
|
m.metricId AS metricId,
|
|
5511
5625
|
toString(d.cursorId) AS cursorId
|
|
5512
|
-
FROM ${
|
|
5513
|
-
INNER JOIN
|
|
5626
|
+
FROM ${TABLE_METRIC_EVENTS} m
|
|
5627
|
+
INNER JOIN (
|
|
5628
|
+
SELECT cursorId, name, timestamp, metricId
|
|
5629
|
+
FROM ${TABLE_METRIC_EVENTS_DELTA}
|
|
5630
|
+
WHERE cursorId > {afterCursor:UInt64}
|
|
5631
|
+
) d
|
|
5514
5632
|
ON m.name = d.name
|
|
5515
5633
|
AND m.timestamp = d.timestamp
|
|
5516
5634
|
AND m.metricId = d.metricId
|
|
5517
|
-
${whereClause
|
|
5635
|
+
${appendWhere(whereClause, `(m.name, m.timestamp, m.metricId) IN (${`SELECT name, timestamp, metricId FROM ${TABLE_METRIC_EVENTS_DELTA} WHERE cursorId > {afterCursor:UInt64}`})`)}
|
|
5518
5636
|
ORDER BY d.cursorId ASC
|
|
5519
5637
|
LIMIT {fetchLimit:UInt32}
|
|
5520
5638
|
`, {
|
|
@@ -5523,18 +5641,24 @@ async function queryMetricsAfterCursor(client, whereClause, params, limit, curso
|
|
|
5523
5641
|
fetchLimit: limit + 1
|
|
5524
5642
|
});
|
|
5525
5643
|
}
|
|
5644
|
+
/**
|
|
5645
|
+
* Newest delta cursor whose metric matches the filters. Without filters this
|
|
5646
|
+
* is the stream head; with filters, the metric scan is bounded below by the
|
|
5647
|
+
* oldest `timestamp` still in the delta table.
|
|
5648
|
+
*/
|
|
5526
5649
|
async function getDeltaCursor$3(client, whereClause, params) {
|
|
5650
|
+
if (!whereClause) return getStreamHeadCursor$3(client);
|
|
5527
5651
|
const cursorId = (await queryJson$1(client, `
|
|
5528
5652
|
SELECT toString(max(d.cursorId)) AS cursorId
|
|
5529
5653
|
FROM mastra_metric_events_delta d
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5654
|
+
WHERE (d.name, d.timestamp, d.metricId) IN (
|
|
5655
|
+
SELECT m.name, m.timestamp, m.metricId
|
|
5656
|
+
FROM mastra_metric_events m
|
|
5657
|
+
${appendWhere(whereClause, `m.timestamp >= (SELECT min(timestamp) FROM mastra_metric_events_delta)`)}
|
|
5658
|
+
)
|
|
5535
5659
|
`, params))[0]?.cursorId ?? null;
|
|
5536
5660
|
if (cursorId) return cursorId;
|
|
5537
|
-
return
|
|
5661
|
+
return getStreamHeadCursor$3(client);
|
|
5538
5662
|
}
|
|
5539
5663
|
async function getStreamHeadCursor$3(client) {
|
|
5540
5664
|
return (await queryJson$1(client, `SELECT toString(max(cursorId)) AS cursorId FROM mastra_metric_events_delta`, {}))[0]?.cursorId ?? "0";
|
|
@@ -5733,29 +5857,26 @@ async function getMetricPercentiles(client, args) {
|
|
|
5733
5857
|
params: { percentileName: args.name }
|
|
5734
5858
|
}, signalFilter);
|
|
5735
5859
|
const whereClause = toWhereClause$1(combined);
|
|
5736
|
-
const
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
});
|
|
5754
|
-
}
|
|
5755
|
-
return { series };
|
|
5860
|
+
for (const p of args.percentiles) if (!Number.isFinite(p) || p < 0 || p > 1) throw new Error(`Percentile value must be a finite number between 0 and 1, got ${p}`);
|
|
5861
|
+
if (args.percentiles.length === 0) return { series: [] };
|
|
5862
|
+
const rows = await queryJson$1(client, `
|
|
5863
|
+
SELECT toStartOfInterval(timestamp, ${intervalSql}) AS bucket,
|
|
5864
|
+
quantiles(${args.percentiles.join(", ")})(value) AS pvalues
|
|
5865
|
+
FROM ${TABLE_METRIC_EVENTS}
|
|
5866
|
+
${whereClause}
|
|
5867
|
+
GROUP BY bucket
|
|
5868
|
+
ORDER BY bucket
|
|
5869
|
+
`, combined.params);
|
|
5870
|
+
return { series: args.percentiles.map((percentile, index) => ({
|
|
5871
|
+
percentile,
|
|
5872
|
+
points: rows.map((row) => ({
|
|
5873
|
+
timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)),
|
|
5874
|
+
value: Number(row.pvalues?.[index] ?? 0)
|
|
5875
|
+
}))
|
|
5876
|
+
})) };
|
|
5756
5877
|
}
|
|
5757
5878
|
async function getMetricNames(client, args) {
|
|
5758
|
-
const conditions = [`kind = 'metricName'`];
|
|
5879
|
+
const conditions = [`kind = 'metricName'`, `key1 = ''`];
|
|
5759
5880
|
const params = {};
|
|
5760
5881
|
if (args.prefix) {
|
|
5761
5882
|
conditions.push(`value LIKE {namePrefix:String}`);
|
|
@@ -6216,6 +6337,22 @@ function currentScoresRelation(sourcePredicate) {
|
|
|
6216
6337
|
${sourcePredicate ? `WHERE ${sourcePredicate}` : ""}
|
|
6217
6338
|
)`;
|
|
6218
6339
|
}
|
|
6340
|
+
/**
|
|
6341
|
+
* Current-state scores narrowed before FINAL merges. `score_events_current` is
|
|
6342
|
+
* keyed by scoreId, so a traceId filter only applies after FINAL has merged
|
|
6343
|
+
* the whole table. When the (unaliased) filter pins traceId, the matching
|
|
6344
|
+
* scoreIds are first read from the history table, whose sort key starts with
|
|
6345
|
+
* traceId. Every current row was written to history, so the candidate set is a
|
|
6346
|
+
* superset; callers still apply the same filter to the current version.
|
|
6347
|
+
*
|
|
6348
|
+
* Time ranges alone are not narrowed this way: scoreIds are hash-distributed,
|
|
6349
|
+
* so the ids from any real time window touch nearly every granule of the
|
|
6350
|
+
* current table and the extra history scan costs more than it saves.
|
|
6351
|
+
*/
|
|
6352
|
+
function prunedCurrentScoresRelation(historyFilter) {
|
|
6353
|
+
if (!historyFilter.conditions.some((condition) => condition.startsWith("traceId "))) return currentScoresRelation();
|
|
6354
|
+
return currentScoresRelation(`scoreId IN (SELECT scoreId FROM ${TABLE_SCORE_EVENTS} WHERE ${historyFilter.conditions.join(" AND ")})`);
|
|
6355
|
+
}
|
|
6219
6356
|
function buildScoreIdentityFilter(args) {
|
|
6220
6357
|
const conditions = ["scorerId = {olapScorerId:String}"];
|
|
6221
6358
|
const params = { olapScorerId: args.scorerId };
|
|
@@ -6360,7 +6497,7 @@ async function listScores(client, args, strategy) {
|
|
|
6360
6497
|
};
|
|
6361
6498
|
}
|
|
6362
6499
|
const currentDeltaCursor = deltaCursorEnabled ? await getDeltaCursor$2(client, whereClause, filter.params) : void 0;
|
|
6363
|
-
const scoresRelation =
|
|
6500
|
+
const scoresRelation = prunedCurrentScoresRelation(buildScoresFilterConditions(parsed.filters));
|
|
6364
6501
|
const countResult = await queryJson(client, `SELECT count() AS total FROM ${scoresRelation} AS s ${whereClause}`, filter.params);
|
|
6365
6502
|
const rows = await queryJson(client, `SELECT * FROM ${scoresRelation} AS s ${whereClause} ORDER BY ${orderBy} LIMIT {limit:UInt32} OFFSET {offset:UInt32}`, {
|
|
6366
6503
|
...filter.params,
|
|
@@ -6398,15 +6535,19 @@ async function queryScoresAfterCursor(client, whereClause, params, limit, cursor
|
|
|
6398
6535
|
s.timestamp AS timestamp,
|
|
6399
6536
|
s.scoreId AS scoreId,
|
|
6400
6537
|
toString(d.cursorId) AS cursorId
|
|
6401
|
-
FROM ${
|
|
6402
|
-
INNER JOIN
|
|
6538
|
+
FROM ${TABLE_SCORE_EVENTS_CURRENT} s FINAL
|
|
6539
|
+
INNER JOIN (
|
|
6540
|
+
SELECT cursorId, scoreId
|
|
6541
|
+
FROM ${TABLE_SCORE_EVENTS_DELTA}
|
|
6542
|
+
WHERE cursorId > {afterCursor:UInt64}
|
|
6543
|
+
AND scoreId NOT IN (
|
|
6544
|
+
SELECT scoreId FROM ${TABLE_SCORE_EVENTS_DELTA}
|
|
6545
|
+
WHERE cursorId <= {afterCursor:UInt64}
|
|
6546
|
+
AND scoreId IN (SELECT scoreId FROM ${TABLE_SCORE_EVENTS_DELTA} WHERE cursorId > {afterCursor:UInt64})
|
|
6547
|
+
)
|
|
6548
|
+
) d
|
|
6403
6549
|
ON s.scoreId = d.scoreId
|
|
6404
|
-
${whereClause
|
|
6405
|
-
AND d.scoreId NOT IN (
|
|
6406
|
-
SELECT scoreId FROM ${TABLE_SCORE_EVENTS_DELTA}
|
|
6407
|
-
WHERE cursorId <= {afterCursor:UInt64}
|
|
6408
|
-
AND scoreId IN (SELECT scoreId FROM ${TABLE_SCORE_EVENTS_DELTA} WHERE cursorId > {afterCursor:UInt64})
|
|
6409
|
-
)
|
|
6550
|
+
${appendWhere(whereClause, `s.scoreId IN (SELECT scoreId FROM ${TABLE_SCORE_EVENTS_DELTA} WHERE cursorId > {afterCursor:UInt64})`)}
|
|
6410
6551
|
ORDER BY d.cursorId ASC
|
|
6411
6552
|
LIMIT 1 BY s.scoreId
|
|
6412
6553
|
LIMIT {fetchLimit:UInt32}
|
|
@@ -6416,16 +6557,24 @@ async function queryScoresAfterCursor(client, whereClause, params, limit, cursor
|
|
|
6416
6557
|
fetchLimit: limit + 1
|
|
6417
6558
|
});
|
|
6418
6559
|
}
|
|
6560
|
+
/**
|
|
6561
|
+
* Newest delta cursor whose current score matches the filters. Without
|
|
6562
|
+
* filters this is the stream head; with filters, FINAL only merges the
|
|
6563
|
+
* scoreIds still in the delta table.
|
|
6564
|
+
*/
|
|
6419
6565
|
async function getDeltaCursor$2(client, whereClause, params) {
|
|
6566
|
+
if (!whereClause) return getStreamHeadCursor$2(client);
|
|
6420
6567
|
const cursorId = (await queryJson(client, `
|
|
6421
6568
|
SELECT toString(max(d.cursorId)) AS cursorId
|
|
6422
6569
|
FROM mastra_score_events_delta d
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6570
|
+
WHERE d.scoreId IN (
|
|
6571
|
+
SELECT s.scoreId
|
|
6572
|
+
FROM mastra_score_events_current s FINAL
|
|
6573
|
+
${appendWhere(whereClause, `s.scoreId IN (SELECT scoreId FROM mastra_score_events_delta)`)}
|
|
6574
|
+
)
|
|
6426
6575
|
`, params))[0]?.cursorId ?? null;
|
|
6427
6576
|
if (cursorId) return cursorId;
|
|
6428
|
-
return (
|
|
6577
|
+
return getStreamHeadCursor$2(client);
|
|
6429
6578
|
}
|
|
6430
6579
|
async function getStreamHeadCursor$2(client) {
|
|
6431
6580
|
return (await queryJson(client, `SELECT toString(max(cursorId)) AS cursorId FROM mastra_score_events_delta`, {}))[0]?.cursorId ?? "0";
|
|
@@ -6442,7 +6591,7 @@ async function getScoreAggregate(client, args) {
|
|
|
6442
6591
|
const identity = buildScoreIdentityFilter(args);
|
|
6443
6592
|
const combined = mergeFilters(identity, buildScoresFilterConditions(args.filters));
|
|
6444
6593
|
const whereClause = toWhereClause(combined);
|
|
6445
|
-
const result = await queryJson(client, `SELECT ${aggSql} AS value FROM ${
|
|
6594
|
+
const result = await queryJson(client, `SELECT ${aggSql} AS value FROM ${prunedCurrentScoresRelation(combined)} ${whereClause}`, combined.params);
|
|
6446
6595
|
const value = result[0]?.value == null ? null : Number(result[0]?.value);
|
|
6447
6596
|
if (args.comparePeriod && args.filters?.timestamp) {
|
|
6448
6597
|
const ts = args.filters.timestamp;
|
|
@@ -6477,7 +6626,7 @@ async function getScoreAggregate(client, args) {
|
|
|
6477
6626
|
}
|
|
6478
6627
|
}));
|
|
6479
6628
|
const prevWhereClause = toWhereClause(prevCombined);
|
|
6480
|
-
const prevResult = await queryJson(client, `SELECT ${aggSql} AS value FROM ${
|
|
6629
|
+
const prevResult = await queryJson(client, `SELECT ${aggSql} AS value FROM ${prunedCurrentScoresRelation(prevCombined)} ${prevWhereClause}`, prevCombined.params);
|
|
6481
6630
|
const previousValue = prevResult[0]?.value == null ? null : Number(prevResult[0]?.value);
|
|
6482
6631
|
let changePercent = null;
|
|
6483
6632
|
if (previousValue !== null && previousValue !== 0 && value !== null) changePercent = (value - previousValue) / Math.abs(previousValue) * 100;
|
|
@@ -6495,7 +6644,7 @@ async function getScoreBreakdown(client, args) {
|
|
|
6495
6644
|
const combined = mergeFilters(buildScoreIdentityFilter(args), buildScoresFilterConditions(args.filters));
|
|
6496
6645
|
const whereClause = toWhereClause(combined);
|
|
6497
6646
|
const resolved = resolveScoreGroupBy(args.groupBy);
|
|
6498
|
-
return { groups: (await queryJson(client, `SELECT ${resolved.map((e) => e.selectSql).join(", ")}, ${aggSql} AS value FROM ${
|
|
6647
|
+
return { groups: (await queryJson(client, `SELECT ${resolved.map((e) => e.selectSql).join(", ")}, ${aggSql} AS value FROM ${prunedCurrentScoresRelation(combined)} ${whereClause} GROUP BY ${resolved.map((e) => e.groupSql).join(", ")} ORDER BY value DESC`, combined.params)).map((row) => ({
|
|
6499
6648
|
dimensions: Object.fromEntries(resolved.map((entry, index) => {
|
|
6500
6649
|
const v = row[`group_by_${index}`];
|
|
6501
6650
|
return [entry.key, v == null ? null : String(v)];
|
|
@@ -6514,7 +6663,7 @@ async function getScoreTimeSeries(client, args) {
|
|
|
6514
6663
|
SELECT toStartOfInterval(timestamp, ${intervalSql}) AS bucket,
|
|
6515
6664
|
${resolved.map((e) => e.selectSql).join(", ")},
|
|
6516
6665
|
${aggSql} AS value
|
|
6517
|
-
FROM ${
|
|
6666
|
+
FROM ${prunedCurrentScoresRelation(combined)} ${whereClause}
|
|
6518
6667
|
GROUP BY bucket, ${resolved.map((e) => e.groupSql).join(", ")}
|
|
6519
6668
|
ORDER BY bucket
|
|
6520
6669
|
`, combined.params);
|
|
@@ -6536,7 +6685,7 @@ async function getScoreTimeSeries(client, args) {
|
|
|
6536
6685
|
const rows = await queryJson(client, `
|
|
6537
6686
|
SELECT toStartOfInterval(timestamp, ${intervalSql}) AS bucket,
|
|
6538
6687
|
${aggSql} AS value
|
|
6539
|
-
FROM ${
|
|
6688
|
+
FROM ${prunedCurrentScoresRelation(combined)} ${whereClause}
|
|
6540
6689
|
GROUP BY bucket
|
|
6541
6690
|
ORDER BY bucket
|
|
6542
6691
|
`, combined.params);
|
|
@@ -6553,26 +6702,22 @@ async function getScorePercentiles(client, args) {
|
|
|
6553
6702
|
const combined = mergeFilters(buildScoreIdentityFilter(args), buildScoresFilterConditions(args.filters));
|
|
6554
6703
|
const whereClause = toWhereClause(combined);
|
|
6555
6704
|
if (!Array.isArray(args.percentiles) || args.percentiles.length === 0) throw new Error("Percentiles must include at least one value between 0 and 1.");
|
|
6556
|
-
const
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
}))
|
|
6573
|
-
});
|
|
6574
|
-
}
|
|
6575
|
-
return { series };
|
|
6705
|
+
for (const p of args.percentiles) if (!Number.isFinite(p) || p < 0 || p > 1) throw new Error(`Percentile value must be a finite number between 0 and 1, got ${p}`);
|
|
6706
|
+
const rows = await queryJson(client, `
|
|
6707
|
+
SELECT toStartOfInterval(timestamp, ${intervalSql}) AS bucket,
|
|
6708
|
+
quantiles(${args.percentiles.join(", ")})(score) AS pvalues
|
|
6709
|
+
FROM ${prunedCurrentScoresRelation(combined)}
|
|
6710
|
+
${whereClause}
|
|
6711
|
+
GROUP BY bucket
|
|
6712
|
+
ORDER BY bucket
|
|
6713
|
+
`, combined.params);
|
|
6714
|
+
return { series: args.percentiles.map((percentile, index) => ({
|
|
6715
|
+
percentile,
|
|
6716
|
+
points: rows.map((row) => ({
|
|
6717
|
+
timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)),
|
|
6718
|
+
value: Number(row.pvalues?.[index] ?? 0)
|
|
6719
|
+
}))
|
|
6720
|
+
})) };
|
|
6576
6721
|
}
|
|
6577
6722
|
//#endregion
|
|
6578
6723
|
//#region src/storage/domains/observability/v-next/trace-query.ts
|
|
@@ -6816,6 +6961,22 @@ const TRACE_SELECT = `
|
|
|
6816
6961
|
r.entityType AS entityType,
|
|
6817
6962
|
r.environment AS environment,
|
|
6818
6963
|
${TRACE_STATUS_SQL} AS status`;
|
|
6964
|
+
/** Candidate columns carried through the page-mode window sort (no payload blobs). */
|
|
6965
|
+
const TRACE_PAGE_COLUMNS = [
|
|
6966
|
+
"traceId",
|
|
6967
|
+
"rootSpanId",
|
|
6968
|
+
"name",
|
|
6969
|
+
"entityId",
|
|
6970
|
+
"parentSpanId",
|
|
6971
|
+
"threadId",
|
|
6972
|
+
"resourceId",
|
|
6973
|
+
"startedAt",
|
|
6974
|
+
"endedAt",
|
|
6975
|
+
"entityName",
|
|
6976
|
+
"entityType",
|
|
6977
|
+
"environment",
|
|
6978
|
+
"status"
|
|
6979
|
+
];
|
|
6819
6980
|
var ParameterBuilder = class {
|
|
6820
6981
|
params = {};
|
|
6821
6982
|
#next = 1;
|
|
@@ -6902,13 +7063,12 @@ function collectThreadRelationCollections(predicate, collections) {
|
|
|
6902
7063
|
}
|
|
6903
7064
|
function compilePredicate(predicate, parameters) {
|
|
6904
7065
|
if (predicate.type === "relation") {
|
|
6905
|
-
const
|
|
6906
|
-
SELECT
|
|
7066
|
+
const matching = `(
|
|
7067
|
+
SELECT s.traceId FROM ${predicate.collection === "spans" ? "current_spans" : predicate.collection === "scores" ? "current_scores" : "current_feedback"} s
|
|
6907
7068
|
WHERE isNotNull(s.traceId)
|
|
6908
|
-
AND s.traceId = r.traceId
|
|
6909
7069
|
AND (${predicate.collection === "feedback" ? compileFeedbackScalarPredicate(predicate.predicate, parameters) : compileScalarPredicate(predicate.predicate, predicate.collection === "spans" ? SPAN_FIELDS : SCORE_FIELDS, parameters)})
|
|
6910
7070
|
)`;
|
|
6911
|
-
return predicate.quantifier === "some" ?
|
|
7071
|
+
return `r.traceId ${predicate.quantifier === "some" ? "IN" : "NOT IN"} ${matching}`;
|
|
6912
7072
|
}
|
|
6913
7073
|
if (predicate.type === "boolean") return predicate.args.map((arg) => `(${compilePredicate(arg, parameters)})`).join(predicate.operator === "and" ? " AND " : " OR ");
|
|
6914
7074
|
if (predicate.type === "not") return `NOT (${compilePredicate(predicate.arg, parameters)})`;
|
|
@@ -6916,12 +7076,12 @@ function compilePredicate(predicate, parameters) {
|
|
|
6916
7076
|
}
|
|
6917
7077
|
function compileThreadPredicate(predicate, parameters) {
|
|
6918
7078
|
if (predicate.type === "relation") {
|
|
6919
|
-
const
|
|
6920
|
-
SELECT
|
|
6921
|
-
WHERE r.threadId
|
|
7079
|
+
const matching = `(
|
|
7080
|
+
SELECT r.threadId FROM eligible_roots r
|
|
7081
|
+
WHERE isNotNull(r.threadId)
|
|
6922
7082
|
AND (${compilePredicate(predicate.predicate, parameters)})
|
|
6923
7083
|
)`;
|
|
6924
|
-
return predicate.quantifier === "some" ?
|
|
7084
|
+
return `t.threadId ${predicate.quantifier === "some" ? "IN" : "NOT IN"} ${matching}`;
|
|
6925
7085
|
}
|
|
6926
7086
|
if (predicate.type === "boolean") return predicate.args.map((arg) => `(${compileThreadPredicate(arg, parameters)})`).join(predicate.operator === "and" ? " AND " : " OR ");
|
|
6927
7087
|
return `NOT (${compileThreadPredicate(predicate.arg, parameters)})`;
|
|
@@ -7022,6 +7182,15 @@ function compileClickHouseTraceScope(selection, relationCollections, parameters,
|
|
|
7022
7182
|
FROM (
|
|
7023
7183
|
SELECT *
|
|
7024
7184
|
FROM ${TABLE_FEEDBACK_EVENTS} FINAL
|
|
7185
|
+
-- ClickHouse cannot push the scope filter through LIMIT 1 BY, so keep
|
|
7186
|
+
-- every version of each feedbackId that ever pointed into the scope
|
|
7187
|
+
-- (traceId leads the sort key). A rewrite may move a feedback to another
|
|
7188
|
+
-- trace, so the current version is still filtered below.
|
|
7189
|
+
WHERE feedbackId IN (
|
|
7190
|
+
SELECT feedbackId
|
|
7191
|
+
FROM ${TABLE_FEEDBACK_EVENTS}
|
|
7192
|
+
WHERE traceId IN (SELECT traceId FROM root_scope)
|
|
7193
|
+
)
|
|
7025
7194
|
ORDER BY feedbackId, writeVersion DESC, timestamp DESC
|
|
7026
7195
|
LIMIT 1 BY feedbackId
|
|
7027
7196
|
) AS current
|
|
@@ -7076,11 +7245,12 @@ LIMIT ${parameters.add(plan.limit + 1, "UInt64")}`,
|
|
|
7076
7245
|
cursorId: "0",
|
|
7077
7246
|
traceId: ""
|
|
7078
7247
|
};
|
|
7248
|
+
const lowerCursor = parameters.add(after.cursorId, "UInt64");
|
|
7079
7249
|
return {
|
|
7080
7250
|
query: `${candidates}, delta_candidates AS (
|
|
7081
7251
|
SELECT traceId, max(cursorId) AS latestCursorId
|
|
7082
7252
|
FROM ${TABLE_TRACE_ROOTS_DELTA}
|
|
7083
|
-
WHERE tuple(cursorId, traceId) > ${`tuple(${
|
|
7253
|
+
WHERE cursorId >= ${lowerCursor} AND tuple(cursorId, traceId) > ${`tuple(${lowerCursor}, ${parameters.add(after.traceId, "String")})`} ${deltaHead ? `AND tuple(cursorId, traceId) <= tuple(${parameters.add(deltaHead.cursorId, "UInt64")}, ${parameters.add(deltaHead.traceId, "String")})` : ""}
|
|
7084
7254
|
GROUP BY traceId
|
|
7085
7255
|
)
|
|
7086
7256
|
SELECT c.*, toString(d.latestCursorId) AS __delta_cursor
|
|
@@ -7094,46 +7264,25 @@ LIMIT ${parameters.add(plan.limit + 1, "UInt64")}`,
|
|
|
7094
7264
|
}
|
|
7095
7265
|
const orderField = resolveOrderField(plan.orderBy.field);
|
|
7096
7266
|
const direction = plan.orderBy.direction === "asc" ? "ASC" : "DESC";
|
|
7097
|
-
if (plan.paginationMode === "page")
|
|
7098
|
-
|
|
7267
|
+
if (plan.paginationMode === "page") {
|
|
7268
|
+
const onPage = `__row_position > ${parameters.add(plan.page * plan.perPage, "UInt64")} AND __row_position <= ${parameters.add((plan.page + 1) * plan.perPage, "UInt64")}`;
|
|
7269
|
+
return {
|
|
7270
|
+
query: `${candidates},
|
|
7099
7271
|
page_rows AS (
|
|
7100
|
-
SELECT
|
|
7101
|
-
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
),
|
|
7105
|
-
page_total AS (
|
|
7106
|
-
SELECT count() AS total
|
|
7272
|
+
SELECT
|
|
7273
|
+
${TRACE_PAGE_COLUMNS.join(",\n ")},
|
|
7274
|
+
row_number() OVER (ORDER BY ${orderField} ${direction}, traceId ASC) AS __row_position,
|
|
7275
|
+
count() OVER () AS total
|
|
7107
7276
|
FROM candidates
|
|
7108
7277
|
)
|
|
7109
|
-
SELECT
|
|
7278
|
+
SELECT *, if(${onPage}, 0, 1) AS __metadata
|
|
7110
7279
|
FROM page_rows
|
|
7111
|
-
|
|
7112
|
-
UNION ALL
|
|
7113
|
-
SELECT
|
|
7114
|
-
'' AS traceId,
|
|
7115
|
-
'' AS rootSpanId,
|
|
7116
|
-
'' AS name,
|
|
7117
|
-
CAST(NULL, 'Nullable(String)') AS entityId,
|
|
7118
|
-
CAST(NULL, 'Nullable(String)') AS parentSpanId,
|
|
7119
|
-
CAST(NULL, 'Nullable(String)') AS metadata,
|
|
7120
|
-
CAST(NULL, 'Nullable(String)') AS input,
|
|
7121
|
-
CAST(NULL, 'Nullable(String)') AS threadId,
|
|
7122
|
-
CAST(NULL, 'Nullable(String)') AS resourceId,
|
|
7123
|
-
toDateTime64(0, 3, 'UTC') AS startedAt,
|
|
7124
|
-
toDateTime64(0, 3, 'UTC') AS endedAt,
|
|
7125
|
-
CAST(NULL, 'Nullable(String)') AS entityName,
|
|
7126
|
-
CAST(NULL, 'Nullable(String)') AS entityType,
|
|
7127
|
-
CAST(NULL, 'Nullable(String)') AS environment,
|
|
7128
|
-
'' AS status,
|
|
7129
|
-
0 AS __row_position,
|
|
7130
|
-
page_total.total AS total,
|
|
7131
|
-
1 AS __metadata
|
|
7132
|
-
FROM page_total
|
|
7280
|
+
WHERE (${onPage}) OR __row_position = 1
|
|
7133
7281
|
ORDER BY __metadata ASC, __row_position ASC`,
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7282
|
+
query_params: parameters.params,
|
|
7283
|
+
sharedSnapshot: true
|
|
7284
|
+
};
|
|
7285
|
+
}
|
|
7137
7286
|
let pageCondition = "";
|
|
7138
7287
|
if (plan.cursor) {
|
|
7139
7288
|
const comparison = plan.orderBy.direction === "asc" ? ">" : "<";
|
|
@@ -7151,6 +7300,23 @@ LIMIT ${limit}`,
|
|
|
7151
7300
|
query_params: parameters.params
|
|
7152
7301
|
};
|
|
7153
7302
|
}
|
|
7303
|
+
/**
|
|
7304
|
+
* Fetches the metadata/input payloads for page-mode rows. Looks rows up by the
|
|
7305
|
+
* trace_roots sort-key prefix `(startedAt, traceId)`, so only the page's
|
|
7306
|
+
* granules are read. A root can have unmerged versions in different `endedAt`
|
|
7307
|
+
* partitions, so `endedAt` is part of the key: the payload comes from the same
|
|
7308
|
+
* version as the candidate row.
|
|
7309
|
+
*/
|
|
7310
|
+
function compileClickHouseTraceRootPayloads(keys) {
|
|
7311
|
+
const parameters = new ParameterBuilder();
|
|
7312
|
+
return {
|
|
7313
|
+
query: `SELECT traceId, spanId AS rootSpanId, metadataRaw AS metadata, input
|
|
7314
|
+
FROM ${TABLE_TRACE_ROOTS}
|
|
7315
|
+
WHERE (startedAt, traceId, spanId, endedAt) IN (${keys.map((key) => `(${parameters.add(key.startedAt, "DateTime64(3, 'UTC')")}, ${parameters.add(key.traceId, "String")}, ${parameters.add(key.rootSpanId, "String")}, ${parameters.add(key.endedAt, "DateTime64(3, 'UTC')")})`).join(", ")})
|
|
7316
|
+
LIMIT 1 BY traceId, spanId`,
|
|
7317
|
+
query_params: parameters.params
|
|
7318
|
+
};
|
|
7319
|
+
}
|
|
7154
7320
|
function compileClickHouseThreadQuery(plan) {
|
|
7155
7321
|
const parameters = new ParameterBuilder();
|
|
7156
7322
|
const relationCollections = collectRelationCollections(plan.traces.where);
|
|
@@ -7344,7 +7510,21 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
|
|
|
7344
7510
|
if (plan.paginationMode === "page") {
|
|
7345
7511
|
const rows = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs: deltaHead ? remaining() : timeoutMs }, compileClickHouseTraceQuery(plan, deltaHead));
|
|
7346
7512
|
const total = Number(rows.at(-1)?.total ?? 0);
|
|
7347
|
-
const
|
|
7513
|
+
const pageRows = rows.filter((row) => Number(row.__metadata) === 0);
|
|
7514
|
+
const payloads = /* @__PURE__ */ new Map();
|
|
7515
|
+
if (pageRows.length > 0) {
|
|
7516
|
+
const payloadRows = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs: remaining() }, compileClickHouseTraceRootPayloads(pageRows.map((row) => ({
|
|
7517
|
+
traceId: String(row.traceId),
|
|
7518
|
+
rootSpanId: String(row.rootSpanId),
|
|
7519
|
+
startedAt: asIsoTimestamp(row.startedAt),
|
|
7520
|
+
endedAt: asIsoTimestamp(row.endedAt)
|
|
7521
|
+
}))));
|
|
7522
|
+
for (const payload of payloadRows) payloads.set(`${payload.traceId}\u0000${payload.rootSpanId}`, payload);
|
|
7523
|
+
}
|
|
7524
|
+
const traces = pageRows.map((row) => ({
|
|
7525
|
+
...row,
|
|
7526
|
+
...payloads.get(`${row.traceId}\u0000${row.rootSpanId}`)
|
|
7527
|
+
})).map((row) => ({
|
|
7348
7528
|
traceId: String(row.traceId),
|
|
7349
7529
|
rootSpanId: String(row.rootSpanId),
|
|
7350
7530
|
name: row.name,
|
|
@@ -7443,6 +7623,201 @@ async function queryThreads(client, plan, timeoutMs) {
|
|
|
7443
7623
|
}) : null }
|
|
7444
7624
|
});
|
|
7445
7625
|
}
|
|
7626
|
+
/** Compile a span-row filter with exactly the same rules as trace span predicates. */
|
|
7627
|
+
function compileSpanQueryPredicate(predicate) {
|
|
7628
|
+
const parameters = new ParameterBuilder();
|
|
7629
|
+
return {
|
|
7630
|
+
sql: compileScalarPredicate(predicate, SPAN_FIELDS, parameters),
|
|
7631
|
+
params: parameters.params
|
|
7632
|
+
};
|
|
7633
|
+
}
|
|
7634
|
+
//#endregion
|
|
7635
|
+
//#region src/storage/domains/observability/v-next/span-query.ts
|
|
7636
|
+
const identityFields = [
|
|
7637
|
+
"organizationId",
|
|
7638
|
+
"resourceId",
|
|
7639
|
+
"traceId",
|
|
7640
|
+
"spanId"
|
|
7641
|
+
];
|
|
7642
|
+
const identities = identityFields.join(", ");
|
|
7643
|
+
const identitySetColumns = "isNull(organizationId), ifNull(organizationId, ''), isNull(resourceId), ifNull(resourceId, ''), traceId, spanId";
|
|
7644
|
+
const identitySetKey = `tuple(${identitySetColumns})`;
|
|
7645
|
+
const displayFields = [
|
|
7646
|
+
"parentSpanId",
|
|
7647
|
+
"name",
|
|
7648
|
+
"spanType",
|
|
7649
|
+
"entityType",
|
|
7650
|
+
"entityId",
|
|
7651
|
+
"entityName",
|
|
7652
|
+
"startedAt",
|
|
7653
|
+
"endedAt",
|
|
7654
|
+
"model",
|
|
7655
|
+
"provider",
|
|
7656
|
+
"durationMs",
|
|
7657
|
+
"status"
|
|
7658
|
+
];
|
|
7659
|
+
var Parameters = class {
|
|
7660
|
+
values = {};
|
|
7661
|
+
add(value, timestamp = false) {
|
|
7662
|
+
const name = `span_${Object.keys(this.values).length}`;
|
|
7663
|
+
this.values[name] = timestamp ? new Date(value).toISOString().replace("T", " ").replace(/Z$/, "") : value;
|
|
7664
|
+
return `{${name}:${timestamp ? "DateTime64(3, 'UTC')" : typeof value === "number" ? "UInt64" : "String"}}`;
|
|
7665
|
+
}
|
|
7666
|
+
};
|
|
7667
|
+
function identityValues(row) {
|
|
7668
|
+
return [
|
|
7669
|
+
row.organizationId === null ? 1 : 0,
|
|
7670
|
+
row.organizationId ?? "",
|
|
7671
|
+
row.resourceId === null ? 1 : 0,
|
|
7672
|
+
row.resourceId ?? "",
|
|
7673
|
+
row.traceId,
|
|
7674
|
+
row.spanId
|
|
7675
|
+
];
|
|
7676
|
+
}
|
|
7677
|
+
function afterCursor(plan, parameters) {
|
|
7678
|
+
if (!plan.cursor) return "1";
|
|
7679
|
+
const cursor = plan.cursor;
|
|
7680
|
+
const timestamp = parameters.add(cursor.sortValue, true);
|
|
7681
|
+
const field = `s.${plan.orderBy.field}`;
|
|
7682
|
+
const terms = [`${field} ${plan.orderBy.direction === "asc" ? ">" : "<"} ${timestamp}`];
|
|
7683
|
+
const equal = [`${field} = ${timestamp}`];
|
|
7684
|
+
for (const key of identityFields) {
|
|
7685
|
+
const value = cursor[key];
|
|
7686
|
+
if (value === null) equal.push(`isNull(s.${key})`);
|
|
7687
|
+
else {
|
|
7688
|
+
const parameter = parameters.add(value);
|
|
7689
|
+
terms.push(`(${equal.join(" AND ")} AND (s.${key} > ${parameter} OR isNull(s.${key})))`);
|
|
7690
|
+
equal.push(`s.${key} = ${parameter}`);
|
|
7691
|
+
}
|
|
7692
|
+
}
|
|
7693
|
+
return `(${terms.join(" OR ")})`;
|
|
7694
|
+
}
|
|
7695
|
+
function predicateFields(predicate, fields = /* @__PURE__ */ new Set()) {
|
|
7696
|
+
if (!predicate) return fields;
|
|
7697
|
+
if (predicate.type === "boolean") for (const arg of predicate.args) predicateFields(arg, fields);
|
|
7698
|
+
else if (predicate.type === "not") predicateFields(predicate.arg, fields);
|
|
7699
|
+
else fields.add(predicate.field);
|
|
7700
|
+
return fields;
|
|
7701
|
+
}
|
|
7702
|
+
function fieldExpression(field) {
|
|
7703
|
+
if (field === "model" || field === "provider") return `if(JSONType(e.attributes, '${field}') = 'String', JSONExtractString(e.attributes, '${field}'), NULL)`;
|
|
7704
|
+
if (field === "durationMs") return "dateDiff('millisecond', e.startedAt, e.endedAt)";
|
|
7705
|
+
if (field === "status") return "if(isNull(e.error), 'success', 'error')";
|
|
7706
|
+
if (field === "error") return "if(isNull(e.error), NULL, 'error')";
|
|
7707
|
+
return `e.${field}`;
|
|
7708
|
+
}
|
|
7709
|
+
function compileClickHouseSpanQuery(plan) {
|
|
7710
|
+
const parameters = new Parameters();
|
|
7711
|
+
const from = parameters.add(plan.timeRange.from, true);
|
|
7712
|
+
const to = parameters.add(plan.timeRange.to, true);
|
|
7713
|
+
const scope = [];
|
|
7714
|
+
if (plan.scope) {
|
|
7715
|
+
scope.push(`organizationId = ${parameters.add(plan.scope.organizationId)}`);
|
|
7716
|
+
if (plan.scope.resourceId !== void 0) scope.push(`resourceId = ${parameters.add(plan.scope.resourceId)}`);
|
|
7717
|
+
}
|
|
7718
|
+
const predicate = plan.where ? compileSpanQueryPredicate(plan.where) : {
|
|
7719
|
+
sql: "1",
|
|
7720
|
+
params: {}
|
|
7721
|
+
};
|
|
7722
|
+
const selectionFields = [.../* @__PURE__ */ new Set([
|
|
7723
|
+
"startedAt",
|
|
7724
|
+
"endedAt",
|
|
7725
|
+
...predicateFields(plan.where)
|
|
7726
|
+
])].filter((field) => !new Set(identityFields).has(field));
|
|
7727
|
+
const expressions = selectionFields.map(fieldExpression);
|
|
7728
|
+
const after = afterCursor(plan, parameters);
|
|
7729
|
+
return {
|
|
7730
|
+
query: `WITH candidate_ids AS (
|
|
7731
|
+
SELECT ${identitySetKey} AS identity FROM ${TABLE_SPAN_EVENTS}
|
|
7732
|
+
PREWHERE endedAt >= ${from}
|
|
7733
|
+
WHERE startedAt >= ${from} AND startedAt < ${to} ${scope.length ? `AND ${scope.join(" AND ")}` : ""}
|
|
7734
|
+
), candidates AS (
|
|
7735
|
+
SELECT ${identities}, ${selectionFields.map((field, index) => `${expressions[index]} AS ${field}`).join(", ")}
|
|
7736
|
+
FROM ${TABLE_SPAN_EVENTS} e
|
|
7737
|
+
PREWHERE e.endedAt >= ${from}
|
|
7738
|
+
WHERE ${identitySetKey} IN (SELECT identity FROM candidate_ids) ${scope.length ? `AND ${scope.join(" AND ")}` : ""}
|
|
7739
|
+
ORDER BY ${identities}, e.endedAt DESC
|
|
7740
|
+
LIMIT 1 BY ${identities}
|
|
7741
|
+
)
|
|
7742
|
+
SELECT ${[
|
|
7743
|
+
...identityFields,
|
|
7744
|
+
"startedAt",
|
|
7745
|
+
"endedAt"
|
|
7746
|
+
].map((field) => `s.${field}`).join(", ")}
|
|
7747
|
+
FROM candidates s WHERE s.startedAt >= ${from} AND s.startedAt < ${to} AND (${predicate.sql}) AND ${after}
|
|
7748
|
+
ORDER BY s.${plan.orderBy.field} ${plan.orderBy.direction === "asc" ? "ASC" : "DESC"}, ${identityFields.map((field) => `s.${field} ASC NULLS LAST`).join(", ")}
|
|
7749
|
+
LIMIT ${parameters.add(plan.limit + 1)}`,
|
|
7750
|
+
query_params: {
|
|
7751
|
+
...parameters.values,
|
|
7752
|
+
...predicate.params
|
|
7753
|
+
}
|
|
7754
|
+
};
|
|
7755
|
+
}
|
|
7756
|
+
function iso(value) {
|
|
7757
|
+
return new Date(value.includes("T") ? value : `${value.replace(" ", "T")}Z`).toISOString();
|
|
7758
|
+
}
|
|
7759
|
+
async function querySpans(client, plan, timeoutMs) {
|
|
7760
|
+
const deadline = performance.now() + _mastra_core_storage.resolveTraceQueryTimeoutMs(timeoutMs);
|
|
7761
|
+
const run = async (compiled) => {
|
|
7762
|
+
const remaining = Math.floor(deadline - performance.now());
|
|
7763
|
+
if (remaining <= 0) throw new _mastra_core_storage.TraceQueryExecutionError();
|
|
7764
|
+
return await runWithClickHouseTraceQueryTimeout(client, {
|
|
7765
|
+
timeoutMs: remaining,
|
|
7766
|
+
memoryLimitBytes: 512 * 1024 * 1024
|
|
7767
|
+
}, compiled);
|
|
7768
|
+
};
|
|
7769
|
+
const selected = (await run(compileClickHouseSpanQuery(plan))).map((row) => ({
|
|
7770
|
+
...row,
|
|
7771
|
+
startedAt: iso(row.startedAt),
|
|
7772
|
+
endedAt: iso(row.endedAt)
|
|
7773
|
+
}));
|
|
7774
|
+
const page = selected.slice(0, plan.limit);
|
|
7775
|
+
if (!page.length) return {
|
|
7776
|
+
spans: [],
|
|
7777
|
+
page: { next: null }
|
|
7778
|
+
};
|
|
7779
|
+
const payloadWhere = `tuple(${identitySetColumns}, endedAt) IN {page:Array(Tuple(UInt8, String, UInt8, String, String, String, DateTime64(3, 'UTC')))}`;
|
|
7780
|
+
const payloads = await run({
|
|
7781
|
+
query: `SELECT ${identities}, ${displayFields.map((field, index) => `data.${index + 1} AS ${field}`).join(", ")},
|
|
7782
|
+
data.${displayFields.length + 1} AS inputPreview, data.${displayFields.length + 2} AS outputPreview
|
|
7783
|
+
FROM (SELECT ${identities}, any(tuple(${displayFields.map(fieldExpression).join(", ")}, substringUTF8(input, 1, ${_mastra_core_storage.SPAN_QUERY_MAX_PREVIEW_CHARACTERS + 1}), substringUTF8(output, 1, ${_mastra_core_storage.SPAN_QUERY_MAX_PREVIEW_CHARACTERS + 1}))) AS data
|
|
7784
|
+
FROM ${TABLE_SPAN_EVENTS} e WHERE ${payloadWhere} GROUP BY ${identities})`,
|
|
7785
|
+
query_params: { page: page.map((row) => new _clickhouse_client.TupleParam([...identityValues(row), row.endedAt.replace("T", " ").replace(/Z$/, "")])) }
|
|
7786
|
+
});
|
|
7787
|
+
const metricWhere = `${identitySetKey} IN {page:Array(Tuple(UInt8, String, UInt8, String, String, String))}`;
|
|
7788
|
+
const metrics = await run({
|
|
7789
|
+
query: `SELECT ${identities}, data.1 AS name, data.2 AS estimatedCost,
|
|
7790
|
+
data.3 AS costUnit, data.4 AS allocation, data.5 AS costError
|
|
7791
|
+
FROM (SELECT ${identities}, metricId, argMax(tuple(name, estimatedCost, costUnit,
|
|
7792
|
+
JSONExtractString(costMetadata, 'allocation'), JSONHas(costMetadata, 'error')), timestamp) AS data
|
|
7793
|
+
FROM ${TABLE_METRIC_EVENTS} WHERE (${metricWhere}) AND name LIKE 'mastra_model_%'
|
|
7794
|
+
GROUP BY ${identities}, metricId)
|
|
7795
|
+
LIMIT ${_mastra_core_storage.SPAN_QUERY_MAX_COST_METRICS + 1}`,
|
|
7796
|
+
query_params: { page: page.map((row) => new _clickhouse_client.TupleParam(identityValues(row))) }
|
|
7797
|
+
});
|
|
7798
|
+
if (metrics.length > _mastra_core_storage.SPAN_QUERY_MAX_COST_METRICS) throw new _mastra_core_storage.TraceQueryResourceLimitError();
|
|
7799
|
+
const byIdentity = new Map(payloads.map((row) => [_mastra_core_storage.spanQueryIdentityKey(row), row]));
|
|
7800
|
+
const hydrated = page.flatMap((identity) => {
|
|
7801
|
+
const row = byIdentity.get(_mastra_core_storage.spanQueryIdentityKey(identity));
|
|
7802
|
+
if (!row) return [];
|
|
7803
|
+
const { inputPreview: _input, outputPreview: _output, ...metadata } = row;
|
|
7804
|
+
return [{
|
|
7805
|
+
...metadata,
|
|
7806
|
+
...identity,
|
|
7807
|
+
durationMs: Number(row.durationMs)
|
|
7808
|
+
}];
|
|
7809
|
+
});
|
|
7810
|
+
const result = _mastra_core_storage.buildSpanQueryResponse(plan, hydrated, payloads, metrics);
|
|
7811
|
+
const last = page.at(-1);
|
|
7812
|
+
result.page.next = selected.length > plan.limit ? _mastra_core_storage.encodeSpanQueryCursor(plan, {
|
|
7813
|
+
organizationId: last.organizationId,
|
|
7814
|
+
resourceId: last.resourceId,
|
|
7815
|
+
traceId: last.traceId,
|
|
7816
|
+
spanId: last.spanId,
|
|
7817
|
+
sortValue: last[plan.orderBy.field]
|
|
7818
|
+
}) : null;
|
|
7819
|
+
return result;
|
|
7820
|
+
}
|
|
7446
7821
|
//#endregion
|
|
7447
7822
|
//#region src/storage/domains/observability/v-next/trace-aggregate.ts
|
|
7448
7823
|
/** Trace-root column expressions (alias `r`) for each groupable dimension; same values `where` sees. */
|
|
@@ -7692,18 +8067,16 @@ async function listTraceRows(client, args, strategy, projection, mapRows) {
|
|
|
7692
8067
|
const page = pagination?.page ?? 0;
|
|
7693
8068
|
const perPage = pagination?.perPage ?? 10;
|
|
7694
8069
|
const { conditions, params } = buildTraceFilterConditions(filters, "r");
|
|
7695
|
-
if (filters?.hasChildError != null)
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
AND
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
SELECT 1 FROM ${TABLE_SPAN_EVENTS} c
|
|
7703
|
-
WHERE c.traceId = r.traceId
|
|
7704
|
-
AND c.parentSpanId IS NOT NULL
|
|
8070
|
+
if (filters?.hasChildError != null) {
|
|
8071
|
+
const scope = buildTraceFilterConditions(filters, "scope_r");
|
|
8072
|
+
const traceScope = scope.conditions.length ? `c.traceId IN (SELECT scope_r.traceId FROM ${TABLE_TRACE_ROOTS} scope_r WHERE ${scope.conditions.join(" AND ")})
|
|
8073
|
+
AND ` : "";
|
|
8074
|
+
conditions.push(`r.traceId ${filters.hasChildError ? "IN" : "NOT IN"} (
|
|
8075
|
+
SELECT c.traceId FROM ${TABLE_SPAN_EVENTS} c
|
|
8076
|
+
WHERE ${traceScope}c.parentSpanId IS NOT NULL
|
|
7705
8077
|
AND c.error IS NOT NULL
|
|
7706
8078
|
)`);
|
|
8079
|
+
}
|
|
7707
8080
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
7708
8081
|
const deltaCursorEnabled = deltaPollingSupported(strategy);
|
|
7709
8082
|
if (mode === "delta") {
|
|
@@ -7759,15 +8132,27 @@ async function listTraceRows(client, args, strategy, projection, mapRows) {
|
|
|
7759
8132
|
const rows = await (await client.query({
|
|
7760
8133
|
query: `
|
|
7761
8134
|
SELECT ${projection.outerSelect} FROM (
|
|
8135
|
+
-- Deferred join: pick the page's sort keys from a narrow sort, then
|
|
8136
|
+
-- read full rows only for those keys. LIMIT 1 BY disables ClickHouse's
|
|
8137
|
+
-- own lazy materialization, so sorting SELECT * directly would carry
|
|
8138
|
+
-- every matching row's payload columns through the sort. The filter is
|
|
8139
|
+
-- applied again on the re-read: a dedupeKey can have unmerged versions
|
|
8140
|
+
-- (in different endedAt partitions) and only a matching one may win.
|
|
7762
8141
|
SELECT ${projection.innerSelect}
|
|
7763
8142
|
FROM ${TABLE_TRACE_ROOTS} r
|
|
7764
|
-
${whereClause
|
|
7765
|
-
|
|
8143
|
+
${appendWhere(whereClause, `(r.startedAt, r.traceId, r.dedupeKey) IN (
|
|
8144
|
+
SELECT startedAt, traceId, dedupeKey
|
|
8145
|
+
FROM ${TABLE_TRACE_ROOTS} r
|
|
8146
|
+
${whereClause}
|
|
8147
|
+
ORDER BY ${orderClause}, dedupeKey ASC
|
|
8148
|
+
LIMIT 1 BY dedupeKey
|
|
8149
|
+
LIMIT {limit:UInt32}
|
|
8150
|
+
OFFSET {offset:UInt32}
|
|
8151
|
+
)`)}
|
|
8152
|
+
ORDER BY ${orderClause}, dedupeKey ASC
|
|
7766
8153
|
LIMIT 1 BY dedupeKey
|
|
7767
8154
|
)
|
|
7768
|
-
ORDER BY ${orderClause}
|
|
7769
|
-
LIMIT {limit:UInt32}
|
|
7770
|
-
OFFSET {offset:UInt32}
|
|
8155
|
+
ORDER BY ${orderClause}, dedupeKey ASC
|
|
7771
8156
|
`,
|
|
7772
8157
|
query_params: {
|
|
7773
8158
|
...params,
|
|
@@ -7801,6 +8186,7 @@ async function listTracesLight(client, args, strategy) {
|
|
|
7801
8186
|
return listTraceRows(client, args, strategy, LIGHT_PROJECTION, (rows) => rows.map(rowToLightSpanRecord));
|
|
7802
8187
|
}
|
|
7803
8188
|
async function queryTracesAfterCursor(client, deltaSelect, whereClause, params, limit, cursorId) {
|
|
8189
|
+
const deltaKeys = `SELECT startedAt, traceId, dedupeKey FROM ${TABLE_TRACE_ROOTS_DELTA} WHERE cursorId > {afterCursor:UInt64}`;
|
|
7804
8190
|
return await (await client.query({
|
|
7805
8191
|
query: `
|
|
7806
8192
|
SELECT
|
|
@@ -7809,12 +8195,16 @@ async function queryTracesAfterCursor(client, deltaSelect, whereClause, params,
|
|
|
7809
8195
|
r.traceId AS traceId,
|
|
7810
8196
|
r.dedupeKey AS dedupeKey,
|
|
7811
8197
|
toString(d.cursorId) AS cursorId
|
|
7812
|
-
FROM ${
|
|
7813
|
-
INNER JOIN
|
|
8198
|
+
FROM ${TABLE_TRACE_ROOTS} r
|
|
8199
|
+
INNER JOIN (
|
|
8200
|
+
SELECT cursorId, startedAt, traceId, dedupeKey
|
|
8201
|
+
FROM ${TABLE_TRACE_ROOTS_DELTA}
|
|
8202
|
+
WHERE cursorId > {afterCursor:UInt64}
|
|
8203
|
+
) d
|
|
7814
8204
|
ON r.startedAt = d.startedAt
|
|
7815
8205
|
AND r.traceId = d.traceId
|
|
7816
8206
|
AND r.dedupeKey = d.dedupeKey
|
|
7817
|
-
${whereClause
|
|
8207
|
+
${appendWhere(whereClause, `(r.startedAt, r.traceId, r.dedupeKey) IN (${deltaKeys})`)}
|
|
7818
8208
|
ORDER BY d.cursorId ASC
|
|
7819
8209
|
LIMIT {fetchLimit:UInt32}
|
|
7820
8210
|
`,
|
|
@@ -7827,27 +8217,30 @@ async function queryTracesAfterCursor(client, deltaSelect, whereClause, params,
|
|
|
7827
8217
|
clickhouse_settings: CH_SETTINGS
|
|
7828
8218
|
})).json();
|
|
7829
8219
|
}
|
|
8220
|
+
/**
|
|
8221
|
+
* Newest delta cursor whose trace root matches the filters. Without filters
|
|
8222
|
+
* every delta row qualifies, so this is the stream head. With filters, the
|
|
8223
|
+
* root scan is bounded below by the oldest `startedAt` still in the delta
|
|
8224
|
+
* table (a 2-day TTL window) instead of reading all of trace_roots.
|
|
8225
|
+
*/
|
|
7830
8226
|
async function getDeltaCursor$1(client, whereClause, params) {
|
|
8227
|
+
if (!whereClause) return getStreamHeadCursor$1(client);
|
|
7831
8228
|
const cursorId = (await (await client.query({
|
|
7832
8229
|
query: `
|
|
7833
8230
|
SELECT toString(max(d.cursorId)) AS cursorId
|
|
7834
8231
|
FROM mastra_trace_roots_delta d
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
8232
|
+
WHERE (d.startedAt, d.traceId, d.dedupeKey) IN (
|
|
8233
|
+
SELECT r.startedAt, r.traceId, r.dedupeKey
|
|
8234
|
+
FROM mastra_trace_roots r
|
|
8235
|
+
${appendWhere(whereClause, `r.startedAt >= (SELECT min(startedAt) FROM mastra_trace_roots_delta)`)}
|
|
8236
|
+
)
|
|
7840
8237
|
`,
|
|
7841
8238
|
query_params: params,
|
|
7842
8239
|
format: "JSONEachRow",
|
|
7843
8240
|
clickhouse_settings: CH_SETTINGS
|
|
7844
8241
|
})).json())[0]?.cursorId ?? null;
|
|
7845
8242
|
if (cursorId) return cursorId;
|
|
7846
|
-
return (
|
|
7847
|
-
query: `SELECT toString(max(cursorId)) AS cursorId FROM mastra_trace_roots_delta`,
|
|
7848
|
-
format: "JSONEachRow",
|
|
7849
|
-
clickhouse_settings: CH_SETTINGS
|
|
7850
|
-
})).json())[0]?.cursorId ?? "0";
|
|
8243
|
+
return getStreamHeadCursor$1(client);
|
|
7851
8244
|
}
|
|
7852
8245
|
async function getStreamHeadCursor$1(client) {
|
|
7853
8246
|
return (await (await client.query({
|
|
@@ -8338,15 +8731,28 @@ async function listBranches(client, args, strategy) {
|
|
|
8338
8731
|
const spans = (await (await client.query({
|
|
8339
8732
|
query: `
|
|
8340
8733
|
SELECT * FROM (
|
|
8734
|
+
-- Deferred join: pick the page's sort keys from a narrow sort, then
|
|
8735
|
+
-- read full rows only for those keys. LIMIT 1 BY disables ClickHouse's
|
|
8736
|
+
-- own lazy materialization, so sorting SELECT * directly would carry
|
|
8737
|
+
-- every matching branch's payload columns through the sort. The filter
|
|
8738
|
+
-- is applied again on the re-read: a dedupeKey can have unmerged
|
|
8739
|
+
-- versions (in different endedAt partitions) and only a matching one
|
|
8740
|
+
-- may win.
|
|
8341
8741
|
SELECT *
|
|
8342
8742
|
FROM ${TABLE_TRACE_BRANCHES} b
|
|
8343
|
-
${whereClause
|
|
8344
|
-
|
|
8743
|
+
${appendWhere(whereClause, `(b.spanType, b.startedAt, b.traceId, b.dedupeKey) IN (
|
|
8744
|
+
SELECT b.spanType, b.startedAt, b.traceId, b.dedupeKey
|
|
8745
|
+
FROM ${TABLE_TRACE_BRANCHES} b
|
|
8746
|
+
${whereClause}
|
|
8747
|
+
ORDER BY b.${sortField} ${sortDirection}, b.dedupeKey ASC
|
|
8748
|
+
LIMIT 1 BY b.dedupeKey
|
|
8749
|
+
LIMIT {limit:UInt32}
|
|
8750
|
+
OFFSET {offset:UInt32}
|
|
8751
|
+
)`)}
|
|
8752
|
+
ORDER BY b.${sortField} ${sortDirection}, b.dedupeKey ASC
|
|
8345
8753
|
LIMIT 1 BY b.dedupeKey
|
|
8346
8754
|
)
|
|
8347
8755
|
ORDER BY ${sortField} ${sortDirection}, dedupeKey ASC
|
|
8348
|
-
LIMIT {limit:UInt32}
|
|
8349
|
-
OFFSET {offset:UInt32}
|
|
8350
8756
|
`,
|
|
8351
8757
|
query_params: {
|
|
8352
8758
|
...params,
|
|
@@ -8368,6 +8774,7 @@ async function listBranches(client, args, strategy) {
|
|
|
8368
8774
|
};
|
|
8369
8775
|
}
|
|
8370
8776
|
async function queryBranchesAfterCursor(client, whereClause, params, limit, cursorId) {
|
|
8777
|
+
const deltaKeys = `SELECT spanType, startedAt, traceId, dedupeKey FROM ${TABLE_TRACE_BRANCHES_DELTA} WHERE cursorId > {afterCursor:UInt64}`;
|
|
8371
8778
|
return await (await client.query({
|
|
8372
8779
|
query: `
|
|
8373
8780
|
SELECT
|
|
@@ -8378,14 +8785,18 @@ async function queryBranchesAfterCursor(client, whereClause, params, limit, curs
|
|
|
8378
8785
|
b.spanId AS spanId,
|
|
8379
8786
|
b.dedupeKey AS dedupeKey,
|
|
8380
8787
|
toString(d.cursorId) AS cursorId
|
|
8381
|
-
FROM ${
|
|
8382
|
-
INNER JOIN
|
|
8788
|
+
FROM ${TABLE_TRACE_BRANCHES} b
|
|
8789
|
+
INNER JOIN (
|
|
8790
|
+
SELECT cursorId, spanType, startedAt, traceId, spanId, dedupeKey
|
|
8791
|
+
FROM ${TABLE_TRACE_BRANCHES_DELTA}
|
|
8792
|
+
WHERE cursorId > {afterCursor:UInt64}
|
|
8793
|
+
) d
|
|
8383
8794
|
ON b.spanType = d.spanType
|
|
8384
8795
|
AND b.startedAt = d.startedAt
|
|
8385
8796
|
AND b.traceId = d.traceId
|
|
8386
8797
|
AND b.spanId = d.spanId
|
|
8387
8798
|
AND b.dedupeKey = d.dedupeKey
|
|
8388
|
-
${whereClause
|
|
8799
|
+
${appendWhere(whereClause, `(b.spanType, b.startedAt, b.traceId, b.dedupeKey) IN (${deltaKeys})`)}
|
|
8389
8800
|
ORDER BY d.cursorId ASC
|
|
8390
8801
|
LIMIT {fetchLimit:UInt32}
|
|
8391
8802
|
`,
|
|
@@ -8398,29 +8809,29 @@ async function queryBranchesAfterCursor(client, whereClause, params, limit, curs
|
|
|
8398
8809
|
clickhouse_settings: CH_SETTINGS
|
|
8399
8810
|
})).json();
|
|
8400
8811
|
}
|
|
8812
|
+
/**
|
|
8813
|
+
* Newest delta cursor whose branch matches the filters. Without filters this
|
|
8814
|
+
* is the stream head; with filters, the branch scan is bounded below by the
|
|
8815
|
+
* oldest `startedAt` still in the delta table.
|
|
8816
|
+
*/
|
|
8401
8817
|
async function getDeltaCursor(client, whereClause, params) {
|
|
8818
|
+
if (!whereClause) return getStreamHeadCursor(client);
|
|
8402
8819
|
const cursorId = (await (await client.query({
|
|
8403
8820
|
query: `
|
|
8404
8821
|
SELECT toString(max(d.cursorId)) AS cursorId
|
|
8405
8822
|
FROM mastra_trace_branches_delta d
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
AND b.dedupeKey = d.dedupeKey
|
|
8412
|
-
${whereClause}
|
|
8823
|
+
WHERE (d.spanType, d.startedAt, d.traceId, d.spanId, d.dedupeKey) IN (
|
|
8824
|
+
SELECT b.spanType, b.startedAt, b.traceId, b.spanId, b.dedupeKey
|
|
8825
|
+
FROM mastra_trace_branches b
|
|
8826
|
+
${appendWhere(whereClause, `b.startedAt >= (SELECT min(startedAt) FROM mastra_trace_branches_delta)`)}
|
|
8827
|
+
)
|
|
8413
8828
|
`,
|
|
8414
8829
|
query_params: params,
|
|
8415
8830
|
format: "JSONEachRow",
|
|
8416
8831
|
clickhouse_settings: CH_SETTINGS
|
|
8417
8832
|
})).json())[0]?.cursorId ?? null;
|
|
8418
8833
|
if (cursorId) return cursorId;
|
|
8419
|
-
return (
|
|
8420
|
-
query: `SELECT toString(max(cursorId)) AS cursorId FROM mastra_trace_branches_delta`,
|
|
8421
|
-
format: "JSONEachRow",
|
|
8422
|
-
clickhouse_settings: CH_SETTINGS
|
|
8423
|
-
})).json())[0]?.cursorId ?? "0";
|
|
8834
|
+
return getStreamHeadCursor(client);
|
|
8424
8835
|
}
|
|
8425
8836
|
async function getStreamHeadCursor(client) {
|
|
8426
8837
|
return (await (await client.query({
|
|
@@ -8434,6 +8845,7 @@ function buildBranchCursor(row) {
|
|
|
8434
8845
|
}
|
|
8435
8846
|
//#endregion
|
|
8436
8847
|
//#region src/storage/domains/observability/v-next/index.ts
|
|
8848
|
+
const spanQueryFeatures = typeof _mastra_core_storage.planSpanQuery === "function" ? ["span-query"] : [];
|
|
8437
8849
|
function buildSignalMigrationRequiredMessage(args) {
|
|
8438
8850
|
const tableList = args.tables.map((table) => ` - ${table.table} (${table.engine})`).join("\n");
|
|
8439
8851
|
return `
|
|
@@ -8871,7 +9283,8 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
|
|
|
8871
9283
|
"trace-query-tenant-scope",
|
|
8872
9284
|
"feedback",
|
|
8873
9285
|
"trace-query-context-ids",
|
|
8874
|
-
"trace-aggregate"
|
|
9286
|
+
"trace-aggregate",
|
|
9287
|
+
...spanQueryFeatures
|
|
8875
9288
|
];
|
|
8876
9289
|
return [
|
|
8877
9290
|
"metrics",
|
|
@@ -8890,7 +9303,8 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
|
|
|
8890
9303
|
"trace-query-tenant-scope",
|
|
8891
9304
|
"feedback",
|
|
8892
9305
|
"trace-query-context-ids",
|
|
8893
|
-
"trace-aggregate"
|
|
9306
|
+
"trace-aggregate",
|
|
9307
|
+
...spanQueryFeatures
|
|
8894
9308
|
];
|
|
8895
9309
|
}
|
|
8896
9310
|
async createSpan(args) {
|
|
@@ -9005,6 +9419,18 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
|
|
|
9005
9419
|
}, error);
|
|
9006
9420
|
}
|
|
9007
9421
|
}
|
|
9422
|
+
async querySpans(plan) {
|
|
9423
|
+
try {
|
|
9424
|
+
return await querySpans(this.#client, plan, this.#traceQueryTimeoutMs);
|
|
9425
|
+
} catch (error) {
|
|
9426
|
+
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;
|
|
9427
|
+
throw new _mastra_core_error.MastraError({
|
|
9428
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("CLICKHOUSE", "QUERY_SPANS", "FAILED"),
|
|
9429
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
9430
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY
|
|
9431
|
+
}, error);
|
|
9432
|
+
}
|
|
9433
|
+
}
|
|
9008
9434
|
async queryTraces(plan) {
|
|
9009
9435
|
try {
|
|
9010
9436
|
return await queryTraces(this.#client, plan, this.#traceQueryTimeoutMs, this.#deltaCursorStrategy);
|
|
@@ -9571,6 +9997,7 @@ var ScoresStorageClickhouse = class extends _mastra_core_storage.ScoresStorage {
|
|
|
9571
9997
|
tableName: _mastra_core_storage.TABLE_SCORERS,
|
|
9572
9998
|
schema: _mastra_core_storage.TABLE_SCHEMAS[_mastra_core_storage.TABLE_SCORERS]
|
|
9573
9999
|
});
|
|
10000
|
+
await this.#db.ensureSkipIndexes(_mastra_core_storage.TABLE_SCORERS);
|
|
9574
10001
|
}
|
|
9575
10002
|
async dangerouslyClearAll() {
|
|
9576
10003
|
await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_SCORERS });
|
|
@@ -9981,6 +10408,7 @@ var WorkflowsStorageClickhouse = class extends _mastra_core_storage.WorkflowsSto
|
|
|
9981
10408
|
schema,
|
|
9982
10409
|
ifNotExists: ["resourceId"]
|
|
9983
10410
|
});
|
|
10411
|
+
await this.#db.ensureSkipIndexes(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT);
|
|
9984
10412
|
}
|
|
9985
10413
|
async dangerouslyClearAll() {
|
|
9986
10414
|
await this.#db.clearTable({ tableName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT });
|
|
@@ -10177,7 +10605,8 @@ var WorkflowsStorageClickhouse = class extends _mastra_core_storage.WorkflowsSto
|
|
|
10177
10605
|
ORDER BY createdAt DESC LIMIT 1
|
|
10178
10606
|
`,
|
|
10179
10607
|
query_params: values,
|
|
10180
|
-
format: "JSONEachRow"
|
|
10608
|
+
format: "JSONEachRow",
|
|
10609
|
+
clickhouse_settings: { use_skip_indexes_if_final: 1 }
|
|
10181
10610
|
})).json();
|
|
10182
10611
|
if (!Array.isArray(resultJson) || resultJson.length === 0) return null;
|
|
10183
10612
|
return this.parseWorkflowRun(resultJson[0]);
|